背景
当前,CORBA、DCOM、RMI等RPC中间件技术已广泛应用于各个领域。但是面对规模和复杂度都越来越高的分布式系统,这些技术也显示出其局限性:
- 同步通信:客户发出调用后,必须等待服务对象完成处理并返回结果后才能继续执行;客户和服务对象的生命周期紧密耦合:客户进程和服务对象进程都必须正常运行;
- 异步通信:如果由于网络故障导致请求不可达,则信息丢失;
- 点对点通信:客户的一次调用只发送给某个单独的目标对象。
面向消息的中间件(Message Oriented Middleware,MOM)较好的解决了以上问题。发送者将消息发送给消息服务器,消息服务器将消息存放在若干队列中,在合适的时候再将消息转发给接收者。
- 这种模式下,发送和接收是异步的,发送者无需等待;
- 二者的生命周期未必相同:发送消息的时候接收者不一定运行,接收消息的时候发送者也不一定运行;
- 一对多通信:对于一个消息可以有多个接收者。
JMS规范
AVA 消息服务(JMS)定义了Java 中访问消息中间件的接口。JMS 只是接口,并没有给予实现,实现JMS 接口的消息中间件称为JMS Provider,例如ActiveMQ。
- JMS Provider:实现JMS 接口的消息中间件;
- PTP:Point to Point,即点对点的消息模型;
- Pub/Sub:Publish/Subscribe,即发布/订阅的消息模型;
- Queue:队列目标;
- Topic:主题目标;
- ConnectionFactory:连接工厂,JMS 用它创建连接;
- Connection:JMS 客户端到JMS Provider 的连接;
- Destination:消息的目的地;
- Session:会话,一个发送或接收消息的线程;
- MessageProducer:由Session 对象创建的用来发送消息的对象;
- MessageConsumer:由Session 对象创建的用来接收消息的对象;
- Acknowledge:签收;
- Transaction:事务。
JMS编程模型
在 JMS 编程模型中,JMS 客户端(组件或应用程序)通过 JMS 消息服务交换消息。消息生产者将消息发送至消息服务,消息消费者则从消息服务接收这些消息。这些消息传送操作是使用一组实现 JMS 应用编程接口 (API) 的对象(由 JMS Provide提供)来执行的。
- 在 JMS 编程模型中,JMS 客户端使用 ConnectionFactory 对象创建一个连接,向消息服务发送消息以及从消息服务接收消息均是通过此连接来进行。Connection 是客户端与消息服务的活动连接。创建连接时,将分配通信资源以及验证客户端。
- 连接用于创建会话。Session 是一个用于生成和使用消息的单线程上下文。它用于创建发送的生产者和接收消息的消费者,并为所发送的消息定义发送顺序。会话通过大量确认选项或通过事务来支持可靠传送。
- 客户端使用 MessageProducer 向指定的物理目标(在 API 中表示为目标身份对象)发送消息。生产者可指定一个默认传送模式(持久性消息与非持久性消息)、优先级和有效期值,以控制生产者向物理目标发送的所有消息。
- 同样,客户端使用 MessageConsumer 对象从指定的物理目标(在 API 中表示为目标对象)接收消息。消费者可使用消息选择器,借助它,消息服务可以只向消费者发送与选择标准匹配的那些消息。
- 消费者可以支持同步或异步消息接收。异步使用可通过向消费者注册 MessageListener 来实现。当会话线程调用 MessageListener 对象的 onMessage 方法时,客户端将使用消息。
JMS消息结构
JMS 消息由以下几部分组成:消息头,属性和消息体。
消息是由jmsprvoider和producer设置,消息发送出去后,变成了read-only属性
消息头(Header)
消息头包含消息的识别信息和路由信息,消息头包含一些标准的属性如:JMSDestination,JMSMessageID等。
消息头 | 由谁设置 | 描述 |
JMSDestination | Jms provider | 消息发送的目的地 |
JMSDeliveryMode | Jms provider | 传送模式: PERSISTENT 和NON_PERSISTENT |
JMSExpiration | Jms provider | 消息过期时间,等于Destination 的send 方法中的timeToLive 值加上发送时刻的GMT 时间值。如果timeToLive值等于零,则JMSExpiration 被设为零,表示该消息永不过期。如果发送后,在消息过期时间之后消息还没有被发送到目的地,则该消息被清除。 |
JMSPriority | Jms provider | 消息优先级 |
JMSMessageID | Jms provider | 唯一识别每个消息的标识,由JMS Provider 产生 |
JMSTimestamp | Jms provider | 一个消息被提交给JMS Provider 到消息被发出的时间 |
JMSRedelivered | Jms provider | 消息发送失败后重发标识 |
JMSCorrelationID | 客户 | 用来连接到另外一个消息,典型的应用是在回复消息中连接到原消息 |
JMSReplyTo | 客户 | 提供本消息回复消息的目的地址 |
JMSType | 客户 | 消息类型的识别符 |
红色部分可以由用户赋值,以如下方式赋值:
msg.setJMSMessageID(id);
其他消息头是自动分配的,即便使用setJMS... 也会被忽略
属性(field)
属性由消息发送者产生,用来添加删除消息头以外的附加信息。名称没有限制,用户可随便定义。
属性分三类 :
应用程序定义的属性 : (即自定义,程序可以通过它传递信息,比如用到消息过滤中)
msg.setStringProperty("property_name", value)
msg.setDoubleProperty(" property name ",value);
JMS定义的属性: JMSUserID JMSXAppIDJMSXGroupID 等
这类属性只能用 msg.setStringProperty(" JMSUserID ",value) ,没有相应的setJMSUserID()
提供者特定的属性,不同厂商有其特定的属性
属性一般第一条最常用 ,对一般用户来说
消息体(Body)
由消息发送者产生, JMS 中定义了 5 种消息体:
ByteMessage 、MapMessage 、 ObjectMessage 、 StreamMessage 和 TextMessage :
消息类型 | 消息体 |
TextMessage | java.lang.String对象,如xml文件内容 |
MapMessage | 名/值对的集合,名是String对象,值类型可以是Java任何基本类型 |
BytesMessage | 字节流 |
StreamMessage | 输入输出流 |
ObjectMessage | 可序列化对象 |
Message | 没有消息体,只有消息头和属性 |
JMS消息传送模型
JMS 支持两种截然不同的消息传送模型:PTP(即点对点模型)和Pub/Sub(即发布/订阅模型),分别称作:PTP Domain 和Pub/Sub Domain。
PTP(使用Queue即队列目标)
消息从一个生产者传送至一个消费者。在此传送模型中,目标是一个队列。消息首先被传送至队列目标,然后根据队列传送策略,从该队列将消息传送至向此队列进行注册的某一个消费者,一次只传送一条消息。可以向队列目标发送消息的生产者的数量没有限制,但每条消息只能发送至、并由一个消费者成功使用。
点对点通讯模型
点对点模型的特点:
- 每条消息有一个消费者: 每条只有一个消费者,如果一条消息被消息者接收,那么其他的消费者就不能得到这条消息了。
- 发送和接受消息与时间没有关系: 生产者在发送消息后,消费者可以在任意的时刻接收,但有两个前提:消息未过期; 消息没有被其他的用户接收
- 消费者必须确认对消息的接收:收到消息后消费者必须确认消息已被接收,否则JMS服务提供者会认为该消息没有被接收,那么这条消息仍然可以被其他人接收。
- 非持久的消息最多只发送一次: 表示消息有可能未被发送,造成未被发送的原因可能有: JMS服务提供者出现宕机等情况,造成非持久信息的丢失; 队列中的消息过期,未被接收
- 持久的消息严格发送一次: 我们可以将比较重要的消息设置为持久化的消息,持久化后的消息不会因为JMS服务提供者的故障或者其他原因造成消息丢失。
Pub/Sub(使用Topic即主题目标)
消息从一个生产者传送至任意数量的消费者。在此传送模型中,目标是一个主题。消息首先被传送至主题目标,然后传送至所有已订阅此主题的活动消费者。
可以向主题目标发送消息的生产者的数量没有限制,并且每个消息可以发送至任意数量的订阅消费者。
发布/订阅模型
发布/订阅模型的特点:
- 每个消息都可以有多个订阅者: 每条消息可以有多个消费者,如果报纸和杂志一样,谁订阅了谁都可以获得。
- 订阅者持久化与非持久化: 持久订阅表示消费者已向主题目标进行注册,但在消息传送时此消费者可以处于非活动状态。当此消费者再次处于活动状态时,它将接收此信息。如果没有已经向主题目标注册的消费者,主题不保留其接收到的消息,除非有非活动消费者注册了持久订阅。
消息过滤
JMS提供了一种机制---消息过滤,消息服务可根据消息选择器中的标准来执行消息过滤。消息选择器作用于某个消费者,对于同一个queue或topic,不同的消费者可以选择不同的消息选择器。
生产者可在消息中放入应用程序特有的属性,而消费者可使用基于这些属性的选择标准来表明对消息是否感兴趣。这就简化了客户端的工作,并避免了向不需要这些消息的消费者传送消息的开销。然而,它也使得处理选择标准的消息服务增加了一些额外开销。
消息选择器是用于MessageConsumer的过滤器,可以用来过滤传入消息的属性和消息头部分(但不过滤消息体),并确定是否将实际消费该消息(见下面例子)。
消息选择器是一些字符串,它们基于某种语法,而这种语法是SQL-92的子集。可以将消息选择器作为MessageConsumer创建的一部分。
如age >10 AND name='abc'语句则仅符全这个条件的消息才会被接收,这里的age name 必须是jms 消息的消息头或自定义属性即 msg.setStringProperty("name","abc" ) 与msg.setIntProperty("age",11 )
除了自定义的属性(如name age)可以用作过滤外,消息头中JMSDeliveryMode、JMSPriority、JMSMessageId、JMSTimestamp、JMSCorrelationId、JMSType 也可以用作过滤,但jmsdestinationjmsreplyto jmsredelivered 不可以,因为前两个对应的值是Destination对象,而者 jmsredelivered 的值不确定,因为不确定这个消息是不是重发的。
消息传输可靠性机制
发送消息和接受消息最可靠的方法就是在事务中发送持久性的消息。但是低可靠性可以降低开销和提高性能,例如发送消息时可以更改消息的优先级或者指定消息的过期时间。消息传送的可靠性越高,需要的开销和带宽就越多。性能和可靠性之间的折衷是设计时要重点考虑的一个方面。
控制消息的签收(Acknowledgment)
客户端成功接收一条消息的标志是这条消息被签收。成功接收一条消息一般包括如下三个阶段:1.客户端接收消息;2.客户端处理消息;3.消息被签收。签收可以由ActiveMQ发起,也可以由客户端发起,取决于Session签收模式的设置。
Jms消息发送
在带事务的Session中,签收自动发生在事务提交时。如果事务回滚,所有已经接收的消息将会被再次传送。
在不带事务的Session中,一条消息何时和如何被签收取决于Session的设置。
1.Session.AUTO_ACKNOWLEDGE
当客户端从receive或onMessage成功返回时,Session自动签收客户端的这条消息的收条。
2.Session.CLIENT_ACKNOWLEDGE
客户端通过调用消息的acknowledge方法签收消息。会话对象依赖于应用程序对被接收的消息调用一个acknowledge()方法。一旦这个方法被调用,会话会确认最后一次确认之后所有接收到的消息。这种模式允许应用程序以一个调用来接收,处理并确认一批消息
3.Session.DUPS_OK_ACKNOWLEDGE
此选项指示Session不必确保对传送消息的签收。它可能引起消息的重复,但是降低了Session的开销,所以只有客户端能容忍重复的消息,才可使用(如果ActiveMQ再次传送同一消息,那么消息头中的JMSRedelivered将被设置为true)。
Ps:只有当session属性设置为Session.CLIENT_ACKNOWLEDGE时,acknowlege()方法才起作用,不然会被忽略掉
指定消息传送模式
ActiveMQ支持两种消息传送模式:PERSISTENT和NON_PERSISTENT两种。
1.PERSISTENT(持久性消息)
这是ActiveMQ的默认传送模式,此模式保证这些消息只被传送一次和成功使用一次。对于这些消息,可靠性是优先考虑的因素。可靠性的另一个重要方面是确保持久性消息传送至目标后,消息服务在向消费者传送它们之前不会丢失这些消息。这意味着在持久性消息传送至目标时,消息服务将其放入持久性数据存储。如果消息服务由于某种原因导致失败,它可以恢复此消息并将此消息传送至相应的消费者。虽然这样增加了消息传送的开销,但却增加了可靠性。
2.NON_PERSISTENT(非持久性消息)
保证这些消息最多被传送一次。对于这些消息,可靠性并非主要的考虑因素。此模式并不要求持久性的数据存储,也不保证消息服务由于某种原因导致失败后消息不会丢失。
允许消息过期
默认情况下,消息永不会过期。如果消息在特定周期内失去意义,那么可以设置过期时间(setTimeToLive方法
有两种方法设置消息的过期时间,时间单位为毫秒:
1.使用setTimeToLive方法为所有的消息设置过期时间;
2.使用send方法为每一条消息设置过期时间;
消息过期时间是指send 方法中的timeToLive 值加上发送时刻的GMT 时间值。如果timeToLive值等于零,则JMSExpiration 被设为零,表示该消息永不过期。如果发送后,在消息过期时间之后消息还没有被发送到目的地,则该消息被清除
创建持久订阅
消息订阅分为非持久订阅(non-durable subscription)和持久订阅(durablesubscription):
- 非持久订阅只有当客户端处于激活状态,也就是和ActiveMQ保持连接状态才能收到发送到某个主题的消息,而当客户端处于离线状态,这个时间段发到主题的消息将会丢失,永远不会收到。
- 持久订阅,客户端向ActiveMQ注册一个识别自己身份的ID,当这个客户端处于离线时,ActiveMQ会为这个ID 保存所有发送到主题的消息,当客户端再次连接到ActiveMQ时,会根据自己的ID 得到所有当自己处于离线时发送到主题的消息。持久订阅会增加开销,同一时间在持久订阅中只有一个激活的用户。
Ps: 消息中间件的 Topic 机制,一般情况下没有保存消息。一没连接,再次连接时不会收到失去连接期间的消息。
非持久化订阅持续到它们订阅对象的生命周期。这意味着,客户端只能在订阅者活动时看到相关主题发布的消息。如果订阅者不活动,它会错过相关主题的消息
持久化的订阅者注册一个带有JMS保持的唯一标识的持久化订阅(subscription)。带有相同标识的后续订阅者会再续前一个订阅者的订阅状态。如果持久化订阅没有活动的订阅者,JMS会保持订阅消息,直到消息被订阅接收或者过期。(代码见附件)
使用事务
在事务中生成或使用消息时,ActiveMQ跟踪各个发送和接收过程,并在客户端发出提交事务的调用时完成这些操作。如果事务中特定的发送或接收操作失败,则出现异常。客户端代码通过忽略异常、重试操作或回滚整个事务来处理异常。在事务提交时,将完成所有成功的操作。在事务进行回滚时,将取消所有成功的操作。
本地事务的范围始终为一个会话。也就是说,可以将单个会话的上下文中执行的一个或多个生产者或消费者操作组成一个本地事务。
不但单个会话可以访问 Queue 或 Topic (任一类型的 Destination ),而且单个会话实例可以用来操纵一个或多个队列以及一个或多个主题,一切都在单个事务中进行。这意味着单个会话可以(例如)创建队列和主题中的生产者,然后使用单个事务来同时发送队列和主题中的消息。因为单个事务跨越两个目标,所以,要么队列和主题的消息都得到发送,要么都未得到发送。类似地,单个事务可以用来接收队列中的消息并将消息发送到主题上,反过来也可以。
由于事务的范围只能为单个的会话,因此不存在既包括消息生成又包括消息使用的端对端事务。(换句话说,至目标的消息传送和随后进行的至客户端的消息传送不能放在同一个事务中。)
引用
activemq官方集群部署(http://activemq.apache.org/clustering.html)
深入浅出JMS—JMS简介(http://blog.csdn.NET/aking21a-linjuju/article/details/6051421)
jms消息过滤及消息的组(http://blog.csdn.Net/jixiuffff/article/details/5780619)
消息中间件原理及JMS简介之二(http://www.blogjava.net/orangelizq/archive/2008/01/27/178030.html)
JMS ActiveMQ研究文档(http://www.uml.org.cn/j2ee/201304245.asp)
关于 Jms Topic 持久订阅(http://blog.chenlb.com/2010/01/jms-topic-durable-subscriber.html)
原文地址:http://blog.csdn.net/yangbl928/article/details/24930169