ActiveMQ认识和深入(二),发布/订阅、事务、签收
JMS是什么?
什么是JAVA消息服务? 百度百科:(https://baike.baidu.com/item/JMS/2836691)
是Java平台上有关面向消息中间件(MOM)的技术规范,JAVA消息服务指的是两个应用程序之间进行异步通信的API,它为标准消息协议和消息提供了一组通用的接口,包括创建,发送读取消息等,用于支持JAVA应用程序开发。在JAVAEE中,当两个应用程序使用JMS进行通信时,它们之间并不是直接相连的,而是通过一个共同的消息收发服务组件关联起来以达到解耦、异步、削峰的效果
MQ中间件的其他产品
JMS的组成结构和特点
JMS provider
实现
JMS
接口和规范的消息中间件,也就是MQ
的服务;
JMS producer
消息生产者,创建和发送JMS消息的客户端应用;
JMS consumer
消息消费者,接收和处理JMS消息的客户端应用;
JMS message
消息头
JMSDesitination
:发送的目的地,主要是指Queue
和 Topic
;
JMSDeliveryMode
:持久模式和非持久模式。一条持久性的消息,应该被传送“一次仅仅一次”,这就意味者如果JMS提供者出现故障,该消息不会丢失,它会在服务器恢复之后再次传递。一条非持久性的消息,最多会传送一次,这意味者服务器出现故障,该消息永久丢失。
JMSExpiration
:可以设置消息在一定时间后过期,默认是永不过期,消息过期时间,等于Destination
的send
方法中的timeToLive
值加上发送时刻的GMT
的时间值,如果timeToLive
值等于零,则JMSExpriration
被设为零,表示该消息永不过期。如果发送后,在消息过期时间之后还没有被发送到目的地,则该消息被清楚。
JMSPriority
:消息优先级, 从0-9十个级别,0到4是普通消息,5-9是加急消息。JMS
不要求MQ
严格按照这十个优先级发送消息,但必须保证加急消息要优于普通消息到达。默认是4级。
JMSMessageID
:唯一识别每个消息的标识由MQ产生。
消息体
封装具体的消息数据
5种消息体格式
TextMessage
:普通字符串消息,包含一个String
;MapMessage
:一个Map
类型的消息,Key
为String
类型,而值为JAVA
的基本类型;BytesMessage
:二进制数组消息,包含一个Byte[]
;StreamMessage
:Java
数据流消息,用标准流操作来顺序的填充和读取;ObjectMessage
:对象消息,包含一个可序列化的Java
对象;
发送和接受的消息体类型必须一致对应
消息属性
它们是以属性名和属性值对的形式指定的。可以将属性是为消息头得扩展,属性指定一些消息头没有包括的附加消息,比如可以在属性里指定消息选择器。
消息的属性就像可以分配给一条消息的附加消息头一样。它们允许开发者添加有关消息的不透明附加消息。它们还用于暴露消息选择器在消息过滤器时使用的数据。
TextMessage textMessage = session.createTextMessage();
textMessage.setText(text);
//自定义属性
textMessage.setStringProperty("username","张三");
- 如果需要除消息字段以外的值,那么可以使用消息属性;
- 识别 / 去重标注等操作非常有用的方法;
JMS的可靠性
持久化
持久化,当服务器宕机,消息依然存在。
messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
Queue 队列 和 Topic ,默认持久化;
持久化消息:这是队列的默认传送模式此模式保证只被传送一次和成功使用一次。对于这些消息,可靠性是优先考虑的因素。可靠性的另一个重要方面是确保持久性消息传送至目标后,消息服务在向消费者传送它们之前不会丢失这些消息。
源码
选择 ActionMQSession ;
查看方法 createProducer() ;
源码 116行, 默认参数 是2 ,持久化。
持久化的Topic 发布和订阅
package com.kelecc.activemq.topic;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
import java.io.IOException;
/**
* 功能描述: 发布和订阅
*
* @author: keLeCc
*/
public class JmsConSumerTopic {
private static final String ACTIVEMQ_URL = "tcp://localhost:61616";
private static final String TOPIC_NAME = "topic01";
public static void main(String[] args) throws JMSException, IOException {
System.out.println("张三开始订阅消息");
//创建连接工厂,安装给定的url地址,采用默认用户名密码;
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//通过连接工厂,获得连接并启动访问;
Connection factoryConnection = factory.createConnection ();
factoryConnection.setClientID("张三");
factoryConnection.start();
//创建会话, 事务不开启,自动签收
Session session = factoryConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//创建目的地主题
Topic topic = (Topic) session.createTopic(TOPIC_NAME);
TopicSubscriber durableSubscriber = session.createDurableSubscriber(topic, "remake....");
factoryConnection.start();
//订阅消息
Message messages = durableSubscriber.receive();
while (null != messages){
TextMessage textMessage = (TextMessage) messages;
System.out.println("=====消费者接收到消息=====" + textMessage.getText());
messages = durableSubscriber.receive(3000L);
}
//保持控制台,因为监听需要时间,所以这里不写,会存在消息没有消费到
session.close();
factoryConnection.close();
}
}
- 一定要先运行一次消费者,等于向MQ注册,类似我关注了某个微信公众号;
- 然后再运行生成者发送消息;
- 无论消费者是否在线,都会接收到,不在线的话,下次连接的时候,会把没有收过的消息都接收下来;
非持久化
非持久化,当服务器宕机,消息不存在。
messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
事务
生产者事务
如果设置事务为true,需要先send在执行commit,消息才被真正的提交到队列中。消息需要批量发送,需要缓冲区处理。后面的签收参数已经没有什么效果。事务的优先级更高。
Session createSession(boolean transacted, int acknowledgeMode) throws JMSException;
session.commit();
如果设置事务为false,只要执行send,就进入队列中。关闭事务,那第2个签收参数的设置需要有效。
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
消费者事务
如果设置事务为true,需要先receive()最后执行commit,消息才被真正的被消费。不然消息会被重复消费。
Session createSession(boolean transacted, int acknowledgeMode) throws JMSException;
session.commit();
如果设置事务为false,只要执行receive(),就直接被消费。
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
签收(针对消费者)
四种签收方式
AUTO_ACKNOWLEDGE
(默认):自动签收;CLIENT_ACKNOWLEDGE
:手动签收;
//非事务签收
Session session = factoryConnection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
TextMessage textMessage = (TextMessage) messages;
System.out.println("=====消费者接收到消息=====" + textMessage.getText());
//消费者客户端调用 acknowledge(); 手动签收
textMessage.acknowledge();
//事务签收
Session session = factoryConnection.createSession(true, Session.CLIENT_ACKNOWLEDGE);
TextMessage textMessage = (TextMessage) messages;
System.out.println("=====消费者接收到消息=====" + textMessage.getText());
// 这里可以不用手动签收,设置了事务,后面的签收会变成自动签收,事务的优先级更高
//textMessage.acknowledge();
session.commit();
DUPS_OK_ACKNOWLEDGE
:可以允许部分重复的签收;SESSION_TRANSACTED
:事务级签收;
JMS的点对点
点对点模型是基于队列的,生成者发送消息到队列,消费者从队列接收消息,队列的存在使得消息的异步传输成为可能。和我们平时给朋友发送短信类型
1.如果在Session 关闭时有部分消息已被收到但还没有被签收(acknowledged),那当消费者下次连接到相同的队列时,这些消息还会被再次签收。
2.队列可以长久地保存消息直到消费者接收到消息。消费者不需要担心消息会丢失而时刻和队列保持激活的连接转态,充分体现了异步传输模式的优势。
JMS的发布订阅
JMS Pub/Sub 模型定义了如何向一个内容节点发布和订阅消息,这些节点被称为 topic;
主题可以被认为是消息的传输中介,发布者(publisher) 发布消息到主题,订阅者(subscribe) 从主题订阅消息。
主题使得消息订阅者和消息发布者保持互相独立,不需要接触即可保证消息的传送。
非持久化的订阅
非持久化的订阅只有当客户端处于激活转态,也就是和MQ保持连接转态才能收到发送到某个主题的消息。
如果消费者处理离线状态,生产者发送的主题消息将会丢失废弃,消费者永远不会收到。
先要订阅注册才能接受到发布,只给订阅者发布消息。
非持久化的订阅下,不能恢复或重新派送一个未签收的消息。
持久的订阅
客户端首先向MQ注册一个自己的身份ID识别码,当这个客户端处理离线时,生产者会为这个ID保存所有发送到主题的消息;
当客户端再次连接到MQ时会根据消费者的ID得到所有当自己处于离线时发送的主题的消息。
持久化的订阅下,才能恢复或重新派送一个未签收的消息。