HornetQ 2.1用户手册

Putting the buzz in messaging


Table of Contents

1. 法律声明
2. 前言
3. 项目信息
3.1. 软件下载
3.2. 其它相关信息
4. 消息的相关概念
4.1. 消息相关的概念
4.2. 消息的种类
4.2.1. 消息队列模式
4.2.2. 消息的广播/订阅模式
4.3. 传送的可靠性
4.4. 交易(Transactions)
4.5. 持久性(Durability)
4.6. 消息API和协议
4.6.1. Java消息服务(JMS)
4.6.2. 专有的API
4.6.3. RESTful API
4.6.4. STOMP
4.6.5. AMQP
4.7. 高可获得性(Availability)
4.8. 集群
4.9. 桥接(Bridge)和路由(Routing)
5. 总体架构
5.1. 核心架构
5.2. 将HornetQ嵌入到你的应用程序中
5.3. 将HornetQ与JEE应用服务器集成
5.4. HornetQ作为独立的服务(standalone)
6. 使用HornetQ服务
6.1. 服务的启动和停止
6.2. 服务器端JVM参数的设置
6.3. 服务器端的classpath
6.4. Library Path
6.5. 系统变量
6.6. 配置文件
6.7. JBoss Microcontainer Beans 文件
6.8. JBoss AS4 MBean 服务
6.9. 主配置文件
7. 使用JMS
7.1. 一个简单的订购系统
7.2. JMS服务的配置
7.3. JNDI的配置
7.4. 程序代码
7.5. 不使用JNDI而直接创建JMS的对象
7.6. Client ID的设置
7.7. 设置DUPS_OK的Batch Size
7.8. 设置事务(Transaction)的Batch Size
8. 使用HornetQ内核
8.1. 内核消息系统的相关概念
8.1.1. 消息
8.1.2. 地址(Address)
8.1.3. Queue
8.1.4. ClientSessionFactory
8.1.5. ClientSession
8.1.6. ClientConsumer
8.1.7. ClientProducer
8.2. 一个内核的应用实例
9. JMS与内核API之间的映射关系
10. 客户端的Classpath
10.1. 使用HornetQ内核的客户端
10.2. JMS客户端
10.3. 需要JNDI的JMS客户端
11. 例子
11.1. JMS 例子
11.1.1. 应用层的失效备援(Failover)
11.1.2. 内核桥(Bridge)例子
11.1.3. 浏览器(Browser)
11.1.4. Client Kickoff
11.1.5. 客户端的负载均衡
11.1.6. 集群分组
11.1.7. 集群队列
11.1.8. 单机集群
11.1.9. 集群的Topic
11.1.10. 限制接收速率
11.1.11. 死消息(Dead Letter)
11.1.12. 延迟再发送
11.1.13. 转移(Divert)
11.1.14. 持久订阅(Durable Subscription)
11.1.15. 嵌入方式(Embedded)
11.1.16. HTTP 传输协议的支持
11.1.17. 直接实例化JMS对象
11.1.18. 拦截器(Interceptor)
11.1.19. JAAS
11.1.20. JMS桥(Bridge)
11.1.21. JMX管理
11.1.22. 大消息
11.1.23. 最新值队列
11.1.24. 分布式队列的负载均衡
11.1.25. 管理
11.1.26. 管理通知
11.1.27. 消息计数器
11.1.28. 消息失效
11.1.29. 消息组
11.1.30. 消息组(例2)
11.1.31. 消息优先级
11.1.32. 零接收缓冲
11.1.33. 带有服务器数据复制的非事务失效备援
11.1.34. 分页(paging)
11.1.35. 预先通知
11.1.36. 消息发送速度限制
11.1.37. 队列
11.1.38. Message再分配
11.1.39. 队列请求
11.1.40. 带消息选择器(selector)的队列
11.1.41. 节点连接重试
11.1.42. 请求/应答
11.1.43. 定时消息
11.1.44. 安全
11.1.45. 发送通知
11.1.46. SSL传输支持
11.1.47. 静态消息选择器
11.1.48. 使用JMS方法来配置静态消息选择器
11.1.49. Stomp
11.1.50. Stomp与Web Sockets
11.1.51. 对称型集群
11.1.52. 临时队列
11.1.53. 话题(Topic)
11.1.54. 话题体系(Topic Hierarchy)
11.1.55. 话题选择器例1
11.1.56. 话题选择器例2
11.1.57. 带有数据复制的事务性失效备援
11.1.58. 事务性会话
11.1.59. XA Heuristic
11.1.60. XA 接收
11.1.61. XA 发送
11.1.62. XA与事务管理器(transaction manager)
11.2. 核心API的例子
11.2.1. 嵌入式
11.3. Java EE 例子
11.3.1. EJB/JMS 事务
11.3.2. HAJNDI (High Availability)
11.3.3. 资源适配器的配置(JCA)
11.3.4. 资源适配器运程服务器的配置
11.3.5. JMS 桥(Bridge)
11.3.6. MDB (消息驱动Bean)
11.3.7. Servlet传输
11.3.8. Servlet SSL 传输
11.3.9. XA 恢复(recovery)
12. 使用通配符实现消息路由
13. 了解 HornetQ 通配符的语法
14. 过滤器表达式
15. 持久化
15.1. 配置绑定日志
15.2. 配置JMS日志
15.3. 配置消息日志
15.4. 关于关闭磁盘写缓冲的重要说明
15.5. 安装AIO
15.6. 配置HornetQ不使用持久化
16. 传输层的配置
16.1. 接收器(Acceptor)
16.2. 连接器(Connectors)
16.3. 在客户端直接配置传输层
16.4. 配置 Netty 传输层
16.4.1. 配置 Netty TCP
16.4.2. 配置Netty SSL
16.4.3. 配置Netty HTTP
16.4.4. 配置Netty Servlet
17. 失效连接的检测
17.1. 服务器端对失效连接的清除
17.1.1. 关闭没有被成功关闭的核心会话或JMS连接
17.2. 客户端的故障检测
17.3. 配置异步连接任务执行
18. 配置资源管理器(Resource Manager)
19. 流控制
19.1. 接收者(consumer)流控制
19.1.1. 基于窗口的流控制
19.1.1.1. 使用核心接口(Core API)进行流控制
19.1.1.2. 使用JMS的流控制
19.1.2. 速率流控制
19.1.2.1. 使用核心接口(Core API)
19.1.2.2. 使用JMS
19.2. 发送者(producer)的流控制
19.2.1. 基于窗口的流控制
19.2.1.1. 使用核心接口(Core API)
19.2.1.2. 使用JMS
19.2.1.3. 限定发送者窗口流控制
19.2.2. 速率流控制
19.2.2.1. 使用核心接口(Core API)
19.2.2.2. 使用 JMS
20. 发送与提交的保证
20.1. 事务保证
20.2. 非事务性消息发送的保证
20.3. 非事务性通知的保证
20.4. 异步发送通知
20.4.1. 异步发送通知
21. 消息再传递及未传递的消息
21.1. 延迟再传递
21.1.1. 延迟再传递的配置
21.1.2. 例子
21.2. 死信地址
21.2.1. 配置死信地址
21.2.2. 死信的属性
21.2.3. 例子
21.3. 传递计数的持久化
22. 过期的消息
22.1. 过期消息的配置
22.2. 配置过期地址
22.3. 配置过期回收线程
22.4. 例子
23. 大消息
23.1. 服务器端的配置
23.2. 设定限制
23.2.1. 使用核心的API
23.2.2. 使用JMS
23.3. 大消息与流(stream)
23.3.1. 核心API中流的使用
23.3.2. 在JMS中使用流
23.4. 不使用流的方式
23.5. 在客户端缓存大消息
23.6. 大消息例子
24. 分页转存
24.1. 分页文件
24.2. 配置
24.3. 分页转存模式
24.3.1. 配置
24.4. 丢弃消息
24.5. 阻塞发送者(producer)
24.6. 对于有多个队列绑定的地址的配置注意事项
24.7. 分页转存与消息的选择器(selector)
24.8. 分页转存与浏览器
24.9. 分页转存与未通知的消息
24.10. 例子
25. 队列属性
25.1. 预定义的队列
25.2. 使用接口(API)创建队列
25.3. 通过地址设置来配置队列属性
26. 定期消息
26.1. 定期传递参数
26.2. 例子
27. 最新值队列(Last-Value Queues)
27.1. 最新值队列的配置
27.2. 使用Last-Value参数
27.3. 例子
28. 消息分组
28.1. 使用核心接口
28.2. 使用JMS
28.3. 例子
28.4. 例子
28.5. 集群中的消息组
28.5.1. 集群消息组的最佳使用惯例
28.5.2. 集群消息组例子
29. 预先通知模式(pre-acknowledge)
29.1. 使用PRE_ACKNOWLEDGE
29.2. 例子
30. 管理
30.1. 管理接口API
30.1.1. 核心管理接口
30.1.1.1. 核心服务器管理
30.1.1.2. 核心地址的管理
30.1.1.3. 核心队列的管理
30.1.1.4. 其它核心资源的管理
30.1.2. JMS管理接口
30.1.2.1. JMS服务器管理
30.1.2.2. JMS连接工厂的管理
30.1.2.3. JMS队列管理
30.1.2.4. JMS话题(Topic)的管理
30.2. 使用JMX
30.2.1. 配置JMX
30.2.1.1. MBeanServer的配置
30.2.2. 例子
30.3. 使用核心接口
30.3.1. 配置核心管理
30.4. 使用JMS进行管理
30.4.1. 配置JMS管理
30.4.2. 例子
30.5. 管理通知
30.5.1. JMX通知
30.5.2. 核心消息通知
30.5.2.1. 配置核心管理通知地址
30.5.3. JMS消息通知
30.5.4. 例子
30.6. 消息计数器
30.6.1. 配置消息计数器
30.6.2. 例子
30.7. 通过JBoss应用服务器的Admin Console来管理HornetQ的资源
30.7.1. JMS队列
30.7.2. JMS话题
30.7.3. JMS连接工厂
31. 安全
31.1. 基于角色的地址安全
31.2. 安全套接字层(SSL)传输
31.3. 基本用户身份信息(Credentials)
31.4. 更换安全管理器
31.5. JAAS安全管理器
31.5.1. 例子
31.6. JBoss 应用服务器安全管理器
31.6.1. 配置客户端登录
31.7. 集群用户名/密码的配置
32. Java EE和应用服务器的集成
32.1. 配置消息Bean
32.1.1. 使用容器管理事务(CMT)
32.1.2. 使用Bean管理事务(BMT)
32.1.3. 在MDB中使用选择器
32.2. 在JEE模块内发送消息
32.3. MDB与接收池的大小
32.4. 配置JCA适配器
32.4.1. 适配器的全局参数
32.4.2. 适配器外部(outbound)配置
32.4.3. 适配器内部(inbound)配置
32.4.4. 配置适配器访问单独的HornetQ服务器
32.4.4.1.
32.4.5. 高可获得性JNDI (HA-JNDI)
32.4.6. XA恢复
32.4.6.1. XA恢复的配置
32.4.6.2. 例子
33. JMS桥(Bridge)
33.1. JMS桥的配置参数
33.2. 源和目的的连接工厂
33.3. 源和目的的目标工厂
33.4. 服务质量
33.4.1. AT_MOST_ONCE
33.4.2. DUPLICATES_OK
33.4.3. ONCE_AND_ONLY_ONCE
33.4.4. JMS bridge中的超时问题
33.4.5. 例子
34. 客户端重新连接与会话恢复
34.1. 100%透明的会话恢复(re-attachment)
34.2. 会话重新连接
34.3. 重新连接/会话恢复的配置参数
34.4. ExceptionListeners and SessionFailureListeners
35. 消息的转发(divert)与分流
35.1. 唯一式转发器
35.2. 不唯一转发器
36. 核心桥
36.1. 桥的配置
37. 重复消息检测
37.1. 在消息发送中应用重复检测
37.2. 配置重复ID缓存
37.3. 桥与重复检测
37.4. 重复检测与集群连接
37.5. 分页转存与重复检测
38. 集群
38.1. 集群概述
38.2. 服务器发现
38.2.1. 广播组
38.2.2. 发现组
38.2.3. 在服务器端定义发现组。
38.2.4. 客户端的发现组
38.2.4.1. 使用JMS时客户端发现的配置方法
38.2.4.2. 使用核心API的客户端的配置
38.3. 服务器端消息的负载均衡
38.3.1. 配置集群连接
38.3.2. 集群用户的安全信息
38.4. 客户端负载均衡
38.5. 显式指定集群服务器
38.5.1. 在客户端指定服务器列表
38.5.1.1. 使用JMS时指定服务器列表
38.5.1.2. 使用核心接口指定服务器列表
38.5.2. 指定服务器列表以组成集群
38.6. 消息再分配
38.7. 集群拓扑结构
38.7.1. 对称式集群
38.7.2. 链式集群
39. 高可获得性(High Availability)和失效备援(Failover)
39.1. 主要-备份对
39.1.1. 高可获得性(HA)的模式
39.1.1.1. 数据复制
39.1.1.2. 存贮共享
39.2. 失效备援的模式
39.2.1. 自动客户端失效备援
39.2.1.1. 关于服务器的复制
39.2.1.2. 失效备援时阻塞调用的处理
39.2.1.3. 事务的失效备援处理
39.2.1.4. 非事务会话的失效备援处理
39.2.2. 连接故障的通知
39.2.3. 应用层的失效备援
40. Libaio平台专有库
40.1. 库文件的编译
40.1.1. 安装要求
40.1.2. 开始编译
41. 线程管理
41.1. 服务器端线程的管理
41.1.1. 服务器端可计划线程池
41.1.2. 服务器通用线程池
41.1.3. 过期回收线程
41.1.4. 异步IO
41.2. 客户端线程管理
42. 日志(Logging)
42.1. 与JBoss应用服务器日志的关系
43. 嵌入式HornetQ
43.1. POJO的初始化
43.2. 使用依赖注入框架
43.3. 连接嵌入式HornetQ
43.3.1. 核心接口
43.3.2. JMS接口
43.4. JMS嵌入式HornetQ的例子
44. 拦截操作
44.1. 实现拦截器
44.2. 配置拦截器
44.3. 客户端拦截器
44.4. 例子
45. 互操作性
45.1. Stomp
45.1.1. 内建Stomp支持
45.1.1.1. 限制
45.1.2. Stomp目标与HornetQ的地址和队列的映射
45.1.3. Stomp与JMS的互操作性
45.1.3.1. 使用JMS目标
45.1.3.2. 使用JMS或核心接口发送和接收Stomp消息
45.1.4. 通过Web Sockets使用Stomp
45.1.5. StompConnect
45.2. REST
45.3. AMQP
46. 性能调优
46.1. 持久层的优化
46.2. 优化JMS
46.3. 其它优化
46.4. 传输层的优化
46.5. 优化虚拟机
46.6. 避免违背设计模式
47. 配置参数索引
47.1. 服务器配置
47.1.1. hornetq-configuration.xml
47.1.2. hornetq-jms.xml

Chapter 1. 法律声明

Red Hat, Inc. 以及其他公司2010年版权所有。

Red Hat公司依照 CC-BY-SA 3.0 Unported(Creative Commons Attribution-Share Alike)条款之规定授权用戶是用本手册中的文字和插图。

有关 CC-BY-SA 的解释请访问http://creativecommons.org/licenses/by-sa/3.0/。根据CC-BY-SA的规定,如果要发布本文档或任何本文档的修改版本,都必须给出原始版本文档的URL。

Red Hat 作为本文档的授权方声明在相关法律允许的最大范围内放弃CC-BY-SA第4d节所规定的权利。

Chapter 2. 前言

什么是HornetQ?

为什么要使用HornetQ? 以下给出了几个理由:

  • HornetQ是100%的开源软件。 HornetQ 采用 Apache v 2.0开源协议,对用户的限制最小。

  • HornetQ的设计强调可用性。

  • 采用Java语言编写。可以在任何Java 6+ 的平台上运行。这几乎包括了从Windows到IBM mainframes的每个平台。

  • 性能出众。不但对非持久化消息的处理性能达到了非常高的性能。独特高效的日志(journal)使持久消息处理接近非持久消息的性能。

  • 功能全面。不仅拥有其它成熟消息产品所具有的全部功能,而且还有很多独特的功能。

  • HornetQ的设计遵从了简约的原则。对第三方软件的依赖极少。根据不同的需要, HornetQ可以单独运行,也可以运行于JEE应用服务器中。它还可以嵌入到你自己的应用程序中。

  • 完美的可获得性。HornetQ提供自动客户端失效备援(automatic client failover)功能,能保证在服务器故障时没有消息丢失或消息重复。

  • 超级灵活的集群方案。可以控制集群进行消息负载均衡的方式。分布在不同地理位置的各个集群间可以通过非可靠的网络连接形成一个全球网络。 还可以非常灵活地配置消息路由。

  • 请访问 wiki 来全面了解HornetQ的所有功能介绍。

Chapter 3. 项目信息

HornetQ的官方网址是 http://hornetq.org/.

3.1. 软件下载

HornetQ的下载地址为:http://hornetq.org/downloads.html

3.2. 其它相关信息

Red Hat 公司聘请全职工程师进行HornetQ项目的开发工作,他们是:

另外我们还有长期的和刚加入的一些贡献者,非常感謝他们的帮助。参见完整的贡献者名单

Chapter 4. 消息的相关概念

HornetQ是一个异步的 面向消息的中间件。在本文档中我们简称为消息系统。

首先我们简要介绍消息系统是做什么的,在哪些领域得到应用,以及与消息相关的一些概念。

如果你已经对消息系统的这些方面的知识很熟悉,可以跳过本章内容。

4.1. 消息相关的概念

消息系统可以将不同异种的系统松散地耦合在一起,提供基本的可靠性,事务及其它功能的支持。

与基于远程过程调用 (RPC) 的系统不同,消息系统主要采用异步传送的方式,请求与响应之间的耦合很松。 大多数的消息系统也支持请求-响应的方式,但这不是消息的主要功能。

端与端之间采用异步通信的好处是可以充分利用硬件资源,最大程度减少IO操作引起的线程阻塞,并充分利用网络带宽。 而采用RPC方式,每一个请求必须要等待响应返回才能继续,因而要依赖你的网络的速度 (latency)。异步系统则能将消息以管道的方式传送, 它只受带宽的限制,并不因网络速度而降低效率。利用异步的方式往往可以创建更高效率的应用。

消息系统将消息的发送方与接收方分开,使消息的发送所接收完全独立于对方,有利于创建灵活的、松耦的系统。

大型的企业应用通常采用消息系统来实现一种消息总线,并基于这个总线将企业的各种不同结构的系统松散地连在一起工作。 消息总线也常常是企业服务总线(ESB)的核心。 采用这种方式搭建的松耦合系统可以非常容易地扩展和修改。由于系统各模块之间的信赖关系很弱,所以在需要时可以对系统灵活地添加和减少模块。

4.2. 消息的种类

消息系统通常支持两种异步的传送模式:消息队列 (又称为点对点消息传送)模式和广播/订阅模式

4.2.1. 消息队列模式

在这种模式中消息被发送到队列中。通常消息会被持久化以保证可靠的传送。消息系统会将队列中的消息传送给接收者 (receiver或consumer)。当接收者处理消息完成后,它会发出完成的通知给消息系统。得到通知的消息就会从队列 中删除,因此该消息不会被再次传送。如果在收到消息前消息服务器发生故障导致系统崩溃,当系统恢复时, 该消息会被再次传送给接收者。

这种模式允许一个队列有多个接收者。但是一个消息最多只传送给一个接收者。一个队列的消息发送者(sender或producer) 与接收者是完全独立的。它们不知道彼此的存在。

图书订单系统是一个典型的消息队列的用例。每一个订单都被包装为一个消息传送到订单队列中。假定有多个图书订购的终 端向订单队列发关订单消息。当一个消息到达队列时它被持久化以防止系统崩溃时订单的丢失。再假定有多个订单处理中心 分布在不同的机器上接收这个订单队列的消息。消息系统将每一个消息发送给其中一个(并且只发送一个)接收者(即一个订单处理模块)。 这样不同的订单可能会被不同的处理模块处理,但一个订单只会被处理一次。

当订单处理模块接收到一个消息,对它进行处理后将订单信息发送给仓库系统并更新订单数据库。处理完成后它会发出通知告诉服务 器可以删除此消息。通常这一系列和处理(接收,发送给仓库系统,更新数据库以及通知)会被作为一个交易来处理以保证它的完整性 (ACID)。

4.2.2. 消息的广播/订阅模式

这种模式中,多个发送者将消息发送到服务器中一个特定的实体,JMS中通常称为话题(topic)。 一个Topic常常有多个订阅者(subscription,即消息的接收者)。

与消息队列的接收者不同,每个订阅者都会收到发送的队列中的每个消息。

订阅者可以选择为固定的方式(durable)。采用这种方式的订阅者, 其消息会保留直到被接收为止。即使是其间服务器发生过故障或重启也不受影响。非固定的订阅者只在其连接期间有效, 一但连接断开其消息将不会保留。

电子消息订阅是消息广播模式的一个例子。当新闻被世界各地的编辑編好后,他们将其发关到新闻topic。 同样对这些新闻兴趣的读者会订阅这个topic。消息系统将保证每个订阅者都能够收到每一篇新闻稿。

4.3. 传送的可靠性

大多数的消息系统是可靠的消息传送系统。消息系统可以保证一个消息被传送 给一个并且只传送给一个队列的接收者或每个话题的固定的订阅者。一个消息不会被传送两次。即使在系统出现故障时也是如此。 这一特性对于很多企业来说是非常重要的。比如你想要保证订单不丢失或被处理两次以上,就可以利用该特性。

在某些情况下这种“一次并且只有一次”的传送方式并不是很重要,重复消息和消息的丢失并不影响系统的功能。比如股票价格 的更新消息,它并不需要保证每次都能收到,因为前一次更新很快就会被下一次代替。这样的功能消息系统也可以支持。

4.4. 交易(Transactions)

消息系统通常支持在一次本地交易中发送并通知多个消息。HornetQ还支持分布式交易。它可以通过Java的XA和JTA接口, 将消息的发送与通知做为一个分布式交易的一部分来完成。

4.5. 持久性(Durability)

消息可以分为持久消息和非持久消息。持久消息被保存到永久的存储介质中,不受服务器故障与重启的影响。非持久消息在服务 器故障与重启时则会丢失。像订单,交易信息属于持久消息,而股票价格更新由于它的即时性则可以做为非持久消息来处理。

4.6. 消息API和协议

客户端的应用程序怎样访问消息系统来进行消息的发送与接收呢?

一些消息系统提供私有的API,客户端可以通过这些私有的API与相应的消息系统交互,实现消息的收发。

除此之外,还存在着一些标准的交互方式可供使用。另外还有一些标准正在不断完善。下面我们就介绍一下这些标准。

Let's take a brief look at these:

4.6.1. Java消息服务(JMS)

JMS 属于Sun公司JEE规范的一部分。 它定义了一套标准的API支持消息队列和广播-订阅模式。JMS是一套非常精简的通用的标准,它将当时已经存在的消息系统的共同功能包括了进去。

JMS是一个广泛使用的API,绝大多数的消息系统都支持它。JMS只有Java的客户端才可以使用。

JMS并没有定义传输的格式(wire format)。因此不同的JMS消息服务器的和客户端相互之间通常不能交互,这是因为每个消息系统都自己的传输格式。

HornetQ全面支持JMS 1.1 API。

4.6.2. 专有的API

很多系统提供自己的一套API来与其消息系统进行通迅,其优势是它可以允许客户端使用其全部的功能。 像JMS那样的标准API往往不能提供许多消息系统所支持的额外的功能。

HornetQ提供了一套自有的核心API,客户端程序可以通过它充分利用HornetQ的强大功能。 这对于一些JMS API满足不了的需求是非常有用的。

4.6.3. RESTful API

采用REST REST[http://en.wikipedia.org/wiki/Representational_State_Transfer]方式与消息系统交互越来越被关注。

由于云计算技术的API标准目前倾向于采用REST的方式,所以采用REST方式的消息系统很有望成为云计算中消息传送的标准。

REST方式中的各种消息资源以URI的方式来定义。用户通过一套很简单的操作与这些资源相交互,如PUT、POST、GET等。HTTP通常用来作为REST方式的通信协议。

采用HTTP的好处是它很简单实用,并且internet经过多年的发展已经能很好的支持HTTP协议。

HornetQ将会很快地支持REST方式的API。

4.6.4. STOMP

Stomp 是为消息系统定义的一套简单的文本传输协议。它定义了一种线上传输的格式, 因此采用Stomp编写的客户端可以与所有支持Stomp的消息系统交互。Stomp的客户端可以用多种编程语言来实现。

有关在HornetQ中如何使用Stomp的详细内容请参见Section 45.1, “Stomp”

4.6.5. AMQP

AMQP 是一套可支持互操作的消息规范。 它定义了自己的传输格式,因些任何AMQP的客户端都可以和支持AMQP的系统进行交互。AMQP的客户端可以用多种编程语言来实现。

HornetQ将会很快地支持AMQP。

4.7. 高可获得性(Availability)

高可获得性是指在系统中有一个或多个服务器发生故障时仍然能够维持运转的特性。不同的消息系统对高可获得性的支持程度是不同的。

HornetQ支持自动失效备援(failover),也就是当主服务器出现故障时,当前的会话会自动连接到备用的服务器上。

Chapter 39, 高可获得性(High Availability)和失效备援(Failover)给出了HornetQ的HA特性的详细信息。

4.8. 集群

许多消息系统支持由多个消息服务器组成的集群。集群可以使发送和接收的负荷分散到不同的服务器中。 通过增加集群服务器,可以有效的增加整个集群处理消息的能力。

然而不同的消息系统有着不同的集群架构。有的集群架构十分简单,有的集群中成员间的联系很少。

HornetQ提供了非常先进的可配置的集群模型。根据每个节点接收者(consumer)的多少以及是否具有接收状态,消息在集群中可以进行智能化负载均衡。

HornetQ还能够在集群中的节点间进行消息的再分发,以避免在某个节点出现消息匮乏(starvation)现象。

有关集群的详细内容参见Chapter 38, 集群

4.9. 桥接(Bridge)和路由(Routing)

有些消息系统可以将一些分散在不可靠的网络(如广域网或internet)上孤立的集群或节点桥接在一起。

通常一个桥的作用是从一台服务器的队列上接收消息然后将消息再转发到另一台服务器的队列中。桥连接可以解决不可靠网络连接的问题。 桥有自动重新连接的功能。一旦网络连接中断,桥可以自动进行重试直到重新连接上为止。

HornetQ的桥接功能可以配置过滤表达式,以实现有条件的转发。另外,它还可以实现消息转换的功能(transformation)。

HornetQ还允许配置消息在队列之间进行路由。利用它可以完成复杂的路由网络以便在不同队列间进行消息转发与复制,形成一个互连的消息代理(broker)网络。

有关的详细内容将在Chapter 36, 核心桥Chapter 35, 消息的转发(divert)与分流给出。

Chapter 5. 总体架构

本章对HornetQ的总体技术架构进行了概括描述。

5.1. 核心架构

HornetQ的核心是由一组简单Java对象(POJO)构成的。同时在设计HornetQ时将对外部jar包的依赖降到最低限度。 实际上HornetQ的核心部分只有一个外部依赖,就是netty.jar。HornetQ使用了其中的用于缓冲的一些类。 我们相信这样的理念应该受到用户的欢迎。

由于依赖性很小,HornetQ可以非常容易地嵌入到其它应用中,或者加入到一些依赖注入式的框架中, 如JBoss Microcontainer,Spring或Google Guice。

每个HornetQ服务器都有自己的超高性能的持久日志(journal)用于消息和其它信息的持久化。

采用这种独特的高效日志要比采用普通数据库作为持久层的系统拥有更高的性能。

通常情况下分布在不同物理机器上的客户端同时访问HornetQ服务器。目前HornetQ提供了两套API供客户端使用:

  1. 核心API。这是一组普通的Java接口,用它可以访问HornetQ的全部功能。

  2. JMS客户端API。这是标准的JMS API。

实际上JMS API是在核心API的外部加上一层简单的封装。

在HornetQ内核是没有JMS的,这样设计的目的是为了支持多个协议。

当客户端通过JMS接口访问HornetQ时,所有JMS的操作都被转换成相应的核心API,然后将请求以HornetQ格式发向服务器。

HornetQ服务器只接收核心API的访问。

图3.1描述了这些操作。

在图3.1中示出了两个用户访问HornetQ服务器。用户1使用JMS API,用户2使用的是核心API。

图中清楚的展示出了JMS是如何通过封装(facade)转化为核心API的。

5.2. 将HornetQ嵌入到你的应用程序中

如果你的应用程序内部需要消息服务,但同时你又不想将消息服务暴露为单独的HornetQ服务器,你可以在应用中直接将HornetQ实例化。

有关嵌入式HornetQ的详细信息请参阅 Chapter 43, 嵌入式HornetQ

5.3. 将HornetQ与JEE应用服务器集成

HornetQ提供了标准的JCA适配器,利用它可以将HornetQ轻松地集成到任何一个符合JEE规范的应用服务器或servlet容器中。

JEE应用服务品提供了消息Bean(MDB)用于处理来自外部的消息,比如来自JMS系统或邮件系统的消息。

最常见的应用应该是用MDB来接收来自JMS系统中的消息了。在JEE规范中规定了JEE应用服务器使用JCA adaptor与JMS消息系统集成, MDB通过这个adaptor来接收消息。

JCA adaptor不仅可以用来接收消息,还可以用来从EJB或servlet中向外部的JMS发送消息。在JEE应用服务器中应该用JCA adaptor与JMS系统进行交互。 实际上JEE规范中不允许在JEE服务器中不通过JCA而直接访问JMS系统。

在EJB中使用消息往往需要连接池或交易,而JCA可以提供这方面的服务,无需额外的开发任务。当然直接访问JMS系统是可能的, 但是你将不能利用JCA所提供的这些有用的功能,因此我们不建议使用直接访问的方式。

图3.2给出了HornetQ通过JCA adaptor与JEE应用服务器集成的示意图。图中可以看出所有的交互都通过JCA adaptor。

图中带有禁止符号的箭头表明的是从EJB会话Bean直接访问HornetQ的情况。由于不通过JCA,这种方法往往造成每次EJB访问HornetQ都要新建一个连接和会话, 使效率大降低。这被视为反设计模式(anti-pattern)。

Chapter 32, Java EE和应用服务器的集成对如何使用JCA给出了更加详细的描述。

5.4. HornetQ作为独立的服务(standalone)

HornetQ可以部署成为独立的服务器。它可运行于任何JEE应用服务器之外,作为一个独立的服务运行。 作为独立服务器运行时,HornetQ消息服务器包括一个核心服务器,一个JMS服务以及一个JNDI服务。

JMS服务用来部署服务器端hornetq-jms.xml配置文件中的JMS Queue,Topic和ConnectionFactory实例。 此外它还提供一组简单的管理接口,通过这些接口可以创建、消毁(destroy)Queue,Topic和ConnectionFactory实例。 用于可以通过JMX或连接使用这些接口。JMS服务是单独的服务,它不是HornetQ核心服务。HornetQ的核心不包含JMS相关的服务。 如果你不需要通过服务器端的xml配置文件部署任何JMS对象,也不需要JMS的管理接口,你可以选择不启动该服务。

启动JNDI服务的目的是因为JMS需要通过JNDI来获得Queue,Topic以及ConnectionFactory。如果不需要,也可以选择不启动该服务。

HornetQ允许在客户端程序中通过编程来直接创建各种JMS对象和核心对象来代替JNDI查找,所以JNDI不是必需的。 HornetQ采用JBoss Microcontainer来引导并实例化服务,并保证模块之间的依赖关系。JBoss Microcontainer是一个轻量级的POJO引导器(bootstrapper)。

图3.3给出了HornetQ独立服务器的架构。

相关配置的相关信息可以在第Section 47.1, “服务器配置”找到。$

Chapter 6. 使用HornetQ服务

本章将介绍如何使用HornetQ服务。

其中的内容包括服务器的位置,如何启动和停止HornetQ服务器。本章还将解释HornetQ的目录结构,其中的文件及其用途。

本章中所提到的HornetQ服务器是指HornetQ默认配置的独立服务器,包含JMS服务和JNDI服务。

对于运行于JBoss应用服务器中的HornetQ,其基本结构是一样的,只是有一些小的差别。

6.1. 服务的启动和停止

在HornetQ的安装目录下bin子目录中包含有一个unit/linux脚本run.sh和对应的Windows批处理文件run.bat。

如果你是在Unix/Linux环境,在bin目录下运行./run.sh

如果是在Windows环境,则在bin目录下运行 run.bat

这个脚本文件会设置classpath以及各种JVM参数,并启动JBoss Microcontainer。JBoss Microcontainer是一个轻量级的容器。 它被用来部署HornetQ的POJO对象。

要停止服务,运行其中的相应脚本:在Unix/Linux环境下,运行 stop.sh。 在Windows环境,运行 run.bat

注意HornetQ需要在Java 6及以上版本才能正常运行。

启动和停止脚本在默认条件下读取config/stand-alone/non-clustered目录下的配置文件。 如果要指向其他目录,可以在命令行实现,例如: ./run.sh ../config/stand-alone/clustered。 这一方法同样适用于Windows批处理文件。

6.2. 服务器端JVM参数的设置

在启动脚本run.shrun.bat中设置了一些JVM参数, 这些参数主要是调整Java 6的运行环境及拉圾回收的策略。我们建议采用并行拉圾回收的方法。 这种方法可以将拉圾回收所造成的延时进行平均分配,有效减少由于拉圾回收引起的长时间暂停的情况。

默认条件下HornetQ需要最大1GB的内存空间。通过-Xms-Xmx可以调整Java程序内存的使用。

你可以向启动脚本中添加其它的参数或修改已有的参数,已满足你的需要。

6.3. 服务器端的classpath

HornetQ在其classpath中寻找配置文件。

classpath被定义在run.shrun.bat脚本中。在HornetQ的发布中,启动脚本将非集群的配置文件目录加进了classpath中。该目录包括了一组配置文件,可以让HornetQ以基本的非集群方式运行。它的具体位置是在HornetQ发布根目录下 config/stand-along/non-clustered/ 子目录。

在HornetQ的发布包中包括了一组标准的配置目录,它们是:

  • 非集群方式的独立服务器配置

  • 集群方式的独立服务器配置

  • 非集群方式运行于JBoss应用服务器

  • 集群方式运行于JBoss应用服务器

当然你可以创建自己定义的配置文件目录。要注意的是将你的目录加到classpath中以便HornetQ能正确找到并加载。

6.4. Library Path

如果要在Linux上使用异步IO的日志(Asynchronous IO Journal), 你需要在java选项中指定java.library.pathrun.sh脚本可以自动完成这一步。

如果没有指定java.library.path,JVM将使用LD_LIBRARY_PATH环境变量。

6.5. 系统变量

HornetQ命令行可以接受系统变量来配置日志(logging)。有关logging配置的具体信息参见Chapter 42, 日志(Logging)

6.6. 配置文件

配置文件的路径定义在 run.shrun.bat 脚本中的classpath里。该路径下可以包含以下文件:

  • hornetq-beans.xml (如果是运行在JBoss应用服务器内,则为 hornetq-jboss-beans.xml)。这是JBoss Microcontainer的bean配置文件。其中定义了HornetQ的 各种bean,以及它们之间的依赖关系。HornetQ的bean都是一些POJO。是JBoss Microcontainer保证了这些bean的正确装载和运行。

  • hornetq-configuration.xml。这个是HornetQ的主要的配置文件。 其中的所有参数在Chapter 47, 配置参数索引中给出了解释. 在 Section 6.9, “主配置文件” 也有更多的相关信息。

  • hornetq-queues.xml。这个文件里包含了预定义的queue以及它们的配置,包括安全设置。 这是一个可选的文件,里面所有的内容都可以放在 hornetq-configuration.xml文件中。 在默认的配置文件目录下并没有这个文件。HornetQ之所以提供这个文件是为了用户便于管理他们的queue。在classpath中 允许包含多个 hornetq-queues.xml 文件。所有的这些文件都会被加载。

  • hornetq-users.xml。用户信息文件。HornetQ本身实现了一个基本的 安全管理器(security manager),它从这个文件内读取用户的安全信息,如用户名,密码和角色。 想了解更多关于安全的信息,参见 Chapter 31, 安全

  • hornetq-jms.xml。这个文件包含有JMS对象。HornetQ的默认配置中包含有JMS服务, 它从这个文件中读取JMS Queue,Topic和ConnectionFactory并将它们部署到JNDI服务中。如果你不使用JMS, 或者你不需要部署这些JMS对象,那么你就不需要这个文件。有关JMS的使用详见Chapter 7, 使用JMS

  • logging.properties 这个文件用于配置logging handlers。详见 Chapter 42, 日志(Logging)

  • log4j.xml。 这是 Log4j handler的配置文件。

Note

如果在hornetq-configuration.xml文件中将file-deployment-enabled 参数 定义为false,则HornetQ将不会加载其它的配置文件。这个参数的默认值是true。

所有配置文件中的参数都可以用系统变量来定义其值。以下用一个connector的配置来说明:

<connector name="netty">
         <factory-class>org.hornetq.integration.transports.netty.NettyConnectorFactory
           </factory-class>
         <param key="host"  value="${hornetq.remoting.netty.host:localhost}" type="String"/>
         <param key="port"  value="${hornetq.remoting.netty.port:5445}" type="Integer"/>
</connector>

在上面的配置中我们定义了两个系统变量 hornetq.remoting.netty.hosthornetq.remoting.netty.port。它们的值会被相应的系统变量的值(如果定义了的话)所替代。 如果没有定义这些系统变量,它们的默认值将分别为 localhost 及 5445。也可以不给出默认值,但如果这样就 必须要定义相应的系统变量。

6.7. JBoss Microcontainer Beans 文件

HornetQ的POJO对象是由 JBoss Microcontainer 进行加载和运行的。JBoss Microcontainer是一个轻量级的加载工具。

Note

如果是在JBoss应用服务器内运行,HornetQ同样需要一个bean的配置文件来将其部署到JBoss中。但是这与单独运行时的配置文件略有不同。 这是因为应用服务器内已经部署了一些服务,如安全服务等。所以在HornetQ中这些服务就不需要再部署了。

让我们看一个HornetQ作为单独服务器时的一个配置文件例子:

<?xml version="1.0" encoding="UTF-8"?>

<deployment xmlns="urn:jboss:bean-deployer:2.0">

<bean name="Naming" class="org.jnp.server.NamingBeanImpl"/>

<!-- JNDI server. Disable this if you don't want JNDI -->
<bean name="JNDIServer" class="org.jnp.server.Main">
   <property name="namingInfo">
      <inject bean="Naming"/>
   </property>
   <property name="port">1099</property>
   <property name="bindAddress">localhost</property>
   <property name="rmiPort">1098</property>
   <property name="rmiBindAddress">localhost</property>
</bean>

<!-- MBean server -->
<bean name="MBeanServer" class="javax.management.MBeanServer">
   <constructor factoryClass="java.lang.management.ManagementFactory"
      factoryMethod="getPlatformMBeanServer"/>
</bean> 

<!-- The core configuration -->
<bean name="Configuration" class="org.hornetq.core.config.impl.FileConfiguration">
</bean>

<!-- The security manager -->
<bean name="HornetQSecurityManager" 
      class="org.hornetq.spi.core.security.HornetQSecurityManagerImpl">
   <start ignored="true"/>
   <stop ignored="true"/>
</bean>

<!-- The core server -->
<bean name="HornetQServer" class="org.hornetq.core.server.impl.HornetQServerImpl">
   <start ignored="true"/>
   <stop ignored="true"/>  
   <constructor>
      <parameter>
         <inject bean="Configuration"/>
      </parameter>
      <parameter>
         <inject bean="MBeanServer"/>
      </parameter>
      <parameter>
         <inject bean="HornetQSecurityManager"/>
      </parameter>        
   </constructor>         
</bean>

<!-- The JMS server -->
<bean name="JMSServerManager" 
      class="org.hornetq.jms.server.impl.JMSServerManagerImpl">
   <constructor>         
      <parameter>
         <inject bean="HornetQServer"/>
      </parameter>         
   </constructor>
</bean>

</deployment>

我们从上可以看出HornetQ的单独服务器(以及核心服务器)包括了一些POJO对象:

  • JNDIServer

    很多客户端需要JNDI来获取JMS的对象,因此我们提供了一个JNDI服务来满足它们。如果不需要JNDI,可以在配置 文件中将它们注释掉。

  • MBeanServer

    这个对象提供了JMX管理接口。它是一个MBean服务器,可管理的对象可以注册到这个服务器上。 通常这就是一个JVM内部的默认的平台MBean服务器。如果不需要些服务,可以在配置文件中将其注释或删除。

  • Configuration

    这是HornetQ的Configuration对象。默认时它是一个FileConfiguration对象。它可以从文件系统中读取 配置信息。有些情况下(如嵌入式HornetQ)你可以将它定义为其它对象,以便用其它方法获得配置信息。

  • Security Manager. 可配置的安全管理器。默认的安全管理器使用 hornetq-users.xml 文件中的配置信息。 它也可以配置为一个JAAS的安全管理器。当HornetQ运行于JBoss应用服务器中时,它还可以配置为JBoss的安全管理器,以达到更紧密的集成。 如果不需要安全管理,你也可以将它删除。

  • HornetQServer

    这是HornetQ的核心服务对象,几乎所有的核心功能都在这里。

  • JMSServerManager

    这个对象将hornetq-jms.xml文件中定义的JMS的对象进行部署,比如JMS Queues, Topics 以及ConnectionFactory。它还提供一套简单的管理接口以方便地对这些JMS对象进行管理。通常它只是将工作代理给 核心服务器。如果你不需要在服务器端进行JMS对象的部署与管理,可以将它从配置中去掉。

6.8. JBoss AS4 MBean 服务

Note

本节只讨论在JBoss AS 4上配置HornetQ。其与JBoss Microcontainer的配置很相似。

<?xml version="1.0" encoding="UTF-8"?>
<server>

   <mbean code="org.hornetq.service.HornetQFileConfigurationService"
      name="org.hornetq:service=HornetQFileConfigurationService">
   </mbean>

   <mbean code="org.hornetq.service.JBossASSecurityManagerService"
      name="org.hornetq:service=JBossASSecurityManagerService">
   </mbean>

   <mbean code="org.hornetq.service.HornetQStarterService" 
      name="org.hornetq:service=HornetQStarterService">
      <!--lets let the JMS Server start us-->
         <attribute name="Start">false</attribute>

      <depends optional-attribute-name="SecurityManagerService"
         proxy-type="attribute">org.hornetq:service=JBossASSecurityManagerService</depends>
      <depends optional-attribute-name="ConfigurationService"
         proxy-type="attribute">org.hornetq:service=HornetQFileConfigurationService</depends>
   </mbean>

   <mbean code="org.hornetq.service.HornetQJMSStarterService"
      name="org.hornetq:service=HornetQJMSStarterService">
      <depends optional-attribute-name="HornetQServer"
         proxy-type="attribute">org.hornetq:service=HornetQStarterService</depends>
   </mbean>
   
</server>
            

这个jboss-service.xml包含在hornetq-service.sar文件中,它用来配置AS4中嵌入运行的HornetQ。 在这个配置文件中我们启动了以下几个服务:

  • HornetQFileConfigurationService

    这个MBean服务的任务是管理 FileConfiguration POJO的生命周期。

  • JBossASSecurityManagerService

    这个MBean服务管理着 JBossASSecurityManager POJO的生命周期。

  • HornetQStarterService

    这个MBean服务管理着HornetQServer POJO。它依赖于 JBossASSecurityManagerService 和 HornetQFileConfigurationService 这两个MBean。

  • HornetQJMSStarterService

    这个MBean服务管理着 JMSServerManagerImpl POJO对象。如果不需要JMS,可以去掉这个服务。

  • JMSServerManager

    用于启动JMSServerManager。

6.9. 主配置文件

HornetQ 核心服务的配置保存在 hornetq-configuration.xml文件中。          FileConfiguration bean 读取这个文件来对消息服务器进行配置。

HornetQ有很多的配置参数。采用默认的配置在绝大多数情况下可以很好的运行。事实上每一个参数都有默认的处理,因此一个只包含有一个空 的configuration的配置文件是一个有效的文件。对各个参数的解释贯穿于本手册。你还可参参照 这里来查找你想看的参数。

Chapter 7. 使用JMS

很多用户喜欢使JMS,因此HornetQ提供了JMS服务。

JMS是一个普遍使用API标准,绝大多数的消息系统都提供JMS接口。如果你对JMS还不熟悉,建议你先参考一下 Sun的 JMS 教程

HornetQ还提供了许多的JMS的示例程序(examples)。比如简单的JMS Queue和Topic的示例,就很适合初学者做为了 解HornetQ JMS的起点。Chapter 11, 例子对这些示例作了详细的说明。

下面我们将带领读者一步步地配置HornetQ的JMS服务,并创建一个简单的JMS程序。我们还将展示如何在没有JNDI的情况下 来使用HornetQ中的JMS。

7.1. 一个简单的订购系统

本章我们将用一个简单的订购系统做为一个例子。尽管它十分简单,但是它能够很好地向大家展示JMS的设置和使用。

本例中有一个名为 OrderQueueJMS队列,还将有一个 MessageProducer 用来向队列发送订购消息。发送到队列的消息由一个 MessageConsumer 来接收。

我们所用的队列是持久(durable)的队列,也就是说这个队列不受服务器故障的影响。当服务器 发生故障重新启动后,这个队列仍然存在。我们需要把这个队列事先部署好。办法就是将队列写到JMS的配置文件中。当服务启动 时将配置文件中的队列自动部署好。

7.2. JMS服务的配置

hornetq-jms.xml文件包含了需要创建与部署的JMS Queue,Topic和ConnectionFactory 的实例。该文件必须要指定在classpath中。从这个文件中部署好的对象都可以用JNDI来找到。

JMS客户端可以利用JMS ConnectionFactory对象来创建与服务器的连接。ConnectionFactory中有关于服务器地址的 信息以及各种参数。通常使用这些参数的默认值即可。

这里我们将要在服务器端部署一个JMS队列和一个JMS ConnectionFactory (连接工厂)。当然完全可以部署多个JMS对象。 下面给出了具体的配置内容:

<configuration xmlns="urn:hornetq" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="urn:hornetq ../schemas/hornetq-jms.xsd ">
    
    <connection-factory name="ConnectionFactory">
        <connectors>
           <connector-ref connector-name="netty"/>
        </connectors>
        <entries>
            <entry name="ConnectionFactory"/>           
        </entries>
    </connection-factory>
    
    <queue name="OrderQueue">
        <entry name="queues/OrderQueue"/>
    </queue>
    
</configuration> 
        

在本文件中我们部署了一个名为 ConnectionFactory 的一个连接工厂,并且将其绑定到 JNDI中。如果需要可以将一个连接工厂绑定为多个名称。只需要将绑定的名字加入到 entry 中即可。

Note

在JMS ConnectionFactory的配置中引用了一个名为 nettyconnector。 它实际上指向的是HornetQ核心中部署的一个连接器(connector)。它的配置在HornetQ的核心配置文件 hornetq-configuration.xml 中。它定义了采用何种传输与服务器连接。

7.3. JNDI的配置

当客户端使用JNDI时需要定义一些JNDI的参数。这些参数主要用来确定JNDI服务的地址。这些参数通常保存在 一个名为 jndi.properties 的文件中。这个文件需要在客户端的classpath中。或者你 可以在创建JNDI的InitialContext时将这些参数传进去。想了解全面的JNDI知识,可以参见 Sun JNDI 教程

要与JBoss的JNDI Server进行通迅,需要指定以下的JNDI参数:

java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
java.naming.provider.url=jnp://myhost:1099
java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces                        
        

其中的 myhost 是 JNDI server的主机名或IP地址。 1099是端口号,根据不同的配置, 端口号也可能不同。

在默认的单独方式(standalone)配置中,JNDI服务端口等参数定义在hornetq-beans.xml 文件中的 JNDIServer bean下,如:

<bean name="JNDIServer" class="org.jnp.server.Main">
    <property name="namingInfo">
        <inject bean="Naming"/>
    </property>
    <property name="port">1099</property>
    <property name="bindAddress">localhost</property>
    <property name="rmiPort">1098</property>
    <property name="rmiBindAddress">localhost</property>
</bean>                        
        

Note

如果你的JNDI服务器与客户端不在同一台机器上,一定不要忘记将bindAddress改成相应的地址, 千万不能用localhost

Note

只有当HornetQ作为独立服务器运行时 才可以配置JNDIServer bean。当HornetQ运行于JBoss应用服务器中时,由于JBOSS服务器已经提供了 JNDI服务,所以就不需要再进行配置了。

7.4. 程序代码

下面给出的例子中的代码:

首先我们创建一个JNDI的Initial Context:

InitialContect ic = new InitialContext();

下面我们查找 connection factory:

ConnectionFactory cf = (ConnectionFactory)ic.lookup("/ConnectionFactory");

然后查找 Queue:

Queue orderQueue = (Queue)ic.lookup("/queues/OrderQueue");

接下来用拿到的ConnectionFactory建立JMS连接:

Connection connection = cf.createConnection();

再创建一个非事务的、AUTO_ACKNOWLEDGE方式的JMS Session:

Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

创建一个 MessageProducer 以向队列发送订单消息:

MessageProducer producer = session.createProducer(orderQueue);

创建一个 MessageConsumer 以从队列中接收订单消息:

MessageConsumer consumer = session.createConsumer(orderQueue);

要启动连接,以使消息能传递给接收者:

connection.start();

发送一个简单的TextMessage:

TextMessage message = session.createTextMessage("This is an order");
producer.send(message);

之后接收这个消息:

TextMessage receivedMessage = (TextMessage)consumer.receive();
System.out.println("Got order: " + receivedMessage.getText());
        

看起来就是这么简单。 在HornetQ有发布包中有很多各种各样的JMS例子供用户参考。

Warning

请注意,JMS的连接(connection)、会话(session)、生产者(producer)和消费者(consumer) 对象是可以重用的。

如果每发送或接收一个消息都要重新创建这些JMS对象,是不符合设计模式的要求的。这样做会造成应用程序 的性能很差。这方面的内容在Chapter 46, 性能调优中将会进一步的讨论。

7.5. 不使用JNDI而直接创建JMS的对象

尽管采用JNDI对 JMS 的各种管理对象(Administered Objects) (即JMS Queue, Topic and ConnectionFactory)是很常用的方法,但在有些 情况时JNDI不可用,或者你不需要用JNDI时,如何还能正常使用JMS呢?

HornetQ允许你不通过JNDI也能使用JMS。HornetQ支持直接创建JMS的各种对象而无需JNDI的存在。

Chapter 11, 例子中包括有这样的例子供读者参考。

下面我们就将上述那个简单的例子重写,以抛开对JNDI的依赖:

我们通过HornetQJMSClient类来方便地创建JMS的ConnectionFactory。注意这里要提供各种连接参数和定义 所用的传输方式。有关连接器(connector)的信息参见Chapter 16, 传输层的配置

              
TransportConfiguration transportConfiguration = 
                     new TransportConfiguration(NettyConnectorFactory.class.getName());                
ConnectionFactory cf = HornetQJMSClient.createConnectionFactory(transportConfiguration);
        

同样利用HornetQJMSClient类创建JMS Queue对象:

Queue orderQueue = HornetQJMSClient.createQueue("OrderQueue");

然后用连接工厂创建 JMS 连接:

Connection connection = cf.createConnection();

还有非事务的\AUTO_ACKNOWLEDGE方式的 JMS 会话(session):

Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

以及用于发送消息的MessageProducer:

MessageProducer producer = session.createProducer(orderQueue);

和接收消息的 MessageConsumer:

MessageConsumer consumer = session.createConsumer(orderQueue);

启动连接:

connection.start();

创建一个简单的 TextMessage 并将其发送到队列:

TextMessage message = session.createTextMessage("This is an order");
producer.send(message);

接收消息:

TextMessage receivedMessage = (TextMessage)consumer.receive();
System.out.println("Got order: " + receivedMessage.getText());
        

7.6. Client ID的设置

在建立持久的订阅(subscription)时,JMS客户需要有一个客户ID (client id)。我们可以通过配置 connection factory来定义它。(其中的 client-id项)。这样所有通过这个 connection factory来创建的连接都具有这个客户ID。

7.7. 设置DUPS_OK的Batch Size

如果JMS的通知模式为DUPS_OK,我们可以配置接收者(consumer)以使得它以批为单位 发送通知,而不是一个一个地发通知。这样做可以节省很多带宽,效率高。配置的方法是设置connection factory下 的dups-ok-batch-size项。单位是字节(byte)。默认值是1024 * 1024 bytes = 1 MiB。

7.8. 设置事务(Transaction)的Batch Size

当在一个事务内接收消息时,可能通过配置使接收者采用批量的方式发送通知,而不是一个一个的发送。这样也可以节省带宽。 配置方法是设置connection factory下的transaction-batch-size项。 单位是字节(byte)。默认值是1024 * 1024。

Chapter 8. 使用HornetQ内核

HornetQ内核是一个与JMS无关的消息系统,它有一套自己的API。我们称它为内核API.

你可以直接使用内核API。使用内核API可以完成JMS同样的功能,只是比起JMS API使用更加简单方便。另外,内核API 还提供了JMS不具有的额外的功能。

8.1. 内核消息系统的相关概念

内核消息系统中有许多概念是与JMS相似的,但有些方面是不同的。总的来说内核消息系统的接口相对简单。这是因为 在内核中没有队列(queue)、话题(topic)和订阅(subscription)的概念。下面我们就内核消息中的概念作逐一介绍。 但是每个API的详细说明还是要参见相应的javadoc。

8.1.1. 消息

  • 一个消息就是客户端与服务器传递信息的单位数据。

  • 一个消息有一个消息体(body),即一个缓存用以写入数据或从中读取数据。

  • 一个消息有一个属性集,这个属性集实际上包含的是主键-值的集合。每个属性的主键是一个字符串,值可 以是一个整数(integer)、长整数(long)、短整数(short)、字节(byte)、字节数组(byte[])、 字符串(String),双精度值(double)、浮点数(float)或是布尔值(boolean)。

  • 每个消息都有一个地址(address)做为它的目的地。当一个消息被发到 服务器上时,它会被路由到与该地址绑定的所有队列中(queue)。如果queue配置了过滤器(filter), 那么只有与过滤器相匹配的消息才会被路由到该queue。一个地址可以绑定多个queue,也可以一个也不 绑定。注意这里所说的queue是内核的概念,不是JMS的queue。除了queue之外,还有其它一些实体可以 绑定到某一地址上。比如divert(转发器)

  • 消息可以是持久的(durable)或非持久的(non-durable)。持久的消息不会因为服务器故障或重启而丢失。 非持久消息则会因为服务器的故障或重启而丢失。

  • 消息具有优先级。优先级的值为从0到9的整数。0代表最低优先级,9代表最高优先级。HornetQ总 会尝试先传送优先级高的消息。

  • 消息还有一个可选的失效时间。如果一个消息过了失效时间,HornetQ将不再传送它。

  • 消息还有一个可选的时间戳(timestamp)。这个时间戳表示的是消息被发送的时间。

  • HornetQ还支持大消息的发送。它可以处理大到内存装不下的超大消息。

8.1.2. 地址(Address)

HornetQ服务器保存有地址和queue的映射集。一个地址对应零个或多个queue。每个queue还可以拥有消息 过滤器(filter)。当一个消息在服务器内进行路由时,它将会被送往与其地址相绑定的所有的queue中。但是 如果其中某个queue有过滤器,那么只有与其过滤器相匹配的消息才会被发到这个queue中。

其它的实体如diverts也可以与一地址进行绑定,消息也会被同样 路由到相应的实体中。

Note

在内核中,没有Topic的概念。只有地址(address)queue

假如想在内核实现JMS topic的功能,只要将一地址绑定到多个queue即可。其中的每一个queue就相当 于一个订阅(subscription)。类似地,一个JMS queue则可以通过一个地址与一个queue的绑定来实现。

8.1.3. Queue

Queue可以的持久的。意思是如果queue中的消息是持久的,那么当发生服务器故障或重启时,这些消息不会丢失。 Queue也可是非持久的,这意谓着如果服务器发的故障或重启,queue中的消息将会丢失,不管消息是不是持久的。

Queue也可以是临时的,意思是临时的queue在客户端断开连接时,它将会被删除。

Queue可以有一个过滤器表达式。服务器在向这样的queue路由消息时,先判定消息是否与过滤器表达式相匹配, 只有匹配的消息才会被发到该queue。

一个地址可以绑定多个queue、。但是一个queue只能被绑定到一个地址上。

8.1.4. ClientSessionFactory

客户端使用 ClientSessionFactory 类的实例创建 ClientSession 实例。 ClientSessionFactory 知道如何连接到服务器并创建会话(session)。它是可以根据不同需要灵活配置的。

ClientSessionFactory实例是通过 HornetQClient 工厂类创建的。

8.1.5. ClientSession

客户端使用ClientSession来发送和接收消息,并控制事务的使用。ClientSession可以支持事务性 和非事务性的应用。它还提供了一个 XAResource 接口,因些它可以加入到一个JTA 交易中。

ClientSession 管理着ClientConsumers和ClientProducers。

ClientSession 实例可以注册一个可选的 SendAcknowledgementHandler。每当消息被送达HornetQ服务器中时, HornetQ就用它来异步地发出通知。有了这个独特的功能,客户可以不必阻塞在每次消息的发送操作上来保证 消息安全到达服务器。如果采用阻塞的方法,那么每一个消息的发送都要包括往返两次的网络传递操作,开销 是很大的。有了这个异步方式就可以避免这种开销,建立真正的异步的端到端间的系统。这是标准的JMS接口 无法做到的。参见 Chapter 20, 发送与提交的保证了解相关的更详细的信息。

8.1.6. ClientConsumer

客户端使用 ClientConsumer 实例来接收来自queue的消息。HornetQ的内核同时支持 同步与异步的消息接收。ClientConsumer 实例可以配置有可选的过滤器。它只接收与过滤 器相匹配的消息。

8.1.7. ClientProducer

客户端使用ClientSession创建 ClientProducer 实例 来向服务器发送消息。ClientProducer可以指定一个地址,用来向其发送消息。或者不指定地址,消息在发送时 再指定地址。

Warning

请注意 ClientSession、 ClientProducer和ClientConsumer 实例是可以被 重用的。

在每次发送或接收一个消息时都创建新的 ClientSession, ClientProducer 和 ClientConsumer是不符合 设计模式的要求的。这样做会导致性能低下。在Chapter 46, 性能调优中我们会做进一步的讨论。

8.2. 一个内核的应用实例

下面是一个非常简单的使用内核API来发送的接收消息的实例:

ClientSessionFactory factory =  HornetQClient.createClientSessionFactory(
                                        new TransportConfiguration(
                                           InVMConnectorFactory.class.getName()));

ClientSession session = factory.createSession();

session.createQueue("example", "example", true);

ClientProducer producer = session.createProducer("example");

ClientMessage message = session.createMessage(true);

message.getBodyBuffer().writeString("Hello");

producer.send(message);

session.start();

ClientConsumer consumer = session.createConsumer("example");

ClientMessage msgReceived = consumer.receive();

System.out.println("message = " + msgReceived.getBodyBuffer().readString());

session.close();

Chapter 9. JMS与内核API之间的映射关系

本意讲述JMS的目标实体(destination)如何映射到HornetQ的地址(addresses)。

HornetQ的内核没有JMS的任何实现。在内核中没有topic的概念,它是通过在一个地址上(相当于topic的名字)绑定 零或多个queue来实现JMS topic的功能的。每个绑定的queue相当于该topic的一个订阅(subscription)。 类似地通过在一个地址上(相当于queue的名字)绑定单一的queue就可以实现JMS queue的功能。

按照惯例,所有的JMS queue所对应的内核queue的名字都以jms.queue.做为开头。比如 当JMS queue的名字是"orders.europe"时,其对应的内核queue的名字应该是"jms.queue.orders.europe"。 那么内核queue所绑定的地址的名字和该内核queue的名字是相同的。

同样,所有JMS topic所对应的内核地址的名字都以 "jms.topic."为前缀。比如当一个JMS topic的名字是"news.europe" 时,它对应的内核的地址应该是"jms.topic.news.europe"。

换句话说就是如果你向JMS queue “orders.europe"发送一个消息,这个消息就会被路由到绑定在内核地址为“jms.queue.orders.europe” 的同名内核queue中。 如果是向JMS topic “news.europe“发送一个消息,它会被路由到绑定到内核地址为 ”jms.topic.news.europe“的所有的内核queue中。

具体要配置一个名为“orders.europe"的JMS队列时,你需要配置相应的内核queue“jms.queue.orders.europe“:

<!-- expired messages in JMS Queue "orders.europe"
    will be sent to the JMS Queue "expiry.europe" -->
<address-setting match="jms.queue.orders.europe">
   <expiry-address>jms.queue.expiry.europe</expiry-address>
   ...
</address-setting>
   

Chapter 10. 客户端的Classpath

HornetQ的客户端Classpath需要有几个jar文件。具体是哪几个要根据客户端 是需要内核API、JMS和JNDI中的哪些服务来确定。

Warning

本章所提及的所有jar文件全部在HorneQ发布包的 lib目录下。在使用中一定 要确保所有的jar文件属于同一个发布版本。将不同版本的jar混在一起使用可能造成一些难以发现的错误。

10.1. 使用HornetQ内核的客户端

如果客户端只使用HornetQ内核(非JMS客户端),需要将 hornetq-core-client.jarnetty.jar 放到classpath中。

如果客户端运行于Java 5 虚拟机上, 请使用hornetq-core-client-java5.jar

10.2. JMS客户端

如果客户端使用JMS,需要在classpath上增加两个jar文件: hornetq-jms-client.jarjboss-jms-api.jar

如果客户端运行于Java 5 虚拟机上,请使用 hornetq-jms-client-java5.jar

Note

jboss-jms-api.jar中包含的只是 javax.jms.* 包中的接口类。 如果这些类已经在你的classpath中,则你就不需要这个jar文件。

10.3. 需要JNDI的JMS客户端

如果你的JMS客户端使用JNDI来查找HornetQ单独服务器上的对象,你需要将 jnp-client.jar 增加 到你的classpath中。

Chapter 11. 例子

在HornetQ的发布包中有超过70个不同的例子。这些例子直接可以运行。它们分别展示了HornetQ所具有的各种功能。

所有的例子都在HornetQ发布包的 examples目录下。所有的例子被分成了两大类: JMS例子和内核例子。JMS例子展现的是JMS的各种功能,内核的例子则展示的是内核API的功能。

此外HornetQ还提供了一些Java EE的例子,这些例子需要JBoss应用服务器才能运行。

11.1. JMS 例子

要运行一个JMS例子,只要进入到相应例子的子目录,运行 ./build.sh (或者 在Windows平台上运行build.bat)即可。

下面列出的这些JMS例子并配有简要的说明。

11.1.1. 应用层的失效备援(Failover)

HornetQ支持应用层的失效备援。这在服务器端没有复制(replication)配置的情况下是很有用的。

应用程序可以注册一个JMS ExceptionListener。当HornetQ检测到连接故障时,它会 通知这个注册的Listener。

这个ExceptionListener在接到HornetQ的通知后可以与其它的节点创建 新的连接、会话等对象,以使应用程序能继续运行。

应用层的失效备援是实现高可获得性(HA)的一种方法。它与自动失效备援不同之处在于它需要编写额外的代码。 同时由于发生故障时旧的会话结束,这会造成那些还没来得及提交的工作丢失,还会造成任何没有通知的消息被重发。

11.1.2. 内核桥(Bridge)例子

bridge例子展示的是将一个内核桥部署到一个服务器上,从本地的queue接收消息并将其转发到 另一服务器的地址上。

内核的bridge可用来在两个互相分开的HornetQ的服务器间建立一个消息流。它可以处理临时性的连接故障,特别适用于 不可靠的网络的情况。广域网就是一个例子。

11.1.3. 浏览器(Browser)

browser例子展示的是在HornetQ中如何使用JMS QueueBrowser

有关JMS queue的概念在JMS 1.1 specification有明确的定义,这里就不再叙述。

一个QueueBrowser可以用来观察queue中的消息而影响它们。它可以观察queue中的全部 消息,也可以定义一个选择器(selector)来选择性地察看消息。

11.1.4. Client Kickoff

client-kickoff例子展示的是如何利用JMX管理接口通过已知的IP地址来断开客户端的连接。

11.1.5. 客户端的负载均衡

client-side-load-balancing例子展示的是通过一个JMS连接可以在集群的不同节点上创建 会话。也就是说HornetQ可以对客户端的会话创建进行集群内的负载均衡。

11.1.6. 集群分组

与分组(grouping)例子相似,只是本例所展示的是集群的情况。发向不同节点的具有相同组id的消息 都会传送到同一个节点上的同一个接收者(consumer)。

11.1.7. 集群队列

clustered-queue 例子将一个JMS queue部署到两个节点上。这两个节点组成一个集群。 我们在每个节点上各创建一个接收者(consumer),但只在其中一个节点上创建一个发送者(producer)。利用发送者 发送一些消息,然后确认两个接收者以轮换方式(round-robin)接收这些消息。

11.1.8. 单机集群

clustered-standalone例子所展示的是如何在同一台机器上配置并运行 3个节点的集群。在每个节点上都创建了一个JMS topic的订阅者(subscriber)。只在其中一个节点上 创建了一相发送者来向这个topic发送一些消息。然后我们确认所有的subscriber都接收到了这些消息。

11.1.9. 集群的Topic

clustered-topic例子将一个JMS topic部署到两个节点上。这两个节点组成一个集群。 然后在每个节点上创建了一个订阅者(subscriber),只在一个节点上创建一个发送者(producer)。通过这个发 送者发送一些消息,确认两个订阅者都收到了这些消息。

11.1.10. 限制接收速率

HornetQ可以控制一个JMS消息接收者接收消息的速度。这是在创建或部署连接工厂时通过其配置参数来完成的。

如果设置了这个速度的限制,HornetQ会保证其向接收者传递消息的速度永远不会超过这个限制。

11.1.11. 死消息(Dead Letter)

dead-letter例子让你了解如何定义和处理死消息。有时候消息由于某种原因不能成功 地传递出去,比如接收者在接收消息的交易中发生回滚。

发生回滚后,消息被”退回“到JMS目标(destination)准备进行重发。这一过程可能会被不停地重复下去造成 消息永远发不出去,而且浪费系统的时间。

为了避免上述情况的发生,消息系统引入了死消息的概念:即当一个消息被反复重发不成功达到一定的次数时,该消息 便成为了死消息,它将从所属目标(destination)中删除并发送到一个称为死消息目标的目标。用户可以从死消息目标           上接收这些死消息以便进行分析。

11.1.12. 延迟再发送

delayed-redelivery是一个展示如何配置HornetQ延迟再发送消息的例子。

当客户端经常发生故障或发生事务回滚时,消息会不停地重复发送,这样会造成CPU和网络资源被不间断的 重复发送所占用,影响其它工作的进行。延迟再发送可以有效地减轻这种情况。

11.1.13. 转移(Divert)

HornetQ通过配置可以将消息从一个地址自动地转移到另一地址。这个例子就是向用户展示转移的配置和使用。

11.1.14. 持久订阅(Durable Subscription)

durable-subscription是一个在HornetQ中如何使用持久订阅(durable subscription)的例子。持久订阅是标准JMS的一部分,在JMS 1.1规范中有它的详细定义。

对于一个持久订阅来说,它的消息可以在订阅没有处于接收状态时被保留。另外,如果发到它的消息是持久 消息的话,这些消息可以在服务器故障或重启时不丢失。

11.1.15. 嵌入方式(Embedded)

embedded是一个如何将HornetQ服务嵌入到你的应用中的例子。

11.1.16. HTTP 传输协议的支持

http-transport展示了HornetQ如何支持在传输层使用HTTP协议来发送和接收消息。

11.1.17. 直接实例化JMS对象

JMS 对象是指 连接工厂(ConnectionFactory)队列(Queue)话题(Topic) 的实例。通常情况下它们通过JNDI服务 来获取。它们在JMS术语中被称为“被管理的对象(administered objects)”。

有的时候客户端没有JNDI服务可用,或者不适合使用JNDI。那么在没有JNDI的情况下HornetQ允许直接在客户端 将这些JMS对象实例化。

11.1.18. 拦截器(Interceptor)

HornetQ可以配置拦截器以便用户可以自己处理各种各样的消息事件。这个例子就是给用户展示如何使用 拦截器。

11.1.19. JAAS

jaas是一个如何配置JAAS安全模式的例子。HornetQ可以使用JAAS来进行用户的验证与权限控制。

11.1.20. JMS桥(Bridge)

jms-brige是一个在两个单独HornetQ服务器之间设置桥的例子。

11.1.21. JMX管理

jmx例子展示了如何使用JMX来管理HornetQ。

11.1.22. 大消息

large-message例子给用户展示了使用HornetQ来发送和接收大消息的功能。HornetQ 支持超大消息的发送与接收。这些消息可以大到内存无法装下。它的大小只受服务器的硬盘空间的限制。

在服务器端大消息是被持久化的,所以它可以承受服务器的崩溃或重启而不丢失或损坏。

11.1.23. 最新值队列

last-value-queue向用户展示了如何定义与使用最新值队列。当在配置文件定义好 最新值的参数后,这些最新值队列就会自动地用新的消息取代旧的消息,也就是说旧的消息被抛弃掉。这样一个最新 值的队列总是保留最新的消息在队列中。

股票价格消息就是一个典型的最新值队列的用例。对用户来说他所关心的是一支股票的最新价格,对于过去的价格 是没有多大兴趣的。

11.1.24. 分布式队列的负载均衡

clustered-queue例子中配置了一个2节点的HornetQ服务集群。在集群上部署了 一个分布式JMS队列。

然后在一个节点上创建了一个发送者(producer),在两个节点上分别创建一个接收者(consumer)。通过 发送者向队列发送一些消息然后被两的接收者以轮流(round-robin)的方式接收。

本例说明了HornetQ可以将消息向集群中的每个接收者分布式地传递消息。

11.1.25. 管理

management例子展示的是如何使用JMS消息来实现对HornetQ的管理。

11.1.26. 管理通知

management-notification展示了HornetQ如何以JMS消息的形式向用户发送 管理通知。当某些事件发生时(如接收都创建,关闭;地址创建与删除;安全验证失败等等),HornetQ会向客户 发出JMS消息以通知客户这些事件的相关信息。客户接收到这些信息后可以作出相应的处理。

11.1.27. 消息计数器

message-counters是一个展示如何使用消息计数器获取JMS队列中的消息信息。

11.1.28. 消息失效

expiry例子中包括了如何定义和使用消息失效期。消息如果在消息服务器中存留超过一定 的时间,就可以被删除。根据JMS规范,接收者就不应该接收到已经过了失效期的消息。(但是并不保证一定接收不到)。

HornetQ可以给一个队列配上一个失效地址,当队列中的消息失效时,它们就会从队列中删除并转移到该失效地址。 这些“失效"的消息可以从失效地址中接收并进行分析。

11.1.29. 消息组

message-group展示的是如何在HornetQ中配置消息组。消息组可以让你的消息 只被一个接收者接收。属于一个消息组中的消息有如下特性:

  • 同一个消息组中的消息都有相同的组ID。即它们的JMSXGroupID属性值相同。

  • 第一个接收到消息组中的消息的接收者将会接收到所有该组中的消息。

11.1.30. 消息组(例2)

message-group2是另外一个消息组的例子。它展示的是通过配置连接工厂来实现 消息组的功能。

11.1.31. 消息优先级

消息优先级会影响消息的传递顺序。

消息优先级由标准的JMS消息头属性JMSPriority的值确定。参见JMS 1.1规范。

优先级是一个0到9之间的整数值。当消息被传递时,根据优先级的不同消息的传递顺序会收到影响。优先级 高的消息往往会比优先级低的先传递给接收者。

优先级相同的消息会按照它们到达目标的顺序来传递。在JMS 1.1规范中有详细的规定。

11.1.32. 零接收缓冲

默认时HornetQ的接收者客户端有一个消息缓冲,它用来保存从服务器上预先接收的消息。这样做是为了提高 性能。因为如果没有这个缓冲,每次调用receive()或onMessage()后,HornetQ就会访问一次服务器请求下 一个消息。

这样每接收一个消息就会增加一次网络往返的传输。因此,HornetQ在默认情况下使用客户端的接收缓冲来 预先接收消息,以提高效率。

然而在某些情况下这样的缓冲不符合应用需要。那么可以将缓冲关闭。本例就是展示如何关闭接收缓冲。

11.1.33. 带有服务器数据复制的非事务失效备援

non-transaction-failover例子展示了由两个服务器组成的高可获得性主/从关系。 客户端使用一个非交易的JMS会话(session)可以在主节点崩溃的情况下从主节点失效备援到备份节点。

HornetQ的这一功能是通过主、备节点间的状态复制来实现的。当主节点发生故障崩溃时,客户端的连接可以自动 转向备份节点以继续的发送或接收消息。当使用非事务性的会话时,有可能发生消息丢失或重复传递的情况。

11.1.34. 分页(paging)

paging例子展示了HornetQ在内存有限时如何支持超大容量的队列。当内存不够时, HornetQ会将消息保存到磁盘上;需要时再将它们从磁盘读入内存。这一过程对用户是透明的。

11.1.35. 预先通知

标准的JMS支持3种通知模式: AUTO_ACKNOWLEDGE(自动通知)、CLIENT_ACKNOWLEDGE客户通知以及 DUPS_OK_ACKNOWLEDGE可重复通知。请参阅JMS规范和教程来进一步了解这几种通知方式。

所有方式都需要从客户端发通知到服务器端。有时当发生故障时你并不在乎丢失一些消息,这样可以采用在服务器端在消息 传递前进行通知就显得比较合理。本例就是展示如何使用这一HornetQ独有的通知方式。

11.1.36. 消息发送速度限制

producer-rte-limit例子展示了如何设置HornetQ的最大消息发送速率。它控制消息的 发送者(JMS producer)发送消息的最大速度。

11.1.37. 队列

这一个简单的JMS队列的例子。

11.1.38. Message再分配

queue-message-redistribution例子展示了如何将消息在集群的各节点同名的队列 间进行再分配。

11.1.39. 队列请求

这是一个简单的实现队列请求的例子。

11.1.40. 带消息选择器(selector)的队列

queue-selector例子展示了如何使用选择器来有条件地选择消息进行接收。

11.1.41. 节点连接重试

reattach-node例子展示了如何使客户端在发生故障时重试连接到原有服务器,而不是 直接放弃并通知用户的ExceptionListener。通过配置,客户端可以自动的不断重试连接直到服务器连接上为止。

11.1.42. 请求/应答

一个简单的展示JMS 请求/应答消息方式的例子。

11.1.43. 定时消息

scheduled-message例子展示了如何向HornetQ发送定时消息(scheduled message)。 所谓定时消息就是在规定的将来的某一时间传递的消息。

11.1.44. 安全

security例子展示了如何配置HornetQ的安全参数。

11.1.45. 发送通知

send-acknowledgements例子展示了如何使用HornetQ提供的高级异步发送通知功能 (asynchronous send acknowledgements)。这是服务器向客户端通知消息已经 被接收。

11.1.46. SSL传输支持

ssl-enabled例子展示了如何配置使用SSL来发送与接收消息。

11.1.47. 静态消息选择器

static-selector例子展示了如何配置HornetQ核心队列的静态消息选择器(又称过滤器)。

11.1.48. 使用JMS方法来配置静态消息选择器

static-selector-jms例子采用JMS方法来配置HornetQ的队列的静态选择器(过滤器)。

11.1.49. Stomp

stomp例子展示了如何配置HornetQ来发送与接收Stomp消息。

11.1.50. Stomp与Web Sockets

stomp-websockets例子给出了如何配置一个HornetQ服务器直接从Web浏览器 中(需要支持Web Socket)发送和接收Stomp消息。

11.1.51. 对称型集群

symmetric-cluster例子展示如何设置一个HornetQ的对称型集群。

HornetQ的集群配置是非常灵活的。你可以根据需要设置不同的集群结构。最常用的就是对称型的集群了。这是在应用 服务器中常见的集群类型。

对称型的集群具有同一性,即每个节点与其他节点处于同等地位,并且每一个节点都与其他任一节点相连接。

11.1.52. 临时队列

本例展示的是如何使用一个JMS临时队列(temporary queue)。

11.1.53. 话题(Topic)

一个简单的JMS topic的例子。

11.1.54. 话题体系(Topic Hierarchy)

HornetQ支持话题体系。所谓话题体系就是允许你使用通配符来注册一个订阅(subscriber),这样所有发送到 与该通配符相匹配的地址的消息都可以被该订阅收到。

11.1.55. 话题选择器例1

topic-selector-example1例子展示的是如何创建带有选择器的JMS话题(Topic)订阅。

11.1.56. 话题选择器例2

topic-selector-example2 是另一个使用带有选择器的JMS话题(Topic)订阅的例子。

11.1.57. 带有数据复制的事务性失效备援

transaction-failover例子展示了由两个服务器组成的高可获得性主/备关系。 客户端使用一个交易的JMS会话(session)可以在主节点崩溃的情况下从主节点失效备援到备份节点。

HornetQ的这一功能是通过主、备节点间的状态复制来实现的。当主节点发生故障崩溃时,客户端的连接可以自动 转向备份节点以继续的发送或接收消息。当使用事务性的会话时,能够保证消息被传递并且只被传递一次。

11.1.58. 事务性会话

transactional例子展示了如何在HornetQ中使用事务性会话。

11.1.59. XA Heuristic

xa-heuristic例子给出了如何通过HornetQ的管理接口来做出一个XA的heuristic决定。 一个XA的heuristic决定是一个单方面的对一个已经准备的(prepared)XA事务分支提交或回滚的决定。

11.1.60. XA 接收

xa-receive例子展示的是如何使用HornetQ在一个XA事务内接收消息。

11.1.61. XA 发送

xa-send例子展示的是如何使用HornetQ在一个XA事务内发送消息。

11.1.62. XA与事务管理器(transaction manager)

xa-with-jta展示了如何在HornetQ中使用JTA接口来控制事务。

11.2. 核心API的例子

运行核心API的例子很简单,只要进到相应的例子目录下运行“ant"即可。

11.2.1. 嵌入式

本例展示了如何将HornetQ服务器嵌入到你的代码中。

11.3. Java EE 例子

绝大多数的Java EE例子都可以按如下步骤运行:进入到相应的目录中,先运行ant deploy。 这一步创建了一个新的JBoss的服务器配置方案并启动它。当JBoss服务器启动后,再运行ant run 启动例子程序。有些例子需要额外的步骤,请参见相关的例子的文档。

11.3.1. EJB/JMS 事务

这个例子展示了在一个事务内使用EJB和JMS的方法。

11.3.2. HAJNDI (High Availability)

这个例子展示了如何使用集群中的JNDI服务。

11.3.3. 资源适配器的配置(JCA)

本例展示了如何配置HornetQ的JCA适配器的各种参数。

11.3.4. 资源适配器运程服务器的配置

本例展示了如何配置HornetQ的JCA适配器来与远程的HornetQ服务器通迅。

11.3.5. JMS 桥(Bridge)

本例展示了如何使用HornetQ的JMS bridge。

11.3.6. MDB (消息驱动Bean)

一个消息驱动bean的例子。

11.3.7. Servlet传输

一个展示在HornetQ中使用servlet作为传输层的例子。

11.3.8. Servlet SSL 传输

一个展示在HornetQ中使用基于SSL之上的servlet传输的例子。

11.3.9. XA 恢复(recovery)

这是一个展示HornetQ在JBoss应用服务器中的XA recovery是如何工作的例子。

Chapter 12. 使用通配符实现消息路由

HornetQ支持使用带通配符的地址对消息路由。

例如,当创建一个队列时使用了地址queue.news.#,那么它就能接收 所有和这个地址通配符相配的每一个地址的消息。这样的地址如 queue.news.europequeue.news.usaqueue.news.usa.sport等。这样一个消息接收者可以接收一组相关 的地址的消息,而不是只能指定一个具体的地址。

Note

用JMS的术语来说,这个功能就是允许创建“话题组”(topic hierarchy)。

要使用本功能需要将wild-card-routing-enabled属性设置为true。 这个属性在 hornetq-configuration.xml 文件中。默认值是true

关于通配符的语法参见Chapter 13, 了解 HornetQ 通配符的语法 章及 Section 11.1.54, “话题体系(Topic Hierarchy)”

Chapter 13. 了解 HornetQ 通配符的语法

HornetQ使用了一种专门的通配符语法来配置安全、地址及接收者(consumer)的创建。

这种语法与 AMQP所用的语法相似。

一个HornetQ的通配符表达式是由一些由“.”分隔的单词组成。

特殊字符“#”和“*”在表达式中可作为一个单词,它们代表 特殊的意义。

字符“#”表示“零或多个单词的任意排列”。

字符“*”表示“一个单词”。

因此,通配符表达式“news.europe.#”可以匹配“news.europe”、“news.europe.sport”、 “news.europe.politics”以及“news.europe.politics.regional”,但是与“news.usa”、 “news.usa.sport” 及 “entertainment”不相匹配。

通配符“news.*”与“news.europe”匹配,但不与“news.europe.sport”匹配。

通配符“news.*.sport”与“news.europe.sport”及“news.usa.sport”匹配,但与 “news.europe.politics”不匹配。

Chapter 14. 过滤器表达式

HornetQ提供了一套强大的过滤器(filter)语言。它的语法是基于SQL 92表达式的部分语法。

实际上它与JMS选择器(selector)的语法是相同的。只是其中有一些预定义的标识符有所不同。有关 JMS选择器的相关知识参见 javax.jms.Message

HornetQ在以下以个地方使用了过滤器表达式:

  • 预定义的队列。当在hornetq-configuration.xmlhornetq-jms.xml定义 队列时,可以使用过滤器。只有与过滤器表达式相匹配的消息才能达到该队列中。

  • 核心桥可以使用可选的过滤器表达式。只有与表达式相匹配的消息才被桥处理。 参见(Chapter 36, 核心桥)。

  • 转移(Divert)也可以使用可选的过滤器表达式。只有与表达式匹配的消息才被转移。 参见(Chapter 35, 消息的转发(divert)与分流)。

  • 另外过滤器还可以在编程方式创建接收者(consumer)和队列时使用。还有一些应用过滤器的地方在 Chapter 30, 管理中有所描述。

HornetQ的内核过滤器表达式与JMS选择器表达式是有所不同的。JMS选择器应用于JMS消息,而HornetQ的内核过滤 器表达式则用于内核消息。

以下标识符可以用在内核消息的过滤器表达式中,用来引用内核消息的属性:

  • HQPriority。代表消息的优先级。消息优先级属性的有效值为0到9间的整数。 0为最低优先级,9为最高优先级。例:HQPriority = 3 AND animal = 'aardvark'

  • HQExpiration。代表消息的失效时间。其值为一长整形数。

  • HQDurable。代表消息是否是持久消息。它是一个字符型的属性,有效值为 DURABLENON_DURABLE

  • HQTimestamp。代表消息的创建时间,其值为一长整形数。

  • HQSize。消息的大小。单位为字节。其值是一个整形数。

任何其它的标识符在内核过滤器的表达式中都认为是代表着该消息的一个属性。

Chapter 15. 持久化

本章我们将描述HornetQ的持久化技术,包括持久化的工作原理和配置方法。

HornetQ拥有一个高性能的日志(journal)模块来处理持久化。因此它并不依赖一个外部的数据库或第三方持久化产品。这个 日志模块针对消息的处理进行了高度的优化。

所谓HornetQ日志是一个只添加系统。它由一组磁盘文件构成。每个文件都是预先创建好的并且 大小是固定的。文件在创建时都进行了格式化。随着HornetQ不断地处理消息,如消息的增加、更新、删除等,一个个记录被添加 到日志中。当一个日志文件写满时,新记录就会写到下一个文件。

由于对日志的写入只是对文件的添加,这样有效减少了随机寻道的操作。而随机寻道的操作是磁盘操作中最耗时的操作。 所以这种设计可以使磁头的运动降到最低,效率最高。

而文件的大小是可以配置的。这使我们可以将文件大小配置为刚好占满一个磁盘柱面。不过现代的磁盘技术是复杂多样的, 我们并不能控制文件与磁盘柱面的对应关系。尽管如此,我们通过最大限度地降低文件对磁盘柱面的占用,来降低磁头的运动。 这是因为在同一个柱面的存取只需要盘面的转动而不需要磁头的运动。

当被删除的记录越来越多时,有的文件最終会变成一个没有有效记录的文件。这样的文件就可以回收再利用。HornetQ有 一套复杂的文件回收算法来判断一个日志文件是否可以被回收。

HornetQ还有一套文件整理的算法,它用来将日志文件中不用的空隙移除以达到更高的存贮效率。

这个日志系统全面支持事务功能。根据需要它可以支持本地事务或XA型事务。

日志系统的大部分是用Java实现的,但是HornetQ在其中实现了一层抽象的文件系统,这样就使得其它的语言实现能 方便地“插入”到日志模块中。实际上HornetQ自带有两种实现:

  • Java NIO

    第一种采用的是标准的Java NIO接口来进行文件的操作。它可以在任何安装有Java 1.6或以上的系统中运行。 NIO的性能是很高的。

  • Linux 异步IO (Asynchronous IO)

    第二种是采用的Linux系统中的异步IO技术(AIO)。它包括了少量的平台相关的代码(native code)来           调用AIO的接口。当数据被保存到磁盘上后,AIO会回调HornetQ进行通知。这样,HornetQ就避免了磁盘写 的同步操作。

    使用AIO通常可以有比NIO更高的性能。

    采用AIO的日志只能在运行 Linux kernel 2.6 或以上版本的内核的系统中才有。另外你需要安装libaio。 有关如何安装libaio请参见 Section 15.5, “安装AIO”

    另外请注意AIO只在以下文件系统上能正确工作:ext2, ext3, ext4, jfs, xfs。其他文件系统如NFS,虽然 AIO看上去可以工作,实际上是以较慢的同步的方式在运行。所以不要在NFS上使用日志。

    有关libaio的更多介绍参见 Chapter 40, Libaio平台专有库

    libaio是Linux内核项目的一部分。

标准的HornetQ核心服务器使用了两种日志:

  • 绑定日志

    这个日志用来保存与绑定有关的数据。其中包括在HornetQ上部署的队列及其属性,还有ID序列计数器。

    绑定日志是一个NIO型日志。与消息日志相比它的呑吐量是比较低的。

  • JMS日志

    这个日志保存所有JMS相关的数据,包括JMS队列,话题及连接工厂,以及它们的JNDI绑定信息。

    通过管理接口创建的JMS资源将被保存在这个日志中。但是通过配置文件配置的资源则不保存。只有使用JMS时JMS的日志 才被创建。

  • 消息日志

    这个日志用来存贮所有消息相关的数据,包括消息本身和重复ID缓存。

    默认情况下HornetQ总是优先使用AIO型日志。如果AIO型日志不可用(比如在非Linux平台上运行,或系统内核版本不同) 它将自动使用NIO型日志。

对于超大消息,Hornet将它们保存在消息日志之外的地方。详见Chapter 23, 大消息.

HornetQ还可以在内存不够用时将消息暂存到磁盘上。相关的配置和说明参见Chapter 24, 分页转存

如果不需要持久功能,HornetQ还可以配置成非持久的消息系统。参见Section 15.6, “配置HornetQ不使用持久化”

15.1. 配置绑定日志

绑定日志的配置参数在 hornetq-configuration.xml文件中。

  • bindings-directory

    这是绑定日志的位置。默认值是data/bindings

  • create-bindings-dir

    如果设置为true,那么在 bindings-directory 所设定的位置不存在的情况下会自动创建它。默认值是true

15.2. 配置JMS日志

JMS日志的配置与绑定日志共用配置。

15.3. 配置消息日志

消息日志的配置在hornetq-configuration.xml文件中。

  • journal-directory

    这是消息日志文件所在的目录。默认值是 data/journal

    为以达到最佳性能,我们建议将日志设定到属于它自己的物理卷中以减少磁头运动。如果日志的位置与 其它进程共用(如数据库,绑定日志或事务的日志等)则磁头的运动显然要增加很多。性能也就没有保证了。

    如果消息日志是贮存在SAN中,我们建议每个日志都拥有自己的LUN(逻辑单元)。

  • create-journal-dir

    如果设为true,则当journal-directory所指定的日志目录不存在时,会自动创建它。默认值是true

  • journal-type

    有效值是NIO 或者 ASYNCIO

    Choosing NIO chooses the Java NIO journal. Choosing AIO 选择作用异步IO型日志。如果你的平台不是Linux或者你没有安装 libaio,HornetQ会自动检测到并使用NIO

  • journal-sync-transactional

    如果设为true,HornetQ会保证在事务的边界操作时(commit, prepare和rollback)将事务数据 写到磁盘上。默认的值是 true

  • journal-sync-non-transactional

    如果设为true HornetQ将保证每次都将非事务性消息数据(发送和通知)保存到磁盘上。默认值是 true

  • journal-file-size

    每个日志文件的大于。单位为字节。默认值是 10485760 bytes (10MiB)。

  • journal-min-files

    最少日志文件数。当HornetQ启动时会创建这一数量的文件。

    创建并初始化日志文件是一项费时的操作,通常不希望这些操作在服务运行时执行。预先创建并初始化这些 日志文件将会使HornetQ在工作时避免浪费不必要的时间。

    根据你的应用中队列中消息量的实际要求可以适当调节这一参数。

  • journal-max-io

    写请求被放到一个队列中,然后再被发送到系统中执行。这个参数限制了在任一时间队列中可以存放的最大数量 的写请求。如果队列达到这个限制,任何新的写请求都将被阻塞,直到队列中有空位为止。

    当使用NIO时,这个参数必须为 1

    当使用AIO时,它的默认值是500

    系统根据不同类型的日志提供不同的默认值。(NIO 为 1, AIO 为 500)。

    如果是AIO,这个参数的上限不能超过操作系统的限制(/proc/sys/fs/aio-max-nr),这个值通常为65536.

  • journal-buffer-timeout

    日志模块中有一个内部缓冲。每次写的内容并不是都立即写到磁盘上,而是先放到这个内部缓存中。当这个缓存已满时,或 者超过了一定的时间(timeout),才将缓存的数据存到硬盘上。NIO和AIO都有这一特点。采用缓存的方式可以很好地满足 大量并发写数据的需要。

    这一参数规定了缓存的失效时间,如果过了这个时间,即使缓存还没有满,也将数据写入磁盘中。AIO的写入 能力通常要比NIO强。因此系统对于不同类型的日志有着不同的默认值。( NIO的默认值是 3333333 纳秒,即每秒300次。 而AIO则是500000纳秒,即每秒2000次。)

    Note

    加在这个参数有可能会增加系统的呑吐量,但可能会降低系统的响应能力。通常情况下默认值应该是比较理想的折中选择。

  • journal-buffer-size

    AIO的定时缓冲的大小,默认值为490KiB

  • journal-compact-min-files

    进行整理压缩日志操作的最少文件数。当日志文件少于这个数时,系统不会进行文件的整理压缩。

    默认值是 10

  • journal-compact-percentage

    开始整理压缩的界限值。当有效数据的比例少于这个值时系统开始整理压缩日志。注意是否进行压缩还要 受到、journal-compact-min-files参数的控制。

    这一参数的默认值是 30

15.4. 关于关闭磁盘写缓冲的重要说明

Warning

大多数磁盘产品都有硬件的写缓冲。写缓冲可以明显提高写的效率。

这样的写缓冲与调用fsync()这样的系统函数无关,也与在Java程序中进行的同步调用无关!

默认情况下许多磁盘的写缓冲是打开的。这样的情况下,即使你在程序中调用了同步操作也不能保证你的数据 就真正写到磁盘介质中了。因此如果故障发生时,关键的数据是有可能丢失的。

有些昂贵的磁盘采用非挥发性的介质或有电源的缓冲来保证故障情况下不丢失数据。但是你仍需要对这些硬盘进行测试!

如果你的磁盘没有非挥发性或有电源的缓存,也不是某种冗余盘阵(如RAID)。要想保证关键数据不丢失,你需要 关闭磁盘的写缓冲。

需要知道的是关闭磁盘的写缓冲会显著降低磁盘的性能。如果平时你在使用磁盘时都打开写缓冲,那么当你为了 保护你的数据而关闭它时,你可能感到两种情况下的明显差异。

Linux可以用hdparm (IDE硬盘) 或 sdparmsginfo (SDSI/SATA 硬盘)工具来查看并修改磁盘的写缓冲。

在Windows平台上你可以右键点击硬盘图标,并选择“属性”菜单项来操作。

15.5. 安装AIO

Java NIO日志的性能是很好的。但是如果你是在Linux 内核2.6版本以上的系统中运行HornetQ,我们强烈建议 你使用 AIO日志,以获得更佳的性能。

在早期的Linux版本中或其它操作系统中不可以使用 AIO日志。

如果你的Linux内核是2.6版本或以上但没有安装 libaio,按照下列步骤可以很容易地安装它:

使用 yum,(如 Fedora 或 Red Hat Enterprise Linux):

yum install libaio

使用 aptitude, (如 Ubuntu 或 Debian):

apt-get install libaio

15.6. 配置HornetQ不使用持久化

在一些情况下消息系统并不需要持久化。这时可以配置HornetQ不使用持久层。只要将hornetq-configuration.xml文件中的persistence-enabled 参数设为false即可。

注意如果你将该参数设为 false来关闭持久化,就意味着所有的绑定数据、消息数据、超大消息数据、重复ID缓冲以及转移(paging)数据都将不会被持久。

Chapter 16. 传输层的配置

HornetQ的传输层是“可插拔的”。通过灵活的配置和一套服务提供接口(SPI),HornetQ可以很容易地更换其传输层。

在本章中我们将对HornetQ的传输相关的概念作出解释,并说明它的配置方法。

16.1. 接收器(Acceptor)

接收器(acceptor)是 HornetQ 的传输层中最为重要的概念之一。首先 介绍一下在文件hornetq-configuration.xml中是怎样定义一个接收器的:

<acceptors>                
    <acceptor name="netty">
        <factory-class>
org.hornetq.core.remoting.impl.netty.NettyAcceptorFactory
        </factory-class>
        <param key="port" value="5446"/>
    </acceptor>
</acceptors>            
        

所有接收器都在 acceptors单元(element)内定义。在acceptors 内可以有零个或多个接收器的定义。每个服务器所拥有的接收器的数量是没有限制的。

每个接收器都要定义其与HornetQ服务器连接的方式。

以上的例子中我们定义了一个Netty接收器。它在端口5446监听连接请求。

acceptor单元内有一个子单元factory-class。这个单元是用来 定义创建连接器的工厂类。一个连接器工厂类必须要实现AcceptorFactory接口。上例中我们定义 的连接器工厂是类NettyAcceptorFactory使用Netty来建立连接。有个这个类定义,HornetQ就知道了用什么传输来建立连接了。

acceptor中还可以配置零或多个参数param。在每个param 中定义的是键-值对(key-value)。这些参数用来配置某个传输实现。不同传输有不同的配置参数。

像IP地址、端口号等都是传输配置参数的例子。

16.2. 连接器(Connectors)

接收器定义的是如何在服务器端接收连接,而连接器则是定义客户端如何连接到服务器。

以下是hornetq-configuration.xml文件中一个连接器配置的例子。

<connectors>
    <connector name="netty">
        <factory-class>
            org.hornetq.core.remoting.impl.netty.NettyConnectorFactory
        </factory-class>
        <param key="port" value="5446"/>
    </connector>
</connectors>            
        

连接器的配置在connectors单元中。可以定义一个或多个连接器。每个服务器配置的连接器 数量是没有限制的。

你可能注意到了,既然连接器是定义客户端如何连接服务器的,那么为什么要定义在 服务器端呢?原因如下:

  • 服务器有时也需要做为客户端去连接其它的服务器,比如当一个服务器通过桥连接到另一个服务器,或者是集群 中服务器之间的互相通迅。在这种情况下服务器就要知道如何与另一台服务器建立连接。因此需要在 connectors下定义连接器。

  • 如果你使用JMS服务,需要创建连接工厂的实例并绑定到JNDI。在HornetQ创建 HornetQConnectionFactory时需要连接器的必要信息,以便这个连接工厂 能知道它如何与HornetQ服务器相连接。

    这一信息被定义在配置文件hornetq-jms.xml中的connector-ref单元下。下面这段配置 就是从该配置文件中提取的相关部分,它展示了JMS的连接工厂是如何引用定义在配置文件hornetq-configuration.xml中的连接器的:

    <connection-factory name="ConnectionFactory">
        <connectors>
           <connector-ref connector-name="netty"/>
        </connectors>
        <entries>
            <entry name="ConnectionFactory"/>
            <entry name="XAConnectionFactory"/>
        </entries>
    </connection-factory>                
                

16.3. 在客户端直接配置传输层

怎样配置一个内核ClientSessionFactory以让它知道如何连接服务器的信息呢?

在直接配置内核ClientSessionFactory的时候,可以间接地使用连接器。当然在这种情况 下在服务器端定义连接器是没有意义的。我们通过将必要参数传给ClientSessionFactory的 方法来告诉使用什么样的连接器工厂。

在下面的例子中,我们创建了一个ClientSessionFactory,它可以直接连接到我们先前定 义的接收器上。它使用的是标准的Netty TCP传输层,连接主机是localhost(默认),端口5446:

Map<String, Object> connectionParams = new HashMap<String, Object>();
    
connectionParams.put(org.hornetq.core.remoting.impl.netty.TransportConstants.PORT_PROP_NAME, 
                    5446);

TransportConfiguration transportConfiguration = 
    new TransportConfiguration(
    "org.hornetq.core.remoting.impl.netty.NettyConnectorFactory", 
    connectionParams);

ClientSessionFactory sessionFactory = HornetQClient.createClientSessionFactory(transportConfiguration);

ClientSession session = sessionFactory.createSession(...);

etc                       
        

如果在客户端直接使用JMS的连接工厂的话,也可以用类似的方法而不需要在服务器端定义连接器或在 hornetq-jms.xml配置文件中创建连接工厂:

Map<String, Object> connectionParams = new HashMap<String, Object>();

connectionParams.put(org.hornetq.core.remoting.impl.netty.TransportConstants.PORT_PROP_NAME, 5446);

TransportConfiguration transportConfiguration = 
    new TransportConfiguration(
    "org.hornetq.core.remoting.impl.netty.NettyConnectorFactory", 
    connectionParams);

ConnectionFactory connectionFactory = HornetQJMSClient.createConnectionFactory(transportConfiguration);

Connection jmsConnection = connectionFactory.createConnection();

etc                       
        

16.4. 配置 Netty 传输层

HornetQ当前使用Netty作为其默认的连接层。Netty是一个高性能的底层网络库.

Netty传输的配置有几种不同的方法。它可以使用传统的Java IO(阻塞方式)、NIO(非阻塞)或直接使用 TCP socket及SSL。或者使用HTTP或HTTPS协议。同时还可能使用servlet进行传输。

采用Netty应该能满足绝大部分的传输要求。

16.4.1. 配置 Netty TCP

Netty TCP 是简单的非加密的基于TCP socket的传输。它可以使用阻塞式的Java IO或非阻塞式的Java NIO。 我们建议在服务器端采用非阻塞式的NIO以获得良好的并发处理能力。当并发能力并不是很重要时,可以使用阻塞式 的方式以增加响应的速度。

如果你的应用是运行在不信任的网络上,你应该选择使用SSL或HTTPS。

Netty TCP的所有连接都是从客户端发起的。服务器端不向客户端发起任何连接。在有防火墙的环境中,这种方式 是比较适合的。因为防火墙只允许单方向的连接。

org.hornetq.core.remoting.impl.netty.TransportConstants类中定义了所 有的配置参数的名称(key)。它们当中绝大多娄既用于配置接收器也用于配置连接器,有一些只适用于接收器。 下面列出的参数用以配置一个简单的Netty TCP:

  • use-nio。如果设为true则使用非阻塞的Java NIO。如果false则使用传统的阻塞方式的Java IO。

    我们建议使用Java NIO处理并行连接。因为Java NIO不是为每一个连接分配一个线程,所以它要比传统的阻塞式 Java IO具有更强的并发连接的处理能力。如果你不需要处理并发连接,那么使用旧的阻塞式的IO性能会好一些。这个参 数的默认值在服务器端是false,在客户端是false

  • host。主机名或IP地址。对于接收器来说,它是服务器接收连接的地址。 对于连接器端,它是客户端连接的目标地址。默认值是localhost。 在配置接收器时可以指定多个主机名或IP地址,中间用逗号隔开。如果指定的主机是0.0.0.0, 则接收器将从主机上所有的网络接口中接受连接请求。连接器不允许指定多个主机地址,它只能与一个 地址建立连接。

    Note

    一定不要忘记指定一个主机名或IP地址!一个服务器要想接受来自其它节点的连接就必需有一个 主机名或IP地址来绑定及监听外部的连接请求。默认的主机名localhost是不能接受外部的 连接请求的!

  • port。连接的端口。用于配置连接器或接收器。连接器用此端口来建立 连接。接收器在些端口上监听连接请求。默认值是5445

  • tcp-no-delay。将它设为true就会使用 Nagle 算法.默认值是true

  • tcp-send-buffer-size。这个参数指定了TCP的发送缓冲大小,单位是字节。 默认值是32768字节(32KiB)。

    这个参数要根据你的网络的带宽与时延的情况而调整。 这个链接对此有很好的论述。

    简言之,TCP的发送/接收缓冲的大小可以用下面公式来计算:

                            缓冲大小 = 带宽 * RTT
                        

    其中带宽的单位是 每秒字节数,RTT(网络往返程时间)的单位是秒。 使用ping工具可以方便地测量出RTT。

    对于快速网络可以适当加大缓冲的大小。

  • tcp-receive-buffer-size。这个参数指定了TCP接收缓冲的大小,单位是字节。 默认值是32768字节(32KiB)。

  • batch-delay。HornetQ可以通过配置该参数,在数据包写入传输层之前有一个 最大延时(毫秒),达到批量写入的目的。这样可以提高小消息的发送效率。但这样做会增加单个消息的平均发送 延迟。默认值为0毫秒。

  • direct-deliver。消息到达服务器后,默认是由一个不同的线程来将消息传递 到接收者。这样可以使服务的呑吐量和可扩展性达到最佳,特别是在多核的系统上效果更为明显。但是线程切换 会带来一些传递的延迟。如果你希望延迟最小,并不在意呑吐量的话,可以将参数direct-deliver设为true。默认值是true。如果你更希望有 较大的呑吐量的话,将它设为false

  • nio-remoting-threads。如果使用NIO,默认情况下HornetQ会使用系统中处理 器内核(或超线程)数量三倍的线程来处理接收的数据包。内核的数量是通过调用Runtime.getRuntime().availableProcessors()来得到的。如果你想改变这个数量, 你可以设定本参数。默认的值是-1,表示线程数为Runtime.getRuntime().availableProcessors() * 3。

16.4.2. 配置Netty SSL

Netty SSL的配置与Netty TCP相似。它采用了安全套接字层(SSL)来提供加密的TCP连接。

我们提供了一个Netty SSL的例子来演示其配置和应用。

Netty SSL拥有Netty TCP一样的参数,另外还有下列的附加参数:

  • ssl-enabled。必须设为true以使用SSL。

  • key-store-path。存放SSL密钥的路径(key store)。这是存放客户端证书的地方。

  • key-store-password。用于访问key store的密码。

  • trust-store-path。服务器端存放可信任客户证书的路径。

  • trust-store-password。用于访问可信任客户证书(trust store)的密码。

16.4.3. 配置Netty HTTP

Netty HTTP 通过HTTP通道传送数据包。在有些用户环境中防火墙只允许有HTTP通信,这时采用Netty HTTP作为HornetQ 的传输层就能解决问题。

我们提供了一个Netty HTTP的例子来演示其配置和应用。

Netty HTTP具有和Netty TCP同样的配置参数,另外它还有以下参数:

  • http-enabled。如果要使用HTTP,这个参数必须设为true

  • http-client-idle-time。客户端空闲时间。如果客户端的空闲时间超过 这个值,Netty就会发送一个空的HTTP请求以保持连接不被关闭。

  • http-client-idle-scan-period。扫描空闲客户端的间隔时间。单位是毫秒。

  • http-response-time。服务器端向客户端发送空的http响应前的最大等待时间。

  • http-server-scan-period。服务器扫描需要响应的客户端的时间间隔。单位是毫秒。

  • http-requires-session-id。如果设为true,客户端在第一次请求后将等待 接收一个会话ID。http 连接器用它来连接servlet接收器(不建议这样使用)。

16.4.4. 配置Netty Servlet

HornetQ可以使用Netty servlet来传输消息。使用servlet可以将HornetQ的数据通过HTTP传送到一个 运行的servlet,再由servlet转发给HornetQ服务器。

servlet与HTTP的不同之处在于,当用HTTP传输时,HornetQ如同一个web服务器,它监听在某个端口上的HTTP 请求并返回响应。比如80端口或8080端口。而当使用servlet时,HornetQ的传输数据是通过运行在某一servlet容器 中的一个特定的servlet来转发的。而这个sevlet容器中同时还可能运行其他的应用,如web服务。当一个公司有多个应用 但只允许一个http端口可以访问时,servlet传输可以很好的解决HornetQ的传输问题。

请参见HornetQ所提供的servlet例子来了解详细的配置方法。

要在HornetQ中使用Netty servlet传输方式,需要以下步骤:

  • 部署servlet。下面是一个web.xml例子:

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
             version="2.4">
       <servlet>
          <servlet-name>HornetQServlet</servlet-name>
          <servlet-class>org.jboss.netty.channel.socket.http.HttpTunnelingServlet</servlet-class>
          <init-param>
                 <param-name>endpoint</param-name>
                 <param-value>local:org.hornetq</param-value>
               </init-param>
               <load-on-startup>1</load-on-startup>
       </servlet>
    
       <servlet-mapping>
          <servlet-name>HornetQServlet</servlet-name>
          <url-pattern>/HornetQServlet</url-pattern>
       </servlet-mapping>
    </web-app>
    
    
  • 我们还需要在服务器端加上一个特殊的Netty invm 接收器。

    下面是从hornetq-configuration.xml配置文件中摘取的定义接收器的配置部分:

                        
    <acceptors>
    
          <acceptor name="netty-invm">
             <factory-class>
                org.hornetq.core.remoting.impl.netty.NettyAcceptorFactory
             </factory-class>
             <param key="use-invm" value="true"/>
             <param key="host" value="org.hornetq"/>
          </acceptor>
    
    </acceptors>                                         
                    
  • 最后我们需要在客户端配置连接器,也是在hornetq-configuration.xml文件中来做。如下所示:

    <connectors>
    
          <connector name="netty-servlet">
             <factory-class>
                org.hornetq.core.remoting.impl.netty.NettyAcceptorFactory
             </factory-class>
             <param key="host" value="localhost"/>
             <param key="port" value="8080"/>
             <param key="use-servlet" value="true"/>
             <param key="servlet-path" value="/messaging/HornetQServlet"/>
          </connector>
    
     </connectors>

下面列出了初始化参数以及它们的用途:

  • endpoint - Netty接收器的名字。servlet将向它转发数据包。它与host参数的值是对应的。

web.xml中定义的servlet的URL形式与在连接器配置文件中定义的 servlet-path值应该相匹配。

servlet可以与SSL一起使用。只需要在连接器配置中加上下面的配置即可:

    <connector name="netty-servlet">
         <factory-class>org.hornetq.core.remoting.impl.netty.NettyAcceptorFactory</factory-class>
         <param key="host" value="localhost"/>
         <param key="port" value="8443"/>
         <param key="use-servlet" value="true"/>
         <param key="servlet-path" value="/messaging/HornetQServlet"/>
         <param key="ssl-enabled" value="true"/>
         <param key="key-store-path" value="path to a keystoree"/>
         <param key="key-store-password" value="keystore password"/>
      </connector>

另外你还需要为服务器指定一个KeyStore。打开server/default/deploy/jbossweb.sar 下的server.xml文件,按照下面的内容编辑其中的SSL/TLS连接器配置:

<Connector protocol="HTTP/1.1" SSLEnabled="true"
           port="8443" address="${jboss.bind.address}"
           scheme="https" secure="true" clientAuth="false"
           keystoreFile="path to a keystore"
           keystorePass="keystore password" sslProtocol = "TLS" />

SSL需要keystore和访问密码。参见servlet ssl例子以了解更多的有关信息。

Chapter 17. 失效连接的检测

本章将讨论连接的生存时间(TTL)以及HornetQ如何处理出现故障的客户端或者异常退出的客户端(即客户端在 退出时没有合理的关闭相关资源)。

17.1. 服务器端对失效连接的清除

当客户端的应用程序退出时,应该关闭所使用的资源。在finally进行资源的关闭 是一个很好的方法。

下面的例子中,一个Hornet客户端在finally中关闭了它的会话(session)和会话工厂(session factory):

ClientSessionFactory sf = null;
ClientSession session = null;

try
{
   sf = HornetQClient.createClientSessionFactory(...);

   session = sf.createSession(...);
   
   ... do some stuff with the session...
}
finally
{
   if (session != null)
   {
      session.close();
   }
   
   if (sf != null)
   {
      sf.close();
   }
}
        

下面的例子给出了一个JMS客户端是如何适当关闭相关资源的:

Connection jmsConnection = null;

try
{
   ConnectionFactory jmsConnectionFactory = HornetQJMSClient.createConnectionFactory(...);

   jmsConnection = jmsConnectionFactory.createConnection();

   ... do some stuff with the connection...
}
finally
{
   if (connection != null)
   {
      connection.close();
   }
}
        

然而有时候资源在客户端得不到合理的关闭。有的客户端应用在结束时忘记了关闭资源,有的客户端有时发生故障导致 程序突然中断,相关资源也没有来得及关闭!

如果上述情况发生了,那么这些资源就会留在服务器端而不会被清理。这就会造成资源泄漏现象并最終导致服务器内存 溢出或其它资源的溢出错误。

因此在服务器端要有某种机制来避免资源的泄漏。也就是对无效资源进行回收。在判断什么是无效资源时,HornetQ 考虑到了客户端重新连接的情况。就是当一个连接由于网络临时中断后又恢复正常时,客户端有可能通过不断重试 成功地连接到服务器端。如果服务器端过早清除了相关的连接资源,则客户端就可能重试失败。

HornetQ的资源回收是完全可配置的。每个 ClientSessionFactory 有一个连接 TTL的参数。 这个参数的意义是当客户端的一个连接没有任何数到达服务器时,服务器充许这个连接有效的最长时间。客户端通过定 时向服务器端发送“ping“数据包来维持连接的有效,以免被服务器关掉。如果服务器在TTL指定的时间内没有收到任何 数据包,则认为该连接无效,继而关闭与该连接相关的所有的会话(session)。

如果使用JMS,HornetQConnectionFactoryConnectionTTL 属性是用来定义连接的存活时间的。如果你将JMS连接工厂部署到JNDI中,则应使用配置文件中的connection-ttl参数来定义连接的TTL。

默认的连接TTL值是60000毫秒,即一分钟。 ConnectionTTL 设为-1表示服务器永远不检测超时的连接。

如果你不想让客户端来规定连接存活时间(TTL),你可以在服务器端的配置文件中定义 connection-ttl-override属性。它的默认值是-1,表示 服务器端该属性无效(即客户端可以定义自己的连接TTL)。

17.1.1. 关闭没有被成功关闭的核心会话或JMS连接

如前所述,在使用完毕后在finally中将所有的核心会话或JMS连接关闭是十分重要的。

如果你没有这样做,HornetQ会在拉圾回收时进行检测并会在日志中打印类似以下的警告(如果是JMS则在警告中 是相应的JMS连接):

                
[Finalizer] 20:14:43,244 WARNING [org.hornetq.core.client.impl.DelegatingSession]  I'm closin
g a ClientSession you left open. Please make sure you close all ClientSessions explicitly before let
ting them go out of scope!
[Finalizer] 20:14:43,244 WARNING [org.hornetq.core.client.impl.DelegatingSession]  The sessi
on you didn't close was created here:
java.lang.Exception
at org.hornetq.core.client.impl.DelegatingSession.<init>(DelegatingSession.java:83)
at org.acme.yourproject.YourClass (YourClass.java:666)    
                
            

HornetQ然后将未关闭的连接/会话关闭。

注意在日志的警告中还给出了创建JMS连接/客户端会话的具体行号,以便准确地确定出错的地方。

17.2. 客户端的故障检测

前面讲述了客户端如何向服务器发送ping以及服务器端如何清理失效的连接。发送ping还有另外一个目的,就是能 让客户端检测网络或服务器是否出现故障。

从客户端的角度来看,只要客户端能从一个连接不断接收服务器的数据,那么这个连接就被认为是一个有效的连接。

如果在属性client-failure-check-period所定义的时间内(单位毫秒)客户端没有 收到任何数据,客户端就认为这们连接发生了故障。根据不同的配置,客户端在这种情况下要么进行failover,要么 调用FailureListener的接口(或者是JMS的ExceptionListener)。

如果使用JMS,这个参数是HornetQConnectionFactoryClientFailureCheckPeriod。 如果你向JNDI部署JMS连接工厂,那么相应的参数在hornetq-jms.xml配置文件中,参数名 为client-failure-check-period

这个参数的默认值是30000毫秒,即半分钟。-1表示客户端不检查 连接的有效性。即不论是否有数据来自服务器,连接都一直有效。这一参数通常要比服务器端的连接TTL小许多,以使 客户端在出现短暂连接故障的情况下可以与服务器成功地重新连接。

17.3. 配置异步连接任务执行

默认情况下,服务器接收到的数据包被远程模块的线程处理。

为了避免远程模块的线程被长时间占用,数据包可以转给另外的一个线程池来处理。要注意这样做的增加了一些时间延迟。 因此如果数据包处理耗时很少,还是由远程模块线程来处理较好。 要配置这样的异步连接很行任务,将hornetq-configuration.xml文件中的 async-connection-execution-enabled 参数设为true (默认值是 true)。

Chapter 18. 配置资源管理器(Resource Manager)

HornetQ有自己的资源管理器来管理JTA事务。当一个事务开始时,资源管理器就得到通知并记录下该事务和它的状态。 有的时候一个事务开始后,最終被忘记。有时客户端崩溃并且再也不能恢复,这样的话该事务就一直存在下去。

为了解决这个问题,可以配置HornetQ来扫描过期的事务,并且将它们回滚。默认值是3000000毫秒(5分钟)。 它表示任何超过5分钟的事务都将被删除。这个超时对应的参数是transaction-timeout,它在配置文件hornetq-configuration.xml中(单位毫秒)。 参数transaction-timeout-scan-period定义了HornetQ扫描过期事务的间隔。

注意HornetQ不会单方面回滚一个已经处于准备状态的XA事务。如果你认为这些事务永远不会被事务管理器(transaction manager) 来处理的话,你必须通过管理接口来进行回滚。

Chapter 19. 流控制

流控制是指对客户端与服务器之间,或者服务器之间的数据流量进行限制,目的是防止通迅双方由于大量数据而过载。

19.1. 接收者(consumer)流控制

这是指对客户端的接收者接收消息流的控制。通常为了提高效率,在客户端通常将消息放入缓存,然后再将缓存中 的消息传递给接收者(consumer)。当接收者处理消息的速度小于服务器向其发送消息的速度时,就可能造成消息在 客户端不断积累,最終引起内存溢出的错误。

19.1.1. 基于窗口的流控制

默认情况下HornetQ的接收者一端会将消息进行缓存以提高性能。如果不这样做,那每次接收者收到一个消息, 都得通知服务器传递下一个消息,然后服务器再将下一个消息传递过来。这就增加了通信的次数。

对于每一次消息传递都有一个网络的往返通信,这样降低了性能。

为了避免这样,HornetQ将每个接收者的消息提前接收到一处缓存中。每个缓存的最大值由 consumer-window-size参数决定(单位字节)。

consumer-window-size的默认值是 1 MiB (1024 * 1024 字节)。

它的值可以是:

合理设置接收者的窗口大小可以显著提高性能。下面是两个极端的例子:

快速接收者

所谓快速接收者是指消息的接收者处理消息的速度大于等于它的接收速度。

对于快速接收者或以将consumer-window-size设为 -1,使得客户端的消息缓存的大小 无限制

请谨慎使用这一设置值: 如果接收者的消息处理速度比接收速度小,可造成客户端内存溢出。

慢接收者

所谓慢接收者是指接收者每处理一个消息就要花很多时间。这样将缓存关闭就比较合理。服务器可以将多余的 消息传递给其它的接收者。

假设一个队列有2个接收者。其中一个接收者非常慢。消息被轮流传递到两个接收者。其中的快速接收者 很快将其缓存中的消息处理完毕。同时慢接收者的缓存中还有一些消息等待处理。这样快速接收者在一段时间 内就处于空闲状态。

这时,将consumer-window-size 设为0 (没有缓存),就可以将它变成 慢接收者。这样在慢接收者一方不会缓存消息,这使得快的接收者可以处理更多的消息,而不至于处于空闲 状态。

这说明将它设置为0可以控制一个队列的消息在多个接收者之间的消息分配。

大多数情况下很难判断哪些接收者是快速的,哪些是慢速的。往往很多接收者是处于两者之间。这样对于 consumer-window-size的值就要视具体情况而定。有时需要进行一定的测试 来决定它的最佳值。通常情况下将其设为1MiB可以满足大多数的应用情况。

19.1.1.1. 使用核心接口(Core API)进行流控制

Hornet的核心接口中,ClientSessionFactory.setConsumerWindowSize()方法和一些 ClientSession.createConsumer()方法可以控制流的窗口大小。

19.1.1.2. 使用JMS的流控制

若使用JNDI来获得连接工厂,则需要通过配置hornetq-jms.xml文件来设定窗口大小:

<connection-factory name="ConnectionFactory">
   <connectors>
      <connector-ref connector-name="netty-connector"/>
   </connectors>
   <entries>
      <entry name="ConnectionFactory"/>       
   </entries>
      
   <!-- Set the consumer window size to 0 to have *no* buffer on the client side -->
   <consumer-window-size>0</consumer-window-size>
</connection-factory>
            

如果直接实例化连接工厂,则使用HornetQConnectionFactory.setConsumerWindowSize() 方法来设定窗口大小。

参见例子Section 11.1.32, “零接收缓冲”来了解如何配置HornetQ来 关闭接收者的缓存。

19.1.2. 速率流控制

我们还可以通过控制 速率的方法来控制流。这是一种像调节节流阀的形式。 这种方法保证一个接收者接收消息的速率不会超过设定的值。

速率必须是一个正整数。它代表最大接收速度,单位是消息每秒。将它设为-1就会关闭速率流控制。 默认值是-1

参见有关速率流控制的例子Section 11.1.10, “限制接收速率”以进一步了解它的工作原理。

19.1.2.1. 使用核心接口(Core API)

HornetQ的核心接口的ClientSessionFactory.setConsumerMaxRate(int consumerMaxRate)方法或 某些ClientSession.createConsumer()方法可以实现对流的速率控制。

19.1.2.2. 使用JMS

如果从JNDI中获取连接工厂,需要通过配置hornetq-jms.xml来进行速率流控制:

<connection-factory name="ConnectionFactory">
      <connectors>
         <connector-ref connector-name="netty-connector"/>
      </connectors>
      <entries>
         <entry name="ConnectionFactory"/>       
      </entries>
      <!-- We limit consumers created on this connection factory to consume messages
             at a maximum rate
      of 10 messages per sec -->
      <consumer-max-rate>10</consumer-max-rate>
 </connection-factory>

如果是直接实例化连接工厂,则通过HornetQConnectionFactory.setConsumerMaxRate(int consumerMaxRate)方法来设定最大流速率。

Note

速率流控制可以与窗口流控制结合使用。速率控制只规定了客户端每秒接收多少消息。因此如果你设定 了一个较低的速率,同时又设定了一个大的缓存窗口,那么客户端的缓存将会很快饱和。

参见接收速率流控制的例子Section 11.1.10, “限制接收速率”进一步了解速率流控制的配置和使用。

19.2. 发送者(producer)的流控制

HornetQ还可以控制客户端向服务器发送消息的速度,以避免服务器因大量数据过载。

19.2.1. 基于窗口的流控制

与接收者的相应的控制相似。在默认条件下,发送者要有足够的份额(credits)才可以向服务器的地址发送消息。 这个份额就是消息的大小。

当发送者的份额不足时,它要向服务器请求更多的份额以便发送更多的消息。

发送者一次向服务器请求的份额值被称为窗口大小

于是窗口大小就是指发送者向服务器不间断发送消息的总最大字节数。当发送完毕时需再向服务器请求份额。这样就避免了 服务器消息过载的情况。

19.2.1.1. 使用核心接口(Core API)

若使用核心接口,ClientSessionFactory.setProducerWindowSize(int producerWindowSize) 方法可以对窗口大小进行设定。

19.2.1.2. 使用JMS

如果使用JNDI来获得连接工厂,则需要配置hornetq-jms.xml文件以设定窗口大小:

               <connection-factory name="ConnectionFactory">
                  <connectors>
                     <connector-ref connector-name="netty-connector"/>
                  </connectors>
                  <entries>
                     <entry name="ConnectionFactory"/>       
                  </entries>
                  <producer-window-size>10</producer-window-size>
               </connection-factory>

如果是直接实例化连接工厂,则使用HornetQConnectionFactory.setProducerWindowSize(int producerWindowSize)方法来设定窗口大小。

19.2.1.3. 限定发送者窗口流控制

通常情况下客户端请求多少份额,HornetQ服务器就给予多少份额。然而我们还可以针对每个地址来设定一个最大 的份额值,以使服务器给出的份额都不大于该值。这样可以防止一个地址的内存溢出。

例如,如果有一个队列称为“myqueue”。将它的最大内存值设为10MiB,则服务器就会控制给出的份额以保证向该队列的地 址发送消息时不会占大于10MiB的内存空间。

当一相地址已经满了的时候,发送者将会阻塞直到该地址有了多余的空间为止,即地址中的消息被接收了一部分后使得 地址腾出了一些空间。

我们将这种控制方法称为限定发送者窗口流控制。这是一种有效的防止服务器内存溢出的手段。

它可以看成是分页转存(paging)的另一种方法。分页转存不阻塞发送者,它将消息转存到存贮介质上以节省内存的空间。

要配置一个地址的最大容量并告诉服务器在地址满了的情况下阻塞发送者,你需要为该地址定义一个 AddressSettings (Section 25.3, “通过地址设置来配置队列属性”) 并设定 max-size-bytesaddress-full-policy

这个配置对所有注册到该地址的队列有效。即所有注册队列的总内存将不超过 max-size-bytes。对于JMS topic情况则意谓着该topic的所有订阅的内存不能超过 max-size-bytes的设定值。

下面是一个例子:

               <address-settings>
                  <address-setting match="jms.queue.exampleQueue">            
                     <max-size-bytes>100000</max-size-bytes>
                     <address-full-policy>BLOCK</address-full-policy>   
                  </address-setting>
               </address-settings>

上面的例子将JMS队列"exampleQueue"的最大内存值设为 100000 字节并且阻塞发送者以防止消息量超过这个值。

注意必须设置 BLOCK的策略才能打开限定发送者窗口控制。

Note

请注意默认的配置下当一个地址中的消息量达到10MiB时,其所有的消息发送者将变为阻塞状态,也就是说 在没有接收的情况下你不能向一个地址不阻塞地一次发送超过10MiB的消息。要想增加这个限制,可以加大 max-size-bytes参数的值,或者调整地址的消息容量限制。

19.2.2. 速率流控制

HornetQ也可以控制发送者发送消息的速率。单位是每秒消息数。通过设定速率可保证发送者的发送速率不超过某个值。

速率必须是一个正整数。如果设为 -1 则关闭速率流控制。默认值是-1

请参见例子Section 11.1.36, “消息发送速度限制”进一步了解速率流控制的使用方法。

19.2.2.1. 使用核心接口(Core API)

如果使用核心接口,ClientSessionFactory.setProducerMaxRate(int consumerMaxRate)方法或 某些 ClientSession.createProducer()方法可以设置最大速率值。

19.2.2.2. 使用 JMS

如果使用JNDI,需要配置hornetq-jms.xml文件:

<connection-factory name="ConnectionFactory">
      <connectors>
         <connector-ref connector-name="netty-connector"/>
      </connectors>
      <entries>
         <entry name="ConnectionFactory"/>       
      </entries>
      <!-- We limit producers created on this connection factory to produce messages 
                at a maximum rate
      of 10 messages per sec -->
      <producer-max-rate>10</producer-max-rate>
 </connection-factory>

如果直接实例化连接工厂,则使用HornetQConnectionFactory.setProducerMaxRate(int consumerMaxRate)方法来设置。

Chapter 20. 发送与提交的保证

20.1. 事务保证

在提交或回滚事务时,HornetQ将提交或回滚的请求发送到服务器,客户端阻塞等待服务器的响应。

当服务器端收到提交或回滚的请求时,它将事务信息记录到日志(journal)中。然后向客户端发回 响应。参数journal-sync-transactional控制着如何向客户端发回响应。 如果它的值是false,服务器向客户端发回响应时事务的处理結果不一定已经被 保存到磁盘中。可能会在之后的某个时间保存。如果期间服务器发生故障那么事务的处理信息可能丢失。 当它的值是true时,服务器将保证在向客户端发回响应时,事务的处理信息 已经被保存到了磁盘中。默认值是true

显然将这个参数设为false可以提高性能,但是要以牺牲事务的持久性为代价。

这个参数在 hornetq-configuration.xml文件中。

20.2. 非事务性消息发送的保证

使用非事务性会话发送消息时,经过适当配置HornetQ,客户端在发送后以阻塞的方式等待,直到确认发出 的消息已经到达服务器后再返回。可以对持久化或非持久化的消息分别配置,具体参数如下:

  • BlockOnDurableSend。如果设为true则通过 非事务性会话发送持久消息时,每次发送都将阻塞直到消息到达服务器并返回通知为止。默认值是 true

  • BlockOnNonDurableSend。如果设为true, 则通过非事务性会话发送非持久消息时,每次发送都将阻塞直到消息到达服务器并返回通知为止。默认值是 false

将发送设置为阻塞方式会降低程序的效率。因为每次发送都需要一次网络往返的过程,然后才可以进行下次发送。 这样发送消息的速度将受网络往返时间(RTT)的限制。这样你的网络带宽就可能没有被充分利用。为了提高效率,我们 建议采用事务来批量发送消息。因为在事务中,只有在提交或回滚时阻塞。另外你还可以利用HornetQ高级的 异步发送通知功能。这一功能在Section 20.4, “异步发送通知” 进行了描述。

使用JMS时,如果JMS的连接工厂是在服务器端被注册到JNDI服务,你需要配置 hornetq-jms.xml文件中的block-on-durable-sendblock-on-non-durable-send。如果不使用JNDI,可以调用 HornetQConnectionFactory相应的设置方法进行配置。

如果你使用的是内核服务,你可以直接在ClientSessionFactory上用相关的方法设置相应的参数。

当服务器从一个非事务性的会话收到一个消息时,如果这个消息是持久的并且此消息被路由到至少一个持久的队列中, 则该消息会被持久化到永久存贮介质中。如果日志(journal)的参数journal-sync-non-transactional设为true,服务器在向客户 发送响应时,它能保证消息已经被持久化到磁盘中。默认值是true

20.3. 非事务性通知的保证

当客户端使用非事务性会话向服务器通知消息收到时,可以配置HornetQ使得客户端的通知阻塞直到服务器收到 了通知并返回为止。其相应的配置参数是BlockOnAcknowledge。如果该参数设为 true则所有的通过非事务会话的消息通知都是阻塞式的。如果你想要的消息传递策略是 最多一次的话,那么你需要将此参数设为。默认值是false

20.4. 异步发送通知

如果你使用的是非事务会话来发送消息,并且希望保证每个发送出去的消息都到达服务器的话,你可以将HornetQ配置 成阻塞的方式,如Section 20.2, “非事务性消息发送的保证”讨论的那样。这样做的一个缺点是性能的降低。 因为这样每发送一个消息就需要一次网络的往返通信。如果网络时延越长,消息发送的效率就越低。同时网络的带宽对消息 的发送没有影响。

我们来做一个简单的计算。假设有一个1Gib的网络,客户端与服务器间往返时间为0.25ms。

这样,在阻塞方式的情况下,客户端最大的消息发送速度为 1000/ 0.25 = 4000 消息每秒。

如果每个消息的大小< 1500字节,而且网络的最大传输单元(MTU)是1500字节。那么理论上1GiB的网络 最大的传输速率是 (1024 * 1024 * 1024 / 8) / 1500 = 89478 消息每秒!尽管这不是一个精确的工程计算但 你可以看出阻塞式的发送对性能的影响会有多大。

为了解决这个问题,HornetQ提供了一种新的功能,称为异步发送通知。 它允许消息以非阻塞的方式发送,同时从另一个连接流中异步地接收服务器的通知。这样就使得消息的发送与通知分开来, 避免了阻塞方式带来的缺点。在保证消息可行发送到服务器的同时提高了呑吐量。

参数用来定义消息发送通知的窗口大小。它属于连接工厂或客户会话工厂。参见Chapter 34, 客户端重新连接与会话恢复 以获取更多的相关信息。

20.4.1. 异步发送通知

如果使用核心API,你需要实现org.hornetq.api.core.client.SendAcknowledgementHandler接口并将一个实例设置到 ClientSession中。

然后使用这个ClientSession发送消息。当消息到达服务器后,服务器向客户端异步地发送通知, 并在客户端调用你的SendAcknowledgementHandler实例的sendAcknowledged(ClientMessage message)方法。其中传入的参数就是发送的消息的引用。

为了使异步发送通知正常工作你必须确保confirmation-window-size的值为一个正整数,例如 10MiB

相关的例子请参见 Section 11.1.45, “发送通知”

Chapter 21. 消息再传递及未传递的消息

消息有可能传递失败(比如相关的事务发生回滚)。失败的消息将退回到队列中准备重新传递。这样就会出现 一种情况,就是同一个消息会被反复的传递而总不成功,以至于使系统处于忙的状态。

对于这样的消息我们有两种处理方法:

  • 延迟再传递

    这种方法是让消息再次传递时有一定的时间延迟,这样客户端就有机会从故障中恢复,同时网络连接和CPU资源 也不致于被过度占用。

  • 死信(Dead Letter)地址

    这种方法是规定一个死信地址,如果消息再被反复传递达到一定次数时,就会从原有队列中删除,转到这个 死信地址中。这样消息就不会永远地重复传递了。

以上两种方法可以合理搭配使用,使解决方案更加灵活。

21.1. 延迟再传递

延迟再传递对于时常出现故障或回滚的客户端十分有用。如果没有延迟,整个系统可能会处于一种”疯狂“的状态。 就是消息被传递、回滚、再传递,这样反复不间断地进行着,将宝贵的CPU和网络资源占用。

21.1.1. 延迟再传递的配置

延迟再传递的配置在地址设定内(address-setting):

     <!-- delay redelivery of messages for 5s -->
     <address-setting match="jms.queue.exampleQueue">
        <redelivery-delay>5000</redelivery-delay>
     </address-setting>
             

如果定义了redelivery-delay,HornetQ在再传递之前等待所定义的时间。

默认是没有延时的(即redelivery-delay的值是0)。

可以使用通配符为一组地址定义再传递的延迟(参见Chapter 13, 了解 HornetQ 通配符的语法)。

21.1.2. 例子

参见 Section 11.1.12, “延迟再发送”。这是一个JMS应用中配置延迟再传递的例子。

21.2. 死信地址

通过定义一个死信地址也可以防止同一个消息被无休止地传递: 当一个消息被重复传递一定次数后,就会从队列中删除并传递到定义好的死信地址中。

这些死信中的消息之后可以转发到某个队列中,以供系统管理员分析处理。

每个HornetQ的地址可以有一个死信地址。当一个消息被反复传递达一定次数时,它就会被从队列中删除并送到 死信地址。这些死信消息可以被接收进行分析处理。

21.2.1. 配置死信地址

死信地址定义在地址设定中(address-setting):

     <!-- undelivered messages in exampleQueue will be sent to the dead letter address 
        deadLetterQueue after 3 unsuccessful delivery attempts
      -->
     <address-setting match="jms.queue.exampleQueue">
        <dead-letter-address>jms.queue.deadLetterQueue</dead-letter-address>
        <max-delivery-attempts>3</max-delivery-attempts>
     </address-setting>
             

如果没有定义dead-letter-address,消息在经过 max-delivery-attempts次重复传递后被删除。

默认的重复传递次数为10。将max-delivery-attempts设定为-1 表示无限次重复传递。

例如,对一组地址设置了一个通用的死信地址后,再将其中某个地址的max-delivery-attempts 设定为-1时,那么只有这个地址的再传递次数是无限的。

可以使用通配符对一组地址设定死信地址(参见Chapter 13, 了解 HornetQ 通配符的语法)。

21.2.2. 死信的属性

从死信地址接收到的消息有以下属性:

  • _HQ_ORIG_ADDRESS

    这是一个字符串属性,它是该死信消息的原始地址

21.2.3. 例子

参见Section 11.1.11, “死消息(Dead Letter)”。这个例子给出了在JMS应用中死信的配置与使用。

21.3. 传递计数的持久化

通常情况下HornetQ在一个消息被回滚之前并不更新持久的传递计数(即在消息传递到接收者之前不会更新传递计数)。 大多数情况下消息被接收、通知、然后被忘掉。这样对每一个消息的传递都要更新一次持久的 传递计数,会显著降低系统的性能。

介是如果在消息传递之前不进行持久传递计数的更新,服务器一旦发生故障而崩溃,就会造成消息可能被传递出去而传递 计数却没有正确反映出传递的結果。在恢复阶段,服务器将错误地将该消息的redelivered设为 false而不是true

这样是不符合严格的JMS要求的。因此HornetQ允许在消息传递前更新传递计数。但是默认不这样做,目的是优先考虑 了它对性能的影响。

要想打开传递计数更新功能,将hornetq-configuration.xml文件中的 persist-delivery-count-before-delivery设为true即可:

<persist-delivery-count-before-delivery>true</persist-delivery-count-before-delivery>
      

Chapter 22. 过期的消息

消息在发送时有一个可选的生存时间属性。

如果一个消息已经超过了它的生存时间,HornetQ不再将它传递给任何接收者。 服务器会将过期的消息抛弃。

HornetQ的地址可以配置一个过期地址,当消息过期时,它们被从队列中删除并被转移到过期地址中。 多个不同的队列可以绑定到一个过期地址上。这些过期的消息过后可以接收下来以供分析用。

22.1. 过期消息的配置

如果使用HornetQ核心API,可以直接在消息上设置过期时间:

// message will expire in 5000ms from now
message.setExpiration(System.currentTimeMillis() + 5000);         
              

JMS的MessageProducer可以设置一个TimeToLive来控制其发送的消息:

// messages sent by this producer will be retained for 5s (5000ms) before expiration           
producer.setTimeToLive(5000);
        

从过期地址中接收下来的消息有以下属性:

  • _HQ_ORIG_ADDRESS

    这是一个字符串,它是该消息的原始地址

  • _HQ_ACTUAL_EXPIRY

    一个长整型量,代表此消息实际过期时间

22.2. 配置过期地址

过期地址配置在地址设置(address-setting)中:

<!-- expired messages in exampleQueue will be sent to the expiry address expiryQueue -->
<address-setting match="jms.queue.exampleQueue">
   <expiry-address>jms.queue.expiryQueue</expiry-address>
</address-setting>
        

如果没有定义过期地址,当一个消息过期时,它将被删除。配置过期地址时可以使用通配符 来给一组地址配置过期地址。(参见Chapter 13, 了解 HornetQ 通配符的语法)。

22.3. 配置过期回收线程

HornetQ有一个回收线程定期地检查队列中的消息,目的是发现是否有消息过期。

hornetq-configuration.xml文件中可以对回收线程进行配置,参数如下:

  • message-expiry-scan-period

    过期消息的扫描间隔(单位毫秒,默认为30000ms)。如要关闭扫描,将其设为-1

  • message-expiry-thread-priority

    回收线程的优先级(为0到9的整数,9优先级最高。默认是3)。

22.4. 例子

参见Section 11.1.28, “消息失效”。这个例子展示了在JMS中如何配置使用消息过期功能。

Chapter 23. 大消息

HornetQ支持超大消息的发送和接收。消息的大小不受客户端或服务器端的内存限制。它只受限于你的磁盘空间的大小。 在我们作过的测试中,消息最大可达8GiB,而客户端和服务器端的内存只有50MiB!

要发送一个大消息,用户需要为大消息提供一个InputStream,当大消息被发送时, HornetQ从该InputStream读取消息。例如,要将一个磁盘中的大文件以消息形式发送,可以 使用FileInputStream

数据从InputStream读出并分解为一个个数据片段向服务器以流的形式发送。服务器在收到 这些片段后将它们保存到磁盘上。当服务器准备向接收者传递消息时,它将这些片段读回,同样以片段流的形式向接收者 一端发送。当接收者开始接收时,最初收到的只是一个空的消息体。它需要为其设置一个OutputStream 以便向大消息保存到磁盘上或其它地方。从发送到接收整个过程中不需要整个消息都在内存中。

23.1. 服务器端的配置

大消息在服务器端是直接保存在磁盘目录中。这一目录可以在HornetQ的配置文件中定义。

这个参数的名字是large-messages-directory

<configuration xmlns="urn:hornetq"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="urn:hornetq /schema/hornetq-configuration.xsd">

...

<large-message-directory>/data/large-messages</large-message-directory>

...

</configuration

默认的大消息保存目录是data/largemessages

为了提高性能,我们建议将大消息的保存目录定义到与消息日志(journal)或分页转存目录分开的物理卷上。

23.2. 设定限制

参数min-large-message-size定义了大消息的最小值。 任何消息的大小如果超过了该值就被视为大消息。一旦成为大消息,它将被分成小的 片段来传送。

默认值是100KiB.

23.2.1. 使用核心的API

如果使用HornetQ的核心,ClientSessionFactory.setMinLargeMessageSize方法 可以设置大消息的最小值。

ClientSessionFactory factory = 
            HornetQClient.createClientSessionFactory(new 
            TransportConfiguration(NettyConnectorFactory.class.getName()), null);
factory.setMinLargeMessageSize(25 * 1024);

Section 16.3, “在客户端直接配置传输层”对于如何实例化一个会话工厂(session factory) 给出了进一步的说明。

23.2.2. 使用JMS

如果连接工厂是通过JNDI方式获得的,则需要在hornetq-jms.xml文件中定义:

...
<connection-factory name="ConnectionFactory">
<connectors>
   <connector-ref connector-name="netty"/>
</connectors>
<entries>
   <entry name="ConnectionFactory"/>
   <entry name="XAConnectionFactory"/>
</entries>
                
<min-large-message-size>250000</min-large-message-size>
</connection-factory>
...

如果是直接实例化连接工厂,则使用HornetQConnectionFactory.setMinLargeMessageSize方法来定义。

23.3. 大消息与流(stream)

在HornetQ中可以定义大消息所使用的输入和输出流(java.lang.io)。

HornetQ将使用定义的流来发送(输入流)和接收(输出流)大消息。

在使用输出流接收大消息时,有两种选择:你可以用ClientMessage.saveOutputStream方法 以阻塞的方式保存大消息;或者你可以使用ClientMessage.setOutputstream方法 以异步方法保存大消息。在采用后一种方法时,必须保证接收者(consumer)在大消息的接收过程中保持 有效状态。

根据需要选择所适合的流。最常见的情况是将磁盘文件以消息方式发送,也有可能是JDBC的Blob数据, 或者是一个SocketInputStream,或是来自HTTPRequests 的数据等等。只要是实现了java.io.InputStreamjava.io.OutputStream的数据源都可以作为大消息传送。

23.3.1. 核心API中流的使用

下表列出了ClientMessage上可以使用的方法。 通过相应的对象属性也可以在JMS中应用。

Table 23.1. org.hornetq.api.core.client.ClientMessage API

名称说明JMS相对应的属性
setBodyInputStream(InputStream)设定大消息发送时所使用的输入流。JMS_HQ_InputStream
setOutputStream(OutputStream)设定异步接收大消息所使用的输出流。JMS_HQ_OutputStream
saveOutputStream(OutputStream)设定保存大消息所使用的输出流。这个方法将会阻塞直到大消息全部 保存完毕才返回。JMS_HQ_SaveStream

下面代码中设定了接收核心消息所用的输出流:

...
ClientMessage msg = consumer.receive(...);


// This will block here until the stream was transferred
msg.saveOutputStream(someOutputStream); 

ClientMessage msg2 = consumer.receive(...);

// This will not wait the transfer to finish
msg.setOutputStream(someOtherOutputStream); 
...
                
            

设定发送核心消息所用的输入流:

...
ClientMessage msg = session.createMessage();
msg.setInputStream(dataInputStream);
...
            

23.3.2. 在JMS中使用流

使用JMS时,HornetQ根据定义的属性值调用对应的核心接口(参见 Table 23.1, “org.hornetq.api.core.client.ClientMessage API”)来使用流。你只需要用 Message.setObjectProperty方法设置适当的输入/输出流即可。

输入流InputStream可以通过JMS属性JMS_HQ_InputStream来定义:

BytesMessage message = session.createBytesMessage();

FileInputStream fileInputStream = new FileInputStream(fileInput);

BufferedInputStream bufferedInput = new BufferedInputStream(fileInputStream);

message.setObjectProperty("JMS_HQ_InputStream", bufferedInput);

someProducer.send(message);

输出流OutputStream可以通过JMS属性JMS_HQ_SaveStream来定义。下面是阻塞式方法:

BytesMessage messageReceived = (BytesMessage)messageConsumer.receive(120000);
                
File outputFile = new File("huge_message_received.dat");
                
FileOutputStream fileOutputStream = new FileOutputStream(outputFile);
                
BufferedOutputStream bufferedOutput = new BufferedOutputStream(fileOutputStream);
                
// This will block until the entire content is saved on disk
messageReceived.setObjectProperty("JMS_HQ_SaveStream", bufferedOutput);
            

也可以使用JMS_HQ_OutputStream属性以非阻塞式(异步)方法来定义输出流OutputStream

// This won't wait the stream to finish. You need to keep the consumer active.
messageReceived.setObjectProperty("JMS_HQ_OutputStream", bufferedOutput);
            

Note

使用JMS时,只有StreamMessageBytesMessage才支持大消息的传送。

23.4. 不使用流的方式

如果不想使用输入流与输出流来传送大消息,可以用另外一种方法。

使用核心接口时,可以直接从消息中读字节。

ClientMessage msg = consumer.receive();
         
byte[] bytes = new byte[1024];
for (int i = 0 ;  i < msg.getBodySize(); i += bytes.length)
{
   msg.getBody().readBytes(bytes);
   // Whatever you want to do with the bytes
}

使用JMS接口时,BytesMessageStreamMessage 本身提供这样的支持。

BytesMessage rm = (BytesMessage)cons.receive(10000);

byte data[] = new byte[1024];

for (int i = 0; i < rm.getBodyLength(); i += 1024)
{
   int numberOfBytes = rm.readBytes(data);
   // Do whatever you want with the data
}        

23.5. 在客户端缓存大消息

大消息通过流在服务器和客户端之间传输。每个大消息被分割成很多小的数据包传递。因此大消息只能被 读取一次。这样一个大消息在收到后就不能再被再次传送。例如,JMS Bridge在发送大消息时如果在出现故障, 将不能把它重新发送。

要解决这个问题,可以在连接工厂上设置cache-large-message-client属性。 这个属性可以使客户端接收者创建一个临时的文件保存收到的大消息,这样就可以在需要时能够重新发送该消息。

Note

如果JMS Bridge用来发送大消息,可以在它使用的连接工厂上使用它。

23.6. 大消息例子

我们在Section 11.1.22, “大消息”提供了一个在JMS中配置和使用大消息的例子。

Chapter 24. 分页转存

HornetQ可以在有限的内存下支持包含百万消息的超大规模的队列。

当有限的内存无法装得下如此多的消息时,HornetQ将它们分页转存到磁盘中,在内存 有空闲时再将消息分页装载到内存。通过这样的处理,不需要服务器有很大的内存就可以支持大容量的队列。

通过配置可以给一个地址设置一个最大消息值。当这个地址消息数在内存中超过了这个值时,HornetQ开始将多余的消息 转存到磁盘中。

默认情况下HornetQ不转存任何消息。这一功能必须显式地通过配置来激活。

24.1. 分页文件

消息按照所属的地址分别保存在不同的文件中。每一个地址有一个单独的文件夹,每个文件夹内消息被保存在 数个文件中(分页文件)。每个文件保存固定数量的消息(由参数page-size-bytes 设定)。当从分页文件中读取消息时,一个文件的所有消息被读到内存并被路由。当所有消息处理后,该文件就 被删除。

24.2. 配置

你可以配置分页转存文件夹的位置。

在主配置文件hornetq-configuration.xml)中 可以定义全局的分页转发参数。

<configuration xmlns="urn:hornetq"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="urn:hornetq /schema/hornetq-configuration.xsd">
            
            ...
            
            <paging-directory>/somewhere/paging-directory</paging-directory>
            
            ...        

Table 24.1. 分页转存的配置参数

参数名说明默认值
paging-directory分页转存文件的位置。HornetQ在这个位置下为每个地址建立一个文件夹。data/paging

24.3. 分页转存模式

一个地址只要消息的数量超过定义的值,它就转到分页转存的模式。

Note

分页转存是针对每个地址设置的。如果你为一个地址配置了一个max-size-bytes,那么每个匹配的地址 都有一个最大值的限制。但是这并不表示所有匹配的地址的大小总和受这个参数的限制。

24.3.1. 配置

有关分页转存的配置在主要配置文件(hornetq-configuration.xml) 的地址设置的部分内。

  <address-settings>
      <address-setting match="jms.someaddress">
         <max-size-bytes>104857600</max-size-bytes>
         <page-size-bytes>10485760</page-size-bytes>
         <address-full-policy>PAGE</address-full-policy>
      </address-setting>
   </address-settings>
        

下面列出了可用的参数:

Table 24.2. 分页转存参数设置

参数名称说明默认值
max-size-bytes地址的最大内存值。当消息占用内存超过此值时,进入分页转存模式。-1 (关闭分页转存功能)
page-size-bytes每个分页文件的大小。10MiB (10 * 1024 * 1024 字节)
address-full-policy要使用分页转存,这个参数必须设为PAGE。PAGE表示多余的消息会被保存到磁盘。 如果设为DROP,那么多余的消息将会被丢弃。如果设为BLOCK,当消息占满设定的最大 内存时,在客户端消息的发送者将被阻塞,不能向服务器发送更多的消息。PAGE

24.4. 丢弃消息

一个地址除了可以分页转存多余的消息外,还可以配置为丢弃多余消息。

只要将address-full-policy设为DROP即可。

24.5. 阻塞发送者(producer)

一个地址除了可以分页转存多余的消息外,还可以通过配置使得消息的发送者在消息达到最大值时阻塞消息 的发送,以防止服务器由于消息过多而耗尽内存。

随着服务器的内存被释放,发送者自动解除阻塞,继续发送消息。

这种方式需要将address-full-policy设为BLOCK

在默认的配置中,所有的地址在消息的量达到10MiB后将阻塞发送者。

24.6. 对于有多个队列绑定的地址的配置注意事项

当一个消息被路由到一个绑定了多个队列(queue)的地址时(比如JMS的订阅),在内存中仍然只有一分消息的拷贝。每个 队列所持有的不过是它的一个引用。因此,只有所有队列中的消息都成功地传递出去后,这个消息才会从内存中清除。也就是说 只要有一个队列没有传递这个消息,那么就会造成这个消息处于未被传递的状态。

例如:

  • 一个地址绑定了10个队列(queue)。

  • 其中一个队列没有传递它的消息(也许因为接收者很慢)。

  • 消息不断增加,触发了分页转存模式。

  • 而其它9个队列尽管发送了消息,但由于地址将多余的消息转存到磁盘,所以它们都是空的。

在这个例子中,必须要等到最后一个队列传递了一些消息后,那些转存的消息被装载回内存,其它队列才有机会得到更多的消息。

24.7. 分页转存与消息的选择器(selector)

Note

请注意消息选择器只对内存的消息进行操作。如果大量的消息被转存在磁盘中,而其中有些消息与选择器是相匹配的, 那么只有内存的消息被传递,这些消息被重新装载入内存后才有可能被传递出去。 HornetQ不会扫描在磁盘中的消息来找出与选择器匹配的消息。这样做的话需要实现并管理一种索引机制才能使扫描有效地进行,另外 需要其它额外的工作。所有这些如果去完成的话,相当于实现一个关系型数据库!这并不是消息系统的主要任务。如果你要完成的任务是 从海量的消息中选择少数消息,那么你很可能需要使用的是一个关系型数据库,不是消息系统。因为这相当于表的查询。

24.8. 分页转存与浏览器

Note

请注意浏览器只对内存中的消息进行操作,它不对转存到磁盘中的消息进行操作。 消息是在被路由到任何队列之前进行转存的,所以在转存时刻,它们还没有进入到任何队列中, 自然也就不可能出现在对某个队列浏览的結果中。

24.9. 分页转存与未通知的消息

Note

请注意如果消息没有被通知,它会一直留在服务器的内存中,占用着内存资源。只要消息在被接收者收到并通知后,它才 会在服务器端被清除,空出内存空间以便转存在磁盘上的消息被装载到内存进行传递。如果没有通知,消息不会被清除, 也就不会空出内存空间,转存到磁盘上的消息也就无法装载到内存进行传递。于是在接收端就会呈现出死机的现象。 如果消息的通知是依靠ack-batch-size的设定进行的批量通知,那么一定要注意不要将 分页转存的消息临界值设得小于ack-batch-size,否则你的系统可能会发生死机现象!

24.10. 例子

Section 11.1.34, “分页(paging)”是一个说明如何使用HornetQ的分页转发功能的例子。

Chapter 25. 队列属性

有两种方法可以设置队列的属性。一种使用配置文件,另一种使用核心接口(core API)。 本章讲述这些属性的配置以及这些属性的作用。

25.1. 预定义的队列

通过配置可以定义队列。队列的定义可以在核心层定义,也可以在JMS层来定义。首先我们看一下JMS层。

下面就是一个在hornetq-jms.xml中定义的一个队列的例子:

<queue name="selectorQueue">
      <entry name="/queue/selectorQueue"/>
      <selector string="color='red'"/>
      <durable>true</durable>
</queue>

这个队列的name属性定义了队列的名字。例子中我们采用了一种命名的惯例,因些对应的核心队列的名字是 jms.queue.selectorQueue

在entry单元内定义的名字用来将队列绑定于JNDI。这是必不可少的。一个队列可以有多个entry定义,每个 定义中的名字都绑定到同一个队列。

selector单元定义的是队列的选择器。定义了选择器后,只有与选择器相匹配的消息才能被加到队列中。 这是一个可选项。如果没有定义选择器,队列将默认没有选择器。

durable定义了队列是否是一个可持久的队列。这也是一个可选项,默认值是true。

如果在核心层定义队列,则使用hornetq-configuration.xml文件。 下面是一个例子:

<queues>     
   	<queue name="jms.queue.selectorQueue">
   	    <address>jms.queue.selectorQueue</address>
   	    <filter string="color='red'"/>
       <durable>true</durable>
   	</queue>
</queues>

它的配置与JMS的配置很相似,但有三个不同之处:

  1. 队列的name属性是队列的真正名字,不是JMS中的名字。

  2. address一项定义了消息路由的地址。

  3. 没有entry单元。

  4. filter的定义使用核心过滤器语法 (在 Chapter 14, 过滤器表达式中描述),不是JMS的选择器语法。

25.2. 使用接口(API)创建队列

队列还可以使用核心接口或管理接口来创建。

核心接口的org.hornetq.api.core.client.ClientSession接口可以用来 创建队列。它有几个createQueue方法,可以在创建队列时对上述的属性进行设置。 除此之外,还有一个额外的属性temporary可以设置。如果将其设为true, 那么队列在会话断开时将被删除。

Chapter 30, 管理中讲述了如何用管理接口来创建队列。

25.3. 通过地址设置来配置队列属性

有些属性的定义中地址可以使用通配符。下面是hornetq-configuration.xml 文件中的一个address-setting的配置例子。

<address-settings>
    <address-setting match="jms.queue.exampleQueue">
        <dead-letter-address>jms.queue.deadLetterQueue</dead-letter-address>
        <max-delivery-attempts>3</max-delivery-attempts>
        <redelivery-delay>5000</redelivery-delay>
        <expiry-address>jms.queue.expiryQueue</expiry-address>
        <last-value-queue>true</last-value-queue>        
        <max-size-bytes>100000</max-size-bytes>
        <page-size-bytes>20000</page-size-bytes>
        <redistribution-delay>0</redistribution-delay>
        <send-to-dla-on-no-route>true</send-to-dla-on-no-route>
        <address-full-policy>PAGE</address-full-policy>
     </address-setting>
</address-settings>

通过上述的地址设定可以将多个属性应用于所有与match属性相匹配的地址。 上面例子中所定义的属性应用于jms.queue.exampleQueue的地址。如果使用 通配符,就可以将这些属性应用于一组匹配的地址。通配符的详细说明在这里

例如在match中定义字符串jms.queue.#,那么 定义的属性就会应用于所有以jms.queue.开头的地址--即所有的JMS队列。

这些属性在本手册的各个地方有相应的介绍。在此处给出了简单的解释各它所在章的连接。

max-delivery-attempts定义了最大重传递的次数。一个消息如果反复传递超过 了这个值将会被发往死信地址dead-letter-address。相关的完整的解释在 这里

redelivery-delay定义了重新传递的延迟。它控制HornetQ在重新 传递一个被取消的消息时要等待的时间。参见这里

expiry-address定义了过期消息的发送地址。参见这里

last-value-queue 定义一个队列是否使用最新值。参见这里

max-size-bytespage-size-bytes用来设置地址的分页转存功能。 它们在这里有详细的解释。

redistribution-delay定义了当最后一个接收者关闭时重新分配队列消息前所等待的时间。 参见这里

send-to-dla-on-no-route。当一个消息被送到某个地址时,可能不会被路由到任何一个队列。 例如该地址没有绑定任何队列的情况,或者它所有的队列的选择器与该消息不匹配时。这样的消息通常情况下会被丢弃。这时 如果将这个参数设为true,则如果这个地址配置了死信地址的话,这样的消息就会被发送到该地址的死信地址(DLA)。

address-full-policy。这个属性有三个可能的值:PAGE、 DROP 或 BLOCK。它决定了 如果地址的消息所占用的内存达到了max-size-bytes所定义的值时,如何处理后继到来的消息。 默认值是PAGE,就是将后续的消息分页转存到磁盘上。DROP则表示丢弃后续的消息。BLOCK表示阻塞消息的发送方发送后续 的消息。参见Chapter 19, 流控制Chapter 24, 分页转存

Chapter 26. 定期消息

与普通消息不同,定期消息是在未来某个指定时间发送的消息。

为了创建定期消息,需要设定一个特殊的参数.

26.1. 定期传递参数

用来标识一个定期消息的参数是"_HQ_SCHED_DELIVERY" (相当于常量Message.HDR_SCHEDULED_DELIVERY_TIME)。

这个参数的值必须是一个大于零的长整型,单位是毫秒。下面例子给出了使用JMS接口创建定期消息的方法:

  TextMessage message = 
   session.createTextMessage("This is a scheduled message message which will be delivered
     in 5 sec.");
  message.setLongProperty("_HQ_SCHED_DELIVERY", System.currentTimeMillis() + 5000);
  producer.send(message);

  ...
         
  // message will not be received immediately but 5 seconds later
  TextMessage messageReceived = (TextMessage) consumer.receive();
      

也可以使用核心接口来发送定期消息。它只需要将同样的参数设定到核心消息上即可。

26.2. 例子

参见Section 11.1.43, “定时消息”,它是一个JMS使用定期消息的例子。

Chapter 27. 最新值队列(Last-Value Queues)

最新值队列是一种特殊的队列。当一个新消息到达一个最新值队列时,它会将所有与该消息定义的Last-Value相同的旧消息 抛弃。换句话说,只有最新的消息被保留下来。

一个典型的用例是股价信息,通常你只关心一支股票的最新价格。

27.1. 最新值队列的配置

最新值队列的配置在address-setting内:

<address-setting match="jms.queue.lastValueQueue">
    <last-value-queue>true</last-value-queue>
</address-setting>
            

默认的last-value-queue值是false。可以使用通配符来匹配地址。 (参见 Chapter 13, 了解 HornetQ 通配符的语法)。

27.2. 使用Last-Value参数

用来标识最新值的参数名是"_HQ_LVQ_NAME" (相当于核心API中定义的常量Message.HDR_LAST_VALUE_NAME)。

如果两个消息具有相同的Last-Value值,那么较新的消息就会保留,另外一个被丢弃:

// send 1st message with Last-Value property set to STOCK_NAME
TextMessage message = 
  session.createTextMessage("1st message with Last-Value property set");
message.setStringProperty("_HQ_LVQ_NAME", "STOCK_NAME");
producer.send(message);

// send 2nd message with Last-Value property set to STOCK_NAME             
message = 
  session.createTextMessage("2nd message with Last-Value property set");
message.setStringProperty("_HQ_LVQ_NAME", "STOCK_NAME");
producer.send(message);
       
...
       
// only the 2nd message will be received: it is the latest with 
// the Last-Value property set
TextMessage messageReceived = (TextMessage)messageConsumer.receive(5000);
System.out.format("Received message: %s\n", messageReceived.getText());             
            

27.3. 例子

参见Section 11.1.23, “最新值队列”。它展示的是在JMS应用中来配置和使用 最新值队列。

Chapter 28. 消息分组

消息组是具有下列特性的消息集合:

  • 在一个消息组中的消息有相同的组标识(id),即它们的JMSXGroupID(JMS)或 _HQ_GROUP_ID(HornetQ核心)的值相同。

  • 不管存在多少个接收者(consumer),一个消息组的所有消息总是被同一个接收者所接收。一个组id总是 与同一个接收者相关联。如果这个接收者被关闭,另外一个接收者就被选中来代替它接收该消息组的消息。

消息组在需要同一个接收者按顺序处理某类消息的时候很有用。

一支股票的订购就是一个例子。某支股票的订单需要同一个接收者按顺序处理。于是可以每支股票有一个接收者 来处理(也可以用少一些的接收者),然后将每支股票的名字设在消息的_HQ_GROUP_ID参数中。

这样可以保证一支股票的消息只被同一个接收者处理。

28.1. 使用核心接口

用来标识一个消息组的参数是 "_HQ_GROUP_ID"" (或者相应的常量MessageImpl.HDR_GROUP_ID)。另一种方法是在SessionFactory 中将autogroup设置为true。这样做的话组id是随机给出的。

28.2. 使用JMS

用来标识一个消息组的参数是JMSXGroupID

 // send 2 messages in the same group to ensure the same
 // consumer will receive both
 Message message = ...
 message.setStringProperty("JMSXGroupID", "Group-0");
 producer.send(message);

 message = ...
 message.setStringProperty("JMSXGroupID", "Group-0");
 producer.send(message);          
       

另一个方法是将HornetQConnectonFactoryautogroup 属性设为true,或者在hornetq-jms.xml文件中进行配置:

<connection-factory name="ConnectionFactory">
      <connectors>
         <connector-ref connector-name="netty-connector"/>
      </connectors>
      <entries>
         <entry name="ConnectionFactory"/>
      </entries>
      <autogroup>true</autogroup>
</connection-factory>

还可以通过连接工厂来设置组id。来自这个连接工厂的所有的发送者(producer)发送的消息的JMSXGroupID将具有指定的值。这种方法需要在hornetq-jms.xml 文件中作如下配置:

         <connection-factory name="ConnectionFactory">
      <connectors>
         <connector-ref connector-name="netty-connector"/>
      </connectors>
      <entries>
         <entry name="ConnectionFactory"/>
      </entries>
      <group-id>Group-0</group-id>
   </connection-factory>
      

28.3. 例子

参见Section 11.1.29, “消息组”。这个例子展示的是在JMS中如何配置与使用消息组。

28.4. 例子

Section 11.1.30, “消息组(例2)”是另外一个消息组的例子,在这个例子中通过配置连接工厂 来使用消息组。

28.5. 集群中的消息组

在集群中使用消息组是相对比较复杂的。这是因在在集群中,一个消息组中的消息有可能被送到集群中的任一全节点, 这就要求每个节点都要知道这个消息是属于哪个节点上的哪个接收者(consumer)。一个消息组的消息往往会被发送到 集群中的一个节点,而该消息组的接收者在另一个节点上。每个节点都要知道这些细节以便能将消息正确路由到所属接收 者所在的节点上。

为了解决上述问题,我们使用了消息组处理器。每个节点都有一个自己的消息组处理器。当一个带有组id的消息收到时, 这些节点的消息组处理器就会协同作出决定该如何对这个消息进行路由。

消息组处理器有两种:本地消息组处理器和远程消息组处理器。在一个集群中要选择一个节点作为本地消息组处理器的 节点,集群中所有其它的节点都持有远程消息组处理器。在集群中由本地消息组处理器最終决定消息怎样路由,其它的远程 处理器配合本地处理器完成决策。消息组处理器的配置在hornetq-configuration.xml 文件中,下面就是一个例子:

   <grouping-handler name="my-grouping-handler">
      <type>LOCAL</type>
      <address>jms</address>
      <timeout>5000</timeout>
   </grouping-handler>

   <grouping-handler name="my-grouping-handler">
      <type>REMOTE</type>
      <address>jms</address>
      <timeout>5000</timeout>
   </grouping-handler>

address属性表示一个集群的连接以及它使用的地址。有关如何配置集群 参见集群章节。timeout属性代表做出路由决定所需要等待的时间。如果超过 了这个时间还没有做出决定,则会抛出异常。这可以保证严格的顺序。

收到消息的节点会首先提出一个消息路由的建议。它采用轮询方式来选择一个合适的路由。它首先选择一个本地的队列,之后 再选择一个有接收者的队列。如果这个建议被所有组处理器接受,消息就会被路由到所选的队列。如果被拒绝就提出另一个路 由方案,如此反复直到方案被接受为止。队列选择后所有其它的节点都将消息路由到这个队列。这样所有的消息组的消息在一个 节点上进行处理,也就是该节点上的接收者接收所有的同组的消息。

由于只有一个本地处理器,如果它的节点出现故障则无法做出决定。这时所有的消息将不能被传递,并且会抛出异常。 为了避免这一单点故障,本地处理器可以在备份节点上有一个复本。只要创建备份节点并配置一个相同的本地处理器即可。

28.5.1. 集群消息组的最佳使用惯例

下面是一些很好的建议:

  1. 尽可能使接收者均匀分布在不同的节点上。由于消息组的消息总是传递到同一个队列的同一个接收者, 如果你经常性地创建与关闭接收者,就可能出现消息由于没有接收者而传递不出去,造成消息在队列中 不断积累的情况。因此,尽量要避免关闭接收者,或者确保有足够数量的接收者。例如,如果你的集群 有3个节点,那么就创建3个接收者。

  2. 尽可能使用持久型队列。如果消息组的消息与一个队列绑定,一旦这个队列被删除,其它节点可能仍然 尝试向这个已删除的队列路由消息。为避免这种情况,要确保这个队列由发送消息的会话来删除。这样如果 下一个消息发出后发现原来的队列被删除,新的路由建议就会提出。另外一种方案是你可以重新使用一个不 同的组ID。

  3. 一定要确保本地的消息组处理器有备份。这样在有故障时消息组仍然可以正常工作。

28.5.2. 集群消息组例子

参见Section 11.1.6, “集群分组”,这个例子给出了如何在HornetQ集群中配置消息组。

Chapter 29. 预先通知模式(pre-acknowledge)

JMS 规定了三种消息通知方式

  • AUTO_ACKNOWLEDGE

  • CLIENT_ACKNOWLEDGE

  • DUPS_OK_ACKNOWLEDGE

还有一种情况JMS不支持:应用程序在出现故障时可以容忍消息丢失,这样可以在消息在传递给客户 端之前就通知服务器。

HornetQ支持这种模式,称为pre-acknowledge

这种模式的缺点是消息在通知后,如果系统出现故障时,消息可能丢失。并且在系统重启后该消息 不能恢复。

使用pre-acknowledgement模式可以节省网络传输和CPU处理资源。

股票价格更新是一个适用于此模式的例子。如果因为服务器故障丢失了一些消息,等服务器重启后新的 股票更新消息很快到达,以前丢失的过时的股票消息即使丢失也无关紧要。

Note

注意如果你使用pre-acknowledge模式,在接收消息端不能支持事务。因为这个模式不是在提交时 通知消息,是在消息在传递之前就通知了。

29.1. 使用PRE_ACKNOWLEDGE

这个模式在hornetq-jms.xml文件中 的connection factory下配置:

<connection-factory name="ConnectionFactory">
      <connectors>
         <connector-ref connector-name="netty-connector"/>
      </connectors>
      <entries>
         <entry name="ConnectionFactory"/>
      </entries>
      <pre-acknowledge>true</pre-acknowledge>
</connection-factory>

另一个选择是使用JMS接口来设置pre-acknowledgement模式。只需要在创建JMS会话(session) 时使用HornetQSession.PRE_ACKNOWLEDGE常数即可。

// messages will be acknowledge on the server *before* being delivered to the client
Session session = connection.createSession(false, HornetQSession.PRE_ACKNOWLEDGE);
      

你还可以直接在HornetQConnectionFactory实例上设置该模式。

另外,如果使用核心接口,则在ClientSessionFactory实例上直接 设置该模式。

29.2. 例子

参见Section 11.1.35, “预先通知”。这是一个使用JMS的例子。

Chapter 30. 管理

HornetQ拥有套丰富的管理接口。用户使用这些接口可以修改服务器配置、创建新的资源(如队列和 话题)、检查这些资源(如队列中有多少消息)并进行管理(从队列中删除消息)。这样用户可以 管理HornetQ。另外,客户还可以订阅管理通知。

有三种方式管理HornetQ:

  • 使用JMX -- JMX是标准的Java应用程序管理方式。

  • 使用核心接口 -- 管理操作通过核心消息的方法发向HornetQ服 务。

  • 使用JMS接口 -- 管理操作通过JMS消息的方式发向HornetQ服务器。

虽然有三种方式,但它们提供相同的功能。使用JMX方法能完成的功能使用核心接口或JMS接口都可以完成。

根据不同的应用环境来选择最适当的方式。

30.1. 管理接口API

不管使用哪种方式,管理接口都是一样的。

对于每个被管理的资源都有一个Java的接口提供可使用的操作。

HornetQ的管理接口分布在2个包中:

  • 核心资源的管理接口在 org.hornetq.api.core.management包中。

  • JMS资源的管理接口在 org.hornetq.api.jms.management包中。

调用管理操作的方法由所使用是方式是JMX、核心消息还是JMS 消息来决定。

Note

一小部分的管理接口需要一个过滤器参数来选择需要的消息。 如果要求所有的消息,传递该参数时使用 null或者一个空的字符串即可。

30.1.1. 核心管理接口

HornetQ定义了一套对核心资源的管理接口。关于它们的详细说明请参见相应的javadoc。 下面是对它们的概述:

30.1.1.1. 核心服务器管理

  • 队列的列表、创建、部署与删除

    getQueueNames() method方法用来列出所有已经部署的队列。

    HornetQServerControl (ObjectName org.hornetq:module=Core,type=Server或资源名core.server)上有队列创建或删除的方法,它们是 createQueue()deployQueue()destroyQueue()

    如果队列已经存在,那么createQueue方法调用会出错,而 deployQueue方法调用没有任何作用。

  • 暂停与恢复队列

    QueueControl可用来暂停与恢复队列。如果一个队列被暂停,它 虽然可以继续接收消息但是不传递消息;当被恢复时,队列又会开始传递消息。

  • 远程连接的列表与关闭

    listRemoteAddresses()方法可以用来列出客户端的远程地址。 还可以使用closeConnectionsForAddress()方法来关闭 与该地址相关的远程连接。

    另外,使用listConnectionIDs()方法可以列出连接ID, 使用listSessions()方法可以列出与一个连接ID相关的所有 会话(session)。

  • 事务的手动操作(heuristic operations)

    当服务器由于故障而重新启动时,可能造成一些事务没有完成而需要人工干预。 listPreparedTransactions()方法可以列出所有处于 准备(prepared)状态的事务(事务是用Base64字符串的形式列出)。如果要提交或回滚, 可以使用commitPreparedTransaction()方法或 rollbackPreparedTransaction()方法。采用启发式 (heuristic)完成的事务可以用listHeuristicCommittedTransactions() 方法和listHeuristicRolledBackTransactions方法列出。

  • 打开和重置消息计数器

    消息计数器可以用enableMessageCounters()方法打开,用 disableMessageCounters()方法关闭。如果要重置消息计数器, 可以使用resetAllMessageCounters()方法和 resetAllMessageCounterHistories()方法。

  • 获得服务器的配置和属性

    HornetQServerControl提供了访问HornetQ服务器所有属性 的方法(例如getVersion()方法可以得到服务器的版本,等等)。

  • 核心桥和转发器的创建,删除与列表

    使用getBridgeNames()可以列出部署的核心桥。 使用getDivertNames()可以列出部署的转发器。

    使用HornetQServerControl (ObjectName org.hornetq:module=Core,type=Server 或资源名 core.server)的方法createBridge()               和destroyBridge()可以创建和删除核心桥。 通过createDivert()destroyDivert() 可以创建和删除转发器。

30.1.1.2. 核心地址的管理

核心地址可以通过AddressControl类进行访问(ObjectName 是 org.hornetq:module=Core,type=Address,name="<the address name>"或者资源名 core.address.<the address name>)。

  • 修改地址的角色和权限。

    你可以使用addRole()方法或removeRole() 方法添加或删除地址的角色。用getRoles()方法可以列出一个地址的所有角色。

30.1.1.3. 核心队列的管理

管理接口中的一大部分是管理核心队列的。QueueControl类定义了核心队列的管理 接口(ObjectName org.hornetq:module=Core,type=Queue,address="<绑定地址 address>",name="<队列名>" 或资源名 core.queue.<队列名>)。

绝大部分的队列管理方法需要一个消息ID参数(如删除一个消息)或一个过滤器参数(如将具有某个 属性值的所有消息设置为过期)。

  • 消息的过期,发向死信地址及删除

    expireMessages()方法可以使消息过期。如果设置了一个过期地址, 这些消息会被发到过期地址。否则这些消息会被丢弃。setExpiryAddress() 方法可以用来设置队列的过期地址。

    消息可以用sendMessagesToDeadLetterAddress()方法发送到 一个死信地址。它返回发到这个死信地址的消息的数量。如果没有设置死信地址,那么消息就会从队列中 删除。用setDeadLetterAddress()方法可以设置队列的死信地址。

    消息还可以从一个队列转移到另一个队列。其方法是 moveMessages()

  • 消息的列表与删除

    listMessages()方法可以列出一个队列中的所有消息。这个方法 返回的是一个Map的数组。每一个Map对应一个消息。

    消息可以用removeMessages()方法删除。如果是使用消息ID, 返回的是一个布尔常量;如果是用过滤器,则返回的 是删除的消息数量。在使用过滤器来删除过滤的消息时,如果传入一个空字符串则表示要删除 所有的消息。

  • 消息计数

    一个队列中的消息数可以用getMessageCount()方法获得。 此外,countMessages()方法可以返回队列中与一 个过滤器匹配的消息数量。

  • 修改消息的优先级

    changeMessagesPriority()方法可以改变消息的优先级。 该方法如果带一个消息ID参数,返回一个布尔常量;如果带一个过滤器参数,返回优先级 被更新的消息的数量。

  • 消息计数器

    listMessageCounter()方法和 listMessageCounterHistory()方法可以列出一个队列的消息计数器。 (参见 Section 30.6, “消息计数器”)。消息计数器还可以 用resetMessageCounter()方法重置。

  • 获得队列的属性

    通过QueueControl可以获得核心队列的属性(例如用 getFilter()方法可以得到队列的 过滤器,isDurable()方法可以知道队列是否是持久的队列等等)。

  • 暂停和恢复队列

    QueueControl可用来暂停与恢复队列。如果一个队列被暂停,它 虽然可以继续接收消息但是不传递消息;当被恢复时,队列又会开始传递消息。

30.1.1.4. 其它核心资源的管理

HornetQ允许用户启动或停止其远程资源(接收器,转发器,桥,等等)。这样可以使服务器暂停工作 而不需要完全停止服务器(比如可以临时对服务器进行一些离线操作,像对一些事务的处理)。这些资源有:

  • 接收器

    AcceptorControl类(ObjectName org.hornetq:module=Core,type=Acceptor,name="<接收器名 >" 或资源名 core.acceptor.<地址名 >)的start()方法启动,用 stop()方法停止。接收器的参数可以通过AcceptorControl 的属性获得。(参见 Section 16.1, “接收器(Acceptor)”)。

  • 转发器

    DivertControl(ObjectName是 org.hornetq:module=Core,type=Divert,name=<转发器名> 或资源名core.divert.<转发器>)类的 start()方法可以启动,用stop()方法可以停止。 通过DivertControl还可以获得转发器的各种属性。(参见 Chapter 35, 消息的转发(divert)与分流)。

  • 桥可以通过BridgeControl类(ObjectName org.hornetq:module=Core,type=Bridge,name="<桥的名字 >" 或资源名 core.bridge.<桥的名字 >)的start() 方法启动,用stop()方法停止。它的属性可以通过 BridgeControl的属性获得(参见 Chapter 36, 核心桥)。

  • 广播组

    广播组可以通过BroadcastGroupControl类(ObjectName org.hornetq:module=Core,type=BroadcastGroup,name="<广播组名 >" 或者资源名 core.broadcastgroup.<广播组名>)的 start()方法启动,用stop()方法停止。 它的属性也可以通过BroadcastGroupControl的属性获得(参见Section 38.2.1, “广播组”)。

  • 发现组

    发现组可以通过DiscoveryGroupControl类 (ObjectName org.hornetq:module=Core,type=DiscoveryGroup, name="<发现组名>" 或资源名core.discovery.< 发现组名>)的 start()方法启动,用stop()方法停止。 它的参数可以通过DiscoveryGroupControl的属性获得(参见 Section 38.2.2, “发现组”)。

  • 集群连接

    集群连接可以通过ClusterConnectionControl类( ObjectName org.hornetq:module=Core,type=ClusterConnection,name="<集群连接名 >" 或资源名 core.clusterconnection.<集群连接名>)的 start()方法启动,用stop()方法停止。 它的参数可以通过ClusterConnectionControl的属性来获得(参见 Section 38.3.1, “配置集群连接”)。

30.1.2. JMS管理接口

HornetQ定义了一套JMS管理接口来管理JMS的可管理的对象 (例如JMS队列,话题及连接工厂)。

30.1.2.1. JMS服务器管理

JMSServerControl类(ObjectName org.hornetq:module=JMS,type=Server 或资源名jms.server)用来创建JMS资源(连接工厂和目标)。

  • 列表、创建、删除连接工厂

    使用getConnectionFactoryNames() 方法可以列出部署的连接工厂的 名字。

    createConnectionFactory()方法和destroyConnectionFactory()方法能创建和删除JMS连接工厂。 这些连接工厂都与JNDI绑定以便于客户端来查找。如果是在图形介面下创建连接工厂,在广本框内输入 有关的传输参数时可使用一组用逗号隔开的键-值对(例如key1=10, key2="value", key3=false)。 如果需要定义多个传输,你需要将每个传输的参数对用大括号括起来,例如{key=10}, {key=20}。 第一个key属于第一个传输配置,第二个key属于第二个传输配置。 (有关传输的各种参数参见Chapter 16, 传输层的配置)。

  • 列表、创建与删除队列

    getQueueNames()方法可以获得部署的JMS队列的名字列表。

    JMS队列可以用createQueue()方法创建,用destroyQueue()方法删除。 创建的队列都绑定到JNDI以便JMS客户端可以查找。

  • 列表、创建与删除话题(topic)

    getTopicNames()方法可以获得部署的JMS话题名字。

    JMS话题可以用createTopic()方法来创建,用destroyTopic()方法来删除。 创建的话题都绑定到JNDI以便客户端查找。

  • 远程连接的列表与关闭

    listRemoteAddresses()方法可以获得JMS客户端的远程地址。 还可以用closeConnectionsForAddress()方法关闭与某个远程地址相关联的连接。

    另外,listConnectionIDs()方法可以列出连接的ID, 而listSessions()方法可以列出一个给定的连接ID的所有会话(session)。

30.1.2.2. JMS连接工厂的管理

使用类(ObjectName org.hornetq:module=JMS,type=ConnectionFactory, name="<连接工厂名>"或者资源名jms.connectionfactory.< 连接工厂名>)可以管理JMS的连接工厂。

  • 获得连接工厂的属性

    ConnectionFactoryControl类可以用来获得连接工厂的属性( 例如getConsumerWindowSize()方法可以获得接收者流控制的窗口大小, isBlockOnNonDurableSend()方法可以知道从这个连接工厂创建的发送 者是否采用阻塞方式发送非持久的消息,等等)。

30.1.2.3. JMS队列管理

使用JMSQueueControl类(ObjectName org.hornetq:module=JMS, type=Queue,name="<队列名>"或资源名 jms.queue.<队列名 >可以管理JMS队列。

JMS队列的管理操作与核心队列的管理十分相似。

  • 过期,发送到死信地址和移动消息

    可以使用expireMessages()方法将队列中的消息设成过期消息。 如果配置有过期地址,消息就会被发到过期地址。过期地址可以用 setExpiryAddress()方法来设定。

    使用sendMessagesToDeadLetterAddress()方法可以将消息发送到死信地址。 它返回发送到死信地址消息的数量。如果没有设定死信地址,那么消息会被丢弃。使用 setDeadLetterAddress()方法可以设定队列的死信地址。

    moveMessages()方法将消息从一个队列移动到另一个队列。

  • 列表与删除消息

    使用listMessages()方法可以列出一个队列中的所有消息。它返回的是一个 Map的数组。每一个Map对应一个消息。

    使用removeMessages()方法可以从队列中删除消息。如果带的参数是消息ID, 返回的是一个布尔常是;如果带的参数是一个过滤器,则返回删除的消息数。带有过滤器参数的removeMessages()方法只删除过滤器选择的消息。如果些参数是一个空字符串,那么将 删除所有的消息。

  • 消息计数

    使用getMessageCount()方法可以得到队列中的消息数。另外,方法 countMessages()可以得到队列中所有与过滤器相匹配的消息数。

  • 修改消息的优先级

    消息的优先级可以用changeMessagesPriority()方法修改。如果是带一个消 息ID参数,它返回的是一个布尔常量;如果是带一个过滤器参数,则它返回的是优先级更新了的消息数。

  • 消息计数器

    listMessageCounter()方法和listMessageCounterHistory() 方法可以用来列出队列中的所有消息计数器。(参见 Section 30.6, “消息计数器”)。

  • 获取队列的属性

    JMSQueueControl类可以用来获取JMS队列的设置参数(例如方法isTemporary() 可以判断队列是否为临时的,方法isDurable()可以判断队列是否为持久的等等)。

  • 队列的暂停与恢复

    JMSQueueControl可以暂停一个队列或恢复一个队列。 如果一个队列被暂停,它虽然可以继续接收消息但是不传递消息; 当被恢复时,队列又会开始传递消息。

30.1.2.4. JMS话题(Topic)的管理

JMS话题的管理是通过TopicControl类( the ObjectName org.hornetq:module=JMS,type=Topic,name="<话题名>" 或资源名 jms.topic.<话题名>)。

  • 订阅和消息的列表

    listAllSubscriptions()listDurableSubscriptions()listNonDurableSubscriptions()方法可以列出话题的不同订阅。 这些方法都返回Object数组,表示订阅的细节(如订阅名, 客户ID,持久性,消息计数等)。用listMessagesForSubscription()方法可以列出一个订阅上的JMS消息。

  • 删除订阅

    持久性订阅可以使用dropDurableSubscription()方法来删除。

  • 订阅消息计数

    countMessagesForSubscription()方法可以得到一个订阅上面所持有 的消息数(还可带一个消息选择器来得出有多少消息与之匹配)。

30.2. 使用JMX

HornetQ提供了JMX

HornetQ通过MBean的接口暴露其JMX管理操作。它将自己的资源注册到org.hornetq域。

比如,用来管理一个名为exampleQueueJMS队列的ObjectName是:

   org.hornetq:module=JMS,type=Queue,name="exampleQueue"   
      

MBean为:

   org.hornetq.api.jms.management.JMSQueueControl   
      

MBean的ObjectNameorg.hornetq.api.core.management.ObjectNameBuilder来产生出来的。你也可以使用jconsole来查找你想要的MBean的ObjectName

使用JMX来管理HornetQ与用JMX管理其它Java应用程序没有什么不同。你可以使用反射或者创建MBean代理的方法。

30.2.1. 配置JMX

默认情况下HornetQ的JMX是打开的。将hornetq-configuration.xml文件中的jmx-management-enabled设置为false就可以关闭JMX:

<!-- false to disable JMX management for HornetQ -->
<jmx-management-enabled>false</jmx-management-enabled>            
         

如果JMX功能是打开的,则使用jconsole可以管理本地的HornetQ。

Note

出于安全考虑,默认情况下JMX远程连接是关闭的。参见Java管理指南来配置服务器的远程管理(系统变量必须在run.shrun.bat中定义)。

HornetQ默认使用JMX域名"org.hornetq"。如果要用一个MBeanServer管理多个HornetQ服务器,可以将每个HornetQ 服务器配置成不同的JMX域。方法就是在hornetq-configuration.xml文件中设置jmx-domain

<!-- use a specific JMX domain for HornetQ MBeans -->
<jmx-domain>my.org.hornetq</jmx-domain>            
         

30.2.1.1. MBeanServer的配置

HornetQ在独立运行时使用Java虚拟机的Platform MBeanServer来注册其MBean。这在JBoss Microcontainer(微容器)的bean 文件中进行配置(参见Section 6.7, “JBoss Microcontainer Beans 文件”):

<!-- MBeanServer -->
<bean name="MBeanServer" class="javax.management.MBeanServer">
   <constructor factoryClass="java.lang.management.ManagementFactory"
                         factoryMethod="getPlatformMBeanServer" />
</bean>            
            

当与AS 5+集成运行时,它使用应用服务器自己的MBean服务,这样就可以使用它的jmx-console:

<!-- MBeanServer -->
<bean name="MBeanServer" class="javax.management.MBeanServer">
   <constructor factoryClass="org.jboss.mx.util.MBeanServerLocator"
                         factoryMethod="locateJBoss" />
</bean>            
            

30.2.2. 例子

参见Section 11.1.21, “JMX管理”,这个例子展示了如何使用远程JMX连接或MBean代理来管理HornetQ。

30.3. 使用核心接口

核心管理接口的调用实际上是向一个特殊的地址发送核心消息。这个特殊地址称为管理地址

管理消息是一些定义了一些固定属性的普通核心消息。服务器通过这些属性来解释管理操作:

  • 管理资源的名称

  • 管理操作的名称

  • 管理操作的参数

当一个管理消息发送到管理地址时,HornetQ服务器将从中提取出相应的信息,再调用相应的管理资源的方法,之后向 该管理消息的回答地址(reply-to address,由ClientMessageImpl.REPLYTO_HEADER_NAME 定义)发送一个管理回答

一个ClientConsumer用来接收管理回答并提取出其中的操作的結果(如果有的话)。 考虑到可移植性,返回的結果采用的是格式的字符串,而没有采用Java的序列化技术 (org.hornetq.api.core.management.ManagementHelper可以用来将JSON字符串 转换成Java对象)。

使用以下步骤可以简化使用核心消息调用管理操作:

  1. 创建一个ClientRequestor对象,用来发送管理消息并接收回答。

  2. 创建一个ClientMessage

  3. 使用org.hornetq.api.core.management.ManagementHelper类来帮助设置消息的管理参数。

  4. 通过ClientRequestor将消息发送

  5. 使用 org.hornetq.api.core.management.ManagementHelper类从管理操作結果中提取返回值。

例如,要得到核心队列exampleQueue中消息的数量:

   ClientSession session = ...
   ClientRequestor requestor = new ClientRequestor(session, "jms.queue.hornetq.management");
   ClientMessage message = session.createMessage(false);
   ManagementHelper.putAttribute(message, "core.queue.exampleQueue", "messageCount");
   ClientMessage reply = requestor.request(m);
   int count = (Integer) ManagementHelper.getResult(reply);
   System.out.println("There are " + count + " messages in exampleQueue");
      

管理操作名及其参数必须和management包中定义的Java接口一致。

资源的名称是用org.hornetq.api.core.management.ResourceNames类来生成的, 命名都非常直观(如核心队列exampleQueue的名称为core.queue.exampleQueue, JMS Topic exampleTopic的名称为jms.topic.exampleTopic,等等)。

30.3.1. 配置核心管理

管理地址的配置在文件hornetq-configuration.xml中:

   <management-address>jms.queue.hornetq.management</management-address>
         

它的默认地址是jms.queue.hornetq.management (地址前缀加上 “jms.queue”是为了方便JMS客户端也可以向它发送管理消息。

管理地址需要一个特殊的用户权限 manage来接收并处理管理消息。这个权限也在hornetq-configuration.xml文件中配置:

   <!-- users with the admin role will be allowed to manage --> 
   <!-- HornetQ using management messages        -->
   <security-setting match="jms.queue.hornetq.management">
      <permission type="manage" roles="admin" />
   </security-setting>
         

30.4. 使用JMS进行管理

使用JMS管理HornetQ与使用核心API管理HornetQ十分相似。

其中一个重要的不同是JMS需要一个JMS队列来发送消息(而核心接口使用的是一个地址)。

管理队列是一个特殊的队列,它需要客户端直接实例化:

   Queue managementQueue = HornetQJMSClient.createQueue("hornetq.management");
      

其余步骤完全和使用核心接口一样,只是相应的对象不同:

  1. 创建一个QueueRequestor来向管理地址发送管理消息并接收回答。

  2. 创建一个消息

  3. 使用 org.hornetq.api.jms.management.JMSManagementHelper类向消息中设置管理参数。

  4. 再使用QueueRequestor发送消息。

  5. 使用org.hornetq.api.jms.management.JMSManagementHelper来从回答中提取返回結果。

例如,要得到一个JMS队列exampleQueue中有多少消息:

   Queue managementQueue = HornetQJMSClient.createQueue("hornetq.management");   
   
   QueueSession session = ...      
   QueueRequestor requestor = new QueueRequestor(session, managementQueue);
   connection.start();
   Message message = session.createMessage();
   JMSManagementHelper.putAttribute(message, "jms.queue.exampleQueue", "messageCount");
   Message reply = requestor.request(message);
   int count = (Integer)JMSManagementHelper.getResult(reply);
   System.out.println("There are " + count + " messages in exampleQueue");
      

30.4.1. 配置JMS管理

JMS管理的配置与核心接口管理的配置步骤是一样的(参见Section 30.3.1, “配置核心管理”)。

30.4.2. 例子

参见Section 11.1.25, “管理”,它展示了如何使用JMS消息来管理HornetQ。

30.5. 管理通知

HornetQ可以向listener发送各种事件的通知(如资源的创建,安全破坏等)。

有三种方式接收管理通知

  • JMX通知

  • 核心消息

  • JMS消息

30.5.1. JMX通知

如果设置了JMX(参见Section 30.2.1, “配置JMX”),就可以通过订阅以下 两个MBean来获得通知:

  • org.hornetq:module=Core,type=Server 可以获得有关 核心资源的通知

  • org.hornetq:module=JMS,type=Server可以获得有关 JMS资源的通知

30.5.2. 核心消息通知

HornetQ定义了一个特殊的管理通知地址。核心队列绑定到该地址后,客户 端就可以接收以核心消息形式发送的管理信通知了。

一个核心客户端要想接收到管理通知,它必须要创建一个队列并绑定到这个管理通知地址上,然后从这个 队列接收通知。

通知消息就是普通的核心消息加上相关的属性(如通知类型,事件发生时间,资源等)。

由于是标准的核心消息,使用选择器还能够过滤掉一部分通知而只接收感兴趣的通知。

30.5.2.1. 配置核心管理通知地址

用来发送管理通知的地址在文件中hornetq-configuration.xml配置:

               <management-notification-address>hornetq.notifications</management-notification-address>
            

默认的地址是hornetq.notifications

30.5.3. JMS消息通知

HornetQ还可以通过JMS消息的方式发送通知。

这种方式与核心消息通知相似,但是有一个重要的不同:JMS消息需要一个JMS的目标(通常是一个Topic)。

要通过一个JMS目标来接收管理通知,必须将服务器的管理通知地址修改为以jms.queue开头(如果是一个 JMS队列)或者jms.topic(如果是一个话题):

            <!-- 通知将从JMS话题 "notificationsTopic"上接收 --> 
            <management-notification-address>jms.topic.notificationsTopic</management-notification-address>
         

这个通知话题一旦被创建,就可以接收消息了(或者使用MessageListener):

   Topic notificationsTopic = HornetQJMSClient.createTopic("notificationsTopic");

   Session session = ...
   MessageConsumer notificationConsumer = session.createConsumer(notificationsTopic);
      notificationConsumer.setMessageListener(new MessageListener()
      {
         public void onMessage(Message notif)
         {
            System.out.println("------------------------");
            System.out.println("Received notification:");
            try
            {
               Enumeration propertyNames = notif.getPropertyNames();
               while (propertyNames.hasMoreElements())
               {
                  String propertyName = (String)propertyNames.nextElement();
                  System.out.format("  %s: %s\n", propertyName, notif.getObjectProperty(propertyName));
               }
            }
            catch (JMSException e)
            {
            }
            System.out.println("------------------------");
         }            
      });            
         

30.5.4. 例子

参见Section 11.1.26, “管理通知”。本例采用了JMS的 MessageListener方法从HornetQ 服务器接收管理通知。

30.6. 消息计数器

HornetQ保存着队列的历史数据,而消息计数器可以从服务器上获取这些信息。

这些信息可以显示队列的一些趋势。例如,使用管理接口你可以定期来查询一个队列 的消息数量。但这个数量不足以说明这个队列是否在工作--也许这个队列既没有发送者也没有接收者;也许这个队列 在不停地发送与接收,但是发送消息的速度与接收的速度相等。两咱情况下都会造成消息数在队列中不变,但实际队列 的状态确完全不一样。

消息计数器可以提供队列的更多的信息:

  • count

    从服务器启动时加到队列中的消息数。

  • countDelta

    上次消息计数器更新后加入到队列的消息数。

  • depth

    队列当前的消息数。

  • depthDelta

    上次消息计数器更新后被加入/删除的消息总数。 例如,如果depthDelta-10,就意谓着有10个消息从 队列中删除了(有可能是2个消息加入了但有12个消息删除了)。

  • lastAddTimestamp

    最后一个消息加入到队列的时间戳。

  • udpateTimestamp

    最后一次消息计数器更新的时间戳。

30.6.1. 配置消息计数器

默认的消息计数器是关闭的,因为它需要占用一些内存。

要打开消息计数器,编辑hornetq-configuration.xml文件将其设为true

<message-counter-enabled>true</message-counter-enabled>
         

消息计数器会保存队列的历史数据(默认是10天)。它以一定间隔(默认10秒一次)对每个队列进行扫描。 如果消息计数器打开,这些参数可以在hornetq-configuration.xml文件中进行调整:

<!-- keep history for a week -->
<message-counter-max-day-history>7</message-counter-max-day-history>            
<!-- sample the queues every minute (60000ms) -->
<message-counter-sample-period>60000</message-counter-sample-period>
         

使用管理接口可以获得消息计数器。例如要使用JMX得到一个JMS队列的消息计数器:

// retrieve a connection to HornetQ's MBeanServer
MBeanServerConnection mbsc = ...
JMSQueueControlMBean queueControl = (JMSQueueControl)MBeanServerInvocationHandler.newProxyInstance(mbsc,
   on,
   JMSQueueControl.class,
   false);
// message counters are retrieved as a JSON String                                                                                                      
String counters = queueControl.listMessageCounter();
// use the MessageCounterInfo helper class to manipulate message counters more easily
MessageCounterInfo messageCounter = MessageCounterInfo.fromJSON(counters);         
System.out.format("%s message(s) in the queue (since last sample: %s)\n",
   counter.getDepth(),
   counter.getDepthDelta());
         

30.6.2. 例子

参见Section 11.1.27, “消息计数器”。这个例子使用消息计数器来获得一个JMS队列的相关数据。

30.7. 通过JBoss应用服务器的Admin Console来管理HornetQ的资源

通过JBoss应用服务器的Admin Console可以创建与配置HornetQ的各种资源。

Admin Console允许你创建各种目标(JMS话题与队列)和JMS的连接工厂。

登录admin console后你在左边的树中会看到JMS Manager节点。所有HornetQ的资源都属于这个节点。在它的下面有JMS Queues、 Topics以及Connection Factories。分别点击它们将会看到相应的资源。下面将解释如何创建并配置它们。

30.7.1. JMS队列

要创建一个新的JMS队列,点击JMS Queues将列出当前的队列。在右边的窗口中有一个“add a new resource“按钮,点击这个按钮 并选择默认(JMS 队列)模板。点击“continue“。填入相应的队列名称与JNDI名称。其它的参数值都给出了合理的默认值,通常情况下 不用改动它们。在底部可以配置安全角色,如果你不提供将使用默认的配置。当这个队列成功创建后这些配置将会显示出来。除了队列的名字 和JNDI名字外,其它参数都可以在contiguration标签页下进行修改。下面就对它们分别解释。

点击 configuration后你将看到如下显示:

name和JNDI name是不能改变的。如果你想改变它们,必须重新创建队列。其它选项是关于地址设置与安全设置。 默认的地址设置来自于服务器的配置。如果你通过console修改或创建一个队列,那么就会增加一条新的地址设置。有关 地址设置的完整说明参见Section 25.3, “通过地址设置来配置队列属性”

要删除一个队列,只要点击队列名称旁边的“delete“按钮即可。与此队列相关的任何地址设置或安全设置也将被删除。

配置的最后一部分是安全角色。如果在创建时没有给出则默认的安全设置将会显示在屏幕上。如果它们被修改并更新则队列的安全设置 将被更新。关于安全设置参见Chapter 31, 安全

在console中还有一个metrics标签页,它显示了队列的各项统计数据,如消息计数,接收者计数等。

在control标签页中可以对队列进行各种操作,比如启动和停止队列,对队列中的消息进行列表,移动,变为过期,删除等。 要进行一项操作只要点击相应的按钮,然后在出现的提示中输入相应的参数,再点击ok按钮即可。操作的結果会显示在屏幕的底部。

30.7.2. JMS话题

创建及配置JMS话题几乎与队列的操作是一样的。唯一不同的是这些配置应用于一个代表订阅的队列。

30.7.3. JMS连接工厂

JMS连接工厂的创建的操作过程与上述队列或话题的操作一致,只是配置具体的参数不同而已。关于连接工厂的参数参见配置索引。

Chapter 31. 安全

本章讲述HornetQ的安全机制以及如何配置它。要完全关闭安全,只要将hornetq-configuration.xml 文件中的security-enabled参数设为false即可。

出于性能的考虑,安全在HornetQ中被缓存一定的时间。要改变这个时间,需要设置参数 security-invalidation-interval,单位是毫秒。默认值是 10000毫秒。

31.1. 基于角色的地址安全

HornetQ采用了基于角色的安全模型来配置地址的安全以及其队列的安全。

正如在Chapter 8, 使用HornetQ内核解释的那样,HornetQ核心主要由绑定到地址上的队列组成。 消息被发送到地址后,服务器查找与之绑定的队列,并将消息路由到这些队列中。

HornetQ可以基于地址来给队列定义权限。在定义权限时可以使用通配符'#'和 '*'。

队列的权限有7种,它们是:

  • createDurableQueue。允许用户在相应的地址上创建持久的队列。

  • deleteDurableQueue。允许用户在相应的地址上删除相应的持久的队列。

  • createNonDurableQueue。允许用户在相应地址上创建非持久的队列。

  • deleteNonDurableQueue。允许用户在相应地址上删除非持久队列。

  • send。允许用户向相应地址发送消息。

  • consume。允许用户从相应地址上的队列接收消息。

  • manage。允许用户调用管理操作,即向管理地址发关管理消息。

每个权限有一个角色表。如果用户的角色在这个表中,那么它将拥有这个权限。

让我们看个简单的例子。下面是从hornetq-configuration.xml文件或 hornetq-queues.xml文件中提取的安全设置:

<security-setting match="globalqueues.europe.#">
    <permission type="createDurableQueue" roles="admin"/>
    <permission type="deleteDurableQueue" roles="admin"/>
    <permission type="createNonDurableQueue" roles="admin, guest, europe-users"/>
    <permission type="deleteNonDurableQueue" roles="admin, guest, europe-users"/>
    <permission type="send" roles="admin, europe-users"/>
    <permission type="consume" roles="admin, europe-users"/>
</security-setting>            
        

在配置中字符'#'代表"任何单词序列“。单词由'.'字符分隔。 有关通配符的语法的完整说明请参见Chapter 13, 了解 HornetQ 通配符的语法。上面的安全配置对以 "globalqueues.europe."开始的地址有效:

只有具有admin角色的用户才可以创建和删除绑定到以"globalqueues.europe."开始的地址的持久化队列。

具有adminguesteurope-users 角色的用户可以在以开头的地址上创建临时的队列。

任何具有admineurope-users角色的用户可以向以"globalqueues.europe."开头的地址 发送消息,并从绑定到相同地址上的队列接收消息。

安全管理器处理一个用户和它的角色的对应关系。HornetQ本身自带一个用户管理器,能从文件中读取用户的身份信息。 另外HornetQ还可以使用JAAS或JBoss应用服务器的安全管理机制。

有关安全管理器的配置信息,请参见Section 31.4, “更换安全管理器”

在每个xml文件中可以有零个或多个 security-setting。当一组地址有多个这样的设置时, HornetQ总是选取更具体的匹配。

让我们来看一个实例,下面是另一个security-setting

<security-setting match="globalqueues.europe.orders.#">
    <permission type="send" roles="europe-users"/>
    <permission type="consume" roles="europe-users"/>
</security-setting>            
        

在这个security-setting块中,字符串 'globalqueues.europe.orders.#' 要比它之前的字符串'globalqueues.europe.#'更具体。 因此当一个地址与'globalqueues.europe.orders.#'匹配时,它选择这个安全配置。

注意安全设置没有继承性。对于像'globalqueues.europe.orders.plastics'的地址,只要上面的设置 能被采用。即角色europe-users有sendconsume权限。权限 createDurableQueuedeleteDurableQueuecreateNonDurableQueuedeleteNonDurableQueue不会从先前的设置中继承。

由于权限的不可继承,如果我们不在更具体的security-setting设置中给出一个权限,这个权限就是没有的,不会因为继承而带来 麻烦。否则就不可能对一组地址中的部分地址进行如此的设置。

31.2. 安全套接字层(SSL)传输

当消息客户端与服务器端,或服务器之间(比如使用桥的情况)通过一个不信任的网络相互通信时,HornetQ 支持使用加密的安全套接字(SSL)传输数据。

关于SSL的详细配置信息,请参见Chapter 16, 传输层的配置

31.3. 基本用户身份信息(Credentials)

HornetQ自带一个安全管理器(security manager)可以从xml文件中读取用户身份信息,即用户名、 密码、角色信息。该xml文件名为hornetq-users.xml,它必须要在classpath中。

如果你要使用这个安全管理器,就将用户名,密码,角色等信息加入到这个文件中。

让我们看一个例子:

<configuration xmlns="urn:hornetq" 
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="urn:hornetq ../schemas/hornetq-users.xsd ">
    
    <defaultuser name="guest" password="guest">
        <role name="guest"/>
    </defaultuser>
    
    <user name="tim" password="marmite">
        <role name="admin"/>      
    </user>
    
    <user name="andy" password="doner_kebab">
        <role name="admin"/>
        <role name="guest"/>
    </user>
    
    <user name="jeff" password="camembert">
        <role name="europe-users"/>
        <role name="guest"/>
    </user>
    
</configuration>
        

首先要注意的是defaultuser,它定义的是默认的用户。当客户端创建会话时 没有提供用户名/密码时,就会使用这个用户。根据上述配置,这个默认用户是guest 并且他的角色是guest。一个默认用户可以有多个角色。

另外三个用户中,用户tim具有角色admin。用户andy具有角色adminguest,用户jeff 具有角色europe-usersguest

31.4. 更换安全管理器

如果你不想用默认的安全管理器,可以通过修改配置文件hornetq-beans.xml (或者在运行JBoss应用服务器情况下hornetq-jboss-beans.xml文件)来更换。同时要更换 HornetQSecurityManager bean 的类。

让我们看一段默认bean文件的内容:

           
<bean name="HornetQSecurityManager" 
      class="org.hornetq.spi.core.security.HornetQSecurityManagerImpl">
    <start ignored="true"/>
    <stop ignored="true"/>
</bean>            
        

org.hornetq.spi.core.security.HornetQSecurityManagerImpl 类就是HornetQ服务器的在独立运行时的默认的安全管理器。

HornetQ自带有另外两个安全管理器可供使用。一个是JAAS安全管理器,另一个是用来与JBoss应用服务 器集成的安全管理器。此外,你还可以编写实现你自己的安全管理器。首先要实现 org.hornetq.core.security.SecurityManager接口,再将你的实现 类定义到hornetq-beans.xml文件中即可(或者在JBoss应用服务器中 使用hornetq-jboss-beans.xml文件)。

以下分别介绍这两咱安全管理器

31.5. JAAS安全管理器

JAAS表示“Java认证与授权服务“。它是Java平台标准的一部分。它提供了进行安全认证与授权的通用接口。 它允许你插入自己的安全管理模块。

要配置使用你自己的JAAS安全实现,需要在bean文件中定义JAASSecurityManager。 下面是一个例子:

&lt;bean name="HornetQSecurityManager"
      class="org.hornetq.integration.jboss.security.JAASSecurityManager"&gt;
    &lt;start ignored="true"/&gt;
    &lt;stop ignored="true"/&gt;

    &lt;property name="ConfigurationName"&gt;org.hornetq.jms.example.ExampleLoginModule&lt;/property&gt;
    &lt;property name="Configuration"&gt;
       &lt;inject bean="ExampleConfiguration"/&gt;
    &lt;/property&gt;
    &lt;property name="CallbackHandler"&gt;
       &lt;inject bean="ExampleCallbackHandler"/&gt;
    &lt;/property&gt;
&lt;/bean&gt;
        

注意你需要为JAAS安全管理器提供三个参数:

  • ConfigurationName: LoginModule的名字。

  • Configuration: Configuration的实现。

  • CallbackHandler: CallbackHandler实现,用于用户交互。

31.5.1. 例子

参见Section 11.1.19, “JAAS”。这个例子展示了怎样在HornetQ中配置使用JAAS。

31.6. JBoss 应用服务器安全管理器

JBoss 应用服务器安全管理器适用于当HornetQ运行于JBoss应用服务器内时。它可以与JBoss应用服务器 的安全模型紧密集成。

此安全管理器的类是 org.hornetq.integration.jboss.security.JBossASSecurityManager

要了解如何配置JBoss安全管理器,可以看一眼HornetQ发布包中相关例子中的 hornetq-jboss-beans.xml文件。

31.6.1. 配置客户端登录

JBoss可以配置使用客户登录。JEE的模块如servlet或EJB可以将安全认证信息设置到安全上下文(security context)中, 用于整个调用过程。如果想在HornetQ在发送和接收消息时使用这些认证(credential)信息,需要将参数 allowClientLogin设为true。它会越过HornetQ的身份验证过程并会传播安全上下文(security context)。如果你想要HornetQ使用传播的安全信息进行身份验证,需要同时将参数authoriseOnClientLogin 设为true。

关于客户端登录的详细信息请访问这里

Note

如果消息是以非阻塞方式发送的,那么有可能在消息到达服务器时,调用线程已经结束,安全上下文也被清除了。 所以如果使用安全上下文,需要采用阻塞方式发送消息。

31.7. 集群用户名/密码的配置

为了使集群连接正常工作,每个节点都必须与其它节点相连接。它们连接所使用的默认用户名和密码在正式使用时 一定要做相应的更改,以防止安全隐患。

请参见Chapter 30, 管理了解怎样去做。

Chapter 32. Java EE和应用服务器的集成

HornetQ可以容易地安装到JBoss 4应用服务器及其以上版本。有关安装的详细说明请参阅快速指南。

HornetQ提供了一个JCA适配器,使得它还可以与其它JEE兼容的应用服务器集成。请参阅其它应用服务器的 有关JCA适配器集成的说明来操作。

JCA适配器的作用是控制消息流入到消息Bean(MDB),并控制消息从各种JEE模块中发出(如EJB和Servlet)。

本章讲述这些JEE模块配置HornetQ的基本信息。

32.1. 配置消息Bean

使用HornetQ向MDB传递消息,需要在文件ra.xml中配置JCA适配器。该文件在 jms-ra.rar文件中。默认配置下HornetQ服务使用InVm连接器接收消息。在本章 后面列出了可配置的选项。

所有MDB都需要有目标类型和目标的相关配置。下面就是一个使用annotation配置MDB的例子:

@MessageDriven(name = "MDBExample",
               activationConfig =
                     {
                        @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
                        @ActivationConfigProperty(propertyName = "destination", propertyValue = "queue/testQueue")
                     })
@ResourceAdapter("hornetq-ra.rar")
public class MDBExample implements MessageListener
{
   public void onMessage(Message message)...
}

上例中配置了MDB从一个队列中接收消息,它的JNDI绑定名为queue/testQueue。 这个队列必须事先在HornetQ服务器配置文件中配置并部署好的。

ResourceAdapter annotation用来指出使用哪个适配器。要使用它必须要引入 org.jboss.ejb3.annotation.ResourceAdapter (JBoss AS 5.x或以上)。 这个类在 jboss-ejb3-ext-api.jar文件中。该文件可以在JBoss的repository中找到。 另外一个方法是使用部署描述文件(deployment descriptor),即在文件jboss.xml中加入类似以下的内容:

<message-driven>
   <ejb-name>ExampleMDB</ejb-name>
   <resource-adapter-name>hornetq-ra.rar</resource-adapter-name>
</message-driven>

你还可以将hornetq-ra.rar改名为jms-ra.rar而不需要任何annotation或额外的部署描述信息。但是你需要 编辑jms-ds.xml文件,将其中的rar-name项改成相应的值。

Note

HornetQ是JBoss AS 6默认的JMS提供者。从这个版本开始HornetQ的资源适配器名字是 jms-ra.rar,并且你不需要在MDB的annotation中指定它。

HornetQ发布包中的所有例子都使用annotation方法。

32.1.1. 使用容器管理事务(CMT)

当MDB使用容器管理事务时,消息的传递被包含在一个JTA事务中。事务的提交与回滚是由容器来控制的。如果事务 被回滚,消息传递会进行相应的处理(默认是重新传递消息,如果重新传递次数超过10次,消息就被发往DLQ)。使用 annotation配置如下:

@MessageDriven(name = "MDB_CMP_TxRequiredExample",
               activationConfig =
                     {
                        @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
                        @ActivationConfigProperty(propertyName = "destination", propertyValue = "queue/testQueue")
                     })
@TransactionManagement(value= TransactionManagementType.CONTAINER)
@TransactionAttribute(value= TransactionAttributeType.REQUIRED)
@ResourceAdapter("hornetq-ra.rar")
public class MDB_CMP_TxRequiredExample implements MessageListener
{
   public void onMessage(Message message)...
}

TransactionManagement 表示这个MDB使用容器管理持久性。 TransactionAttribute 表示这个MDB要求JTA事务。注意这个annotation的 另外唯一的合法值是TransactionAttributeType.NOT_SUPPORTED,它表示 MDB不需要JTA事务支持。

如果要回滚事务,可以调用MessageDrivenContextsetRollbackOnly方法。如下面的代码所示:

   @Resource
   MessageDrivenContextContext ctx;

   public void onMessage(Message message)
   {
      try
      {
         //something here fails
      }
      catch (Exception e)
      {
         ctx.setRollbackOnly();
      }
   }

如果你不需要使用XA事务,你可以用一相本地的事务来代替(比如你只有一个JMS资源)。 如下所示:

@MessageDriven(name = "MDB_CMP_TxLocalExample",
               activationConfig =
                     {
                           @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
                           @ActivationConfigProperty(propertyName = "destination", propertyValue = "queue/testQueue"),
                           @ActivationConfigProperty(propertyName = "useLocalTx", propertyValue = "true")
                     })
@TransactionManagement(value = TransactionManagementType.CONTAINER)
@TransactionAttribute(value = TransactionAttributeType.NOT_SUPPORTED)
@ResourceAdapter("hornetq-ra.rar")
public class MDB_CMP_TxLocalExample implements MessageListener
{
   public void onMessage(Message message)...
}

32.1.2. 使用Bean管理事务(BMT)

消息Bean可以通过配置使用Bean管理的事务(BMT)。在种情况下会创建一个用户事务 (User Transaction)。如下所示:

@MessageDriven(name = "MDB_BMPExample",
               activationConfig =
                     {
                        @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
                        @ActivationConfigProperty(propertyName = "destination", propertyValue = "queue/testQueue"),
                        @ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue = "Dups-ok-acknowledge")
                     })
@TransactionManagement(value= TransactionManagementType.BEAN)
@ResourceAdapter("hornetq-ra.rar")
public class MDB_BMPExample implements MessageListener
{
   public void onMessage(Message message)
}

使用BMT时,消息的传递在用户事务的范围之外,它的通知模式由acknowledgeMode参数决定。 该参数有两个合法的值,即Auto-acknowledgeDups-ok-acknowledge。请注意,由于消息的传递在事务之外,在MDB中如果发生错误消息 是不会重新传递的。

用户可以像如下所示控制事务的生命周期:

   @Resource
   MessageDrivenContext ctx;

   public void onMessage(Message message)
   {
      UserTransaction tx;
      try
      {
         TextMessage textMessage = (TextMessage)message;

         String text = textMessage.getText();

         UserTransaction tx = ctx.getUserTransaction();

         tx.begin();
         
         //do some stuff within the transaction

         tx.commit();

      }
      catch (Exception e)
      {
         tx.rollback();
      }
   }

32.1.3. 在MDB中使用选择器

MDB可以配置消息选择器。如下所示:

@MessageDriven(name = "MDBMessageSelectorExample",
               activationConfig =
                     {
                        @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
                        @ActivationConfigProperty(propertyName = "destination", propertyValue = "queue/testQueue"),
                        @ActivationConfigProperty(propertyName = "messageSelector", propertyValue = "color = 'RED'")
                     })
@TransactionManagement(value= TransactionManagementType.CONTAINER)
@TransactionAttribute(value= TransactionAttributeType.REQUIRED)
@ResourceAdapter("hornetq-ra.rar")
public class MDBMessageSelectorExample implements MessageListener
{
   public void onMessage(Message message)....
}

32.2. 在JEE模块内发送消息

JCA适配器支持发送消息。连接工厂的默认配置在jms-ds.xml文件中,对应的JNDI名字是 java:/JmsXA。在JEE中使用它发送消息将作为JTA事务的一部分来对待。

如果消息发送失败,整个事务将回滚,消息会被重新发送。下面是一个MDB发送消息的例子:

@MessageDriven(name = "MDBMessageSendTxExample",
               activationConfig =
                     {
                        @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
                        @ActivationConfigProperty(propertyName = "destination", propertyValue = "queue/testQueue")
                     })
@TransactionManagement(value= TransactionManagementType.CONTAINER)
@TransactionAttribute(value= TransactionAttributeType.REQUIRED)
@ResourceAdapter("hornetq-ra.rar")
public class MDBMessageSendTxExample implements MessageListener
{
   @Resource(mappedName = "java:/JmsXA")
   ConnectionFactory connectionFactory;

   @Resource(mappedName = "queue/replyQueue")
   Queue replyQueue;

   public void onMessage(Message message)
   {
      Connection conn = null;
      try
      {
         //Step 9. We know the client is sending a text message so we cast
         TextMessage textMessage = (TextMessage)message;

         //Step 10. get the text from the message.
         String text = textMessage.getText();

         System.out.println("message " + text);

         conn = connectionFactory.createConnection();

         Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);

         MessageProducer producer = sess.createProducer(replyQueue);

         producer.send(sess.createTextMessage("this is a reply"));

      }
      catch (Exception e)
      {
         e.printStackTrace();
      }
      finally
      {
         if(conn != null)
         {
            try
            {
               conn.close();
            }
            catch (JMSException e)
            { 
            }
         }
      }
   }
   }

在JBoss应用服务器的EJB(包括会话Bean, 实体Bean和消息Bean)、Servlet(包括jsp)我定制的MBean中 都可以使用JMS的JCA适配器来发送消息。

32.3. MDB与接收池的大小

包括JBoss在内的绝大多数应用服务器允许用户定义一个池中的MDB数量。在JBoss中这个参数名是MaxPoolSize,它在文件 ejb3-interceptors-aop.xml中配置。这个参数不影响实际创建的会话/接收者的数量。资源适配器不知道MDB的具体实现。所以即使你设置MDB池在大小 为1,仍然会有15个会话/接收者被创建(默认值)。如果你想改变会话/接收者创建的数量,你可以通过设置资源适配器的参数 maxSession实现。也可以通过设置MDB的激活配置参数来完成:

@MessageDriven(name = "MDBMessageSendTxExample",
               activationConfig =
                     {
                        @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
                        @ActivationConfigProperty(propertyName = "destination", propertyValue = "queue/testQueue"),
                        @ActivationConfigProperty(propertyName = "maxSession", propertyValue = "1")
                     })
@TransactionManagement(value= TransactionManagementType.CONTAINER)
@TransactionAttribute(value= TransactionAttributeType.REQUIRED)
@ResourceAdapter("hornetq-ra.rar")
public class MyMDB implements MessageListener
{ ....}
      

32.4. 配置JCA适配器

通过JCA适配器可以将HornetQ集成到JEE兼容的模块中,如MDB和EJB。它的配置决定了这些模块如何接收和发送消息。

HornetQ的JCA适配器是通过jms-ra.rar部署的。它的配置文件中其中的 META-INF/ra.xml

下面是它的具体配置内容:

<resourceadapter>
      <resourceadapter-class>org.hornetq.ra.HornetQResourceAdapter</resourceadapter-class>
      <config-property>
         <description>The transport type</description>
         <config-property-name>ConnectorClassName</config-property-name>
         <config-property-type>java.lang.String</config-property-type>
         <config-property-value>org.hornetq.core.remoting.impl.invm.InVMConnectorF
         actory</config-property-value>
      </config-property>
      <config-property>
         <description>The transport configuration. These values must be in the form of key=val;key=val;</description>
         <config-property-name>ConnectionParameters</config-property-name>
         <config-property-type>java.lang.String</config-property-type>
         <config-property-value>server-id=0</config-property-value>
      </config-property>

      <outbound-resourceadapter>
         <connection-definition>
            <managedconnectionfactory-class>org.hornetq.ra.HornetQRAManagedConnection
            Factory</managedconnectionfactory-class>

            <config-property>
               <description>The default session type</description>
               <config-property-name>SessionDefaultType</config-property-name>
               <config-property-type>java.lang.String</config-property-type>
               <config-property-value>javax.jms.Queue</config-property-value>
            </config-property>
            <config-property>
               <description>Try to obtain a lock within specified number of seconds; less
               than or equal to 0 disable this functionality</description>
               <config-property-name>UseTryLock</config-property-name>
               <config-property-type>java.lang.Integer</config-property-type>
               <config-property-value>0</config-property-value>
            </config-property>

            <connectionfactory-interface>org.hornetq.ra.HornetQRAConnectionFactory
            </connectionfactory-interface>
            <connectionfactororg.hornetq.ra.HornetQConnectionFactoryImplonFactoryImpl
            </connectionfactory-impl-class>
            <connection-interface>javax.jms.Session</connection-interface>
            <connection-impl-class>org.hornetq.ra.HornetQRASession
            </connection-impl-class>
         </connection-definition>
         <transaction-support>XATransaction</transaction-support>
         <authentication-mechanism>
            <authentication-mechanism-type>BasicPassword
            </authentication-mechanism-type>
            <credential-interface>javax.resource.spi.security.PasswordCredential
            </credential-interface>
         </authentication-mechanism>
         <reauthentication-support>false</reauthentication-support>
      </outbound-resourceadapter>

      <inbound-resourceadapter>
         <messageadapter>
            <messagelistener>
               <messagelistener-type>javax.jms.MessageListener</messagelistener-type>
               <activationspec>
                  <activationspec-class>org.hornetq.ra.inflow.HornetQActivationSpec
                  </activationspec-class>
                  <required-config-property>
                      <config-property-name>destination</config-property-name>
                  </required-config-property>
               </activationspec>
            </messagelistener>
         </messageadapter>
      </inbound-resourceadapter>

   </resourceadapter>

整个配置可以分为三个主要部分

  1. 适配器的全局参数

  2. 适配器外部(outbound)配置。用于在JEE环境中创建JMS资源。

  3. 适配器内部(inbound)配置。用于控制MDB的消息接收。

32.4.1. 适配器的全局参数

首先看到的第一个参数是resourceadapter-class。这是HornetQ 的适配器类。此参数不可更改。

在此之后是可配置的参数。前两个配置适配器所使用的传输。其余的用来配置连接工厂。

Note

所有连接工厂的参数在没有定义时使用默认值。参数reconnectAttempts 的默认值取-1,表示如果连接失败,HornetQ会不停地尝试重新连接。这个参数只适用于创建远程 连接的情况。如果是InVm连接器,则永远不可能发生连接故障。

下面给出了每个参数的说明:

Table 32.1. 全局配置参数

参数名参数类型参数说明
ConnectorClassNameString连接器的类名,参见 Chapter 16, 传输层的配置
ConnectionParametersString传输配置参数。它的值必须是采用 key1=val1;key2=val2;的形式。不同连接器有不同的参数。
useLocalTxboolean设为True,则进行本地事务优化。
UserNameString用于创建连接时使用的用户名
PasswordString用于创建连接时使用的用户密码
BackupConnectorClassNameString发生故障是使用的备份传输
BackupConnectionParametersString备份传输的配置参数
DiscoveryAddressString服务器自动检测所使用的发现组(discovery group)地址
DiscoveryPortInteger检测所使用的端口号
DiscoveryRefreshTimeoutLong刷新的时间(timeout)。单位为毫秒
DiscoveryInitialWaitTimeout Long检测之前所需等待的时间
ConnectionLoadBalancingPolicyClassNameString负载均衡策略使用的类
DiscoveryInitialWaitTimeout Long向服务器发送ping的周期,单位毫秒
ConnectionTTLLong连接的存活时间(TTL)
CallTimeoutLong每个数据包的调用超时。单位毫秒
DupsOKBatchSizeIntegerDups OK的情况下消息批量的大小。
TransactionBatchSizeInteger在事务中发送消息的批量大小
ConsumerWindowSizeInteger接收者内部缓存的窗口大小

接上页..

ConsumerMaxRateInteger接收者接收消息的最大速度
ConfirmationWindowSizeInteger用于确认的窗口大小
ProducerMaxRateInteger发送者发送消息的最大速度
MinLargeMessageSizeInteger大消息的最小数值,单位字节。
BlockOnAcknowledgeBoolean如果为true,表示以阻塞方法发送消息通知。
BlockOnNonDurableSendBoolean如果为true,表示以阻塞方式发送非持久消息
BlockOnDurableSendBoolean如果为true,表示以阻塞方式发送持久消息
AutoGroupBoolean如果为true,表示自动消息分组
PreAcknowledgeBoolean决定是否进行消息的预先通知(pre-acknowledge)。
ReconnectAttemptsInteger连接重试的次数,默认为 -1
RetryIntervalLong每次连接重试前等待的时间,单位毫秒。
RetryIntervalMultiplierDouble用于计算重试间隔
FailoverOnServerShutdownBoolean如果设为true表示尝试连接其它的服务器
ClientIDString连接的客户端ID
ClientFailureCheckPeriodLong客户端如果在这个时间内没有收到服务器数据包,将认为连接出现故障。
UseGlobalPoolsBoolean是否使用全局线程池
ScheduledThreadPoolMaxSizeInteger可计划线程池的最大线程数
ThreadPoolMaxSizeInteger线程池的大小

32.4.2. 适配器外部(outbound)配置

外部配置参数应该保持不变。这是因为它所定义的连接工厂要被Java EE的模块所使用。这些连接工厂 可以定义在名字样式为*-ds.xml的配置文件中。在JBoss的部署目录 hornetq.sar下有一个默认的配置文件jms-ds.xml。 在这个文件中的连接工厂的配置从主要的配置文件ra.xml中继承, 但是可以在这里重新定义。下面的例子中给出了重新定义的方法。

Note

请注意这里的配置只适用于在JBoss应用服务器中安装的HornetQ。如果要在其它JEE应用服务器中 使用并配置HornetQ,请参照相应的应用服务器手册。

<tx-connection-factory>
      <jndi-name>RemoteJmsXA</jndi-name>
      <xa-transaction/>
      <rar-name>jms-ra.rar</rar-name>
      <connection-definition>org.hornetq.ra.HornetQRAConnectionFactory
</connection-definition>
      <config-property name="SessionDefaultType" type="String">javax.jms.Topic
      </config-property>
      <config-property name="ConnectorClassName" type="String">
        org.hornetq.core.remoting.impl.netty.NettyConnectorFactory
      </config-property>
      <config-property name="ConnectionParameters" type="String">
          port=5445</config-property>
      <max-pool-size>20</max-pool-size>
</tx-connection-factory>

上面的例子中的连接工厂绑定到JNDI名字RemoteJmsXA。EJB和MDB可以用 下面的方法来得到它:

@Resource(mappedName="java:RemoteJmsXA")
private ConnectionFactory connectionFactory;

config-property覆盖了ra.xml文件中的配置。 以此类推,其它有关连接工厂的参数也可以在此覆盖。

除了全局的配置参数外,外部的配置还定义了其它一些参数。

Table 32.2. 外部配置参数

参数名参数类型说明
SessionDefaultTypeString默认的会话类型
UseTryLockInteger在规定的秒数内获得锁。如果不想使用这个功能,将它设为0或负数

32.4.3. 适配器内部(inbound)配置

内部配置参数也应该保持不变。它们控制向MDB转发消息的各种属性。通过在MDB上添加相应的激活配置 (activation configuration)可以覆盖这些参数的值。它可以用来配置一个MDB从另外一个服务器 接收消息。

除了全局的配置参数外,内部的配置还定义了其它一些参数。

Table 32.3. Inbound Configuration Properties

参数名参数类型说明
DestinationString目标的JNDI名字
DestinationTypeString目标的类型,javax.jms.Queue或者javax.jms.Topic (默认是javax.jms.Queue)
AcknowledgeModeString通知模式,Auto-acknowledgeDups-ok-acknowledge (默认值是Auto-acknowledge). AUTO_ACKNOWLEDGEDUPS_OK_ACKNOWLEDGE也是有效值
MaxSessionInteger内部配置创建的最大会话数(默认是5)
MessageSelectorString接收者的消息选择器
SubscriptionDurabilityString订阅的类型,Durable或者NonDurable
SubscriptionNameString订阅的名字
TransactionTimeoutLong事务超时(毫秒,默认是0,表示事务不会超时)
UseJNDIBoolean是否使用JNDI来查找目标(默认是true)

32.4.4. 配置适配器访问单独的HornetQ服务器

在有的情况下,消息服务器与应用服务器运行在不同的机器上。这时客户端只需要安装HornetQ的客户端的库文件。本节给出了具体的配置和所需要的jar文件。

你需要配置两个配置文件。一个是MDB所用的内部适配器,另一个是可以创建外部连接的外部适配器。

32.4.4.1.1. 配置内部适配器

首先在deploy目录下创建一个以.rar为结尾的文件夹。 在这里我们将其命名为hornetq-ra.rar。主注意这一点很重要,因为MDB和外部配置都需要引用 该文件夹的名字。

Note

在JBoss中该文件夹的默认名为jms-ra.rar。你可以直接使用这个名字。但是你可能需要避免 与其它的通用适配器相冲突。

在文件夹 hornetq-ra.rar 下创建名为 META-INF 的文件夹,然后在些文件夹内创建一个 ra.xml配置文件。在HornetQ发布包中 有一个ra.xml模板文件供你使用。

要配置MDB接收远程HornetQ服务器的消息,你需要修改deploy/hornet-ra.rar/META-INF下面的 ra.xml文件,将传输层改为netty连接器(不要使用invm连接器)及其相应的参数。下面 是一个配置的例子:

            <resourceadapter-class>org.hornetq.ra.HornetQResourceAdapter</resourceadapter-class>
              <config-property>
                 <description>The transport type</description>
                 <config-property-name>ConnectorClassName</config-property-name>
                 <config-property-type>java.lang.String</config-property-type>
                 <config-property-value>org.hornetq.core.remoting.impl.netty.NettyConnectorFactory</config-property-value>
              </config-property>
              <config-property>
                 <description>The transport configuration. These values must be in the form of key=val;key=val;</description>
                 <config-property-name>ConnectionParameters</config-property-name>
                 <config-property-type>java.lang.String</config-property-type>
                 <config-property-value>host=127.0.0.1;port=5446</config-property-value>
              </config-property>
          

上面的配置中,适配器连接到一个运行在本机上端口为5446的服务器。

32.4.4.1.2. 配置外部适配器

你还需要创建一个hornetq-ds.xml文件来配置外部连接。该文件需要放置在deploy 下的任意一个文件夹中。在一相标准的安装中这个文件夹是horneq 或者 hornetq.sar。 当然你可以选择其它文件夹。该文件的名字只要是以-ds.xml即可。在HornetQ的发布包中包含有一个模板文件, 它的名字是jms-ds.xml,位置就在config文件夹下。

下面是一个配置的例子。

             <tx-connection-factory>
              <jndi-name>RemoteJmsXA</jndi-name>
              <xa-transaction/>
              <rar-name>hornetq-ra.rar</rar-name>
              <connection-definition>org.hornetq.ra.HornetQRAConnectionFactory</connection-definition>
              <config-property name="SessionDefaultType" type="java.lang.String">javax.jms.Topic</config-property>
              <config-property name="ConnectorClassName" type="java.lang.String">org.hornetq.core.remoting.impl.netty.NettyConnectorFactory</config-property>
              <config-property name="ConnectionParameters" type="java.lang.String">host=127.0.0.1;port=5446</config-property>
              <max-pool-size>20</max-pool-size>
           </tx-connection-factory>
          

这个配置同样是连接到运行在本机上的端口为5446的HornetQ服务器。JEE模块可以通过JNDI查找 java:/RemoteJmsXA(由jndi-name参数定义)。如上节所述, 资源适配器根据文件夹下的配置来创建外部连接。

32.4.4.1.2.1. 依赖的jar文件

下面列出了HornetQ所需要的第三方jar文件

Table 32.4. Jar文件

Jar文件名说明位置
hornetq-ra.jarHornetQ资源适配器文件deploy/hornetq-ra.rar或相应的位置
hornetq-core-client.jarHornetQ核心客户类库在JBoss的default/lib下或者是$JBOSS_HOME/common/lib下。
hornetq-jms-client.jarHornetQ的JMS类同上
netty.jarNetty类库同上

32.4.5. 高可获得性JNDI (HA-JNDI)

采用JNDI来查找JMS对象时(队列,话题及连接工厂),使用HA-JNDI可以增加容错的能力。即当你正在使用 的JNDI服务器发生故障时,客户端可以使用集群中的其它JNDI服务器继续工作。

HA-JNDI是JBoss应用服务器的一项服务,它为客户端提供JNDI服务,客户端无需知道JNDI具体服务器的连接 细节。这个服务只有在集群的JBoss应用服务器上才可使用。

要使用HA-JNDI,需要使用下面的JNDI参数。

Hashtable<String, String> jndiParameters = new Hashtable<String, String>();
jndiParameters.put("java.naming.factory.initial", 
    "org.jnp.interfaces.NamingContextFactory");
jndiParameters.put("java.naming.factory.url.pkgs=", 
    "org.jboss.naming:org.jnp.interfaces");

initialContext = new InitialContext(jndiParameters);

有关HA-JNDI更多的信息请参见JBoss应用服务器集群文档

32.4.6. XA恢复

XA恢复解决的是事务在系统或应用出现故障时的处理。它可以保证在应用进程或主机出现故障 或网络崩溃等情况下,事务内所有资源的状态的一致性。有关XA恢复的更多信息请见 JBoss 事务

当HornetQ与JBoss应用服务器集成时,它可以利用JBoss的事务处理来对消息资源进行恢复。如果消息处理包括 在一个XA事务中,如果服务器出现故障并重启时,恢复管理器将负责恢复事务,这样其中的消息要么被提交,要么被回滚(取 决于事务的处理决定)。

32.4.6.1. XA恢复的配置

要想HornetQ具有XA恢复功能,则必须配置恢复管理器连接到HornetQ并恢复其资源。下面的参数必须要加到 conf/jbossts-properties.xml文件中的jta部分:

<properties depends="arjuna" name="jta">
   ...
                     
   <property name="com.arjuna.ats.jta.recovery.XAResourceRecovery.HornetQ1"
                value="org.hornetq.jms.server.recovery.HornetQXAResourceRecovery;[连接配置]"/>
</properties>
            

其中[连接配置]包括连接HornetQ节点所必需的信息。 它的格式是[连接工厂类名],[用户名], [密码], [连接参数]

  • [连接工厂类名]指的是ConnectorFactory 的类名,用来连接HornetQ服务器。其值可以是org.hornetq.core.remoting.impl.invm.InVMConnectorFactoryorg.hornetq.core.remoting.impl.netty.NettyConnectorFactory

  • [用户名]是用于创建客户会话的用户名。是可选项。

  • [密码]是创建客户会话时所用的密码。只有在用户名存在时需要。

  • [连接参数] 是用逗号分隔的一串键-值对。它们会传递给连接器工厂 (参见 Chapter 16, 传输层的配置)。

Note

HornetQ必须有一个与conf/jbossts-properties.xml中定义的连接器相对应的接受器(acceptor)。

32.4.6.1.1. 配置参数

如果HornetQ配置了一个默认的in-vm接受器:

<acceptor name="in-vm">
    <factory-class>org.hornetq.core.remoting.impl.invm.InVMAcceptorFactory</factory-class>
</acceptor>
                

相应地在 conf/jbossts-properties.xml文件中:

<property name="com.arjuna.ats.jta.recovery.XAResourceRecovery.HORNETQ1"
   value="org.hornetq.jms.server.recovery.HornetQXAResourceRecovery;org.hornetq.core.remoting.impl.invm.InVMConnectorFactory"/>        			
                

如果配置了一个netty接受器,并且端口不是默认的:

<acceptor name="netty">
    <factory-class>org.hornetq.core.remoting.impl.netty.NettyConnectorFactory</factory-class>
    <param key="port" value="8888"/>
</acceptor>
                

相应的在 conf/jbossts-properties.xml文件中:

<property name="com.arjuna.ats.jta.recovery.XAResourceRecovery.HORNETQ1"
       value="org.hornetq.jms.server.recovery.HornetQXAResourceRecovery;org.hornetq.core.remoting.impl.netty.NettyConnectorFactory, , , port=8888"/>        			                    
                

Note

注意在没有用户名和密码时,逗号是不能省略的。

如果恢复必须要求是admin, adminpass,则其配置 应为如下所示:

                    <property name="com.arjuna.ats.jta.recovery.XAResourceRecovery.HORNETQ1"
                           value="org.hornetq.jms.server.recovery.HornetQXAResourceRecovery;org.hornetq.core.remoting.impl.netty.NettyConnectorFactory, admin, adminpass, port=8888"/>        			                    
                

推荐在XA恢复中,将HornetQ配置一个invm接受器,并配置恢复管理器使用invm连接器。

32.4.6.2. 例子

参见Section 11.3.9, “XA 恢复(recovery)”。这个例子展示了如何来配置XA恢复以便在服务器崩溃后恢复消息。

Chapter 33. JMS桥(Bridge)

HornetQ提供了JMS消息桥服务。

桥的作用是从一个消息源队列或话题(topic)接收消息,然后将它们发送到一个目标队列或话题。通常源和 目的不在同一台服务器上。

作为消息源的服务器与目的服务器不必在同一个集群内。通过桥的作用,两台服务器可以通过非可靠的网络连接 起来,比如WAN。

桥可以作为单独的服务部署,或者部署于HornetQ单独服务器内,或者部署在JBoss应用服务器中。源或目的可以 在同一个VM中,也可以在其它的VM中。

桥还可以在HornetQ服务器与其它JMS 1.1 兼容的服务器之间进行消息的传递。

Note

还要将JMS桥与核心桥混淆。JMB桥可以连接两个JMS 1.1兼容的服务器,它使用的是JMS接口。 而核心桥(在Chapter 36, 核心桥中描述)使用核心API将两个HornetQ实例连接 起来。核心桥的性能通常要比JMS桥的性能高,所以尽可能使用核心桥。另外核心桥可以不用XA 就可以实现一次并只有一次的消息传递保证。

桥可以适当处理连接故障。当源的连接或目的的连接发生故障时,例如网络故障,桥将不断重试连接直到连接 恢复为止。当连接恢复后,桥会继续正常工作。

桥还可以有一个可选的JMS选择器,它可以使桥只接收选择器选择的消息。

可以配置桥从队列还是从话题中接收消息。如果配置成从话题中接收消息,还以设定是以非持久订阅的方式接收,还是 以持久订阅的方式接收。

通常桥是通过一个bean配置文件由JBoss Micro Container部署到JBoss应用服务器中。下面的就是一 个桥的bean文件例子。这个桥将同一服务器上的两个目标连接起来。

<?xml version="1.0" encoding="UTF-8"?>

<deployment xmlns="urn:jboss:bean-deployer:2.0">

       <bean name="JMSBridge" class="org.hornetq.api.jms.bridge.impl.JMSBridgeImpl">
           <!-- HornetQ must be started before the bridge -->
           <depends>HornetQServer</depends>
           <constructor>
               <!-- Source ConnectionFactory Factory -->
               <parameter>
                   <inject bean="SourceCFF"/>
               </parameter>
               <!-- Target ConnectionFactory Factory -->
               <parameter>
                   <inject bean="TargetCFF"/>
               </parameter>
               <!-- Source DestinationFactory -->
               <parameter>
                   <inject bean="SourceDestinationFactory"/>
               </parameter>
               <!-- Target DestinationFactory -->
               <parameter>
                   <inject bean="TargetDestinationFactory"/>
               </parameter>
               <!-- Source User Name (no username here) -->
               <parameter><null /></parameter>
               <!-- Source Password (no password here)-->
               <parameter><null /></parameter>
               <!-- Target User Name (no username here)-->
               <parameter><null /></parameter>
               <!-- Target Password (no password here)-->
               <parameter><null /></parameter>
               <!-- Selector -->
               <parameter><null /></parameter>
               <!-- Failure Retry Interval (in ms) -->
               <parameter>5000</parameter>
               <!-- Max Retries -->
               <parameter>10</parameter>
               <!-- Quality Of Service -->
               <parameter>ONCE_AND_ONLY_ONCE</parameter>
               <!-- Max Batch Size -->
               <parameter>1</parameter>
               <!-- Max Batch Time (-1 means infinite) -->
               <parameter>-1</parameter>
               <!-- Subscription name (no subscription name here)-->
               <parameter><null /></parameter>
               <!-- Client ID  (no client ID here)-->
               <parameter><null /></parameter>
               <!-- Add MessageID In Header -->
               <parameter>true</parameter>
               <!-- register the JMS Bridge in the AS MBeanServer -->
               <parameter>
                   <inject bean="MBeanServer"/>
               </parameter>
               <parameter>org.hornetq:service=JMSBridge</parameter>
             </constructor>
           <property name="transactionManager">
               <inject bean="RealTransactionManager"/>
           </property>
       </bean>

       <!-- SourceCFF describes the ConnectionFactory used to connect to the 
            source destination -->
       <bean name="SourceCFF" 
            class="org.hornetq.api.jms.bridge.impl.JNDIConnectionFactoryFactory">
           <constructor>
               <parameter>
                   <inject bean="JNDI" />
               </parameter>
               <parameter>/ConnectionFactory</parameter>
           </constructor>  
       </bean>

       <!-- TargetCFF describes the ConnectionFactory used to connect to the 
        target destination -->
       <bean name="TargetCFF" 
            class="org.hornetq.api.jms.bridge.impl.JNDIConnectionFactoryFactory">
           <constructor>
               <parameter>
                   <inject bean="JNDI" />
               </parameter>
               <parameter>/ConnectionFactory</parameter>
           </constructor>  
       </bean>

       <!-- SourceDestinationFactory describes the Destination used as the source -->
       <bean name="SourceDestinationFactory" 
            class="org.hornetq.api.jms.bridge.impl.JNDIDestinationFactory">
           <constructor>
               <parameter>
                   <inject bean="JNDI" />
               </parameter>
               <parameter>/queue/source</parameter>
           </constructor>  
       </bean>

       <!-- TargetDestinationFactory describes the Destination used as the target -->
       <bean name="TargetDestinationFactory" 
            class="org.hornetq.api.jms.bridge.impl.JNDIDestinationFactory">
           <constructor>
               <parameter>
                   <inject bean="JNDI" />
               </parameter>
               <parameter>/queue/target</parameter>
           </constructor>  
       </bean>
       
       <!-- JNDI is a Hashtable containing the JNDI properties required -->
       <!-- to connect to the sources and targets JMS resrouces         -->       
      <bean name="JNDI" class="java.util.Hashtable">
         <constructor class="java.util.Map">
            <map class="java.util.Hashtable" keyClass="String"
                                             valueClass="String">
               <entry>
                  <key>java.naming.factory.initial</key>
                  <value>org.jnp.interfaces.NamingContextFactory</value>
               </entry>
               <entry>
                  <key>java.naming.provider.url</key>
                  <value>jnp://localhost:1099</value>
               </entry>
               <entry>
                  <key>java.naming.factory.url.pkgs</key>
                  <value>org.jboss.naming:org.jnp.interfaces"</value>
               </entry>
               <entry>
                  <key>jnp.timeout</key>
                  <value>5000</value>
               </entry>
               <entry>
                  <key>jnp.sotimeout</key>
                  <value>5000</value>
               </entry>
            </map>
         </constructor>
      </bean>

      <bean name="MBeanServer" class="javax.management.MBeanServer">
         <constructor factoryClass="org.jboss.mx.util.MBeanServerLocator"
                      factoryMethod="locateJBoss"/>
      </bean>
</deployment>

33.1. JMS桥的配置参数

桥的主要的bean是JMSBridge。所有的配置参数需要传递给这个bean的 构造函数。

Note

如果不想指定某个参数的值(例如匿名认证或没有选择器),将该参数设为<null />即可。

  • 源连接工厂的工厂(Source ConnectionFactory Factory)

    这个参数注入一个SourceCFFbean(由bean文件定义)。它被 用来创建ConnectionFactory

  • 目标连接工厂的工厂(Target ConnectionFactory Factory)

    这个参数注入一个TargetCFFbean(由bean文件定义)。它被 用来创建目的ConnectionFactory

  • 源目标工厂(Source DestinationFactory)

    这个参数注入一个SourceDestinationFactorybean(由 bean文件定义)。它用来创建 目标(Destination)

  • 目的目标工厂(Target DestinationFactory)

    这个参数注入一个TargetDestinationFactorybean(由 bean文件定义)。它用来创建目的 目标(Destination)

  • 源用户名(Source User Name)

    用于创建到的连接的用户名

  • 源密码(Source Password)

    用于创建连接的密码

  • 目的用户名(Target User Name)

    用于创建目的连接的用户名

  • 目的密码(Target Password)

    t用于创建目的连接的密码

  • 选择器(Selector)

    这是一个JMS的选择器表达式,它用于从源目标接收消息。只有与选择器相匹配的消息才会被桥 转发到目的目标

    选择器必须符合JMS 选择器语法

  • 故障重试间隔(Failure Retry Interval)

    代表当桥发现连接故障时在每两次重试连接之间所要等待的时间间隔,单位毫秒

  • 最大重试次数(Max Retries)

    表示桥在发现连接故障时所进行的最大重试次数。超过这个次数,桥就放弃重试。 -1代表一直重试下去

  • 服务质量(Quality Of Service)

    这个参数代表所需要的服务质量模式

    有效的值为:

    • AT_MOST_ONCE

    • DUPLICATES_OK

    • ONCE_AND_ONLY_ONCE

    有关这些模式的解释,参见Section 33.4, “服务质量”

  • 最大批量(Max Batch Size)

    表示桥一次性从源目标最多接收多少消息,并将它们一次发往目的地。它的值必须是 >= 1

  • 最大批时间(Max Batch Time)

    代表桥在将一批消息发向目的之前等待的最大毫秒数。这个时间过后,即使接收的消息数小于 MaxBatchSize,桥也会开始向目的发送消息。它的值必须是 -1 (代表永远等待)或>= 1

  • 订阅名(Subscription Name)

    如果源的目标是一个话题(topic),你想使用持久的订阅来接收消息的话,这个参数可以指定 订阅名。

  • 客户ID(Client ID)

    如果源的目标是一个话题(topic),你想使用持久的订阅来接收消息的话,这个参数可以指定 JMS的客户ID。它用于创建/查找持久订阅。

  • 在消息头添加MessageID(Add MessageID In Header)

    如果值为true,原始的消息ID在发往目的是回到消息的名为HORNETQ_BRIDGE_MSG_ID_LIST的头中。如果一个消息被桥转发了多次, 则每次转发的消息ID都添加在这个头中。这用于分布式请求/回答的消息模式。

    Note

    当收到一个消息时,通过它的相关ID(coorelation id)可以发送一个回答。这样 在消息发送方得到这个回答消息时,它可以与原消息相关联起来。

  • MBean服务器(MBean Server)

    要使用JMX管理JMS桥,需指定JMS桥所要注册的MBeanServer(如JVM Platform MBeanServer 或 JBoss 应用服务器的MBeanServer)

  • ObjectName

    设置了MBeanServer后,你还需要设置JMS桥MBean注册用的ObjectName(必须是唯一的)

33.2. 源和目的的连接工厂

源工目的的连接工厂分别用于创建到源和到目的的连接。

上面的配置例子中使用的是HornetQ提供的默认实现。它使用JNDI查找连接工厂。对于其它的应用服务器 或JMS提供者,需要实现相应的实现,即实现org.hornetq.jms.bridge.ConnectionFactoryFactory接口。

33.3. 源和目的的目标工厂

它们用来创建或查找相应的目标。

上面例子中,我们使用了HornetQ的默认实现,从JNDI中查找相应的对象。

要提供新的实现,只要实现接口org.hornetq.jms.bridge.DestinationFactory即可。

33.4. 服务质量

下面给是桥的三种服务质量的详细说明。

33.4.1. AT_MOST_ONCE

这种QoS模式表示的是消息最多送达目标一次。在消息发往目的之前,消息就会被通知。因此, 如果在消息被源删除但并未到达目的时发生故障,消息有可能丢失。所以说消息的 发送最多一次。

这个模式适用于持久或非持久的消息。

33.4.2. DUPLICATES_OK

在这个QoS模式下,消息从源接收后再发送到目的,之后才对源进行消息通知。这样如果在发送成功之后 消息通知前的时间内发生故障的话,在故障恢复时同一个消息可能被再次传递。結果可能是在目的处 该消息收到了两次。

这个模式适用于持久或非持久的消息。

33.4.3. ONCE_AND_ONLY_ONCE

这个模式保证消息从源发送到目的一次,并且只有一次。(有时这个模式又称为“只一次”)。若源与目的处于 同一个HornetQ服务器中,这个模式通过本地事务来保证消息的发送和通知。如果是在不同的服务器上, 则会使用一个JTA的事务将发送和接收包括其中。这里使用的JTA事务是JBoss的实现,它包含有一个 完整的事务恢复管理器,所以能提供高度可靠的持久性。如果使用JTA则桥的所有连接工厂必须是 XAConnectionFactory。这种模式的效率通常是最低的,因为它需要额外记录事务的日志。

这个模式只适用于持久性消息。

Note

某些情况下可以不使用ONCE_AND_ONLY_ONCE模式,而同样可以保证“一次且只一次”的效果。 这是通过使用DUPLICATES_OK模式,加上在目的端应用程序来检测重复的消息,如果有则将其丢弃。 一些JMS服务器本身提供自动重复消息检测的功能,这样节省了在应用层实现的工作。在应用层常见 的实现方法是将接收到的消息的ID存放到缓存文件中,然后与每个新到的消息进行对比。这种方式 可能在某段时间内有效,所以它不如ONCE_AND_ONLY_ONCE那样严格,它视具体情况也可能是一个 不错的选择。

33.4.4. JMS bridge中的超时问题

有时候目标服务器或源服务器会连接不上,这里桥就会尝试重新连接。重新连接的次数由Max Retries 参数决定,两次重新连接的时间间隔由Failure Retry Interval定义。

在重新尝试时会进行JNDI的查找。HornetQ的JNDI使用的是JBoss的实现,如果在JNDI查找过程中网络出现故障, 查找的操作可能挂起。为了避免这种情况的发生,我们可以为JNDI设置适当的超时。这是通过定义初始上下文的 jnp.timeout参数和jnp.sotimeout参数来 实现的。第一个参数定义了初始连接的超时,第二个参数定义的是套接字的读取超时。

Note

一旦JNDI连接成功,所有调用都是通过RMI完成。如果你想要控制RMI连接的超时,你需要定义相应的系统变量。 JBoss使用Sun的RMI实现,它的控制参数可以在这里找到。 默认的连接超时是10秒,默认的读超时是18秒。

如果你使用自己的实现来查找JMS资源,你需要注意超时问题。

33.4.5. 例子

参见Section 11.3.5, “JMS 桥(Bridge)”。这个例子展示了如何在JBoss应用服务器中配置并使用 JMS桥从一处目标将消息转发到另一个目标。

关于如何在两个单独HornetQ服务器间使用桥的例子,请参见Section 11.1.20, “JMS桥(Bridge)”

Chapter 34. 客户端重新连接与会话恢复

通过配置,HornetQ的客户端在与服务器的连接出现故障时,可以自动地重新建立连接并恢复与服务器的通迅。

34.1. 100%透明的会话恢复(re-attachment)

如果网络出现暂时性连接故障,并且服务器没有重启的情况下,当前的会话还会存在服务器中,其状态如同客户端 没有断开超过连接TTLChapter 17, 失效连接的检测时间。

在这种情况下,当客户端重新连接上服务器后,HornetQ自动将客户端和会话与服务器端的会话重新连接起来。整个过程 对于客户端是完全透明的,在客户端就好像什么都没有发生一样。

具体工作原理如下:

客户端再向服务器发送命令时,它将每个命令保存到内存的一块缓存中。当连接出现故障时客户端会尝试与该服务 器恢复会话。做为恢复协议的一部分,服务器在会话恢复时通知客户端最后一个成功接收的命令id。

根据这个命令id,客户端可以判断它的缓存中是否有命令还未被服务器成功接收。如果有,客户端可以重新发送 这些命令。

缓存的大小由ConfirmationWindowSize参数决定。当服务器成功接收了 ConfirmationWindowSize字节的命令时,会向客户端发送一个命令确认,以使客户端 及时清除缓存。

如果使用JMS服务,并且JMS的连接工厂是注册到JNDI的话,相应的参数是hornetq-jms.xml文件中的confirmation-window-size项。如果你并不将JMS连接工厂注册到JNDI,则你需要在 HornetQConnectionFactory上使用相应的方法直接设置该参数。

如果使用核心服务,你可以直接在ClientSessionFactory实例上直接设置该参数。

参数的单位是字节。

如果该参数是值设为-1,则关闭缓存,即关闭了重新恢复功能,迫使进行重新连接。默认 值是-1(表示没有自动恢复)。

34.2. 会话重新连接

有时服务器发生故障后进行了重启。这时服务器将丢失所有当前的会话,上面所述的会话恢复就不能做到完全透明了。

在这种情况下,HornetQ自动地重新建立连接并重新创建会话 和接收者。这一过程与向备份服务器进行失效备援(failover)完全一样。

客户重新连接的功能还用在其它一些模块上,如核心桥,以使它们能够重新连接到目标服务器上。

要全面理解事务性会话和非事务性会话在失效备援/重连接情况下的细节,以及如何保证 一次并且只有一次的消息传递,请参见Section 39.2.1, “自动客户端失效备援”的有关内容。

34.3. 重新连接/会话恢复的配置参数

下面是客户端用于重新连接的参数:

  • retry-interval。可选参数。它决定了两次重新连接尝试间隔的时间。单位 是毫秒。默认值是2000毫秒。

  • retry-interval-multiplier。可选参数。它表示下一次重试时间间隔的 系数。即下一次重试的时间间隔是本次时间间隔乘以该参数。

    这样可以实现重试间隔的指数延迟(exponential backoff)

    让我们看一个例子:

    假设retry-interval1000 ms,并且我们 将retry-interval-multiplier设为2.0,如果 第一次尝试失败,则等待1000毫秒后进行第二次重试,如果再失败,则每三次重 试要在2000毫秒后进行,第四次要等待4000毫秒, 以此类推。

    默认值是1.0,表示每次重试间隔相同的时间。

  • max-retry-interval。可选参数。它决定了重试间的最大时间间隔。 使用retry-interval-multiplier可以使重试的时间间隔以指数级增加。 有可能造成时间间隔增加到一个非常大的数值。通过设置一个最大值可对其增长进行限制。默认 值是2000毫秒。

  • reconnect-attempts。可选参数。它表示要进行多少重试后才放弃 并退出。-1表示进行无限次重试。默认值是0

如果使用JMS并且将JMS的连接工厂绑定到JNDI服务中,则需要在hornetq-jms.xml 文件中对这些参数进行配置,如下例所示:

<connection-factory name="ConnectionFactory">
<connectors>
   <connector-ref connector-name="netty"/>
</connectors>
<entries>
   <entry name="ConnectionFactory"/>
   <entry name="XAConnectionFactory"/>
</entries>
<retry-interval>1000</retry-interval>
<retry-interval-multiplier>1.5</retry-interval-multiplier>
<max-retry-interval>60000</max-retry-interval>
<reconnect-attempts>1000</reconnect-attempts>
</connection-factory>          
    

如果使用JMS但是直接实例化JMS连接工厂,你可以使用适当的方法在 HornetQConnectionFactory 对象上直接设置这些参数。

如果使用核心接口直接创建 ClientSessionFactory实例,则用它的适当的方法可以设置这些参数。

如果客户端重新连接后发现会话已经丢失(如服务器重启或超时),则无法完成恢复。如果在连接上或会话上注册了 ExceptionListenerFailureListener, 它们将会被通知。

34.4. ExceptionListeners and SessionFailureListeners

请注意当客户端进行重新连接或恢复会话时,注册的JMS ExceptionListener 或核心接口的 SessionFailureListener 将会被调用。

Chapter 35. 消息的转发(divert)与分流

在HornetQ中可以配置一些称为转发器(diverts)的对象。

转发器可以将路由到一个地址的消息透明地转发到其它的地址去,不需要客户端的参与。

转发器可以是唯一(exclusive)的,即消息只转发到新的地址,不发到原 来的地址;也可以是不唯一(non-exclusive)的,即消息在发往原有地址的 同时,它的一个拷贝被发往新的地址。不唯一的转发器可以在应用中将消息进行 分流(splitting)。比如在一个订单系统中它可以用于监视发往订单队列中 的每个订单消息。

转发器还可以带一个可选的消息选择器。只有被选择器选择的消息才会被转发。

转发器还可以带有一个转换器(Transformer)。它可以将消息进行转换。

转发器只在同一个服务器中的地址间进行转发。如果要向另外服务器中的地址进行转发,可以采用转发器与桥配合 来实现。先将消息通过转发器转发到一个存储与转发的队列中,再由一个桥将这个队列的消息转发到远程服务器的目的 地址中。

由转发器与桥进行配合可以组成复杂的路由系统。在服务器中由一组转发器可以形成一个消息路由表。如果加上桥,就 可以进一步形成一个分布式的可靠的消息路由网。

转发器的配置在hornetq-configuration.xml中定义。可以配置零个或多个转发器。

参见转发器的例子Section 11.1.13, “转移(Divert)”,它展示了如何配置与使用转发器。

让我们看一些转发器的配置例子:

35.1. 唯一式转发器

下面是一个唯一式转发器的例子。它将所有符合条件的消息转发到新的地址,而旧的地址将不能得到这些消息。

以下配置来自于divert例子:

<divert name="prices-divert">                  
    <address>jms.topic.priceUpdates</address>
    <forwarding-address>jms.queue.priceForwarding</forwarding-address>    
    <filter string="office='New York'"/>
    <transformer-class-name>
        org.hornetq.jms.example.AddForwardingTimeTransformer
    </transformer-class-name>     
    <exclusive>true</exclusive>
</divert>                        
        

在这里我们定义了一相名为“prices-divert”的转发器,它将发往 “jms.topic.priceUpdates”(对应JMS话题priceUpdates)的消息转向另一个本地地址“jms.queue.priceForwarding”(对应JMS队列 priceForwarding)。

我们还配置了一相消息过滤器。只有office属性值为New York 的消息才被转发到新地址,其它消息则继续发往原地址。如果没有定义过滤器,所有消息将被转发。

本例中还配置了一个转换器的类。当每转发一个消息时,该转换器就被执行一次。转换器可以对消息在转发前进行 更改。这里的转换器只是在消息中加入了一个记录转发时间的消息头。

本例中消息被转发到一个’存贮与转发是‘队列,然后通过一个桥将消息转发到另一个HornetQ服务器中。

35.2. 不唯一转发器

下面我们来看一个不唯一的转发器。不唯一转发器将消息的拷贝转发到新的地址中, 原消息则继续发往原有地址。

因此不唯一转发器可以将消息进行分流(splitting)。

不唯一转发器的配置与唯一转发器的配置中一样的,也可以带一个可选的过滤器和转换器。下面的配置也是出自 divert例子:

<divert name="order-divert">                 
    <address>jms.queue.orders</address>
    <forwarding-address>jms.topic.spyTopic</forwarding-address>         
    <exclusive>false</exclusive>
</divert>                       
        

The above divert example takes a copy of every message sent to the address 'jms.queue.orders' (Which corresponds to a JMS Queue called 'orders') and sends it to a local address called 'jms.topic.SpyTopic' (which corresponds to a JMS Topic called 'SpyTopic').

Chapter 36. 核心桥

桥的功能是从一个源队列中接收消息,再将消息转发到目的地址。通常这个目的地址在另外一个HornetQ服务器中。

源与目的不需要在同一个集群中。所以桥很适合将消息从一个集群中可靠地转发到另一个集群。比如通过一个WAN,或 internet,等连接不稳定的网络。

桥有处理故障的能力。如果目的服务器的连接失败(像网络故障),桥会重试与目的服务器的连接,直接连接成功 为止。当连接成功后,桥则继续进行工作。

总之,桥是可靠连接两个HornetQ服务器的一种手段。使用核心桥时源和目的服务器必须都是HornetQ服务器。

桥可以通过配置提供一次且只有一次的传递保证。其采用的方法是重复检测(详细 描述在Chapter 37, 重复消息检测)。

Note

核心桥的功能与JMS桥的功能相似,但是不能将它们混淆!

核心桥用来连接两个HornetQ节点,它不使用JMS接口。JMS桥使用的是JMS接口,它连接的是任何两个符合 JMS 1.1规范的服务器。因此,JMS桥可以将两个不同的JMS服务器连接起来。从性能角度考虑,核心桥由于采用 重复检测来实现一次且只一次的传递保证,可以提供更高的性能。 JMS桥则需要使用XA这种复杂的机制来提供同样的传递保证,因些性能要比核心桥低。

36.1. 桥的配置

桥的配置在hornetq-configuration.xml文件中。让我们先看一个配置的例子 (它实际上出自bridge例子):

<bridge name="my-bridge">
    <queue-name>jms.queue.sausage-factory</queue-name>
    <forwarding-address>jms.queue.mincing-machine</forwarding-address>
    <filter-string="name='aardvark'"/>
    <transformer-class-name>
        org.hornetq.jms.example.HatColourChangeTransformer
    </transformer-class-name>
    <retry-interval>1000</retry-interval>
    <retry-interval-multiplier>1.0</retry-interval-multiplier>
    <reconnect-attempts>-1</reconnect-attempts>
    <failover-on-server-shutdown>false</failover-on-server-shutdown>
    <use-duplicate-detection>true</use-duplicate-detection>
    <confirmation-window-size>10000000</confirmation-window-size>
    <connector-ref connector-name="remote-connector" 
        backup-connector-name="backup-remote-connector"/>     
    <user>foouser</user>
    <password>foopassword</password>
</bridge>                        
        

在上面的配置中包括了桥的所有参数。在实际应用中可能其中很多的参数可以使用默认值,不需要在配置中 指定。

下面我们对每个参数分别说明:

  • name参数。所有桥都必须有一个唯一的名字。

  • queue-name。本地队列的名字。桥从本地队列中接收消息。 这是一个必要的参数。

    这个队列在桥的启动之前必须已经存在。

    Note

    如果使用JMS,JMS的配置文件hornetq-jms.xml在核心配置文件 hornetq-configuration.xml之后装载。所以如果你的桥要从JMS 队列接收消息,就需要保证JMS队列同时要作为核心队列部署。具体方法可以参见 bridge例子。

  • forwarding-address。目的服务器中的地址。消息将被转发到这个地址。 如果没有指定这个转发地址,消息的原始地址将会保留。

  • filter-string。一个可选的过滤器表达式。它表示只有过滤器表达式选择 的消息才被转发。过滤器表达式的语法参见 Chapter 14, 过滤器表达式

  • transformer-class-name。可选的转换器类名。这是一个用户定义的 类,它需要实现接口org.hornetq.core.server.cluster.Transformer

    如果指定了这个类,每当一个消息被转发之前,它的transform()方法 就会被调用。用户利用这个机会可以对消息本身或消息头信息进行修改。

  • retry-interval。这个可选参数决定了在进行连接重试时,两次重试 之间的时间间隔。默认值是2000毫秒。

  • retry-interval-multiplier。这个可选参数基于前一次重试连接 的时间间隔来计算下一次重试的间隔,即前一次的间隔乘以该参数。

    这样可以实现重试间隔的指数延迟(exponential backoff)

    让我们看一个例子:

    假设retry-interval1000 ms,并且我们 将retry-interval-multiplier设为2.0,如果 第一次尝试失败,则等待1000毫秒后进行第二次重试,如果再失败,则每三次重 试要在2000毫秒后进行,第四次要等待4000毫秒, 以此类推。

    默认值是1.0,表示每次重试间隔相同的时间。

  • reconnect-attempts。可选参数。它表示要进行多少重试后才放弃 并退出。-1表示进行无限次重试。默认值是-1

  • failover-on-server-shutdown。可选参数。它指定了当目的服务器正常 退出时桥是否尝试失效备援(failover)到备份服务器(如果配置了的话)上。

    桥的连接器可以配置一个主服务器和一个备份服务器。如果配置了备份服务器,并且这个参数是 true时,在主服务器正常退出时,桥会自动地连接到备份服务器上继续工作。 如果桥的连接器没有配置备份服务器,则这个参数不起作用。

    你的桥配置了备份服务器后,有时你需要临时将主服务器关闭进行一些维护,此时并不希望桥连接到备份服务 器中。使用该参数就可以达到这个目的。

    这个参数的默认值是false

  • use-duplicate-detection。可选参数。它控制桥是否在转发的消息中自动 添加一个重复ID的属性。

    添加这样一个属性可以使目的服务器对来自源服务器的消息进行重复检测。当出现连接故障或服务器崩溃时, 桥在恢复时将重新转发那些没有被通知的消息。这在目的服务器端有可能造成重复发送。使用重复检测功能,可 以将重复发送的消息过滤掉。

    使用这个功能,服务器就可以保证 一次并且只有一次的传递,而不需要使用 重量级的方法,如XA(参见 Chapter 37, 重复消息检测)。

    默认的值是true.

  • confirmation-window-size。这个可选参数决定了向目的服务器转发消息时 所使用的确认窗口的大小。详细的描述在Chapter 34, 客户端重新连接与会话恢复

    Warning

    当桥从一个设置了max-size-bytes参数的队列接收并转发消息时,一个重要的事情就是要将 confirmation-window-size的值设置为小于等于 max-size-bytes的值,以避免造成消息流的停止。

  • connector-ref。这是一个必需的参数。它指定了桥用来连接目的服务器的 连接器

    connector包含了所用的传输(TCP, SSL, HTTP等),以及服务器连接参数 (如主机名,端口等)。关于连接器的详细信息请参见(Chapter 16, 传输层的配置)。

    connector-ref有两个参数:

    • connector-name。这个指的是核心配置文件hornetq-configuration.xml中定义的连接器的名字。桥使用 这个连接器创建与目的服务器的连接。这个参数是必需指定的。

    • backup-connector-name。这个可选参数同样指定一个在核心 配置文件hornetq-configuration.xml中定义的连接器名字。 当目的服务器出现故障时,或者正常退出但是参数failover-on-server-shutdown的值设为true时,桥使用这个参数指定的连接器通过失效备援(failover)连接 到备用的服务器。

  • user。这个可选参数指定了桥在创建与远程服务器连接时所用的用户名。如果 没有指定用户名,在配置文件hornetq-configuration.xmlcluster-user所定义的默认集群用户名将被使用。

  • password。这个可选的参数给出的是桥创建与远程服务器连接所使用的密码。 如果没有指定密码,在配置文件hornetq-configuration.xmlcluster-password所定义的默认集群密码将被使用。

Chapter 37. 重复消息检测

HornetQ具有强大的自动检测重复消息的功能。应用层无需实现复杂的重复检测。本章解释了什么是重复检测,它 在HornetQ中如何工作的,以及如何进行配置。

当客户端向服务器端发送消息时,或者从一个服务器向另一个服务器传递消息时,如果消息发送后目标服务器或者 连接出现故障,导致发送一方没有收到发送成功的确认信息,发送方因此就无法确定消息是否已经成功发送到了目标地 址。

如果上述的故障发生在消息被成功接收并处理后,但是在向发送方返回功能确认信息之前,那么消息实际上可以到达 其目的地址;如果故障发生在消息的接收及处理过程中,则消息不会到达其目的地址。从发送方的角度看,它无法区分 这两种情况。

当服务器恢复后,客户端面临的困难的选择。它知道服务器出了故障,但是不知道刚刚发送的消息是否成功到达目的 地址。如果它重新发送这个消息,就有可能造成消息的重复。如果这个消息是一个订单的话,重复发送消息就会产生两 个相同的订单,这当然不是所希望的結果。

将消息的发送放到一个事务中也不能解决这个问题。如果在事务提交的过程中发生故障,同样不能确定这个事务是否提交 成功!

为了解决这个问题,HornetQ提供了自动消息重复检测功能。

37.1. 在消息发送中应用重复检测

在消息发送中启用重复检测功能十分简单:你只需将消息的一个特殊属性设置一个唯一值。你可以用任意方法来 计算这个值,但是要保证它的唯一性。当目标服务器接收到这个消息时,它会检查这个属性是否被设置,如果设置了, 就检查内存缓存中是否已经接收到了相同值的消息。如果发现已经接收过具有相同属性值的消息,它将忽略这个消息。

Note

在节点之间的消息传递使用重复消息检测可以保证一次且只一次的传递,和使用 XA事务接收消息的效果一样,但是比XA消耗的资源要少,并且更容易。

如果是在一个事务中发送消息,则只需要设置其中一个消息的属性值。在服务器端如果服务器检测到一个事务中某一个 消息重复,则会忽略整个事务。

这个属性的名称由org.hornetq.api.core.HDR_DUPLICATE_DETECTION_ID定义,即: _HQ_DUPL_ID

该属性的值可以是byte[]类型或SimpleString类型(核心接口)。如果使用JMS,它必须是String 类型。它的值一定是唯一的。一个简单的方法是使用UUID。

下面是一个使用核心接口设置这个属性的例子:

...     

ClientMessage message = session.createMessage(true);

SimpleString myUniqueID = "This is my unique id";   // Could use a UUID for this

message.setStringProperty(HDR_DUPLICATE_DETECTION_ID, myUniqueID);

...
        

下面则是一个使用JMS的例子:

...     

Message jmsMessage = session.createMessage();

String myUniqueID = "This is my unique id";   // Could use a UUID for this

message.setStringProperty(HDR_DUPLICATE_DETECTION_ID.toString(), myUniqueID);

...
        

37.2. 配置重复ID缓存

服务器缓存中保存接收到的消息的org.hornetq.core.message.impl.HDR_DUPLICATE_DETECTION_ID属性值。每个地址有 单独的缓存。

缓存的大小是固定的,循环使用。如果缓存的最大可以存放n条记录,那么n + 1条记录将会覆盖缓存中的第0 条记录。

缓存的最大容量在文件hornetq-configuration.xml中配置,参数是id-cache-size。默认值是2000条记录。

在文件hornetq-configuration.xml中还可以配置将缓存持久化到磁盘。相应的参数 是persist-id-cache。如果设为true,则每加入一个id就将 它同时保存到磁盘中。默认值是true

Note

注意在设置缓存大小时,一定要保证缓存能保存足夠数量的记录,当消息被重新发送时,之前发送的ID不被 覆盖掉。

37.3. 桥与重复检测

核心桥可以通过配置在将消息发向目的服务器之前自动向消息中添加唯一的id(如果消息中还没有的话)。这样 如果目的服务器发生故障,核心桥在重新发送消息时,目的服务器就可以自动检测重复的消息,发现重复消息即丢弃。

要配置核心桥的自动添加id的功能,需要在hornetq-configuration.xml中桥的配置 里将use-duplicate-detection参数设为true

这个参数的默认值是true

关于核心桥的配置和使用,参见Chapter 36, 核心桥

37.4. 重复检测与集群连接

集群连接内部使用核心桥在节点间可靠地移动消息,因此它们的核心桥也可以配置自动添加id的功能。

配置的方法是在hornetq-configuration.xml文件中将集群连接的 use-duplicate-detection参数设为true

这个参数的默认值是true

有关集群连接配置的更多信息,请参见Chapter 38, 集群

37.5. 分页转存与重复检测

HornetQ在将消息进行分页转存中也使用了重复检测。当分页转存消息被从磁盘中读回到内存时,如果服务器发生故障, 重复检测可以避免在这一过程中有消息被重复读入,即避免了消息的重复传递。

关于分页转存的配置信息请参见Chapter 24, 分页转存

Chapter 38. 集群

38.1. 集群概述

HornetQ集群是由一组HornetQ服务器组成的集合,它们协同合作进行消息处理。集群中每个主节点就是一个 HornetQ服务器,它管理自己的连接并处理自己的消息。要将一个HornetQ服务器配置为集群服务器,需要将配置 文件hornetq-configuration.xmlclustered的值设 为true。默认值是false

要组成一个集群,每个节点都要在其核心配置文件hornetq-configuration.xml 中声明集群连接,用来建立与集群中其它节点的通迅。每两个节点间都是通过内部的一个 核心桥(参见Chapter 36, 核心桥)连接的。这些连接的建立是 透明的--你不需要为每个连接显式地声明一个桥。集群连接的作用是在集群的各个节点间进行负载平衡。

HornetQ可以采用不同的拓扑结构来组成集群。本章后面将讲述几种常用的拓扑结构。

我们还将讨论客户端的负载均衡--客户端如何均衡其与集群各节点的连接,以及消息的再分配--在节点间合理 的分配消息以避免消息匮乏(starvation)。

本章还涉及集群的另一个重要方面--服务器发现,即服务器通过广播的方式将 自己的连接信息告诉客户端或其它服务器,以使它们能与其建立连接,不需要额外的配置。

38.2. 服务器发现

服务器发现是指服务器通过广播的方式将自己的连接设置发送到网络上的机制,它有两个目的:

  • 被消息客户端发现。客户端接到广播后可以知道集群中有哪些服务器处于工作状态以及如何与它们 建立连接。虽然客户端可以可以在初始化时接受一个集群服务器的列表, 但是这样做与广播方式相比不够灵活。比如集群中有服务器离开或新加入时,列表的方式不能及时更新这些信息。

  • 被其它服务器发现。通过广播,服务器之间可以自动建立彼此间的连接,不需要事先知道集群中其它 服务器的信息。

服务器发现使用UDP协议来广播连接设置。如果网络中UDP被关闭,则不能使用服务器发现功能。只有用显式 地指定服务器的方法来设置集群或集群的客户端。

38.2.1. 广播组

服务器以广播组的方式来广播它的连接器信息。连接器定义了如何与该服务器建立连接的信息。关于连接器更多的 解释,请参见Chapter 16, 传输层的配置

广播组包括了一系列的连接器对。每个连接器对由主服务器的连接器和备份(可选)服务器连接器信息组成。 广播组还定义了所使用的UDP的在址和端口信息。

广播组的配置中服务器配置文件hornetq-configuration.xml中。一个HornetQ服务器可以有多个广播组。所有的广播组 必需定义在broadcast-groups内。

让我们来看一个hornetq-configuration.xml文件中广播组的例子:

<broadcast-groups>
   <broadcast-group name="my-broadcast-group">
      <local-bind-address>172.16.9.3</local-bind-address>
      <local-bind-port>54321</local-bind-port>
      <group-address>231.7.7.7</group-address>
      <group-port>9876</group-port>
      <broadcast-period>2000</broadcast-period>
      <connector-ref connector-name="netty-connector" 
        backup-connector="backup-connector"/>
   </broadcast-group>
</broadcast-groups>

有些广播组的参数是可选的,通常情况下可以使用默认值。在上面例子中我们为了说明目的给出了这些参数。 下面是这些参数的说明:

  • name。每个广播组需要有一个唯一的名字。

  • local-bind-address。这个参数是套接字的本地绑定地址。如果在服务器 中有多个网络接口卡时,必须要指定使用的是哪个接口。如果这个参数没有指定,那么将使用系统内核 所选定的IP地址。

  • local-bind-port。这个参数指定了套接字的本地绑定端口。通常情况下 可以使用其默认值-1,表示使用随机的端口。这个参数总是和 local-bind-address一起定义。

  • group-address。这个参数指定的是广播地址。它是一个D类的IP地址, 取值范围是224.0.0.0239.255.255.255。 地址224.0.0.0是保留地址,所以不能使用。这个参数是必需指定。

  • group-port。这个参数设定广播的UDP端口。 是一个必需指定的参数。

  • broadcast-period。指定两次广播之间的时间间隔,单位毫秒。 这是一个可选参数,它的默认值是1000毫秒。

  • connector-ref。这个参数指定了要广播的连接器以及可选的备份连接器。 (参见Chapter 16, 传输层的配置)。 connector-name属性的值是连接器的名字, backup-connector属性是备份连接器的名字,是可选属性。

38.2.2. 发现组

广播组规定了如何广播连接器的信息,发现组则定义的如何接收连接器的信息。

一个发现组包括了一系列的连接器对--每个连接器对代表一个不同的服务器广播的连接器信息。每当接收一次广播, 这个连接对的列表就被更新一次。

如果在一定时间内没有收到某个服务器的广播,则其相应的连接器对将从列表中删除。

发现组在HornetQ中有两处应用:

  • 在创建集群连接时用来判断集群中哪些服务器是可以连接的。

  • 客户端用来发现哪些服务器可以连接。

38.2.3. 在服务器端定义发现组。

服务器端的发现组定义在hornetq-configuration.xml配置文件中。所有的发现组都必须 在discovery-groups内定义。发现组可以定义多个。请看下面的例子:

<discovery-groups>
   <discovery-group name="my-discovery-group">
      <local-bind-address>172.16.9.7</local-bind-address>
      <group-address>231.7.7.7</group-address>
      <group-port>9876</group-port>
      <refresh-timeout>10000</refresh-timeout>
   </discovery-group>
</discovery-groups>

下面是对每个参数的解释:

  • name属性。每个发现组都必须有一个唯一的名字。

  • local-bind-address。如果你的主机有多个网络接口,你可能希望发现组只监听一个指定的 网络接口。这个参数就可以用于这个目的。它是一个可选参数。

  • group-address。需要监听的广播地址。它需要与广播组的 group-address一致才可以收到广播组的信息。这是一个必要参数。

  • group-port。需要监听的UDP端口。它需要与广播组的 group-port值相同才可以收到广播组的信息。这是一个必要参数。

  • refresh-timeout。这个参数决定了在收到某个服务器的广播后,需要等待 多长时间下一次广播必须收到,否则将该服务器的连接器对从列表中删除。通常这个参数的值应该远大于 广播组的broadcast-period,否则会使服务器信息由于小的时间差异而丢失。 这个参数是可选的,它的默认值是10000毫秒(10秒)。

38.2.4. 客户端的发现组

现在讨论如何配置HornetQ客户端来发现可以连接的服务器列表。使用JMS时所用的方法与使用核心接口时所用的 方法有所不同。

38.2.4.1. 使用JMS时客户端发现的配置方法

如果使用JMS,并且在服务器端的JMS连接工厂是注册到JNDI的情况下,你可以在服务器端的配置文件 hornetq-jms.xml中指定连接工厂所用的发现组。如下面所示:

<connection-factory name="ConnectionFactory">
   <discovery-group-ref discovery-group-name="my-discovery-group"/>
    <entries>
       <entry name="ConnectionFactory"/>
    </entries>
</connection-factory>

其中discovery-group-ref的值是定义在 hornetq-configuration.xml文件中的一个发现组。

当连接工厂从JNDI下载到客户端时,使用它创建连接就会在列表中的服务器间进行负载均衡。 客户端通过监听发现组中的广播地址可以不断更新这个服务器列表。

如果使用JMS但是不用JNDI,而是直接实例化JMS的连接工厂的话,可以用适当的方法来设置发现组的各个 参数。如下所示:

final String groupAddress = "231.7.7.7";

final int groupPort = 9876;

ConnectionFactory jmsConnectionFactory = 
        HornetQJMSClient.createConnectionFactory(groupAddress, groupPort);

Connection jmsConnection1 = jmsConnectionFactory.createConnection();

Connection jmsConnection2 = jmsConnectionFactory.createConnection();

refresh-timeout参数可以直接在连接工厂上使用 setDiscoveryRefreshTimeout()方法设置。

连接工厂还有一个方法setDiscoveryInitialWaitTimeout()。它可以设置连接工厂的 初始等待时间。当一个连接工厂被创建后立即进行用于创建连接的话,连接工厂可能没有足够的时间来接收各 个服务器发出的广播信息,也就无法建立完整的服务器列表。有了这个参数,连接工厂会在首次创建连接时 等待一定的时间,以接收广播。默认值是10000毫秒。

38.2.4.2. 使用核心API的客户端的配置

如果使用核心接口直接创建ClientSessionFactory的实例,可以使用相应的方法 直接进行参数的设置,如:

                    final String groupAddress = "231.7.7.7"; 
                    final int groupPort = 9876;
                    SessionFactory factory = HornetQClient.createClientSessionFactory(groupAddress, groupPort);
                    ClientSession session1 = factory.createClientSession(...); ClientSession
                    session2 = factory.createClientSession(...);
                
                

方法setDiscoveryRefreshTimeout()可以用来直接设置参数 refresh-timeout

会话工厂还有一个方法setDiscoveryInitialWaitTimeout()。它可以设置会话工厂的 初始等待时间。当一个会话工厂被创建后立即进行用于创建连接的话,该会话工厂可能没有足够的时间来接收各 个服务器发出的广播信息,也就无法建立完整的服务器列表。有了这个参数,会话工厂会在首次创建连接时 等待一定的时间,以接收广播。默认值是10000毫秒。

38.3. 服务器端消息的负载均衡

如果集群和各节点间定义了集群连接,HornetQ可以对到达一个节点的消息进行负载均衡。

举一个简单的例子。一个集群有4个节点,分别称为节点A、B、C和节点D。它们组成了一个 对称式集群(有关对称式集群参见Section 38.7.1, “对称式集群”)。 在每个节点上部署了一个名为OrderQueue的队列。

一个客户端Ca连接到节点A并向其发送订单消息。客户端Pa、Pb、Pc和Pd分别连接到节点A、B、C和D并接收处理 这些订单消息。如果在节点A中没有定义集群连接,那么订单消息都发送到节点A中的队列OrderQueue 中。因此只有连接到节点A的客户端Pa才能接收到订单消息。

如果在节点A定义了集群连接的话,发送到节点A的消息被轮流(round-robin)从节点A分配到各个节点上的 OrderQueue队列中。这种消息分配完全在服务器端完成,客户端只向节点A发送消息。

例如到达节点A的消息可能以下列顺序进行分配:B、D、C、A、B、D、C、A、B、D。具体的顺序取决于节点启动的 先后,但是其算法是不变的(即round-robin)。

HornetQ集群连接在进行消息负载均衡时,可以配置成统一负载均衡模式,即不管各个节点上有无合适的接收者,一律在 所有节点间进行消息的分配。也可以配置成为智能负载均衡模式,即只将消息分配到有合适接收者的节点上。这两种模式我们 都将举例说明。首先我们先介绍一般的集群连接配置。

38.3.1. 配置集群连接

集群连接将一组服务器连接成为一个集群,消息可以在集群的节点之间进行负载均衡。集群连接的配置在 hornetq-configuration.xml文件中的 cluster-connection内。一个HornetQ服务器可以有零个或多个集群连接。 下面是一个典型的例子:

<cluster-connections>
    <cluster-connection name="my-cluster">
        <address>jms</address>
        <retry-interval>500</retry-interval>
        <use-duplicate-detection>true</use-duplicate-detection>
        <forward-when-no-consumers>false</forward-when-no-consumers>
        <max-hops>1</max-hops>
        <discovery-group-ref discovery-group-name="my-discovery-group"/>
    </cluster-connection>
</cluster-connections>                
            

上面给出了集群连接的所有可配置参数。在实际应用中有些你可以使用默认值,不必全部给出。

  • address。每个集群连接只服务于发送到以这个参数的值为开始的 地址的消息。

    本例中的集群连接只对发往以jms为开始的地址的消息进行负载均衡的 处理。这个集群连接实际上能够处理所有JMS队列和话题的订阅中的消息,这是国为所有JMS的队列 或订阅都映射到内核中以“jms“开头的队列。

    这个地址可以为任何值,而且可以配置多个集群连接,每个连接的地址值可以不同。这样HornetQ 可以同时对不同地址同时进行消息的负载均衡。有的地址甚至可能在其它集群的节点中。这也就意谓着 一个HornetQ服务器可以同时参与到多个集群中。

    要注意别造成多个集群连接的地址互相重复。比如,地址“europe“和”europe.news“就互相重复, 就会造成同一个消息会被多个集群连接进行分配,这样有可能发生重复传递。

    本参数是必须指定的。

  • retry-interval。如前所述,一个集群连接实际上在内部是用桥将两 个节点连接起来。如果集群连接已经创建但是目的节点还未启动,或正在重启,这时集群连接就会不断 重试与这个节点的连接,直到节点启动完毕连接成功为止。

    这个参数决定了两次重试之间的时间间隔,单位是毫秒。它与桥的参数retry-interval 的含义相同(参见Chapter 36, 核心桥)。

    这个参数是可选的,默认值是500毫秒。

  • use-duplicate-detection。集群连接使用桥来连接各节点,而桥可以 通过配置向每个转发的消息添加一个重复id的属性。如果目的节点崩溃并重启,消息可以被重新发送。 重复检测的功能就是在这种情况下将重复发送的消息进行过滤并丢弃。

    这个参数与桥的参数use-duplicate-detection相同。关于重复检测的更多信息,请参见 Chapter 37, 重复消息检测

    这参数是可选的,默认值是true

  • forward-when-no-consumers。这个参数决定了是否向没有合适接收者 的节点分配消息。即不管有没有合适的接收者,消息在所有的节点间轮流分配。

    如果这个参数设为true,则消息就会轮流在每个节点间分配,不管是否 节点上有没有相应的接收者(或者有接收者但是具有不匹配的选择器)。注意,如果其它节点中没有 与本节点同名的队列,HornetQ不会将消息转发到那些节点中去,不受本参数的限制。

    如果参数设为false, HornetQ中将消息转发到集群中那些有着适合接收者 的节点中。如果接收者有选择器,则至少有一个选择器与所转发的消息匹配才可,否则不转发。

    本参数是可选的,默认值是false

  • max-hops。当一个集群连接在确定进行消息负载均衡的节点组时,这些 节点不一定是与本节点直接相连的节点。HornetQ可以通过其它HornetQ节点作为中介向那些非直接相 连的节点转发消息。

    这样可以使HornetQ组成更加复杂的拓扑结构并且仍可提供消息的负载均衡。在本章的后面我们还要作 进一步的讨论。

    本参数是可选参数,它的默认值是 1,表示消息只向直接相连的节点进行负载均衡。

  • discovery-group-ref。这个参数决定了使用哪个发现组来获得集群服务器的列表。 集群连接与列表中的服务器建立连接。

38.3.2. 集群用户的安全信息

当集群中两个节点建立连接时,HornetQ使用一个集群用户和集群密码。它们定义在 hornetq-configuration.xml文件中:

                <cluster-user>HORNETQ.CLUSTER.ADMIN.USER</cluster-user>
                <cluster-password>CHANGE ME!!</cluster-password>
            

Warning

强烈建议在实际应用中不要使用默认的值,否则任意远程客户端会使用这些默认值连接到服务器上。当使用默认值时, HornetQ会检测到并在每次启动的时候给出警告。

38.4. 客户端负载均衡

HornetQ的客户端负载均衡使同一个会话工厂每次创建一个会话时,都连接到集群不同的节点上。这样可以使所的有会话 均匀分布在集群的各个节点上,而不会‘拥挤’到某一个节点上。

客户端负载均衡的策略是可配置的。HornetQ提供两种现成的负载均衡策略。你也可以实现自己的策略。

两种现成的策略是:

  • 轮流策略(Round Robin)。这个策略是先随机选择一个节点作为第一个节点,然后依次选择各个节点。

    例如一个顺序可能是 B, C, D, A, B, C, D, A, B,另一个也可能是 D, A, B, C, D,A, B, C, D, A 或者 C, D, A, B, C, D, A, B, C, D, A等等。

  • 随机策略。每次都是随机选择一个节点来建立会话。

你可以实现自己的策略。只需要实现接口org.hornetq.api.core.client.loadbalance.ConnectionLoadBalancingPolicy即可。

根据你使用的是JMS还是核心接口,指定负载均衡的方法是有所不同的。如果你不指定策略,默认的策略是org.hornetq.api.core.client.loadbalance.RoundRobinConnectionLoadBalancingPolicy.

如果使用的是JMS,并且JMS连接工厂注册到JNDI,则你需要在hornetq-jms.xml文件中定义策略,如:

<connection-factory name="ConnectionFactory">
    <discovery-group-ref discovery-group-name="my-discovery-group"/>
    <entries>
        <entry name="ConnectionFactory"/>
    </entries>
    <connection-load-balancing-policy-class-name>
    org.hornetq.api.core.client.loadbalance.RandomConnectionLoadBalancingPolicy
    </connection-load-balancing-policy-class-name>
</connection-factory>            
        

上面的配置将部署一个连接工厂,它的连接负载均衡策略是随机策略。

如果使用JMS,但是你在客户端是直接创建连接工厂的实例,那么你需要用相应的方法在HornetQConnectionFactory上直接设置:

ConnectionFactory jmsConnectionFactory = HornetQJMSClient.createConnectionFactory(...);
jmsConnectionFactory.setLoadBalancingPolicyClassName("com.acme.MyLoadBalancingPolicy");
        

如果你使用核心接口的话,你要直接在ClientSessionFactory上设置策略:

ClientSessionFactory factory = HornetQClient.createClientSessionFactory(...);
factory.setLoadBalancingPolicyClassName("com.acme.MyLoadBalancingPolicy");
            

连接工厂进行负载均衡的服务器组可以有两种方法来确定:

  • 显式指定服务器

  • 使用发现组功能

38.5. 显式指定集群服务器

有的网络并不开放UDP,所以就不能使用服务器发现功能来获取服务器列表。

在这种情况下,可以显式地在每个节点或客户端指定服务器的列表。下面介绍如何做:

38.5.1. 在客户端指定服务器列表

根据使用的是JMS还是核心接口,所用的方法也不同。

38.5.1.1. 使用JMS时指定服务器列表

如果使用JMS,并且JMS连接工厂是注册到JNDI的话,你需要在服务器端的配置文件 hornetq-jms.xml中来指定,如下面的例子:

<connection-factory name="ConnectionFactory">
   <connectors>
      <connector-ref connector-name="my-connector1" 
           backup-connector-name="my-backup-connector1"/>
      <connector-ref connector-name="my-connector2" 
           backup-connector-name="my-backup-connector2"/>
      <connector-ref connector-name="my-connector3" 
           backup-connector-name="my-backup-connector3"/>
   </connectors>
   <entries>
      <entry name="ConnectionFactory"/>
   </entries>
</connection-factory>

其中的connection-factory内可以包含零或多个 connector-ref。每个connector-ref 都拥有connector-name属性和一个可选的backup-connector-name属性。connector-name 属性指向的是一个在hornetq-configuration.xml 文件中定义的连接器。而backup-connector-name属性也是指向在 hornetq-configuration.xml文件中定义的一个连接器。 有关连接器更多的信息参见Chapter 16, 传输层的配置

连接工厂这样就保存有一组[连接器, 备份连接器]对,用于客户端在创建连接时的负载均衡。

如果你使用JMS,但不使用JNDI,你可以直接创建HornetQConnectionFactory 的实例,然后用相应的方法来设定连接器对列表,如下例:

List<Pair<TransportConfiguration, TransportConfiguration>> serverList = 
        new ArrayList<Pair<TransportConfiguration, TransportConfiguration>>();

serverList.add(new Pair<TransportConfiguration, 
        TransportConfiguration>(liveTC0, backupTC0));
serverList.add(new Pair<TransportConfiguration, 
        TransportConfiguration>(liveTC1, backupTC1));
serverList.add(new Pair<TransportConfiguration, 
        TransportConfiguration>(liveTC2, backupTC2));

ConnectionFactory jmsConnectionFactory = HornetQJMSClient.createConnectionFactory(serverList);

Connection jmsConnection1 = jmsConnectionFactory.createConnection();

Connection jmsConnection2 = jmsConnectionFactory.createConnection();

上面的代码中我们创建了一组TransportConfiguration对象。每个 TransportConfiguration对象包括了如何连接某个特定服务器的信息。

然后,使用这个服务器列表创建了一个HornetQConnectionFactory实例。 这样通过这个工厂创建的连接就可以使用这个列表,由所用的客户连接负载均衡策略来进行连接的负载均衡。

38.5.1.2. 使用核心接口指定服务器列表

如果使用核心接口,你可以直接在ClientSessionFactory实例上设置服务器列表。 如下例:

List<Pair<TransportConfiguration, TransportConfiguration>> serverList = 
        new ArrayList<Pair<TransportConfiguration, TransportConfiguration>>();

serverList.add(new Pair<TransportConfiguration, 
        TransportConfiguration>(liveTC0, backupTC0));
serverList.add(new Pair<TransportConfiguration, 
        TransportConfiguration>(liveTC1, backupTC1));
serverList.add(new Pair<TransportConfiguration, 
        TransportConfiguration>(liveTC2, backupTC2));

ClientSessionFactory factory = HornetQClient.createClientSessionFactory(serverList);

ClientSession sesison1 = factory.createClientSession(...);

ClientSession session2 = factory.createClientSession(...);

在上面的代码中我们创建了一组ClientSessionFactoryImpl对象。每个 TransportConfiguration对象包括了如何连接某个特定服务器的信息。 有关信息请参见Chapter 16, 传输层的配置

然后,使用这个服务器列表创建了一个HornetQConnectionFactory实例。 这样通过这个工厂创建的会话就可以使用这个列表,由所用的客户连接负载均衡策略来进行连接的负载均衡。

38.5.2. 指定服务器列表以组成集群

下面我们考虑一个对称集群的例子,我们配置了每个集群连接,但是不使用发现功能来获得服务器信息。我们 采用配置的方法来显式指定集群的所有成员。

下面就是一个集群连接的配置:

<cluster-connections>
    <cluster-connection name="my-explicit-cluster">
        <address>jms</address>
        <connector-ref connector-name="my-connector1" 
            backup-connector-name="my-backup-connector1"/>
        <connector-ref connector-name="my-connector2" 
            backup-connector-name="my-backup-connector2"/>
        <connector-ref connector-name="my-connector3" 
            backup-connector-name="my-backup-connector3"/>
    </cluster-connection>
</cluster-connections>

cluster-connection中可以包括零或多个connector-ref, 每个connector-ref都有一个connector-name属性和 一个可选的backup-connector-name属性。connector-name属性指向一个在hornetq-configuration.xml文件中定义的一个连接器,它是主连接器。可选的 backup-connector-name指向的也是在 hornetq-configuration.xml文件中定义的一个连接器。 有关连接器的详细信息参见Chapter 16, 传输层的配置

Note

由于HornetQ 2.0.0的限制,使用静态节点列表的集群不支持失效备援(failover)。要想支持失效备援, 就必须使用发现组。

38.6. 消息再分配

集群的另一个重要功能是消息的再分配。前面我们知道在服务器端可以对消息大集群节点间进行轮流方式的负载均衡。如果 forward-when-no-consumers参数为false,消息将不会转发到那些没有相应接收者的节点中。 这样可以有效避免了消息被送到一个不可能被接收的节点上。但仍然有一个问题无法解决:就是如果在消息发到一个节点后, 它的接收者被关闭,那么这些消息仍然不能被接收了,造成了一种消息匮乏情形。 这种情况下如何处理?

这里就需要消息再分配功能。通过配置,HornetQ可以将没有接收者的队列中的消息再次分配 到有接收者的节点上去。

通过配置,消息可以在队列最后一个接收者关闭时立即进行,也可以配置成等待一段时间再进行。默认消息再分配功能是 关闭的。

消息再分配功能可以基于地址进行配置,即在地址设置中指定再分配的延时。关于地址设置的更多信息,请参见 Chapter 25, 队列属性

下面是从hornetq-configuration.xml文件中提取的消息再分配的配置:

<address-settings>     
   <address-setting match="jms.#">
      <redistribution-delay>0</redistribution-delay>
   </address-setting>
 </address-settings>

上面address-settings中设置的redistribution-delay值为0。它适用于所有以“jms“开头的 地址。由于所有JMS队列与话题订阅都绑定到以”jms“为开头的地址,所以上述配置的立即方式(没有延迟)消息 再分配适用于所有的JMS队列和话题订阅。

match属性可以是精确匹配,也可以使用通配符。通配符要符合HornetQ的通配符 语法(在Chapter 13, 了解 HornetQ 通配符的语法中描述)。

redistribution-delay定义了队列最后一个接收者关闭后在进行消息再分配前所等待的 时间,单位毫秒。如果其值是0,表示立即进行消息再分配。-1表示不会进行消息再分配。 默认值是-1

通常为消息分配定义一个延迟是有实际意义的。很多时候当一个接收者被关闭时,很快就会有一个新的接收者被创建。 在这种情况下加一延迟可以使消息继续在本地进行接收,而不会将消息转发到别处。

38.7. 集群拓扑结构

HornetQ集群可以有多种拓扑结构。我们来看两个最常见的结构。

38.7.1. 对称式集群

对称式集群可能是最常见的集群方式了。如果你接触过JBoss应用服务器的集群,你就对这种方式很熟悉。

在一个对称集群中,每一个节点都与集群中其它任一节点相连。换句话说,集群中任意两个节点的连接都 只有一跳(hop)。

要组成一个对称式的集群,每个节点在定义集群连接时要将属性max-hops 设为1。通常集群连接将使用服务器发现的功能来获得集群中其它服务器的连接 信息。当然在UDP不可用的时候,也可以通过显式方式为集群连接指定服务器。

在对称集群中,每个服务器都知道集群中其它服务器中的所有队列信息,以及它们的接收者信息。利用这些 信息它可以决定如何进行消息的负载均衡及消息再分配。

38.7.2. 链式集群

在链式集群中,并不是每个节点都与其它任何节点直接相连,而是由两个节点组成头和尾,其余节点在中间连接 成为一个链的结构。

比如有三个节点A、B和C。节点A在一个网络中,它有许多消息的发送者向它发送订单消息。由于公司的政策,订单 的接收者需要在另一个网络中接收消息,并且这个网络需要经过其它第三个网络才可以访问。这种情况下我们将节点 B部署到第三个网络中,作为节点A与节点C的中间节点将两个节点连接起来。当消息到达节点A时,被转发到节点B, 然后又被转发到节点C上,这样消息就被C上的接收者所接收。节点A不需要直接与节点C连接,但是所有三个节点仍然 组成了一个集群。

要想组成一个这样的集群,节点A的集群连接要指向节点B,节点B的集群连接要指向C。本例我们只想组成一个单向 的链式集群,即我们只将消息按节点A->B->C的方向流动,而不要向 C->B->A方向流动。

对于这种集群拓扑,我们需要将max-hops设为2. 这个值可以使节点C上队列的信息传送到节点B,再传送到节点A。因此节点A就知道消息到达时即将 其转发给节点B。尽管节点B可能没有接收者,可它知道再经过一跳就可以将消息转到节点C,那里就有接收者了。

Chapter 39. 高可获得性(High Availability)和失效备援(Failover)

高可获得性是指当系统中有一台甚至多台服务器发生故障时还能继续运转的能力

作为高可获得性的一部分,失效备援的含意是 当客户端当前连接的服务器发故障时,客户端可以将连接转到另一台正常的服务器,从而能够继续工作

39.1. 主要-备份对

HornetQ可以将两个服务器以主要-备份对的形式连接在一起。目前HornetQ允许一个 主要服务器有一个备份服务器,一个备份服务器只有一个主要服务器。在正常情况下主要服务器工作,备份服务器只有当 发生失效备援发生时工作。

没有发生失效备援时,主要服务器为客户端提供服务,备份服务器处于待机状态。当客户端在失效备援后连接到备份服务 器时,备份服务器开始激活并开始工作。

39.1.1. 高可获得性(HA)的模式

HornetQ的高可获得性有两种模式:一种模式通过由主服务器日志向备份服务器日志 复制数据。另一种模式则是主服务器与备份服务器间存贮共享

Note

只有持久消息才可以在失效备援时不丢失。所有非持久消息则会丢失。

39.1.1.1. 数据复制

在这种模式下,保存在HornetQ主服务器中日志中的数据被复制到备份服务器日志中。注意我们并不复制 服务器的全部状态,而是只复制日志和其它的持久性质的操作。

复制的操作是异步进行的。数据通过流的方式复制,复制的結果则通过另一个流来返回。通过这样的异步方式 我们可以获得比同步方式更大的呑吐量。

当用户得到确认信息如一个事务已经提交、准备或加滚,或者是一个持久消息被发送时,HornetQ确保这些状态 已经复制到备份服务器上并被持久化。

数据复制这种方式不可避免地影响性能,但是另一方面它不依赖于昂贵的文件共享设备(如SAN)。它实际上是 一种无共享的HA方式。

采用数据复制的失效备援比采用共享存储的失效备援要快,这是因为备份服务器在失效备援时不用重新装载日志。

39.1.1.1.1. 配置

首先在主服务器的 hornetq-configuration.xml文件中配置备份服务器。 配置的参数是backup-connector-ref。这个参数指向一个连接器。这个连接器 也在主服务器上配置。它定义了如何与备份服务器建立连接。

下面就是一个在hornetq-configuration.xml文件中的例子:

  <backup-connector-ref connector-name="backup-connector"/>

  <connectors>
     <!-- 这个连接器用于连接备份服务喝咖啡    -->
     <!-- 备份服务器在主机"192.168.0.11"上,端口"5445" -->
     <connector name="backup-connector">
       <factory-class>org.hornetq.core.remoting.impl.netty.NettyConnectorFactory</factory-class>
       <param key="host" value="192.168.0.11"/>
       <param key="port" value="5445"/>
     </connector>
  </connectors>

其次在备份服务器上,我们设置了备份服务器的标志,并且配置了相应的接受器以便主服务器能够建立 连接。同时我们将shared-store参数设为false。

  <backup>true</backup>
  
  <shared-store>false<shared-store>
  
  <acceptors>
     <acceptor name="acceptor">
        <factory-class>org.hornetq.core.remoting.impl.netty.NettyConnectorFactory</factory-class>
        <param key="host" value="192.168.0.11"/>
        <param key="port" value="5445"/>
     </acceptor>
  </acceptors>               
              

为了使备份服务器正常工作,一定要保证它与主服务器有着同样的桥、预定义的队列、集群连接、 广播组和发现组。最简单的作法是拷贝主服务器的全部配置然后再进行上述的修改。

39.1.1.1.2. 备份服务器与主服务器间的同步

为了能正常工作,备份服务器与主服务器必须同步。这意谓着备份服务器不能是当前任意一个备份服 务器。如果你这样做,主服务器将不能成功启动,在日志中会出现异常。

要想将一个现有的服务器配置成一个备份服务器,你需要将主服务器的data 文件夹拷贝到并覆盖这个备份 服务器的相同文件夹,这样做保证了备份服务器与主服务器的持久化数据完全一致。

当失效备援发生后,备份服务器代替主服务器工作,原来的主服务器失效。这时简单的重启主服务 器是不行的。要想将主服务器与备份重新进行同步,就必须先将主服务器和备份服务器同时停止,再将 主服务器的数据拷贝到备份服务器,然后再启动。

HornetQ以后将支持备份与主服务器间的自动同步,无需停止主服务器。

39.1.1.2. 存贮共享

使用存贮共享,主服务器与备份服务器共用相同目录的日志数据,通常是一个共享的 文件系统。这包括转存目录,日志目录,大消息及绑定日志。

当发生失效备援时,工作由备份服务器接管。它首先从共享的文件系统中读取主服务器的持久数据,然后 才能接受客户端的连接请求。

与数据复制方式不同的是这种方式需要一个共享的文件系统,主服务器与备份服务器都可以访问。典型的 高性能的共享系统是存贮区域网络(SAN)系统。我们不建议使用网络附加存贮(NAS),如NFS,来存贮共享 日志(主要的原因是它们比较慢)。

共享存贮的优点是不需要在主服务器与备份服务器之间进行数据复制,因此对性能不会造成影响。

共享存贮的缺点是它需要一个共享文件系统。同时,当备份服务器激活时它需要首先从共享日志中读取相应 的信息,从而占用一定的时间。

如果你需要在一般工作情况下保持高性能,并且拥有一个快速的SAN系统,同时能够容忍较慢的失效备援 过程(取决于数据量在多少),我们建议你采用存贮共享方式的高可获得性。

39.1.1.2.1. 配置

要使用存贮共享模式,在两个服务器的配置文件hornetq-configuration.xml 中将作如下设置:

                   <shared-store>true<shared-store>
                

此外,备份服务器必须显式地指定:

                   <backup>true</backup>
                     

另外,需要将主服务器和备份服务器的日志文件位置指向同一个共享位置。 (参见Section 15.3, “配置消息日志”

如果客户端使用JMS自动失效备援,主服务器除了要配置一个连接器以连接到备份服务器外,还要在 配置文件hornetq-jms.xml中指向这个连接器,如 Section 39.2.1, “自动客户端失效备援”中所解释的那样。

39.1.1.2.2. 备份服务器与主服务器间的同步。

由于主备服务器之间共享存贮,所以它们不需要进行同步。但是它需要主备服务器同时工作以提供 高可获得性。如果一量发生失效备援后,就需要在尽可能早的时间内将备份服务器(处于工作状态)停下来, 然后再启动主服务器和备份服务器。

HornetQ以后将支持自动同步功能,不需要先停止服务器。

39.2. 失效备援的模式

HornetQ定义了两种客户端的失效备援:

  • 自动客户端失效备援

  • 应用层的客户端失效备援

HornetQ还支持100%透明的同一个服务器的自动连接恢复(适用于网络的临时性故障)。这与失效备援很相似, 只不过连接的是同一个服务器,参见Chapter 34, 客户端重新连接与会话恢复

在发生失效备援时,如果客户端有非持久或临时队列的接收者时,这些队列会自动在备份服务器上重新创建。对于 非持久性的队列,备份服务器事先是没有它们的信息的。

39.2.1. 自动客户端失效备援

HornetQ的客户端可以配置主/备份服务器的信息,当客户端与主服务器的连接发生故障时,可以自动检测到故障并 进行失效备援处理,让客户端连接到备份服务器上。备份服务器可以自动重新创建所有在失效备援之前存在的会话与接收 者。客户端不需要进行人工的连接恢复工作,从而节省了客户端的开发工作。

HornetQ的客户端在参数client-failure-check-period(在 Chapter 17, 失效连接的检测中进行了解释)规定的时间内如果没有收到数据包,则认为连接发生故障。 当客户端认为连接故障时,它就会尝试进行失效备援。

HornetQ有几种方法来为客户端配置主/备服务器对的列表。可以采用显式指定的方法,或者采用更为常用的 服务器发现的方法。有关如何配置服务器发现的详细信息,请参见 Section 38.2, “服务器发现”。 关于如何显式指定主/备服务器对的方法,请参见Section 38.5.2, “指定服务器列表以组成集群”中的解释。

要使客户端具备自动失效备援,在客户端的配置中必须要指定重试的次数要大于零(参见 Chapter 34, 客户端重新连接与会话恢复中的解释)。

有时你需要在主服务器正常关机的情况下仍然进行失效备援。如果使用JMS,你需要将HornetQConnectionFactoryFailoverOnServerShutdown属性设为true,或者是在hornetq-jms.xml(参数为failover-on-server-shutdown)文件中进行相应的配置。如果使用的是核心接口,可以在创建 ClientSessionFactoryImpl实例时将上述同名属性设置为true。 这个属性的默认值是false。这表示如果主服务器是正常关机,客户端将不会进行失效备援

Note

默认正常关机不会不会导致失效备援。

使用CTRL-C来关闭HornetQ服务器或JBoss应用服务器属于正常关机,所以不会触发客户端的失效 备援。

要想在这种情况下进行失效备援必须将属性FailoverOnServerShutdown 设为true。

默认情况下至少创建了一个与主服务器的连接后失效备援才会发生。换句话说,如果客户端每一次创建与 主服务器的连接失败,它会根据参数reconnection-attempts的设置进行连接重试,而不是进行失效备援。 如果重试次数超过的该参数的值,则连接失败。

在有些情况下,你可能希望在初始连接失败和情况下自动连接到备份服务器,那么你可以直接在 ClientSessionFactoryImplHornetQConnectionFactory上设置FailoverOnInitialConnection 参数,或者在配置文件中设置failover-on-initial-connection。默认的值是false

有关事务性及非事务性JMS会话的自动失效备援的例子,请参见 Section 11.1.57, “带有数据复制的事务性失效备援”Section 11.1.33, “带有服务器数据复制的非事务失效备援”

39.2.1.1. 关于服务器的复制

HornetQ在主服务器向备份服务器复制时,并不复制服务器的全部状态。所以当一个会话在备份服务器 中重新创建后,它并不知道发送过的消息或通知过的消息。在失效备援的过程中发生的消息发送或通知也可 能丢失。

理论上如果进行全部状态的复制,我们可以提供100%的透明的失效备援,不会失去任何的消息或通知。 但是这样做要付出很大的代价:即所有信息都要进行复制(包括队列,会话等等)。也就是要求复制服务 器的每个状态信息,主服务器的每一步操作都将向其备份进行复制,并且要在全局内保持顺序的一致。这样 做就极难保证高性能和可扩展性,特别是考虑到多线程同时改变主服务器的状态的情况,要进行全状态复制 就更加困难。

一些技术可以用来实现全状态复制,如虚拟同步技术 (virtual synchrony)。但是这些技术往往没有很好的可扩展性,并且将所有操作都 进行序列化,由单一线程进行处理,这样明显地将底了并行处理能力。

另外还有其它一些多线程主动复制技术,比如复制锁状态或复制线程调度等。这些技术使用Java语言非常 难于实现。

因此得出结论,采用大量牺牲性能来换取100%透明的失效备援是得不偿失的。没有100%透明的失效 备援我们仍然可以轻易地保证一次且只有一次的传递。这是通过在发生故障时采用重复检测结合事务重试 来实现的。

39.2.1.2. 失效备援时阻塞调用的处理

如果当发生失效备援时客户端正面进行一个阻塞调用并等待服务器的返回,新创建的会话不会知道这个调用, 因此客户端可能永远也不会得到响应,也就可能一直阻塞在那里。

为了防止这种情况的发生,HornetQ在失效备援时会解除所有的阻塞调用,同时抛出一个 javax.jms.JMSException异常(如果是JMS)或HornetQException异常。异常的错误代码是HornetQException.UNBLOCKED。客户端需要自行处理这个异常,并且进行 必要的操作重试。

如果被解除阻塞的调用是commit()或者prepare(),那么这个事务会被自动地回滚,并且HornetQ 会抛出一个javax.jms.TransactionRolledBackException(如果是JMS) 或都是一个HornetQException,错误代码为 HornetQException.TRANSACTION_ROLLED_BACK(如果是核心接口)。

39.2.1.3. 事务的失效备援处理

如果在一个事务性会话中,在当前事务中消息已经发出或通知,则服务器在这时如果发生失效备援,它不 能保证发出的消息或通知没有丢失。

因此这个事务就会被标记为只回滚,任何尝试提交的操作都会抛出一个javax.jms.TransactionRolledBackException异常(如果是JMS),或者是一 个HornetQException的异常,错误代码为HornetQException.TRANSACTION_ROLLED_BACK(如果是核心接口)。

客户端需要自行处理这些异常,进行必要的回滚处理。注意这里不需要人工将会话进行回滚-此时它已经 被回滚了。用户可以通过同一个会话重试该事务操作。

HornetQ发布包中包括了一个完整的例子来展示如何处理这种情况。参见 Section 11.1.57, “带有数据复制的事务性失效备援”

如果是在提交过程中发生了失效备援,服务器将这个阻塞调用解除。这种情况下客户端很难确定在事故发生 之前事务是否在主服务器中得到了处理。

为了解决这个问题,客户端可以在事务中使用重复检测(Chapter 37, 重复消息检测) ,并且在提交的调用被解除后重新尝试事务操作。如果在失效备援前事务确实在主服务器上已经完成提交,那么 当事务进行重试时,重复检测功能可以保证重复发送的消息被丢弃,这样避免了消息的重复。

Note

通过处理异常和重试,适当处理被解除的阻塞调用并配合重复检测功能,HornetQ可以在故障条件下保证 一次并且只有一次的消息传递,没有消息丢失和消息重复。

39.2.1.4. 非事务会话的失效备援处理

如果会话是非事务性的,那么通过它的消息或通知在故障时可能会丢失。

如果你在非事务会话中要保证一次并且只有一次 的消息传递,你需要使用重复检测功能,并适当处理被解除的阻塞调用。参见 Section 39.2.1.2, “失效备援时阻塞调用的处理”

39.2.2. 连接故障的通知

JMS提供了标准的异步接收连接故障通知的机制:java.jms.ExceptionListener。 请参考JMS的javadoc或者其它JMS教程来进一步了解怎样使用这个接口。

HornetQ的核心接口也提供了一个相似的接口 org.hornet.core.client.SessionFailureListener

任何ExceptionListener或SessionFailureListener的实例,在发生连接故障时,都会被HornetQ 调用,不管该连接是否得到了失效备援、重新连接还是得到了恢复。

39.2.3. 应用层的失效备援

在某些情况下你可能不需要自动的客户端失效备援,希望自己来处理连接故障,使用自己的重新连接方案等。 我们把它称之为应用层失效备援,因为它是发生在应用层的程序中。

为了实现应用层的失效备援,你可以使用监听器(listener)的方式。如果使用的是JMS,你需要在JMS连接上 设置一个ExceptionListener类。这个类在连接发生故障时由HornetQ调用。在这个类 中你可以将旧的连接关闭,使用JNDI查找新的连接工厂并创建新的连接。这里你可以使用 HA-JNDI 来保证新的连接工厂来自于另一个服务器。

请参见Section 11.1.1, “应用层的失效备援(Failover)”。这是一个完整的应用层失效备援的例子。

如果你使用核心接口,则过程也是很相似的:你在核心的ClientSession实例上设置一个 FailureListener,然后在这个类中进行相应的处理即可。

Chapter 40. Libaio平台专有库

HornetQ发布包中包括一个平台专有的库,它可以使HornetQ使用Linux操作系统的libaio。

libaio是Linux项目的一个库。它将用户提交的写操作用异步的方式执行。通过 回调用户的代码来通知写操作的完成。

通过配置,HornetQ可以使用这个库来访问高性能的日志,具体请参见 Chapter 15, 持久化

下面列出了HornetQ所带的平台专有库文件:

  • libHornetQAIO32.so - x86 32 位平台

  • libHornetQAIO64.so - x86 64 位平台

当使用libaio时,HornetQ会在库路径中寻找并装 载这些文件。

40.1. 库文件的编译

如果你的Linux平台不是x86_32或x86_64(比如Itanium 64或IBM Power),你需要自己编译相应的库文件, 因为HornetQ不提供这些平台的库文件。

40.1.1. 安装要求

Note

目前libaio只在Linux上有。所以它不可能在其它操作系统上编译。

编译需要autoconf工具,它用来简化编译过程。除此之外还需要一些安装包:

  • gcc - C 编译器

  • gcc-c++ or g++ - gcc的c++编译工具扩展

  • autoconf - 自动编译工具

  • make - make 工具

  • automake - make文件自动生成工具

  • libtool - 库连接工具

  • libaio - 磁盘异步IO库

  • libaio-dev - libaio的编译支持

  • 完整的JDK,JAVA_HOME要指向正确的位置

如果在RHEL或Fedora上进行安装,输入以下命令:

sudo yum install automake libtool autoconf gcc-g++ gcc libaio libaio-dev make

如果是 debian系统,则:

sudo apt-get install automake libtool autoconf gcc-g++ gcc libaio libaio-dev make

Note

在有些Linux的版本中上述的安装包名可能有一些差别。(例如Fedora中的gcc-c++在Debian系统中 的名称为g++)

40.1.2. 开始编译

在HornetQ发布包的native-src目录下,执行shell脚本 bootstrap。这个脚本会调用 automake以及make来创建所有的make文件和专有库。

someUser@someBox:/messaging-distribution/native-src$ ./bootstrap 
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /bin/mkdir -p

...

configure: creating ./config.status
config.status: creating Makefile
config.status: creating ./src/Makefile
config.status: creating config.h
config.status: config.h is unchanged
config.status: executing depfiles commands
config.status: executing libtool commands

...

编译好的库文件在./native-src/src/.libs/libHornetQAIO.so。将该文件移到发布包的 bin目录下,或者你的库目录 所指向的目录即可。

如果你修改了HornetQ的libaio代码,只需要在native-src目录下直挂运行make即可完成编译。

Chapter 41. 线程管理

本章讲述HornetQ如何使用线程池以及如何管理线程。

首先我们讨论在服务器端线程是如何被管理的,然后我们再讨论客户端的情况。

41.1. 服务器端线程的管理

每个HornetQ服务器都有一个线程池作为一般线程使用,另外还有一个可计划线程池。Java的可计划线程池不能作为 标准的线程池使用,因此我们采用了两个单独的线程池。

当使用旧的(阻塞)IO时,使用了一个单独的线程池来处理连接。但是旧的IO要求一个线程配一个连接,所以如果你 的应用有很多并发的连接,这个线程池会很快用光所有的线程,造成服务器出现“挂起”现象。因此,对于大量并发连接 的应用,一定要使用NIO。

如果使用NIO,默认情况下HornetQ会使用系统中处理器内核(或超线程)数量三倍的线程来处理接收的数据包。 内核的数量是通过调用Runtime.getRuntime().availableProcessors()来得到 的。如果你想改变这个数量,可以设置传输层配置参数nio-remoting-threads。 参见Chapter 16, 传输层的配置

另外在其它一些地方直接使用了线程,没有用线程池。我们将对这些线程作出解释。

41.1.1. 服务器端可计划线程池

服务器可计划线程池可以定期地或延迟地执行所交给的任务,它用来完成HornetQ中绝大部分这样的任务。 它内部使用的是一个 java.util.concurrent.ScheduledThreadPoolExecutor实例。

最大线程数可以在hornetq-configuration.xml文件中进行配置,参数名是scheduled-thread-pool-max-size。默认值是5。 通常这个线程池不需要很大数量的线程。

41.1.2. 服务器通用线程池

服务器端绝大部分的异步操作都是由这个线程池来完成的。在它的内部使用了一个java.util.concurrent.ThreadPoolExecutor的实例。

这个线程池的最大线程数在hornetq-configuration.xml文件中配置,相应的参数名为thread-pool-max-size

如果将参数设为-1则表示该线程池没有线程限制。也就是说当线程不够用时,线程池就 会创建新的线程。当任务不多时,空闲的线程将会超时并被关闭。

如果这个参数的值是一个大于零的整数n,则该线程池的线程数是有限的。当所有线程都 处于忙的状态并且线程数已经达到n时,任何新的请求都将被阻塞直到有线程空闲为止。在设置线程上限时,我们建议 要非常谨慎。因为如何线程数量过低会造成死锁情况的发生。

thread-pool-max-size的默认值是30

参见J2SE javadoc有关无边界(缓存)和有边界(固定)线程池的解释。

41.1.3. 过期回收线程

HornetQ使用一个单独的线程来扫描队列中过期的消息。由于这个线程需要自己的优先级配置,所以不能使用上述的 任何一个线程池。

关于回收线程的配置请参阅Chapter 22, 过期的消息

41.1.4. 异步IO

HornetQ使用一个线程池来进行异步IO的操作,包括事件的接收和发送。这些线程的名字都是以 HornetQ-AIO-poller-pool为开头。每个打开的日志文件都对应有一个线程为其服务(通常只有 一个)。

还有一个单独的线程用于向libaio发送写请求。这样做是为了避免上下文转换带来的性能下降。该 线程的名字以HornetQ-AIO-writer-pool开头。

41.2. 客户端线程管理

在客户端HornetQ有一个静态的可计划线程池和一个静态的通用线程池,它们在一个JVM中由同一个classloader装载的所有客户端 共同使用。

静态的可计划的线程池的最大线程数为 5,通用线程池则没有线程数限制。

如果需要还可以配置一个ClientSessionFactory实例以使它拥有自己的可计划与通用线程池。通过这个工厂创建的会话都 将使用这些线程池。

要想配置ClientSessionFactory使用自己的线程池,只要调用它相应的方法取出可,如:

ClientSessionFactory myFactory = HornetQClient.createClientSessionFactory(...);
myFactory.setUseGlobalPools(false);
myFactory.setScheduledThreadPoolMaxSize(10);
myFactory.setThreadPoolMaxSize(-1);   

如果使用JMS,你可以先用同样的参数设置ClientSessionFactory,然后再用这样工厂创建ConnectionFactory的实例。如:

ConnectionFactory myConnectionFactory = HornetQJMSClient.createConnectionFactory(myFactory);     

如果你使用JNDI来创建HornetQConnectionFactory 实例,你还可以在hornetq-jms.xml文件中进行配置。如:

<connection-factory name="ConnectionFactory">
    <connectors>
       <connector-ref connector-name="netty"/>
    </connectors>
    <entries>
        <entry name="ConnectionFactory"/>
        <entry name="XAConnectionFactory"/>
    </entries>
    <use-global-pools>false</use-global-pools>
    <scheduled-thread-pool-max-size>10</scheduled-thread-pool-max-size>
    <thread-pool-max-size>-1</thread-pool-max-size>
</connection-factory>

Chapter 42. 日志(Logging)

HornetQ有自己的独立的日志系统,不依赖于任何其它的日志框架。在默认情况下所有HornetQ的日志将输入到 标准的JDK日志系统, (即JUL-Java Util Logging)。服务器在默认条件下读取config目录下的 logging.properties文件做为JUL的配置文件。它配置了使用HornetQ自己的格式化 方法,将日志输出到屏幕终端(Console)及文件中。请访问Sun公司的相关网址来进一步了解如何配置使用JUL。

你可以通过编程或定义系统变量的方法来配置不同的日志代理(Logging Delegate)。

采用编程方法,只需要调用方法:

org.hornetq.core.logging.Logger.setDelegateFactory(new Log4jLogDelegateFactory())

其中Log4jLogDelegateFactory实现了org.hornetq.spi.core.logging.LogDelegateFactory 接口。

如果要使用系统变量方法,则需要设置变量org.hornetq.logger-delegate-factory-class-name为相应的代理工厂,即

-Dorg.hornetq.logger-delegate-factory-class-name=org.hornetq.integration.logging.Log4jLogDelegateFactory

上面的例子可以看出HornetQ提供了一些代理工厂以方便用户使用,它们是:

  1. org.hornetq.core.logging.impl.JULLogDelegateFactory - 默认的JUL日志代理工厂。

  2. org.hornetq.integration.logging.Log4jLogDelegateFactory - Log4J的日志代理工厂。

如果在客户端使用JUL代理,注意要提供logging.properties文件,并且在客户端启动之前设置java.util.logging.config.file属性。

42.1. 与JBoss应用服务器日志的关系

当HornetQ部署到JBoss应用服务器版本5.x或以上时,虽然HornetQ仍然使用JUL,但是所有的日志输出被重定向到 JBoss logger。请参阅相关的JBoss文档来了解更多的信息。如果是以前版本的JBoss,则必需指定你所需要的日志代理。

Chapter 43. 嵌入式HornetQ

HornetQ是由简单传统Java对象(POJO)实现,因此它可以在任何依赖注入的框架中运行,比如JBoss Microcontainer,Sprint或Google Guice。另外如果你的应用程序内部需要消息功能,你可以在程序中 直接实例化HornetQ的客户端或服务器端。我们称之为嵌入式 HornetQ。

有些应用需要高性能、事务性及持久化的消息服务,但是又不希望自己去费时费力实现它。于是嵌入式HornetQ就 成为了一个很适当的选择。

要使用嵌入式HornetQ只需要几个简单的步骤。首先初始化配置对象,再初始化服务器并启动它,在你的虚拟机 中就运行越来了一个HornetQ服务器。就是这么简单。

43.1. POJO的初始化

按照以下步骤去做:

创建配置对象--这个对象包装了HornetQ的配置信息。如果你想使用配置文件,则使用FileConfigurationImpl

import org.hornetq.core.config.Configuration;
import org.hornetq.core.config.impl.FileConfiguration;

...


Configuration config = new FileConfiguration();
config.setConfigurationUrl(urlToYourconfigfile);
config.start();

如果不需要配置文件,可以用ConfigurationImpl,只要将其中的各种配置参数设置好 即可。如添加适当的接收器。

ConfigurationImpl用来配置接收器。和主要配置文件相似,只需要添加 NettyAcceptorFactory即可。

import org.hornetq.core.config.Configuration;
import org.hornetq.core.config.impl.ConfigurationImpl;

...

Configuration config = new ConfigurationImpl();
HashSet<TransportConfiguration> transports = new HashSet<TransportConfiguration>();
      
transports.add(new TransportConfiguration(NettyAcceptorFactory.class.getName()));
transports.add(new TransportConfiguration(InVMAcceptorFactory.class.getName()));

config.setAcceptorConfigurations(transports);

接着就需要初始化并启动HornetQ服务。org.hornetq.api.core.server.HornetQ类有一些静态方法可用来创建HornetQ服务器。

import org.hornetq.api.core.server.HornetQ;
import org.hornetq.core.server.HornetQServer;

...

HornetQServer server = HornetQ.newHornetQServer(config);

server.start();

你还可以直接实例化HornetQServerImpl

HornetQServer server = 
        new HornetQServerImpl(config);
server.start();

43.2. 使用依赖注入框架

你还可以使用一个依赖注入框架来启动HornetQ,比如JBoss Microcontainer™或Spring框架™。

HornetQ独立服务器使用的是JBoss Microcontainer作为其框架。在HornetQ的发布中包括的HornetQBootstrapServerhornetq-beans.xml文件共同实现了 在JBoss Microcontainer中对HornetQ服务器的引导。

要使用JBoss Microcontainer,需要在xml文件中声明HornetQServerConfiguration对象。另外还可以注入一个安全管理器和一个MBean服务器。但是这些 注入是可选的。

下面是一个基本的JBoss Microcontainer的XML Bean的声明:

<?xml version="1.0" encoding="UTF-8"?>

<deployment xmlns="urn:jboss:bean-deployer:2.0">
   
   <!-- The core configuration -->
   <bean name="Configuration" 
         class="org.hornetq.core.config.impl.FileConfiguration">
   </bean>

   	<!-- The core server -->
   <bean name="HornetQServer" 
         class="org.hornetq.core.server.impl.HornetQServerImpl">      
      <constructor>
         <parameter>
            <inject bean="Configuration"/>
         </parameter>            
      </constructor>         
   </bean>
   </deployment>

HornetQBootstrapServer实现了JBoss Microcontainer的简单封装。

HornetQBootstrapServer bootStrap = 
        new HornetQBootstrapServer(new String[] {"hornetq-beans.xml"});
        bootStrap.run();

43.3. 连接嵌入式HornetQ

嵌入式HornetQ的连接和普通的连接一样要创建连接工厂:

43.3.1. 核心接口

使用核心接口,需要创建一个ClientSessionFactory然后正常建立连接。

ClientSessionFactory nettyFactory =  HornetQClient.createClientSessionFactory(
                                        new TransportConfiguration(
                                           InVMConnectorFactory.class.getName()));

ClientSession session = factory.createSession();

session.createQueue("example", "example", true);

ClientProducer producer = session.createProducer("example");

ClientMessage message = session.createMessage(true);

message.getBody().writeString("Hello");

producer.send(message);

session.start();

ClientConsumer consumer = session.createConsumer("example");

ClientMessage msgReceived = consumer.receive();

System.out.println("message = " + msgReceived.getBody().readString());

session.close();

43.3.2. JMS接口

使用JMS接口连接嵌入HornetQ同样简单。只需要直接实例化 ConnectionFactory即可。如下面例子所示:

ConnectionFactory cf =
    HornetQJMSClient.createConnectionFactory(
       new TransportConfiguration(InVMConnectorFactory.class.getName()));

Connection conn = cf.createConnection();

conn.start();

Session sess = conn.createSession(true, Session.SESSION_TRANSACTED);

MessageProducer prod = sess.createProducer(queue);

TextMessage msg = sess.createTextMessage("Hello!");

prod.send(msg);

sess.commit();

MessageConsumer consumer = sess.createConsumer(queue);

TextMessage txtmsg = (TextMessage)consumer.receive();

System.out.println("Msg = " + txtmsg.getText());

sess.commit();

conn.close();

43.4. JMS嵌入式HornetQ的例子

有关如何设置与运行JMS嵌入式HornetQ的例子请参见Section 11.2.1, “嵌入式”

Chapter 44. 拦截操作

HornetQ支持拦截器。拦截器可以拦截进入服务器的数据包。每进入服务器 一个数据包,拦截器就被调用一次,允许一些特殊和处理,例如对包的审计、过滤等。拦截器可以对数据包 进行改动。

44.1. 实现拦截器

拦截器必须要实现Interceptor接口

package org.hornetq.api.core.interceptor;

public interface Interceptor
{   
   boolean intercept(Packet packet, RemotingConnection connection) 
                throws HornetQException;
}
         

它的方法的返回值是很重要的:

  • 如果返回true,处理正常进行下去。

  • 如果返回false,则处理被中断,其它的拦截器将不会被调用,数据包将不会 被服务器所处理。

44.2. 配置拦截器

拦截器的配置在hornetq-configuration.xml文件中:

<remoting-interceptors>
   <class-name>org.hornetq.jms.example.LoginInterceptor</class-name>
   <class-name>org.hornetq.jms.example.AdditionalPropertyInterceptor</class-name>
</remoting-interceptors>
         

拦截器的类(以及它们依赖的类)必须要在服务器的classpath中,否则不能被正常初始化及调用。

44.3. 客户端拦截器

在客户端也可以有拦截器来拦截来自服务器的数据包。ClientSessionFactoryaddInterceptor()方法可以用来添加拦截器。

同样拦截器的类(以及它们依赖的类)必须要在客户端的classpath中,否则它们不能被正常初始化及调用。

44.4. 例子

参见Section 11.1.18, “拦截器(Interceptor)”。这个例子中展示了如何使用拦截器向发往服务器的消息中 添加属性。

Chapter 45. 互操作性

45.1. Stomp

Stomp是一个基于文本的协议。使用Stomp协议的 客户端可以与Stomp的代理(broker)进行通迅。

Stomp客户端支持多种语言和平台,因此 它有着很好的互操作性。

45.1.1. 内建Stomp支持

HornetQ内建支持Stomp功能。要使用Stomp发送与接收消息,必须配置一个NettyAcceptor, 其中的protocol参数值应设为stomp

	<acceptor name="stomp-acceptor">
		<factory-class>org.hornetq.core.remoting.impl.netty.NettyAcceptorFactory</factory-class>		<param key="protocol"  value="stomp"/>
		<param key="port"  value="61613"/>
	</acceptor>

有了上述的配置,HornetQ就可以在端口61613(这是Stomp代理的默认端口)接受Stomp连接了。

stomp例子展示了如何在HornetQ中配置Stomp。

45.1.1.1. 限制

消息的通知不是事务性的。ACK信号不能作为事务的一部分来传输(如果设置了transaction 属性,它将被忽略)。

45.1.2. Stomp目标与HornetQ的地址和队列的映射

Stomp客户端在消息发送和订阅中使用的是目标(destination)。目标名称是简单的字符串,对应的是服务 器端的目的地。不同服务器对这种映射有着不同的实现。

在HornetQ中这些目标被映射为地址队列。 当一个Stomp客户端发送一个消息(使用SEND信号)到一个目标时,这个目标被映射到一个地址。 如果一个Stomp客户端订阅(或解除订阅)一个目标时(使用SUBSCRIBEUNSUBSCRIBE),这个目标被映射到一个HornetQ的队列。

45.1.3. Stomp与JMS的互操作性

45.1.3.1. 使用JMS目标

正如Chapter 9, JMS与内核API之间的映射关系解释的那样,JMS的目标同样映射到HornetQ的地址与队列。如果你使用 Stomp向JMS的目标发送消息,那么Stomp的目标必须要遵照相同的命名规则:

  • 如果向JMS队列发送数据或订阅它,则队列的名称前缀必须是jms.queue.

    例如,如果向名为orders的JMS队列发送消息,Stomp客户端必须发送以下信息:

    SEND
    destination:jms.queue.orders
    
    hello queue orders
    ^@
                    
  • 如果向JMS 话题(topic)发送或订阅消息,话题名称前缀必须是jms.topic.

    例如,如果订阅名为 stocks的JMS话题,Stomp客户端必须发送以下信息:

      SUBSCRIBE
      destination:jms.topic.stocks
      
      ^@
                    

45.1.3.2. 使用JMS或核心接口发送和接收Stomp消息

Stomp基本上是一个基于文本的协议。为了使用更简单,我们的Stomp实现通过检查content-length的值 来决定如何将一个Stomp消息映射成一个JMS消息或核心消息。

如果在Stomp消息中没有content-length头,它将被映射为一个JMS的 TextMessage,或者是一个核心消息,其消息体的缓存是一个SimpleString。

如果Stomp消息中content-length,则它被映射为一个JMS的 BytesMessage,或者是一个核心消息,其消息体缓存中是一个字节数组byte[]。

从一个JMS消息或核心消息映射为Stomp消息时遵从同样的逻辑。一个Stomp客户端可以通过检查 content-length来决定消息体的类型(字符串或字节)。

45.1.4. 通过Web Sockets使用Stomp

HornetQ还支持通过Web Sockets使用Stomp。任何支持 Web Socket的浏览器中可以利用HornetQ来发送和接收Stomp消息。

要使用些功能,必须配置一个NettyAcceptor,并设置protocol 的值为stomp_ws

<acceptor name="stomp-ws-acceptor">
	<factory-class>org.hornetq.core.remoting.impl.netty.NettyAcceptorFactory</factory-class>
	<param key="protocol" value="stomp_ws"/>
	<param key="port" value="61614"/>
</acceptor>
         

使用上面配置,HornetQ在URL路径/stomp下端口61614接收Stomp连接。 浏览器然后就可以连接到ws://<server>:61614/stomp,使用Web Socket来发送和接收 Stomp消息了。

为了简化客户端的开发,在GitHub 上提供了一个JavaScript库(参见文档)。

stomp-websockets例子给出一如何配置HornetQ服务器以使浏览器和Java应用程序通过一个JMS话题 进行消息的传递。

45.1.5. StompConnect

StompConnect是一个Stomp代理服务器, 它可以将Stomp协议转换为标准的JMS接口调用。因此,通过StompConnect的作用HornetQ可以作为一个Stomp代理, 与任何一个Stomp客户端通迅。这些客户端可以由C、C++、C#及.net等语言实现。

要运行StompConnect首先要启动HornetQ服务以及JNDI服务。

Stomp需要jndi.properties文件要在classpath中。该文件 应有如下类似的内容:

java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
java.naming.provider.url=jnp://localhost:1099
java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces

要确保该文件与StompConnect的jar包以及HornetQ的jar文件都在classpath中。最后,运行 java org.codehaus.stomp.jms.Main

45.2. REST

HornetQ即将支持REST!

45.3. AMQP

HornetQ即将支持AMQP!

Chapter 46. 性能调优

本章讲述如何优化HornetQ的性能

46.1. 持久层的优化

  • 将消息日志放到单独的物理卷上。如果与其它数据共享,例如事务管理、数据库或其它日志等,那么就会 增加读写的负担,磁头会在多个不同文件之间频繁地移动,极大地降低性能。我们的日志系统采用的是只 添加的模式,目的就是最大程度減少磁头的移动。如果磁盘被共享,那么这一目的将不能达到。另外如果 你使用分页转存或大消息功能时,你最好分别将它们放到各自的独立卷中。

  • 尽量减少日志文件的数量。journal-min-files参数的设置应以满足平均 运行需要为准。如果你发现系统中经常有新的日志文件被创建,这说明持久的数据量很大,你需要适当增加 这个参数的值,以使HornetQ更多时候是在重用文件,而不是创建新文件。

  • 日志文件的大小。日志文件的大小最好要与磁盘的一个柱面的容量对齐。默认值是10MiB,它在绝大多数 的系统中能够满足需要。

  • 使用AIO日志。在Linux下,尽量使用AIO型的日志。AIO的可扩展性要好于Java的NIO。

  • 优化 journal-buffer-timeout。如果增加它的值,吞吐量会增加,但是 延迟也会增加。

  • 如果使用AIO,适当增加journal-max-io可能会提高性能。如果使用的是NIO, 请不要改变这个参数。

46.2. 优化JMS

如果使用JMS接口,有以下几个方面可以改进性能。

  • 关闭消息id。如果你不需要这个id,用MessageProducersetDisableMessageID()方法可以关闭它。这可以减少消息的大小并且 省去了创建唯一ID的时间。

  • 关闭消息的时间戳。如果不需要时间戳,用MessageProducersetDisableMessageTimeStamp()方法将其关闭。

  • 尽量避免使用ObjectMessageObjectMessage会带 来额外的开销。ObjectMessage使用Java的序列化将它序列化为字节流。在对小的对象 进行序列化会占用大量的空间,使传输的数据量加大。另外,Java的序列化与其它定制的技术相比要慢。只有在不得 以的情况下才使用它。比如当你在运行时不知道对象的具体类型时,可以用ObjectMessage。

  • 避免使用AUTO_ACKNOWLEDGEAUTO_ACKNOWLEDGE 使得每收到一个消息就要向服务器发送一个通知--这样增加的网络传输的负担。如果可能,尽量使用 DUPS_OK_ACKNOWLEDGE或者CLIENT_ACKNOWLEDGE。或者使用事务性会话,将通知在提交时批量完成。

  • 避免持久化消息。默认情况下JMS消息是持久的。如果你不需要持久消息,则将其设定为非持久。 持久消息都会被写到磁盘中,这给系统带来了明显的负担。

  • 将多个发送或通知放到一个事务中完成。这样HornetQ只需要一次网络的往返来发生事务的提交,而不是每次发送 或通知就需要一次网络的往返通迅。

46.3. 其它优化

在HornetQ中还有其它一些地方可以优化:

  • 使用异步发送通知。如果你在非事务条件下发送持久的消息,并且要保证在send()返回时持久消息已经到达服 务器,不要使用阻塞式发送的方式,应该使用异步发送通知的方式。参见Chapter 20, 发送与提交的保证中的说明。

  • 使用预先通知模式。预先通知就是在消息发往客户端之前进行通知。它节省了正常 的消息通知所占用的通迅时间。详细的解释请参见 Chapter 29, 预先通知模式(pre-acknowledge)

  • 关闭安全。将hornetq-configuration.xml文件中的security-enabled 参数设为false以关闭安全。这可以带来一些性能的提高。

  • 关闭持久化。如果不你不需要消息持久化,可以将hornetq-configuration.xml 文件中的persistence-enabled参数设为false来完全关闭持久功能。

  • 采用延迟方式事务同步。将hornetq-configuration.xml文件中的journal-sync-transactional参数设为false可以得到 更好的事务持久化的性能。但是这样做可能会造成在发生故障时事务的丢失。有关详细的说明参见 Chapter 20, 发送与提交的保证

  • 采用延迟方式非事务同步。将hornetq-configuration.xml文件中的journal-sync-non-transactional参数设为false可以得到 更好的非事务持久化的性能。但是这样做可能会造成在发生故障时持久消息的丢失。有关详细的说明参见 Chapter 20, 发送与提交的保证

  • 采用非阻塞方式发送消息。将文件hornetq-jms.xml中的参数 block-on-non-durable-send设为false (使用JMS和JNDI时)或者直接在上进行相应的设置,可以使 消息发送时不阻塞等待服务器的响应。参见 Chapter 20, 发送与提交的保证

  • 如果你的接收者速度很快,你可以增加consumer-window-size。这样实际上就关闭了流控制的功能。

  • 套接字NIO与旧的IO对比。默认情况下HornetQ在服务器端使用套接字NIO技术,而在客户端则使用旧的(阻塞) IO(参见传输配置一章Chapter 16, 传输层的配置)。NIO比旧的阻塞式IO有更 强的可扩展性,但是也会带来一些延时。如果你的服务器要同时有数千个连接,使用NIO效果比较好。但是如果 连接数并没有这么多,你可以配置接收器使用旧的IO还提高性能。

  • 尽量使用核心接口而不用JMS。使用JMS接口会稍微比使用核心接口性能要低些。这是因为所有JMS操作 实际上要转化为核心的操作才能为服务器所处理。在使用核心接口时,尽量使用带有 SimpleString类型参数的方法。SimpleString与 java.lang.String不同,它在写入传输层时不需要拷贝。所以你如果在调用中重用SimpleString对象可以避免不必要的拷贝。

46.4. 传输层的优化

  • TCP缓存大小。如果你的网络速度很快,并且你的主机也很快,你可以通过增加TCP的发送和接收缓存 来提高性能。参见Chapter 16, 传输层的配置中的详细说明。

    Note

    注意某些操作系统,如最近的Linux版本中,包括了TCP自动优化功能。如果再手工设置TCP缓存 会导致自动优化失效,最終使性能下降!

  • 增加服务器中文件句柄数量限制。如果你的服务器将要处理很多并行的连接,或者客户端在快速不停地 打开和关闭连接,你要确保在服务器端有足够的文件句柄以供使用。

    这个限制在不同平台有不同的方法。在Linux系统中,你可以编辑文件/etc/security/limits.conf,增加以下内容:

    serveruser     soft    nofile  20000
    serveruser     hard    nofile  20000                   
                    

    它设置了用户serveruser可以最多打开20000个文件句柄。

  • 利用参数batch-delay并将参数direct-deliver 设为false来提高小消息的处理效率。HornetQ在其hornetq-configuration.xml 中预先配置了一个连接器/接受器对(netty-throughput),并且在 hornetq-jms.xml中配置了一个JMS连接工厂( ThroughputConnectionFactory)。它们可以用在小消息的处理应用中以提 供最佳呑吐量。参见Chapter 16, 传输层的配置

46.5. 优化虚拟机

我们强烈建议你使用最新的Java 6虚拟机。它在很多方面对以前Java 5的虚拟机进行了改进,特别是在网络功能方面。 这是根据我们内部使用Sun的实现测试的結果,可能不适用于其它的Java实现(例如IBM或JRockit)。

  • 拉圾回收。为了使服务器的运行比较平滑,我们建议使用并行拉圾回收的算法。例如在Sun的JDK使用 JVM选项-XX:+UseParallelGC.

  • 内存设置。尽量为服务器分配更多的内存。HornetQ利用其分页转存技术可以在很少的内存下运行(在 Chapter 24, 分页转存中有说明)。但是如果所有队列都在内存运行,性能将会很好。具体需要 多少内存要由你的队列的大小和数量以及消息的大小和数量决定。使用JVM参数-Xms-Xmx来为你的服务器分配内存。我们建议两个参数的设为相同的值。

  • 主动选项(Aggressive options)。不同JVM有不同的JVM优化参数。对于Sun的Hotspot JVM,在这里有一个完整的参数列表。我们建议至少要使用 -XX:+AggressiveOpts -XX:+UseFastAccessorMethods选项。根据不同的平台,可能还有其它一些参数供你使用, 以提高JVM的性能。

46.6. 避免违背设计模式

  • 重用连接/会话/接收者/发送者。最常见的错误恐怕就是每发送/接收一个消息都要创建一个新的连接 /会话/发送者或接收者。这样非常浪费资源。这些对象的创建要占用时间和网络带宽。它们应该进行重用。

    Note

    有些常用的框架如Spring JMS Template在使用JMS时违背了设计模式。如果你在使用了它后性能 受到了影响。这不是HornetQ的原因!Spring的JMS模板只有与能缓存JMS会话的应用服务器一起使用 才是安全的,并且只能是用于发送消息。使用它在应用服务器中同步接收消息是不安全的。

  • 避免使用繁锁的消息格式。如XML,它会使数据量变大进而降低性能。所以应该尽量避免在消息体中使用XML。

  • 不要为每个请求都创建新的临时队列。临时队列通常用于请求-响应模式的消息应用。在这个模式中消息被发往 一个目的,它带有一个reply-to的头属性指向一个本地的临时队列的地址。当消息被收到后,接收方将响应做为消息发 往那个reply-to指定的临时的地址。如果每发一个消息都创建一个临时队列,那么性能将会受很大影响。正确的 作法是在发送消息时重用临时队列。

  • 尽量不要使用MDB。使用MDB,消息的接收过程要比直接接收复杂得多,要执行很多应用服务器内部的代码。 在设计应用时要问一下是否真的需要MDB?可不可以直接使用消息接收者完成同样的任务?

Chapter 47. 配置参数索引

本章给出了HornetQ配置参数的索引,以便查阅。点击任意一个配置参数可以转到其所属的章节。

47.1. 服务器配置

47.1.1. hornetq-configuration.xml

HornetQ主要核心服务器配置文件

Table 47.1. 服务器配置

名称类型说明默认值
backupBooleantrue表示本服务器是集群中另一个服务器的备份服务器false
backup-connector-refString用于连接备份服务器的连接器 
bindings-directoryString保存持久绑定的目录the directory to store the persisted bindings todata/bindings
clusteredBooleantrue表示服务器是集群中的节点false
connection-ttl-overrideLong表示在接收到一个ping之前连接保持有效的时间(单位毫秒)-1
create-bindings-dirBooleantrue表示服务器在启动时创建绑定的目录true
create-journal-dirBooleantrue表示创建日志目录true
file-deployment-enabledBooleantrue表示服务器从配置文件中读取配置true
id-cache-sizeInteger预先生成的消息id的缓存大小2000
journal-buffer-sizeLong日志内部缓存的大小128 KiB
journal-buffer-timeoutLongT将日志缓冲数据刷新的等待时间(纳秒)20000
journal-compact-min-filesInteger开始整理数据的最小数据文件数。10
journal-compact-percentageInteger开始压缩日志时的有效数据百分比。30
journal-directoryString日志文件所在的位置data/journal
journal-file-sizeLong每个日志文件的大小(字节)128 * 1024
journal-max-ioInteger某一时刻保存在AIO队列中的最大写请求数500
journal-min-filesInteger预先创建的日志文件数2
journal-sync-transactionalBoolean如果是true,等事务数据同步到日志后再向客户端返回true
journal-sync-non-transactionalBoolean如果是true,等非事务数据到日志后再向客户端返回true
journal-typeASYNCIO|NIO日志存取的方式ASYNCIO
jmx-management-enabledBooleantrue表示通过JMX可以使用管理接口true
jmx-domainString用于HornetQ MBean注册到MBeanServer的JMX域名org.hornetq
large-messages-directoryString存放大消息的目录data/largemessages
management-addressString管理消息发送的地址jms.queue.hornetq.management
cluster-userString集群连接的用户名HORNETQ.CLUSTER.ADMIN.USER
cluster-passwordString集群连接的用户密码CHANGE ME!!
management-notification-addressString用于接收管理通知的地址hornetq.notifications
message-counter-enabledBooleantrue表示使用消息计数器false
message-counter-max-day-historyInteger消息计数器历史保存的天数10
message-counter-sample-periodLong消息计数器取样的间隔(毫秒)10000
message-expiry-scan-periodLong过期消息扫描周期(毫秒)30000
message-expiry-thread-priorityInteger过期消息线程的优先级3
paging-directoryString分页转存消息的保存目录data/paging
persist-delivery-count-before-deliveryBooleantrue表示传递计数在传递之前保存。false表示只有当消息被取消时才保存。false
persistence-enabledBooleantrue表示服务器使用基于文件的日志做持久化true
persist-id-cacheBooleantrue表示id被保存到日志中true
scheduled-thread-pool-max-sizeInteger可计划线程池的线程数。5
security-enabledBooleantrue表示使用安全功能true
security-invalidation-intervalLong安全缓存的有效时间(毫秒)10000
thread-pool-max-sizeInteger主线程池的线程数。-1表示数量不限-1
async-connection-execution-enabledBoolean接收的数据包是否由线程池的线程来处理true
transaction-timeoutLong事务在建立后经过多长时间后可以从资源管理器(resource manager)删除(毫秒)60000
transaction-timeout-scan-periodLong扫描超时事务的间隔(毫秒)1000
wild-card-routing-enabledBooleantrue表示服务器支持通配符路由true
memory-measure-intervalLong采样JVM内存的周期(毫秒,-1表示不采样)-1
memory-warning-thresholdInteger可用内存达到这个百分比时,给出警告信息25
connectorsConnector连接器的配置 
connector.name (attribute)String连接器的名字-必需指定的参数 
connector.factory-classString连接工厂的实现类名-必需指定的参数 
connector.param连接器的配置参数一个键-值对,表示一个连接器的参数配置。一个连接器可以有多个这样的参数 
connector.param.key (属性)String参数的键 - 必需指定的参数 
connector.param.value (attribute)String参数的值 - 必需指定的参数 
acceptorsAcceptor一组接收器 
acceptor.name (属性)String接收器的名字 - 可选 
acceptor.factory-classString接收器工厂实现类名 - 必需指定的参数 
acceptor.param一个接收器的配置参数用来配置接收器的键-值对。一个接收器可以有多个参数。 
acceptor.param.key (属性)String参数的键 - 必需指定的参数 
acceptor.param.value (attribute)String参数的值 - 必需指定的参数 
broadcast-groupsBroadcastGroup一组广播组 
broadcast-group.name (attribute)String广播组的名字(必需是唯一的)- 必需指定的参数 
broadcast-group.local-bind-addressString数据报文套接字的本地绑定地址内系统内核选择的IP地址
broadcast-group.local-bind-portInteger数据报文套接字的本地绑定端口-1 (匿名端口)
broadcast-group.group-addressString广播地址 - 必需指定的参数 
broadcast-group.group-portInteger广播使用的UDP端口 - 必需指定的参数 
broadcast-group.broadcast-periodLong广播的时间间隔(毫秒)2000
broadcast-group.connector-ref一个连接器对广播的一个连接器和一个可选的备份连接器。一个广播组可以有多个这样的连接器对 
broadcast-group.connector-ref.connector-name (attribute)String主连接器名 - 必需指定的参数 
broadcast-group.connector-ref.backup-connector-name (attribute)String备份连接器名 - 可选的参数 
discovery-groupsDiscoveryGroup一组发现组 
discovery-group.name (属性)String发现组的名字(具有唯一性) - 必需指定的参数 
discovery-group.local-bind-addressString发现组所绑定的本地地址 
discovery-group.group-addressString发现组监听的广播IP地址 - 必需指定的参数 
discovery-group.group-portInteger广播组监听的UDP端口 - 必需指定的参数 
discovery-group.refresh-timeoutInteger发现组等待更新的时间。如果某个服务器上次广播到达后经过这个时间后还没有收到下次广播,那么 该服务器的连接器对将从列表中删除5000 (毫秒)
divertsDivert一组转发器 
divert.name (attribute)String转发器的名字(必需是唯一的) - 必需指定的参数 
divert.routing-nameString转发器的路由名称 - 必需指定的参数 
divert.addressString转发器的源地址 - 必需指定的参数 
divert.forwarding-addressString转发器的转发地址 - 必需指定的参数 
divert.exclusiveBoolean转发器是否是唯一的false
divert.filterString可选的核心过滤器表达式null
divert.transformer-class-nameString可选的转换器的名字 
queuesQueue一组预先配置的队列 
queues.name (属性)String队列独特的名字 
queues.addressString队列的地址 - 必需指定的参数 
queues.filterString队列可选的核心过滤器表达式null
queues.durableBoolean是否持久队列true
bridgesBridge一组桥 
bridges.name (属性)String桥的独特名字 
bridges.queue-nameString桥接收消息的源队列名 - 必需指定的参数 
bridges.forwarding-addressString桥要转发的地址。如果没有指定则使用原始的地址null
bridges.filterString可选的核心过滤器表达式null
bridges.transformer-class-nameString可选的转换器类名null
bridges.retry-intervalLong重试的时间间隔(毫秒)2000 ms
bridges.retry-interval-multiplierDouble重试间隔的递增系数1.0
bridges.reconnect-attemptsInteger最大重试次数,-1代表无限次-1
bridges.failover-on-server-shutdownBoolean如果目标服务器正常关机是否仍进行失效备援false
bridges.use-duplicate-detectionBoolean是否在转发的消息中添加重复检测的头true
bridges.discovery-group-refString桥使用的发现组名null
bridges.connector-ref.connector-name (属性)String主连接的连接器名 
bridges.connector-ref.backup-connector-name (属性)String备份连接的连接器名(可选)null
cluster-connectionsClusterConnection一组集群连接 
cluster-connections.name (attribute)String集群连接的独特名字 
cluster-connections.addressString集群连接的地址名 
cluster-connections.forward-when-no-consumersBoolean如果目标没有合适的接收者,是否仍然向它分配消息?false
cluster-connections.max-hopsInteger集群拓扑中的最大跳数(hops)1
cluster-connections.retry-intervalLong集群连接重试的间隔(毫秒)2000
cluster-connections.use-duplicate-detectionBoolean是否在转发的消息中添加重复检测的消息头true
cluster-connections.discovery-group-refString桥所使用的发现组的名字null
cluster-connections.connector-ref.connector-name (属性)String主连接的连接器名字 
cluster-connections.connector-ref.backup-connector-name (属性)String可选的备份连接所使用的连接器名字null
security-settingsSecuritySetting一组安全设置 
security-settings.match (属性)String安全所应用的地址匹配字符串 
security-settings.permission安全许可地址的一个许可 
security-settings.permission.type (attribute)许可类型许可的类型 
security-settings.permission.roles (attribute)Roles以逗号分隔的一组角色,这些角色将拥有相应的许可 
address-settingsAddressSetting一组地址设置 
address-settings.dead-letter-addressString死消息所发往的地址 
address-settings.max-delivery-attemptsInteger发往死信地址之前所尝试的传递次数10
address-settings.expiry-addressString过期消息所发往的地址 
address-settings.redelivery-delayLong重新传递一个取消的消息前所等待的时间(毫秒)0
address-settings.last-value-queueboolean队列是否是一个最新值队列false
address-settings.page-size-bytesLong一个地址的分页的大小(字节)10 * 1024 * 1024
address-settings.max-size-bytesLong地址的分页转存的最大值(字节)-1
address-settings.redistribution-delayLong队列最后一个接收者关闭后需要等待多长时间再将消息重新分配(毫秒)-1

47.1.2. hornetq-jms.xml

该文件是服务器端JMS服务所使用的,用来装载JMS队列,话题和连接工厂。

Table 47.2. JMS服务器配置

名称类型说明默认值
connection-factoryConnectionFactory一组注册到JNDI的连接工厂 
connection-factory.auto-groupBoolean是否自动使用消息组false
connection-factory.connectorsString一组供连接工厂使用的连接器 
connection-factory.connectors.connector-ref.connector-name (属性)String连接主服务器的连接器名 
connection-factory.connectors.connector-ref.backup-connector-name (attribute)String连接备份服务器的连接器名 
connection-factory.discovery-group-ref.discovery-group-name (属性)String连接工厂的发现组名 
connection-factory.discovery-initial-wait-timeoutLong发现组首次等待广播的时间10000
connection-factory.block-on-acknowledgeBoolean消息是否以同步方式通知false
connection-factory.block-on-non-durable-sendBoolean是否以同步方式发送非持久消息false
connection-factory.block-on-durable-sendBoolean是否以同步方式发送持久消息true
connection-factory.call-timeoutLong远程调用超时时间(毫秒)30000
connection-factory.client-failure-check-periodLong客户端如果在这个时间内没有收到服务器数据包,将认为连接出现故障。5000
connection-factory.client-idString连接工厂预先配置的客户IDnull
connection-factory.connection-load-balancing-policy-class-nameString负载均衡类名org.hornetq.api.core.client.loadbalance.RoundRobinConnectionLoadBalancingPolicy
connection-factory.connection-ttlLong连接的存活时间(毫秒)1 * 60000
connection-factory.consumer-max-rateInteger接收者每秒钟接收消息的最快速度-1
connection-factory.consumer-window-sizeInteger接收者流控制容器大小(字节)1024 * 1024
connection-factory.dups-ok-batch-sizeInteger在DUPS_OK_ACKNOWLEDGE模式下批量通知的大小(字节)1024 * 1024
connection-factory.failover-on-initial-connectionBoolean如果首次连接主服务器失败是否失效备援连接到备份服务器false
connection-factory.failover-on-server-shutdownBoolean在服务器停机时是否进行失效备援false
connection-factory.min-large-message-sizeInteger大消息的最小值,大小超过该值的按大消息处理100 * 1024
connection-factory.cache-large-message-clientBooleantrue表示这个连接工厂会将大消息保存在临时文件中false
connection-factory.pre-acknowledgeBoolean是否在消息发送之前提前通知false
connection-factory.producer-max-rateInteger发送消息的最大速度-1
connection-factory.producer-window-sizeInteger发送者发送消息的窗口大小1024 * 1024
connection-factory.confirmation-window-sizeInteger会话恢复的确认窗口大小(字节)1024 * 1024
connection-factory.reconnect-attemptsInteger重试的最大次数, -1 表示无限次0
connection-factory.retry-intervalLong每次重试的时间间隔(毫秒)2000
connection-factory.retry-interval-multiplierDouble重试间隔时间的递增系数1.0
connection-factory.max-retry-intervalInteger最大重试间隔。2000
connection-factory.scheduled-thread-pool-max-sizeInteger可计划线程池的大小5
connection-factory.thread-pool-max-sizeInteger线程池大小-1
connection-factory.transaction-batch-sizeInteger使用事务性会话时发送通知的批量大小(字节)1024 * 1024
connection-factory.use-global-poolsBoolean是否使用全局线程池true
queueQueue创建并注册到JNDI的队列 
queue.name (属性)String队列的唯一名字 
queue.entryString队列的JNDI的上下文(context)。一个队列可以有多个JNDI的上下文 
queue.durableBoolean是否持久队列true
queue.filterString可选的队列过滤器表达式 
topicTopic创建并注册到JNDI的话题 
topic.name (属性)String话题的唯一名 
topic.entryString话题的JNDI上下文(context)。一个话题可以有多个上下文。