消息队列学习-ActiveMQ(二)
5 JMS规范和落地产品
5.1 是什么
5.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 Page)
- 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)
5.1.2 JMS
Java消息服务指的是两个应用程序之间进行异步通信的API,它为标准消息协议和消息服务提供了一组通用接口,包括创建、发送、读取消息等,用于支持JAVA应用程序开发。
5.2 MQ中间件的其他落地产品
消息队列的详细比较
特性 | ActiveMQ | RabbitMQ | Kafka | RocketMQ |
---|---|---|---|---|
PRODUCER-COMSUMER | 支持 | 支持 | 支持 | 支持 |
PUBLISH-SUBSCRIBE | 支持 | 支持 | 支持 | 支持 |
PRODUCER-COMSUMER | 支持 | 支持 | 支持 | 支持 |
REQUEST-REPLY | 支持 | 支持 | - | 支持 |
API完备性 | 高 | 高 | 高 | 低(静态配置) |
多语言支持 | 支持,JAVA优先 | 语言无关 | 支持,JAVA优先 | 支持 |
单机吞吐量 | 万级 | 万级 | 十万级 | 单机万级 |
消息延迟 | - | 微秒级 | 毫秒级 | - |
可用性 | 高(主从) | 高(主从) | 非常高(分布式) | 高 |
消息丢失 | - | 低 | 理论上不会丢失 | - |
消息重复 | - | 可控制 | 理论上会有重复 | - |
文档的完备性 | 高 | 高 | 高 | 中 |
提供快速入门 | 有 | 有 | 有 | 无 |
首次部署难度 | - | 低 | 中 | 高 |
5.3 JMS的组成结构和特点
组成结构:
- JMS provider:实现JMS接口和规范的消息中间件,也就是MQ服务器
- JMS producer:消息生产者,创建和发送JMS消息的客户端应用
- JMS consumer:消息消费者,接受和处理JMS消息的客户端应用
- JMS message:消息(包括消息头、消息属性、消息体)
5.3.1 JSM message消息头组成
- JMSDestination:消息发送的目的地,主要指Queue和Topic
- JMSDeliveryMode:持久模式/非持久模式
- JMSExpiration:设置过期时间,默认永不过期
- JMSPriority:优先级,0-4普通消息,5-9加急消息,默认是4级,只保证加急先于普通
- JMSMessageID:唯一识别每个消息的标识由MQ产生
5.3.2 消息体
- 封装具体的消息数据
- 五种消息体格式
- TextMessage:普通字符串消息,包含一个string
- MapMessage:一个Map类型的消息,key为string类型,而值为Java的基本类型
- ByteMessage:二进制数组消息,包含一个byte[]
- StreamMessage:Java数据流消息,用标准流操作来顺序的填充和读取
- ObjectMessage:对象消息,包含一个可序列化的Java对象
- 发送和接受的消息体类型必须一致对应
5.3.3 消息属性
是啥:以属性名和属性值对的形式制定,可以看做消息头的扩展
场景:如果需要除消息字段以外的值,那么可以使用消息属性。
作用:识别/去重/重点标注等操作非常有用的方法
用法:textMessage.setStringProperty("c01", "vip");
5.4 JMS的可靠性
5.4.1 PERSISTENT:持久性
- 参数说明:
- 非持久
messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
非持久化:当服务器宕机,消息不存在 - 持久
messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
持久化:当服务器宕机,消息依然存在
- 非持久
- 持久化的Queue
队列默认是持久化的,此模式保证这些消息只被传送一次和成功使用一次。 - 持久的Topic
JmsConsumer_Topic_Persist.java
public class JmsConsumer_Topic_Persist {
private static final String ActiveMQ_URL = "tcp://192.168.188.128:61616";
private static final String TOPIC_NAME = "Topic-Persist";
public static void main(String[] args) throws JMSException, IOException {
System.out.println("****我是李四");
// 1. 创建连接工厂,按照给定的URL地址,采用默认用户名和密码
ActiveMQConnectionFactory activeMQConnectionFactory
= new ActiveMQConnectionFactory(ActiveMQ_URL);
// 2. 通过连接工厂,获得连接connection并启动
Connection connection = activeMQConnectionFactory.createConnection();
connection.setClientID("李四");
// 3. 创建会话session
// 两个参数,第一个叫事务/第二个叫签收
Session session = connection.createSession(false,
Session.AUTO_ACKNOWLEDGE);
// 4. 创建目的地(具体是队列还是主题)
Topic topic = session.createTopic(TOPIC_NAME);
TopicSubscriber topicSubscriber
= session.createDurableSubscriber(topic, "remarl");
connection.start();
Message message = topicSubscriber.receive();
while (null != message) {
TextMessage textMessage = (TextMessage) message;
System.out.println("****收到持久化topic"+textMessage.getText());
message = topicSubscriber.receive();
}
session.close();
connection.close();
}
}
JmsProduce_Topic_Persist.java
public class JmsProduce_Topic_Persist {
private static final String ActiveMQ_URL = "tcp://192.168.188.128:61616";
private static final String TOPIC_NAME = "Topic-Persist";
public static void main(String[] args) throws JMSException {
ActiveMQConnectionFactory activeMQConnectionFactory
= new ActiveMQConnectionFactory(ActiveMQ_URL);
Connection connection = activeMQConnectionFactory.createConnection();
Session session = connection.createSession(false,
Session.AUTO_ACKNOWLEDGE);
Topic topic = session.createTopic(TOPIC_NAME);
MessageProducer messageProducer = session.createProducer(topic);
messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
connection.start();
for (int i = 1; i <= 3; i++) {
TextMessage textMessage
= session.createTextMessage("TOPIC_NAME--->" + i);
messageProducer.send(textMessage);
}
messageProducer.close();
session.close();
connection.close();
System.out.println("***********TOPIC_NAME消息发布到MQ完成");
}
}
启动订阅者,并将订阅者停止,此时其处于离线订阅者:
启动生产者,发送消息:
在启动被停止的离线订阅者,依然能获取到消息:
5.4.2 事务
- 设置为
false
:只要执行send,就进入到队列中。关闭事务,那第2个签收参数的设置需要有效。 - 设置为
true
:先执行send在执行commit,消息才被真正的提交到队列中。消息需要批量发送的,需要缓冲区处理。
public class JmsProduce_TX {
private static final String ActiveMQ_URL = "tcp://192.168.188.131:61616";
private static final String QUEUE_NAME = "queue01";
public static void main(String[] args) throws JMSException {
ActiveMQConnectionFactory activeMQConnectionFactory
= new ActiveMQConnectionFactory(ActiveMQ_URL);
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
Session session = connection.createSession(true,
Session.AUTO_ACKNOWLEDGE);
Queue queue = session.createQueue(QUEUE_NAME);
MessageProducer messageProducer = session.createProducer(queue);
for (int i = 1; i <= 3; i++) {
TextMessage textMessage
= session.createTextMessage("tx msg--->" + i);
messageProducer.send(textMessage);
}
session.commit();
messageProducer.close();
session.close();
connection.close();
System.out.println("***********消息发布到MQ完成");
}
}
注意:若未提交,则没有发送数据数据
public class JmsConsumer_TX {
private static final String ActiveMQ_URL = "tcp://192.168.188.131:61616";
private static final String QUEUE_NAME = "queue01";
public static void main(String[] args) throws JMSException, IOException {
ActiveMQConnectionFactory activeMQConnectionFactory
= new ActiveMQConnectionFactory(ActiveMQ_URL);
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
Session session = connection.createSession(true,
Session.AUTO_ACKNOWLEDGE);
Queue queue = session.createQueue(QUEUE_NAME);
MessageConsumer messageConsumer = session.createConsumer(queue);
while (true) {
TextMessage textMessage=(TextMessage)messageConsumer.receive(4000L);
if (textMessage != null) {
System.out.println("****消费者收到消息:msg---"
+ textMessage.getText());
}else {
break;
}
}
session.commit();
messageConsumer.close();
session.close();
connection.close();
}
}
注意:若未提交,则会出现多次消费数据
5.4.3 Acknowledge:签收
- 非事务
- 自动签收(默认):
Session.AUTO_ACKNOWLEDGE
- 手动签收:
Session.CLIENT_ACKNOWLEDGE
,客户端调用acknowledge
方法手动签收 - 允许重复消息:
Session.DUPS_OK_ACKNOWLEDGE
- 自动签收(默认):
- 事务
生产事务开启,只有commit后才能将全部消息变成已消费 - 签收和事务关系
- 在事务性会话中,当一个事务被成功提交则消息会被自动签收。如果事务回滚,则消息会被再次传送。
- 非事务性会话中,消息何时被确认取决于创建会话时的应答模式
5.5 JMS的点对点总结
点对点模型是基于队列的,生产者发消息到队列,消费者从队列接收消息,队列的存在使得消息的异步传输成为可能。
1:如果在Session关闭时有部分消息被收到但还没有被签收(acknowledge),那当消费者下次连接到相同的队列时,这些消息还会被再次接收
2:队列可以长久的保存消息直到消费者收到消息。消费者不需要因为担心消息会丢失而时刻和队列保持激活的链接状态,充分体现了异步传输模式的优势
5.6 JMS的发布订阅总结
JMS Pub/Sub 模型定义了如何向一个内容节点发布和订阅消息,这些节点被称作topic
主题可以被认为是消息的传输中介,发布者**(publisher)发布消息到主题,订阅者(subscribe)**从主题订阅消息。
主题使得消息订阅者和消息发布者保持互相独立不需要解除即可保证消息的传送
- 非持久订阅
非持久订阅只有当客户端处于激活状态,也就是和MQ保持连接状态才能收发到某个主题的消息。
如果消费者处于离线状态,生产者发送的主题消息将会丢失作废,消费者永远不会收到。
一句话:先订阅注册才能接受到发布,只给订阅者发布消息。 - 持久订阅
客户端首先向MQ注册一个自己的身份ID识别号,当这个客户端处于离线时,生产者会为这个ID保存所有发送到主题的消息,当客户再次连接到MQ的时候,会根据消费者的ID得到所有当自己处于离线时发送到主题的消息
当持久订阅状态下,不能恢复或重新派送一个未签收的消息。
持久订阅才能恢复或重新派送一个未签收的消息。 - 用哪个?
当所有的消息必须被接收,则用持久订阅。当消息丢失能够被容忍,则用非持久订阅