一、JMS规范
JavaEE 是一套使用 Java进行企业级开发的13个核心规范工业标准,包括:
JDBC 数据库连接
JNDI Java的命名和目录接口
EJB Enterprise java bean
RMI 远程方法调用 一般使用TCP/IP协议
Java IDL 接口定义语言
Jsp
Servlet
XML
JMS Java消息服务
JTA
JTS
JavaMail
JAF
JMS规范介绍
JMS 部件 | JMS provider | JMS producer | JMS consumer | JMS message |
含义 | 实现JMS 的消息中间件,也就是MQ服务器 | 消息生产者,创建和发送消息的客户端 | 消息消费者,接收和处理消息的客户端 | JMS 消息,分为消息头、消息属性、消息体 |
JMS message的消息头
消息头 | JMSDestination | JMSDeliveryMode | JMSExpiration | JMSPriority | JMSMessageId |
含义 | 头在哪儿 | 是持久还是非持久 | 过期时间,默认永久 | 优先级,默认是4 有0~9 ,5-9 是紧急的,0-4 是普通的 | 唯一的消息ID |
消息体;封装具体的消息数据
5种消息体 | TextMessage | Mapmessage | BytesMessage | StreamMessage | ObjectMessage |
含义 | 普通字符串消息,包含一个String | Map 类型的消息, k-> String v -> Java 基本类型 | 二进制数组消息,包含一个byte[] | Java 数据流消息,用标准流操作来顺序的填充读取 | 对象消息,包含一个可序列化的Java 对象 |
发送和接收的消息类型必须一致
消息属性:可以自行封装消息携带的其他属性信息
二、通过JMS规范是如何确保消息的可靠性
JMS可靠性通过:Persistent持久性、事务、Acknowledge 签收来保证的
1.消息的持久化
1)对于queue模式的消息
// 在队列为目的地的时候持久化消息
messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
// 队列为目的地的非持久化消息
messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
queue形式的消息,默认为持久化的,服务器宕机后消息依旧存在,只是没有入队,当服务器再次启动,消费者还可以对消息进行消费。
但是非持久化的消息,服务器宕机后消息永远丢失。
2)对于目的地为主题(topic)来说,默认就是非持久化的,让主题的订阅持久化的意义在于:对于订阅了此主题的人来说,就算客户端没有在线,生产者端生产了消息,在客户端上线后依然可以收到在此期间发布的消息。
生产者代码实现
public class TopicProducer {
public static final String ACTIVE_URL = "tcp://192.168.6.10:61616";
public static final String TOPIC_NAME = "topic01";
public static void main(String[] args) throws JMSException {
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVE_URL);
Connection connection = activeMQConnectionFactory.createConnection();
Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
Topic topic = session.createTopic(TOPIC_NAME);
MessageProducer producer = session.createProducer(topic);
//与之前的实现没有区别,只是将消息设置为持久化后再开始连接即可
producer.setDeliveryMode(DeliveryMode.PERSISTENT);
connection.start();
for (int i = 0; i < 3; i++) {
TextMessage textMessage = session.createTextMessage("msg---" + i);
producer.send(textMessage);
}
producer.close();
session.close();
connection.close();
System.out.println("发送持久化topic完成~");
}
}
消费者代码实现
public class TopicConsumer {
public static final String ACTIVE_URL = "tcp://192.168.6.10:61616";
public static final String TOPIC_NAME = "topic01";
public static void main(String[] args) throws JMSException {
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVE_URL);
Connection connection = activeMQConnectionFactory.createConnection();
//要设置一个订阅该主题的订阅者Id
connection.setClientID("xijian");
Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
Topic topic = session.createTopic(TOPIC_NAME);
//这里与之前不同,要通过session创建一个持久化的订阅者
//参数为订阅的主题与备注
TopicSubscriber t = session.createDurableSubscriber(topic, "我是xijian");
connection.start();
//阻塞式接收消息
Message message = t.receive();
while(message != null){
TextMessage textMessage = (TextMessage) message;
System.out.println("收到的持久化topic :"+textMessage.getText());
message = t.receive();
}
}
}
因为topic主题这种模式是基于发布/订阅的,如果想要消息被消费,仍需要先订阅到这个主题,再进行消息的消费,当启动客户端时,我们通过访问其web控制台查看一下
可以看到,上线的订阅者有我刚刚运行的客户端。
这时启动服务端发送消息,客户端就可以收到了,客户端如果下线,也会在下次上线前接收到发布的消息,重点就是要设置消息的持久化,如果不设置持久化,那这个订阅信息是不会被保留的,下线即失效。
2.事务
createSession的第一个参数为true 为开启事务,开启事务之后必须在将消息提交,才可以在队列中看到消息。
Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
事务的提交
session.commit();
事务开启的意义在于,如果对于多条必须同批次传输的消息,可以使用事务,如果一条传输失败,可以将事务回滚,再次传输,保证数据的完整性。
1)对于生产者来说
消息生产者偏重于事务,开启事务后send的消息,只有在session执行commit后才会被提交到服务器,不然无效。
2)对于消费者来说
消费者偏重于签收,但开启事务后,消息是否被消费就只和是否commit有关了,消息在commit后会自动签收,如回滚,则消息会被继续传送,如不开启事务,则需要通过签收来进行消息的消费(下文会提到)
3.签收
签收一般会在非事务状态下生效,且主要针对于消费者,生产者的签收功能并没有什么意义,因为如果开启事务的话,签收与否取决于是否提交和回滚。
签收一共有四种状态,最常用的签收状态有两种
Session.AUTO_ACKNOWLEDGE 自动签收
Session.CLIENT_ACKNOWLEDGE 手动签收
手动签收需要acknowledge
textMessage.acknowledge();
在非事务状态下,如果设置了手动签收,那必须在recieve到一条消息后进行签收,不然消息仍然是未被消费的状态,下次消费时依旧能获取到没被签收的消息。
总结:在ActiveMQ保证消息可靠性的四种方式
1)进行消息的持久化:queue默认为持久化,topic模式下需要手动设置
2)开启消息的事务
3)在消费者的非事务状态下要开启消息手动签收功能
4)集群搭建实现高可用