JMS简介
JMS基本概念
JMS,全称Java Mesage Service,即Java消息服务应用程序接口,是 JavaEE 中规范标准之一,是一个Java平台中关于面向消息中间件的API,用于在两个应用程序之间、或者分布式系统中发送消息,进行异步通信,它类似于JDBC。本文主要介绍下 JMS 1.1 规范的基本内容并简要说明下 JMS 2.0。
JMS可以自己使用Java代码或者别的代码来编写,开源的实现有Active MQ、阿里的Rocket MQ(已贡献给Apache)、Kafka等。
JMS API定义了一组规范,允许应用程序组件通过消息传递进行通信,从而实现松耦合的消费者和生产者。JMS支持两种消息发送和接收模型:点对点(P2P)模型和发布/订阅模型。
在点对点模型中,消息生产者发送消息到队列,消息消费者从队列中接收消息,每个消息只有一个消费者。而在发布/订阅模型中,消息生产者向主题发布消息,而消息消费者订阅主题以接收消息,允许一个消息被多个消费者消费。

JMS Provider(提供者) 实现 JMS 接口规范的消息中间件,也就是 MQ 服务器
JMS Producer(生产者) 创建和发送 JMS 消息的客户端应用
JMS Consumer(消费者) 接收和处理 JMS 消息的客户端应用
JMS Message(消息) 消息由消息头、消息属性和消息体组成
JMS Queue(消息队列) 消息保存的地方,用于点对点的消息模型
JMS Topic(消息主题) 消息保存的地方,用于发布订阅的消息模型
我们为什么要使用消息队列呢?
有些网站会在某些时间段的访间量暴增,比如各电商购物平台的秒杀活动、12306抢票等场景,而在访问量暴增的时候,对服务器就是一个非常大的考验。服务器能够处理的访问量是有限的,例如某公司的服务器能够同时处理50W连接消求,但是在活动的期间,访问最到达了70W,那么此时会有20W的访问被丢弃,不这么干能服务器就会崩溃。那么,如何去处理这样的场景呢?此时就必须要借助消息队列了!
面对比较大的流量冲击,在网站系统中一般都会有一个消息存储/缓存系统,网站就可以按照自己的负载能力来消费这些消息,这就是一个消息队列,或者叫消息中间件。

JMS 消息模型
点对点消息模型(Point-to-Point Messaging Domain)

该消息模型的特点:
a、每个消息只有一个消费者,消息一旦被消费,就不在消息队列中了。
b、提供者和消费者之间在时间上没有依赖性,也就是说当提供者发送了消息之后,不管消费者有没有正在运行,它不会影响到消息被发送到队列。
c、每条消息仅会传送给一个消费者。可能会有多个消费者在一个队列中侦听,但是每个队列中的消息只能被队列中的一个消费者所消费。
d、消息存在先后顺序。一个队列会按照消息服务器将消息放入队列中的顺序,把它们传送给消费者。当已被消费时,就会从队列头部将它们删除(除非使用了消息优先级)。
e、消费者在成功接收消息之后需向队列应答成功。
发布/订阅消息模型(Publish/Subscribe Messaging Domain)

该消息模型的特点:
a、每个消息可以有多个消费者
b、发布者和订阅者之间有时间上的依赖性。针对某个主题的订阅者,它必须创建一个订阅者之后,才能消费发布者的消息,而且只能消费订阅时间之后的消息;JMS允许订阅者创建一个可持久化的订阅。这样,即使订阅者宕机恢复后,也能接收宕机时生产者发布的消息。
d、每条消息都会传送给称为订阅者的多个消息消费者。
f、消息是被推送给消费者的。
JMS API 接口

