MQ
MQ:消息中间件,常见的消息中间件有Kafka,RabbitMQ、Rocket MQ、ActiveMQ
ActiveMQ
1、解耦
2、削峰
3、异步
在设计系统时可以明确要达到的目标
1、要做到系统解耦,当新的模块接进来时,可以做到代码改动最小;【能够解耦】
2、设计流量缓冲池,可以让后端系统按照自身吞吐能力进行消费,不被冲垮【能够削峰】
3、强弱依赖梳理将非关键调用链路的操作异步化并提升整体系统的吞吐能力。【能够异步】
发送者把消息发送给消息服务器,消息服务器将消息存放在若干队列/主题中,在合适的时候,消息服务器将消息转发给接收者。在这个过程中,发送和接受是异步的,也就是发送无需等待,而且发送者和接收者的生命周期也没有必然的关系;
尤其在发布pub/订阅sub(公众号消息转发),也可以完成一对多的通信,既让一个消息有多个接收者。
Dockers安装ActiveMQ
#下拉容器
docker pull docker.io/webcenter/activemq
#运行容器,前后控制台
docker run -d --name myactivemq -p 8161:8161 -p 6161:61616 docker.io/webcenter/activemq
#查看运行
ps -ef|grep activemq|ggrep -v grep
#查看端口
netstat -anp|grep 6161
连接过程
发送过程
一对一、一对多
接口
javax.jms包
Queue
生产者发送消息Queue
private static final String ACTIVE_URL = "tcp://120.25.241.237:6161";
@Test
public void test() throws Exception {
//1 创建连接工厂,按照给定的url地址,采用默认用户名和密码
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVE_URL);
//2 通过连接工厂,获得连接Connection并启动访问
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//3 创建会话 session
//两个参数,第一个叫事务/第二个叫签收
Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
//4 创建目的地(具体是队列还是主题topic)
Queue queue = session.createQueue("queue01");
//5 创建消息的生产者
MessageProducer messageProducer = session.createProducer(queue);
//6 通过使用messageProducer生产3条消息发送到MQ队列中
for (int i = 0; i < 3; i++) {
//7 创建消息,好比学生按照老师的要求写好的面试题消息
TextMessage textMessage = session.createTextMessage("msg---" + i);
//8 通过messageProducer 发送给mq
messageProducer.send(textMessage);
}
//9 关闭资源
messageProducer.close();
session.close();
connection.close();
}
控制台:
参数说明:
消费者接收消息Queue
同步阻塞
@Test
public void test1() throws Exception{
//1 创建连接工厂,按照给定的url地址,采用默认用户名和密码
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVE_URL);
//2 通过连接工厂,获得连接Connection并启动访问
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//3 创建会话 session
//两个参数,第一个叫事务/第二个叫签收
Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
//4 创建目的地(具体是队列还是主题topic)
Queue queue = session.createQueue("queue01");
//5 创建消费者
MessageConsumer messageConsumer = session.createConsumer(queue);
while (true){
TextMessage textMessage = (TextMessage) messageConsumer.receive(2000);
if (null != textMessage){
System.out.println("****消费者接收到消息:"+textMessage.getText());
}else {
break;
}
}
messageConsumer.close();
session.close();
connection.close();
}
后台结果:
监听
@Test
public void test2() throws Exception{
//1 创建连接工厂,按照给定的url地址,采用默认用户名和密码
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVE_URL);
//2 通过连接工厂,获得连接Connection并启动访问
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//3 创建会话 session
//两个参数,第一个叫事务/第二个叫签收
Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
//4 创建目的地(具体是队列还是主题topic)
Queue queue = session.createQueue("queue01");
//5 创建消费者
MessageConsumer messageConsumer = session.createConsumer(queue);
//通过监听的方式来接收消息
messageConsumer.setMessageListener(message -> {
if (null != message && message instanceof TextMessage){
TextMessage textMessage = (TextMessage) message;
try {
System.out.println(textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
});
System.in.read();
messageConsumer.close();
session.close();
connection.close();
}
多个消费者争夺Queue
启动两个消费者,再生产6条消息,请问,消费者情况如何?
一人一半
总结
JMS开发步骤
- 创建connection factory
- 通过connection factory来创建JMS connection
- 启动JMS connection
- 通过connection 创建JMS session
- 创建JMS destination
- 创建 JMS producer或者创建一个JMS message 并设置destination
- 创建JMS consumer或者是注册一个JMS message listener
- 发送或者接收JMS message(s)
- 关闭所有的JMS资源。
两种消费方式
-
同步阻塞方式(receive())
订阅者或者接收者调用receive()方法来接受消息,receive方法能够在接收到消息之前(或者超时之前)将一直阻塞。
-
异步非阻塞方式(监听器onMessage())
订阅者或者接收者通过setMessageListener(…)注册 一个消息监听器。当消息到达之后,系统自动调用监听器的onMessage(Message message)方法。
Topic(主题)
特点
1、生产者将消息发布到topic中,每个消息可以有多个消费者,属于1:N的关系。
2、生产者和消费者之间有时间上的相关性。订阅某一个主题的消费者只能消费自它订阅之后发布的消息。
3、生产者生产时,topic不保存消息,它是无状态的不落地,假设无人订阅就去生产,那就是一条废消息,所有,一般先启动消费者再启动生产者。
案例
@SpringBootTest
class TopicApplicationTests {
private static final String ACTIVE_URL = "tcp://120.25.241.237:6161";
@Test
public void test() throws Exception {
//1 创建连接工厂,按照给定的url地址,采用默认用户名和密码
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVE_URL);
//2 通过连接工厂,获得连接Connection并启动访问
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//3 创建会话 session
//两个参数,第一个叫事务/第二个叫签收
Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
//4 创建目的地(具体是队列还是主题topic)
Topic topic = session.createTopic("topic");
//5 创建消息的生产者
MessageProducer messageProducer = session.createProducer(topic);
//6 通过使用messageProducer生产3条消息发送到MQ队列中
for (int i = 0; i < 6; i++) {
//7 创建消息,好比学生按照老师的要求写好的面试题消息
TextMessage textMessage = session.createTextMessage("msg---" + i);
//8 通过messageProducer 发送给mq
messageProducer.send(textMessage);
}
//9 关闭资源
messageProducer.close();
session.close();
connection.close();
}
/**
* 同步阻塞
* */
@Test
public void test1() throws Exception{
System.out.println("消费者一号");
//1 创建连接工厂,按照给定的url地址,采用默认用户名和密码
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVE_URL);
//2 通过连接工厂,获得连接Connection并启动访问
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//3 创建会话 session
//两个参数,第一个叫事务/第二个叫签收
Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
//4 创建目的地(具体是队列还是主题topic)
Topic topic = session.createTopic("topic");
//5 创建消费者
MessageConsumer messageConsumer = session.createConsumer(topic);
while (true){
TextMessage textMessage = (TextMessage) messageConsumer.receive();
if (null != textMessage){
System.out.println("****消费者接收到消息:"+textMessage.getText());
}else {
break;
}
}
messageConsumer.close();
session.close();
connection.close();
}
/**
* 监听
* */
@Test
public void test2() throws Exception{
System.out.println("消费者二号");
//1 创建连接工厂,按照给定的url地址,采用默认用户名和密码
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVE_URL);
//2 通过连接工厂,获得连接Connection并启动访问
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//3 创建会话 session
//两个参数,第一个叫事务/第二个叫签收
Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
//4 创建目的地(具体是队列还是主题topic)
Topic topic = session.createTopic("topic");
//5 创建消费者
MessageConsumer messageConsumer = session.createConsumer(topic);
//通过监听的方式来接收消息
messageConsumer.setMessageListener(message -> {
if (null != message && message instanceof TextMessage){
TextMessage textMessage = (TextMessage) message;
try {
System.out.println(textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
});
System.in.read();
messageConsumer.close();
session.close();
connection.close();
}
}
总结
JSM
JSM是JavaEE中的一个规范,javaEE的规范还有JDBC、Servlet、XML等,JMS(Java Message Service)java消息服务。
java消息服务指的是两个应用程序之间进行异步通讯的API,它为标准消息协议和消息服务提供了一组通用接口,包括创建、发送、读取消息等,用与支持JAVA应用程序开发。在Java EE中,当两个应用程序使用JMS进行通信时,它们之间并不是直接相连的,而是通过一个共同的消息收发服务组件关联起来以达到解耦/异步削峰的效果。
MQ产品的对比
!
JMS的组成结构和特点
JMS provider
实现JMS接口和规范的消息中间件,也就是我们的MQ服务器
JMS producer
消息生产者,创建和发送JMS消息的客户端应用
JMS consumer
消息消费者,接收和处理JMS消息客户端应用
JMS message
1、消息头
-
JMSDestination
消息发送的目的地,主要指的是Queue和Topic。
-
JMSDeliveryMode
持久化与非持久化模式。
一条持久化的消息:应该被传送一次仅仅一次,这意味着如果JMS提供者如果出现故障,该消息并不会丢失,它会在服务器恢复之间后再次传递。
一条非持久化的消息:最多会传送一次,这意味着服务器出现故障,该消息将永久丢失。
-
JMSEXpiration
消息持久化,可设置消息在一定时间后过期,消息过期时间,等于Destination的send方法中的timeToLive值加上发送时刻的FGMT时间值,如果timeToLive值等于0,则JMSEXpiration被设为0,表示该消息永不过期。
如果发送后,在消息过期时间之后消息还没有被发送到目的地,则该消息被清除。
-
JMSPriority
消息优先级,从09十个级别,04是普通消息,5~9是加急消息。
JMS不要求MQ严格按照这十个优先级发送消息,但必须保证加急消息要先于普通消息到达。默认是4级。
-
JMSMessage
唯一识别每个消息的标识由MQ产生。
2、消息体
-
封装具体的消息数据
-
5种消息体格式
- TextMessage 普通字符串类型,包含一个String
- MapMessage 一个Map类型的消息,key为String类型,而值为java的基本数据类型
- BytesMessage 二进制消息,包含一个byte[]
- StreamMessage java数据流消息,用标准流操作来顺序的填充和读取。
- ObjectMessage 对象消息,包含一个可序列化的java对象
-
发送和接收的消息体类型必须一致对应
3、消息属性
如果需要除消息头字段以外的值,那么可以使用消息属性,识别/去重/重点标注等操作非常有用的方法。
他们是以属性名和属性值对的形式制定的,可以将属性是为消息头得扩展,属性指定一些消息头没有包括的附加信息。
JMS的可靠性
PERSISTENT(持久化)
1、参数设置说明
-
非持久:当服务器宕机,消息不存在。
messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
-
持久化:当服务器宕机,消息仍然存在。
messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
2、持久的Queue
持久化消息,这是队列的默认传送模式,此模式保证这些消息只被传送一次和使用一次。
3、持久的Topic(类似微信公众号订阅发布)
先启动订阅再启动生产。
一定要先运行一次消费者,等于向MQ注册,类似我订阅了这个主题。
然后在运行生产者发送消息,此时,
无论消费者是否在线,都会接收到,不在线的话,下次连接的时候,会把没有收过的消息都接收下来。
private static final String ACTIVE_URL = "tcp://120.25.241.237:6161";
@Test
public void test() throws Exception {
//1 创建连接工厂,按照给定的url地址,采用默认用户名和密码
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVE_URL);
//2 通过连接工厂,获得连接Connection并启动访问
Connection connection = activeMQConnectionFactory.createConnection();
//3 创建会话 session
//两个参数,第一个叫事务/第二个叫签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4 创建目的地(具体是队列还是主题topic)
Topic topic = session.createTopic("topic");
//5 创建消息的生产者
MessageProducer messageProducer = session.createProducer(topic);
//启动持久化
messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
connection.start();
//6 通过使用messageProducer生产3条消息发送到MQ队列中
for (int i = 0; i < 6; i++) {
//7 创建消息,好比学生按照老师的要求写好的面试题消息
TextMessage textMessage = session.createTextMessage("msg---" + i);
//8 通过messageProducer 发送给mq
messageProducer.send(textMessage);
}
//9 关闭资源
messageProducer.close();
session.close();
connection.close();
}
/**
* 同步阻塞
* */
@Test
public void test1() throws Exception{
System.out.println("消费者一号");
//1 创建连接工厂,按照给定的url地址,采用默认用户名和密码
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVE_URL);
//2 通过连接工厂,获得连接Connection
Connection connection = activeMQConnectionFactory.createConnection();
connection.setClientID("消费者1号");
//3 创建会话 session
//两个参数,第一个叫事务/第二个叫签收
Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
//4 创建目的地(具体是队列还是主题topic)
Topic topic = session.createTopic("topic");
TopicSubscriber topicSubscriber = session.createDurableSubscriber(topic,"remark");
connection.start();
//5 创建消费者
javax.jms.Message message = topicSubscriber.receive();
while (null != message){
TextMessage textMessage = (TextMessage) message;
System.out.println("***********收到的持久化topic:"+textMessage.getText());
message = topicSubscriber.receive(3000L);
}
topicSubscriber.close();
session.close();
connection.close();
}
事务
producer提交时的事务
1、false
只要执行send,就进入到队列中。
关闭事务,第二个签收参数的设置需要有效。
2、true
先执行send再执行commit,消息才被真正的提交到队列中。
消息需要批量发送,需要缓冲区处理。
设置true的好处,程序正常批处理直接提交,程序异常可以回滚。
事务偏生产者/签收偏消费者
Acknowledge(签收)
1、自动签收(默认)
Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
2、手动签收
非事务
客户端需要调用acknowledge方法手动签收。
Session session = connection.createSession(false,Session.CLIENT_ACKNOWLEDGE);
//手动签收
textMessage.acknowledge();
事务
如果按照事务提交,默认自动签收。事务大于签收。
3、允许重复消息
事务与签收的关系
在事务性会话中,当一个事务被成功提交则消息被自动签收。如果事务回滚,则消息会被再次传送。
非事务会话中,消息何时被确认取决于创建会话的应答模式(自动不需要ack,手动需要ack)。
总结
JMS的点对点总结
点对点模型是基于队列的,生产者发消息到队列,消费者从队列接收消息,队列的存在使得消息的异步传输成为可能,和我们平时给朋友发送短信类似。
1、如果在Session关闭有部分消息已接受到但还没有被签收(acknowledge),那当消费者下次连接到相同的队列时,这些消息还会被再次接收。
2、队列可以长久地保存消息直到消费者收到消息。消费者不需要因为担心会丢失而时刻和队列保存激活的连接状态,充分体现了异步传输模式的优势。
JMS的发布订阅总结
JMS PUb/Sub模型定义了如何向一个内容节点发布和订阅消息,这些节点被称做topic。
主题可以被认为是消息的传输中介,发布者(publisher)发布消息到主题,订阅者(subscribe)从主题订阅消息。
主题使得消息订阅者和消息发布者保存互相独立,不需要接触即可保证消息的传送。
非持久订阅
非持久化订阅只有当客户端处于激活状态,也就是和MQ保持连接状态才能接收到发送到某个主题的消息。
如果消费者处于离线状态,生产者发送的主题消息将会丢失作废,消费者永远不会得到。
一句话:先要订阅注册才能接受到发布,只给订阅者发布消息。
持久订阅
客户端首先向MQ注册一个自己的身份ID识别号,当这个客户端处于离线时,生产者就会为这个ID保存所有发送到主题的消息,
当客户端再次连接到MQ时会根据消费者的ID得到所有当自己处于离线时发送到主题的消息。
非持久订阅状态下,不能恢复或者重新派送一个未签收的消息。
持久订阅才能恢复或者重新发送一个未签收的消息。
如何选择
当所有的消息必须被接收,则用持久订阅。当丢失消息能被容忍,则用非持久订阅。
Broker
相当于一个ActiveMQ服务器实例。(类对象的实例)
简单来说,Broker其实就是实现了用代码的形式启动ActiveMQ将MQ嵌入到java代码中,以便随时用随时启动。在用的时候再去启动这样能节省资源,也保证了可靠性。