ActiveMQ 4. JMS规范和落地产品
4.1. JMS是什么
4.1.1. JavaEE
JavaEE是一套使用Java进行企业级应用开发的大家一致遵循的13个核心规范工业标准。JavaEE平台提供了一个基于组件的方法来加快设计、开发、装配及部署企业应用程序。
- JDBC(Java Database)数据库连接
- JNDI(Java Naming and Directory Interfaces)Java的命名和目录接口
- EJB(Enterprise JavaBean)
- RMI(Remote Method Invoke)远程方法调用
- Java IDL(Interface Description Language)/ CORBA(Common Object Broker Architecture)接口定义语言/公用对象请求代理程序体系结构
- JSP(Java Server Pages)
- Servlet
- XML(Extensible Markup Language)可扩展标记语言
- JMS(Java Message Service)Java消息服务
- JTA(Java Transaction API)Java事务API
- JTS(Java Transaction Service)Java事务服务
- JavaMail
- JAF(JavaBean Activation Framework)
4.1.2. JMS
Java Message Service(Java消息服务是JavaEE中的一个技术)
Java消息服务指的是两个应用程序之间进行异步通信的API,它为标准消息协议和消息服务提供了一组通用接口,包括创建、发送、读取消息等,用于支持Java应用程序开发。在JavaEE中,当两个应用程序使用JMS进行通信时,它们之间并不是直接相连的,而是通过一个共同的消息收发服务组件关联起来以达到解耦/异步/削峰的效果。
4.2. MQ中间件的其他落地产品
4.2.1. MQ产品对比
4.3. JMS的组成结构和特点
4.3.1. JMS部件
JMS 部件 | JMS provider | JMS producer | JMS consumer | JMS message |
---|---|---|---|---|
含义 | 实现JMS 的消息中间件,也就是MQ服务器 | 消息生产者,创建和发送消息的客户端 | 消息消费者,接收和处理消息的客户端 | JMS 消息,分为消息头、消息属性、消息体 |
4.3.2. JMS message
4.3.2.1. 消息头
-
JMSDestination
消息发送的目的地,主要是指Queue和Topic
-
JMSDeliveryMode
持久和非持久模式。
一条持久性的消息:应该被传送“一次仅仅一次”,这就意味着如果JMS提供者出现故障,该消息并不会丢失,它会在服务器恢复之后再次传递。
一条非持久的消息:最多会传递一次,这意味着服务器出现故障,该消息将会永远丢失。
-
JMSExpiration
可以设置消息在一定时间后过期,默认是永不过期。
消息过期时间,等于Destination的send方法中的
timeToLive
值加上发送时刻的GMT时间值。如果timeToLive值等于0,则JMSExpiration被设为0,表示该消息永不过期。
如果发送后,在消息过期时间之后还没有被发送到目的地,则该消息被清除。
-
JMSPriority
消息优先级,从0-9十个级别,0-4是普通消息,5-9是加急消息。
JMS不要求MQ严格按照这十个优先级发送消息但必须保证加急消息要先于普通消息到达。默认是4级。
-
JMSMessageId
唯一标识每个消息的标识由MQ产生。
使用方法:
javax.jms.Message
接口的setJMSXXX()
方法javax.jms.MessageProducer
接口的send()
重载方法
4.3.2.2. 消息体
-
封装具体的消息数据
-
5种消息体格式
消息体 备注 TextMessage 普通字符串消息,包含一个String Mapmessage Map类型的消息,key为Strng类型,而值为Java基本类型 BytesMessage 二进制数组消息,包含一个byte[] StreamMessage Java 数据流消息,用标准流操作来顺序的填充读取 ObjectMessage 对象消息,包含一个可序列化的Java 对象 -
发送和接收的消息体类型必须一致对应
使用方法:
javax.jms.Session
接口的createXXXMessage()
方法
4.3.2.3. 消息属性
- 如果需要除消息字段以外的值,那么可以使用消息属性
- 识别/去重/重点标注等操作非常有用的方法
他们是以属性名和属性值对的形式制定的。可以将属性视为消息头的扩展,属性指定一些消息头没有包括的附加信息,比如可以在属性里指定消息选择器。
消息的属性就像分配给一条消息的附加消息头一样。它们允许开发者添加有关消息的不透明附加消息。
它们还用于暴露消息选择器在消息过滤时使用的数据。
使用方法:
javax.jms.Message
接口的seXXXProperty()
方法
4.4. JMS的可靠性
4.4.1. Persistent:持久性
4.4.1.1. 参数设置说明
-
非持久
当服务器宕机,消息不存在
messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
-
持久
当服务器宕机,消息依然存在
messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
4.4.1.2. 持久的Queue
持久化消息
这是队列的默认传递模式,此模式保证这些消息只被传送一次和成功使用一次。对于这些消息,可靠性是优先考虑的因素。
可靠性的另一个重要方面是确保持久性消息传送至目标后,消息服务在向消费者传送它们之前不会丢失这些消息。
4.4.1.3. 持久的Topic
要先启动订阅(消费者)再启动生产者
类似场景:微信公众号订阅发布
- 一定先运行一次消费者,等于向MQ注册,类似我订阅了这个主题
- 然后再运行生产者发送消息
- 此时无论消费者是否在线,都会接收到,不在线的话,下次连接的时候,会把没有收过的消息都接收下来
持久的发布主题生产者:
public class JmsProducer_Topic_Persist {
public static final String ACTIVEMQ_URL = "tcp://192.168.181.128:61616/";
public static final String TOPIC_NAME = "topic-persist";
public static void main(String[] args) throws JMSException {
// 1. 创建连接工厂,按照给定的URL地址,采用默认用户名密码
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
// 2. 通过连接工厂,获得连接Connection并启动访问
Connection connection = activeMQConnectionFactory.createConnection();
// 3. 创建会话Session
// 两个参数,第一个是事务控制,第二个是签收控制
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
// 4. 创建目的地(具体是队列queue或主题topic)
Topic topic = session.createTopic(TOPIC_NAME);
// 5. 创建消息的生产者
MessageProducer messageProducer = session.createProducer(topic);
messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
connection.start();
// 6. 通过消息生产者发送消息
for (int i = 0; i < 3; i++) {
// 7. 创建消息
TextMessage textMessage = session.createTextMessage("topic-persist---" + i);
// 8. 发送给MQ
messageProducer.send(textMessage);
}
// 9. 关闭资源
messageProducer.close();
session.close();
connection.close();
System.out.println("*****topic-persist消息发布到MQ完成*****");
}
}
持久的发布主题消费者:
public class JmsConsumer_Topic_Persist {
public static final String ACTIVEMQ_URL = "tcp://192.168.181.128:61616/";
public static final String TOPIC_NAME = "topic-persist";
public static void main(String[] args) throws JMSException, IOException {
System.out.println("z3");
// 1. 创建连接工厂,按照给定的URL地址,采用默认用户名密码
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
// 2. 通过连接工厂,获得连接Connection并启动访问
Connection connection = activeMQConnectionFactory.createConnection();
connection.setClientID("z3");
// 3. 创建会话Session
// 两个参数,第一个是事务控制,第二个是签收控制
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
// 4. 创建目的地(具体是队列queue或主题topic)
Topic topic = session.createTopic(TOPIC_NAME);
TopicSubscriber topicSubscriber = session.createDurableSubscriber(topic, "remark...");
connection.start();
Message message = topicSubscriber.receive();
while (null != message) {
TextMessage textMessage = (TextMessage) message;
System.out.println("********收到持久化topic:" + textMessage.getText());
message = topicSubscriber.receive(1000L);
}
// 6. 关闭资源
session.close();
connection.close();
}
}
4.4.2. Transaction:事务
事务偏生产者
签收偏消费者
javax.jms.Connection#createSession(boolean transacted, int acknowledgeMode)
这里第一个参数是事务相关,第二个参数是签收相关
producer提交时的事务:
-
false
只要执行send,就进入到队列中。
关闭事务,那第2个签收参数的设置需要有效
-
true
先执行send再执行commit,消息才被真正的提交大队列中。
消息需要批量发送,需要缓冲区处理。
// 创建session时,开启事务
Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
...
// session关闭前,提交事务
try {
session.commit();
} catch (Exception e) {
session.rollback();
} finally {
if (session != null) {
session.close();
}
}
事务开启的意义在于,如果对于多条必须同批次传输的消息,可以使用事务,如果一条传输失败,可以将事务回滚,再次传输,保证数据的完整性。
对于消息消费者来说,开启事务的话,可以避免消息被多次消费,以及后台和服务器数据的不一致性。举个栗子:
如果消息消费的 createSession 设置为 ture ,但是没有 commit ,此时就会造成非常严重的后果,那就是在后台看来消息已经被消费,但是对于服务器来说并没有接收到消息被消费,此时就有可能出现重复消费。
4.4.3. Acknowledge:签收
4.4.3.1. 非事务
-
自动签收(默认)
Session.AUTO_ACKNOWLEDGE
-
手动签收
Session.CLIENT_ACKNOWLEDGE
客户端调用acknowledge方法手动签收
如果消费者没有调用acknowledge方法,MQ会认为消息没有出队,导致重复消费;
// 设置手动签收 Session session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE); ... // 手动签收 textMessage.acknowledge();
-
允许重复消息
Session.DUPS_OK_ACKNOWLEDGE
4.4.3.2. 事务
事务开启,只有commit后才能将全部消息变为已消费;
对于开启事务时,设置手动签收和自动签收没有多大的意义,都默认自动签收,也就是说事务的优先级更高一些。
4.4.3.3. 签收和事务关系
- 在事务性会话中,当一个事务被成功提交则消息被自动签收。如果事务回滚,则消息会被再次传送。
- 非事务性会话中,消息何时被确认取决于创建会话时的应答模式( acknowledgement mode)
4.5. JMS的点对点总结
点对点模型是基于队列的,生产者发送消息到队列,消费者从队列接收消息,队列的存在使得消息的异步传输成为可能。
和我们平时给朋友发送短信类似。
- 如果在Session关闭时有部分消息被收到但还没有被签收(acknowledge),那当消费者下次连接到相同的队列时,这些消息还会被再次接收
- 队列可以长久的保存消息直到消费者收到消息。消费者不需要因为担心消息会丢失而时刻和队列保持激活的链接状态,充分体现了异步传输模式的优势
4.6. JMS的发布订阅总结
JMS Pub/Sub 模型定义了如何向一个内容节点发布和订阅消息,这些节点被称作topic。
主题可以被认为是消息的传输中介,发布者(publisher)发布消息到主题,订阅者(subscribe)从主题订阅消息。
主题使得消息订阅者和消息发布者保持互相独立不需要解除即可保证消息的传送。
4.6.1. 非持久化订阅
非持久订阅只有当客户端处于激活状态,也就是和MQ保持连接状态才能收发到某个主题的消息。
如果消费者处于离线状态,生产者发送的主题消息将会丢失作废,消费者永远不会收到。
一句话:先订阅注册才能接受到发布,只给订阅者发布消息。
4.6.2. 持久化订阅
客户端首先向MQ注册一个自己的身份ID识别号,当这个客户端处于离线时,生产者会为这个ID保存所有发送到主题的消息,当客户再次连接到MQ的时候,会根据消费者的ID得到所有当自己处于离线时发送到主题的消息。
当持久订阅状态下,不能恢复或重新派送一个未签收的消息。
持久订阅才能恢复或重新派送一个未签收的消息。
4.6.3. 用哪个
当所有的消息必须被接收,则用持久订阅。当消息丢失能够被容忍,则用非持久订阅