ConnectionFactory 客户端用来创建连接的受管对象;可以通过 JNDI 来查找 ConnectionFactory 对象。
Connection 客户端到 JMS 提供者之间的活动连接。
Session 发送和接收消息的一个单线程上下文
Destination 由 Session 创建的 Queue 或 Topic 对象。
MessageProducer 由 Session创建的对象,用于发送消息到 Queue 或 Topic。
MessageCosumer 由 Session 创建的对象,用于接收 Queue 或 Topic 中的消息。
Message 消费者和生产者之间传送的数据。
MessageListener 消息监听器,消费者注册消息监听器,有消息到达,将调用该接口的 onMessage 方法。
JMS Message
JMS 消息由 消息头、消息属性、消息体 三部分组成。
消息头
JMSDestination 消息发送的目的地,主要有 Queue 和 Topic,它们都是 Destination 的实现。
JMSDeliveryMode:消息传输模式,有两种模式:持久化模式和非持久化模式;持久化的消息在消息服务器宕机后再重启不会丢失,而非持久化的消息则会丢失,可通过将消息设置为持久化来保证消息的可靠性,Queue 中的消息默认是持久化的,Topic 中的消息默认是非持久化的。
JMSExpiration:消息的过期时间,默认永不过期。若给 MessageProducer 对象设置了 timeToLive 属性值或者在调用 MessageProducer.send() 时指定了 timeToLive 的值,则消息将在 timeToLive 之后过期;如果设置 timeToLive 的值为 0,则永不过期,也可以给消息设置 JMSExpiration 属性值指定该消息的过期时间。消息发送后,在消息过期后若还没有被消费则会被清除。
JMSPriority 消息的优先级,有 0-9 十个级别,0-4 是普通消息,5-9 是加急消息。JMS 不要求 MQ 严格按照这十个优先等级发送消息,但必须保证加急消息先于普通消息到达目的地,默认的消息优先级是 4 级。
JMSMessageID 每条消息的唯一标识,默认由 MQ 产生,也可以自定义。
JMSTimestamp 消息发送时的时间。
JMSCorrelationID 关联的消息 ID,通常用在需要回传消息的时候。
JMSReplyTo 消息回复的目的地,其值为一个 Topic 或 Queue, 这个由发送者设置,但是接收者可以决定是否响应。
JMSRedelivered 消息是否重复发送过,如果该消息之前发送过,那么这个属性的值需要被设置为 true;客户端可以根据这个属性的值来确认这个消息是否重复发送过,以避免重复处理。
JMSType 消息类型,包括 TextMessage、BytesMessage、MapMessage、StreamMessage 和 ObjectMessage。
消息体
消息传输的内容
消息属性
消息属性可看作消息头的补充,消息属性按类型可以分为标准属性(JMSX 作为前缀),消息组件自定义的属性(JMS_ 作为前缀),以及应用自定义的属性。自定义的属性不要以前面两种为前缀。标准的JMSX属性如下:

消息的可靠性
消息的可靠性通过三个方面保证:消息的持久化、事务、消息的签收。
消息的持久化
消息的持久化是通过设置DeliveryMode实现的,DeliveryMode 有两种模式:
DeliveryMode.PERSISTENT:持久化,服务器宕机重启后消息依然存在
DeliveryMode.NON_PERSISTENT:非持久化,服务器宕机再重启消息将不存在
Queue 中的消息默认是持久化的,Topic中的消息默认是非持久化的。
对于 Topic,消费者采用 MessageConsumer 和采用 TopicSubscriber 消费消息是不同的,JMS 不会将 MessageConsumer 对象持久化(也就无法记录时间节点),但是会将 TopicSubscriber 对象持久化,这样就可以记录每个订阅者的订阅时间点,即使消费者掉线,也能在恢复后消费掉线时产生的消息;采用 TopicSubscriber 方式消费消息时需要消息持久化。
消息的持久化和消息的订阅模式是完全不同的两个概念,它们之间没有任何关系,只不过消息的持久化是否有意义需要参考消息的消费方式。
消息的持久化可以在两个地方设置:
a、调用生产者的 MessageProducer.setDeliveryMode() 方法,设置该生产者生产的所有消息的持久化模式,除非单独为某个消息设置了持久化模式
b、调用消息的 Message.setJMSDeliveryMode(),只设置这一条消息的持久化模式
事务
在通过 Connection 创建 Session 的时候可以指明这个 Session 下的消息生产者和消息消费者是否以事务的方式发送和消费消息:
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
1.
事务中的操作作为一个原子的整体,要么一次性全部提交,要么全部回滚。以事务的方式发送和消费消息,需要显示的提交和回滚事务,在事务未提交之前是不会生效的。
try {
...
session.commit();
} catch (JMSException e) {
session.rollback();
e.printStackTrace();
}消息的签收
消息的签收是消息被消费的标志,消息的签收机制是为了避免消息的重复消费,因此消息的签收是相对于消费者的,对于生产者几乎没有意义。
对于Queue中的消息,一旦消息被签收则这条消息的状态就会从待消费状态(Pending Messages)变为已消费状态(Messages Dequeued),从而从待消费队列中移除。
对于Topic中的消息,若采用 MessageConsumer 消费消息则签收机制是没有意义的,因为 MessageConsumer 只能消费 Topic 中自消费者在 MQ 服务器注册之后推送到 Topic 中的消息,至于这之前的消息签收与否 MessageConsumer 不关心(因为看不到之前的消息),也就是说使用 MessageConsumer 消费 Topic 中的消息时是不会存在重复消费的问题的;若采用 TopicSubscriber 消费消息,签收机制避免重复消费消息的作用就凸显出来了,此时消息的签收将会作为某个订阅者(以 Connection 的 ClientID 作为标识)已消费过 Topic 中的某个消息的标志,也就是说消费者每次上线后都只会消费订阅的Topic中未被签收的消息,已签收的消息则不会被重复消费。
消息的签收机制有四种:
1、Session.AUTO_ACKNOWLEDGE:值为 1,自动签收,消费一条签收一条
2、Session.CLIENT_ACKNOWLEDGE:值为 2,客户端手动签收,需显示调用 Message.acknowledge() 方法完成签收
3、Session.DUPS_OK_ACKNOWLEDGE:值为 3,不必必须签收,消息可能会重复发送。在第二次重新传递消息的时候,消息头的 JmsDelivered 会被置为 true 标示当前消息已经传送过一次,客户端需要进行消息的重复处理控制。
4、Session.SESSION_TRANSACTED:值为 0,以事务的方式签收,该种方式创建 Session 时事务必须设置为 true。
事务对消息签收的影响:消息签收是事务控制的一部分
1、若创建 Session 时是以事务的方式创建的,此时只要事务提交就会将所有消息的签收状态置为已签收,只要事务不提交则消息的签收状态就不起作用
2、若创建 Session 时是以非事务的方式创建的,则对消息的签收有没有影响
JMS 2.0 新特性
新增特性
a、延迟投递:消息生产者现在可以指定一个消息不立即投递而是在特定的时间间隔以后投递
b、可以异步发送消息
c、JMS提供者必须设置JMSXDeliveryCount消息属性
改进扩展性的变更
持久订阅或非持久订阅现在可以是“共享的”,共享的订阅可以有多个消费者,多个消费者共同消费消息;该功能类型 Kafka 的消费者组的概念。
简化 JMS API 使用的变更
a、Connection,Session 和其他带有 close() 方法的对象现在实现了 java.lang. AutoCloseable 接口,这允许它们被用于 Java SE7 的 try-with-resources 语句中
b、添加了新的方法用于创建 session,而无需提供过多的参数
c、虽然创建非共享持久订阅时 Client ID 还是必须的,但是现在创建共享持久订阅时 Client ID 是可选的
d、Message 增加 getBody 方法,可以直接从消息中抽取内容而不需要预先将其转换到一个子类型
e、增加了一个新的简化 API,相对于标准 API 更加简单,特别是用于 Java EE 应用的时候
简化 API 与传统 API 提供的消息功能是一样的,但是它需要的接口更少、使用更方便。 简化 API 提供的主要接口如下:
ConnectionFactory:客户端用来创建连接的受管对象,传统API也会使用此接口。
JMSContext:客户端到 JMS 提供者之间的活动连接,以及发送和接收消息的一个单线程上下文。
JMSProducer:由 JMSContext 创建的对象,用于发送消息到 Queue 或 Topic。
JMSConsumer:由 JMSContext 创建的对象,用于接收 Queue 或 Topic 中的消息
在简化 API 中,一个 JMSContext 对象封装了传统 API 中 Connection 和 Session 两个对象的行为。
















