文章目录
1.前言
微服务架构后,链式调用是我们在写程序时候的一般流程,为了完成一个整体功能会将其拆分成多个函数(或子模块),比如模块A调用模块B,模块B调用模块C,模块C调用模块D。但在大型分布式应用中,系统间的RPC交互繁杂,一个功能背后要调用上百个接口并非不可能,从单机架构过渡到分布式微服务架构的通例,这些架构会带来很多问题。
1、系统之间接口耦合严重;
2、高并发时容器冲垮;
3、 等待同步存在性能问题;
根据上述的几个问题,在设计系统时可以明确要达到的目标:
1、要做到系统解耦,当新的模块接进来时,可以做到代码改动最小:能解耦
2、设置流量缓冲池,可以让后端系统按照自身吞吐能力进行消费,不被冲垮:能削峰
3、强弱依赖梳理能将非关键调用链路的操作异步化并提升整体系统的吞吐能力:能异步
此时我们引入了消息中间件(MQ)
定义
面向消息的中间件(message-oriented
middleware)MOM能够很好的解决以上问题,是指利用高效可靠的消息传递机制与平台无关的数据交流,并基于数据通信来进行分布式系统的集成。
通过提供消息传递和消息排队模型在分布式环境下提供应用解耦,弹性伸缩,冗余存储、流量削峰,异步通信,数据同步等功能。
过程:
发送者把消息发送给消息服务器,消息服务器将消息存放在若干队列/主题topic中,在合适的时候,消息服务器回将消息转发给接受者。在这个过程中,发送和接收是异步的,也就是发送无需等待,而且发送者和接受者的生命周期也没有必然的关系;
尤其在发布pub/订阅sub模式下,也可以完成一对多的通信,即让一个消息有多个接受者。
功能
1、解耦
消息的生产者和消费者不必了解对方,也不需要同时在线,他们之间只需要约定一种协议,规定如何通信,即生产者如何发送消息,消费者如何接收消息。
2、异步
生产者发送一条消息,不需要等待消费者响应,生产者将消息发送到目的地(queue或topic)消息发送完成,生产者可以处理其他事情。
消费者可以监听或订阅该目的地。
一条消息最终可以发送给一个或多个消费者,而生产者无需等待响应,整个过程是异步的。
案例:
A系统想要发送一条消息给B系统,A系统将消息发送到MQ上,这时A就不关心这条消息“死活了”,此时B系统从MQ上消费这条消息,而至于B如何处理这条消息,什么时候处理,是否处理完毕,都是B系统的事情,这些与A系统无关。
整体架构
2.安装
环境:Linux-CentOS7
版本:apache-activemq-5.15.11
前提:安装JDK
步骤:
1、进入目录 /usr/local/src
cd /usr/local/src
2、上传ActiveMQ安装包
3、解压
tar -zxvf apache-activemq-5.15.11-bin.tar.gz
4、普通启动
服务器端默认端口号:61616
# 进入bin目录
cd apache-activemq-5.15.11/bin
# 启动activemq
./activemq start
# 检查是否启动成功
ps -ef|grep activemq|grep -v grep
5、普通关闭
# 关闭
./activemq stop
#重启
./activemq restart
6、带日志的启动
activemq本身是有日志的,我们可以自定义写日志。
# ./activemq start 启动命令
# >> 追加日志(文件不存在则创建)
# > 覆盖(文件不存在则创建)
# /usr/local/src/apache-activemq-5.15.11/run_activemq.log 日志文件路径
# run_activemq.log 日志文件名称
./activemq start >> /usr/local/src/apache-activemq-5.15.11/run_activemq.log
3.activemq控制台
客户端默认端口号:8161
http://192.168.64.3:8161/admin/
默认用户名和密码都是:admin
4.Java编码实现activemq通讯
1、JMS编码架构
JMS即Java消息服务(Java Message Service)应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。Java消息服务是一个与具体平台无关的API,绝大多数MOM提供商都对JMS提供支持。
编码步骤类似于JDBC
2、Destination(Queue和Topic)
一条消息发出,发送到MQ上,具体在MQ哪里,就是Destination。
Destination有两种模式:队列(queue)和主题(topic)。
Queue(队列):
- 一条消息发送到队列上,有且只能被一个消费者接收,即1对1。就像我们使用微信,给某个人发送消息,这条消息会先到达TXMQ(腾讯服务器),最终由某个人接收。
- 消息的生产者和消费者是没有时间相关性的。生产者发送消息时,无论消费者是否运行,消费者运行后都可以提取消息。类比微信,当你关机时,女朋友给你发了条消息,你开机启动微信后就可以看到那条消息,消息内容是:***
- 消息被消费者消费后,队列中不会再存储该条消息,所以消费者不会消费到已经消费的消息。
Topic(主题):
- 一条消息发送到主题上,可以被多个订阅了这个主题的消费者接收,即1对多。类比微信,我们订阅了英雄联盟公众号,当该公众号推送消息时,订阅了公众号的召唤师都可以接收到消息。
- 生产者和消费者在时间上是有相关性的,一个消费者只能消费自他订阅某个主题之后发布的消息。
- 生产者发布消息时是无状态的,加入没有人订阅就去生产消息,那么这条消息就是废消息,所以,一般情况下,先启动消费者再启动生产者。
5.入门案例
1、队列(Queue)
1、创建Maven项目
2、pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.xd</groupId>
<artifactId>activemq</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-all</artifactId>
<version>5.9.1</version>
</dependency>
</dependencies>
</project>
3、Producer
package com.xd.queue;
import java.util.UUID;
import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnectionFactory;
public class Producer {
public static final String BROKER_URL = "tcp://192.168.64.3:61616";
public static final String QUEUE_NAME = "queue01";
public static void main(String[] args) throws JMSException {
//1.创建连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(BROKER_URL);
//2.创建连接,并开启连接
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//3.创建会话
/*
* createSession(boolean transacted, int acknowledgeMode)
* transacted 事务 true开启事务 false不开启事务
* acknowledgeMode 签收方式
*/
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4.创建目的地(队列)
Queue queue = session.createQueue(QUEUE_NAME);
//5.创建消息生产者
MessageProducer messageProducer = session.createProducer(queue);
//6.生产消息,并发送
/*
* 随机生成UUID,截取前6位。
*/
TextMessage textMessage = session.createTextMessage("TextMessage : " + UUID.randomUUID().toString().substring(0, 6));
messageProducer.send(textMessage);
MapMessage mapMessage = session.createMapMessage();
mapMessage.setBoolean("client01", true);
messageProducer.send(mapMessage);
//7.关闭资源
messageProducer.close();
session.close();
connection.close();
System.out.println("消息发送成功");
}
}
4、控制台
控制台说明
- Number Of Pending Messages=等待消费的消息,这个是未出队列的数量,公式=总接收数-总出队列数。
- Number Of Consumers=消费者数量,消费者端的消费者数量。
- Messages Enqueued=进队消息数,进队列的总消息量,包括出队列的。这个数只增不减。
- Messages Dequeued=出队消息数,可以理解为是消费者消费掉的数量。
5、Consumer
package com.xd.queue;
import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnectionFactory;
public class Consumer {
public static final String BROKER_URL = "tcp://192.168.64.3:61616";
public static final String QUEUE_NAME = "queue01";
public static void main(String[] args) throws JMSException {
//1.创建连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(BROKER_URL);
//2.创建连接,并启动连接
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//3.创建会话
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4.创建目的地(队列)
Queue queue = session.createQueue(QUEUE_NAME);
//5.创建消费者
MessageConsumer messageConsumer = session.createConsumer(queue);
//6.接收消息
while(true) {
Message message = messageConsumer.receive();
if(message != null && message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
System.out.println("消费者收到消息:" + textMessage.getText());
}else if(message != null && message instanceof MapMessage) {
MapMessage mapMessage = (MapMessage) message;
System.out.println("消费者收到消息:" + mapMessage.getBoolean("client01"));
}else {
break;
}
}
//7.关闭资源
messageConsumer.close();
session.close();
connection.close();
}
}
6、控制台
7、生产者消费者代码细节
- 生产者生产消息可以有多种类型的消息
TextMessage textMessage = session.createTextMessage("TextMessage : " + UUID.randomUUID().toString().substring(0, 6));
messageProducer.send(textMessage);
MapMessage mapMessage = session.createMapMessage();
mapMessage.setBoolean("client01", true);
messageProducer.send(mapMessage);
BytesMessage createBytesMessage() throws JMSException;
MapMessage createMapMessage() throws JMSException;
Message createMessage() throws JMSException;
ObjectMessage createObjectMessage() throws JMSException;
ObjectMessage createObjectMessage(Serializable object) throws JMSException;
StreamMessage createStreamMessage() throws JMSException;
TextMessage createTextMessage() throws JMSException;
TextMessage createTextMessage(String text) throws JMSException;
对应的,消费者消费消息时需要用相同的类型接收。
Message message = messageConsumer.receive();
if(message != null && message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
System.out.println("消费者收到消息:" + textMessage.getText());
}else if(message != null && message instanceof MapMessage) {
MapMessage mapMessage = (MapMessage) message;
System.out.println("消费者收到消息:" + mapMessage.getBoolean("client01"));
}else {
break;
}
- 消费者接收消息(同步阻塞)
Message message = messageConsumer.receive();
这种方式是同步阻塞的,即如果没有消息,会一直在这里死等。我们可以观察一下Eclipse控制台,消费者一直处于运行状态。
我们还可以使用重载的方法(这种方法也是阻塞式的)
Message receive(long timeout) throws JMSException;
修改消费者代码,消费者等待4秒,如果没有消息会停止等待,此时我们观察Eclipse控制台,已经停止运行。
Message message = messageConsumer.receive(4000L);
- 消费者接收消息(异步非阻塞监听)
修改消费者第6步接收消息
//6.接收消息
// while(true) {
// //Message message = messageConsumer.receive();
// Message message = messageConsumer.receive(4000L);
// if(message != null && message instanceof TextMessage) {
// TextMessage textMessage = (TextMessage) message;
// System.out.println("消费者收到消息:" + textMessage.getText());
// }else if(message != null && message instanceof MapMessage) {
// MapMessage mapMessage = (MapMessage) message;
// System.out.println("消费者收到消息:" + mapMessage.getBoolean("client01"));
// }else {
// break;
// }
// }
messageConsumer.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
if(message != null && message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("消费者收到消息:" + textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}else if(message != null && message instanceof MapMessage) {
MapMessage mapMessage = (MapMessage) message;
try {
System.out.println("消费者收到消息:" + mapMessage.getBoolean("client01"));
} catch (JMSException e) {
e.printStackTrace();
}
}
}
});
System.in.read();
注意一定要加上System.in.read(); 作用让Eclipse控制台处于运行状态才能监听消息。
2、主题(Topic)
主题模式下,需要先启动消费者,再启动生产者。否则如果先启动生产者,该主题没有消费者订阅,那么生产者生产的消息就是一条废消息。
1、Consumer
package com.xd.topic;
import java.io.IOException;
import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;
import org.apache.activemq.ActiveMQConnectionFactory;
public class Consumer {
public static final String BROKER_URL = "tcp://192.168.64.3:61616";
public static final String TOPIC_NAME = "topic01";
public static void main(String[] args) throws JMSException, IOException {
//1.创建连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(BROKER_URL);
//2.创建连接,并启动连接
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//3.创建会话
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4.创建目的地(主题)
Topic topic = session.createTopic(TOPIC_NAME);
//5.创建生产者
MessageConsumer messageConsumer = session.createConsumer(topic);
//6.接收消息
messageConsumer.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
if(message != null && message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("消费者收到消息: " + textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
});
System.in.read();
//7.关闭资源
messageConsumer.close();
session.close();
connection.close();
System.out.println("消息发送成功");
}
}
2、启动消费者控制台
3、Producer
package com.xd.topic;
import java.util.UUID;
import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;
import org.apache.activemq.ActiveMQConnectionFactory;
public class Producer {
public static final String BROKER_URL = "tcp://192.168.64.3:61616";
public static final String TOPIC_NAME = "topic01";
public static void main(String[] args) throws JMSException {
//1.创建连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(BROKER_URL);
//2.创建连接,并启动连接
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//3.创建会话
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4.创建目的地(主题)
Topic topic = session.createTopic(TOPIC_NAME);
//5.创建生产者
MessageProducer messageProducer = session.createProducer(topic);
//6.生产消息
TextMessage textMessage = session.createTextMessage("Topic-TextMessage : " + UUID.randomUUID().toString().substring(0, 6));
messageProducer.send(textMessage);
//7.关闭资源
messageProducer.close();
session.close();
connection.close();
System.out.println("消息发送成功");
}
}
4、启动生产者控制台
3、两种模式(Queue和Topic)比较
比较项目 | Topic主题模式 | Queue队列模式 |
---|---|---|
工作模式 | "订阅-发布"模式,如果当前没有订阅者,消息将会被丢弃,如果有多个订阅者,那么这些订阅者都会收到消息 | "负载均衡"模式,如果当前没有消费者,消息也不会丢弃;如果有多个消费者,那么一条消息也只会发送给其中一个消费者,并且要求消费者ack信息 |
有无状态 | 无状态 | Queue数据默认会在mq服务器上已文件形式保存,比如Active MQ一般保存在$AMQ_HOME\data\kr-store\data下面,也可以配置成DB存储 |
传递完整性 | 如果没有订阅者,消息会被丢弃 | 消息不会被丢弃 |
处理效率 | 由于消息要按照订阅者的数量进行复制,所以处理性能会随着订阅者的增加而明显降低,并且还要结合不同消息协议自身的性能差异 | 由于一条消息只发送给一个消费者,所以就算消费者再多,性能也不会有明显降低。当然不同消息协议的具体性能也是有差异的 |
消费者3种问题
/*
* 消费者3种问题
* 1.先启动生产者,只启动1号消费者 问题:1号消费者能接受到消息吗?
* YES
* 2.先启动生产者,接着启动1号消费者,再启动2号消费者 问题:1号消费者和2号消费者能接受到消息吗?
* 1号消费者YES
* 2号消费者NO
* 3.先启动2个消费者,再启动生产者生产6条消息 问题:2个消费者消费情况如何?
* 2个消费者分别接收到3条消息
*/
6.JMS规范和落地产品
1、什么是JMS
Java Message Service(Java消息服务是JavaEE中的一个技术)。
Java消息服务指的是两个应用程序之间进行异步通信的API,它为标准协议和消息服务提供了一组通用接口,包括创建、发送、读取消息等,用于支持Java应用程序开发。在JavaEE中,当两个应用程序使用JMS进行通信时,它们之间不是直接相连的,而是通过一个共同的消息收发服务组件关联起来以达到解耦/异步/削峰的效果。
备注:JavaEE是一套使用Java进行企业级应用开发的大家一致遵循的13个核心规范工业标准。JavaEE平台提供了一个基于组件的方法来加快设计,开发。装配及部署企业应用程序。
1,JDBC(Java Databease)数据库连接 2,JNDI(Java Naming and Directory Interfaces)Java的命令和目录接口 3,EJB(Enterprise JavaBean) 4,RMI(Remote Method
Invoke)远程方法调用 5,Java IDL(Interface Description Language)/CORBA(Common
Object Broker Architecture)接口定义语言/共用对象请求代理程序体系结构 6,JSP(Java Server
Page) 7,Servlet 8,XML(Extensible Markup Language)可标记白标记语言
2、MQ中间件的其他产品
3、JMS组成结构
- JMS Provier: 实现JMS接口和规范的消息中间件,也就是我们说的MQ服务器。
- JMS Producer: 消息生产者,创建和发送JMS消息的客户端应用。
- JMS Consumer: 消息消费者,接收和处理JMS消息的客户端应用。
- JMS Message:
- 消息头
- 消息体
- 消息属性
上面已经说过Producer和Consumer了,现在说一下Message。
1、消息头
消息头有如下属性可以设置
JMSDestination: 消息发送的目的地,主要是指Queue和Topic;
可以在创建生产者时指定目的地,表示该生产者发送的所有消息都会到达这个目的地。
MessageProducer messageProducer = session.createProducer(queue);
也可以在发送消息时指定目的地,表示这条消息会到达这个目的地。
TextMessage textMessage = session.createTextMessage("TextMessage : " + UUID.randomUUID().toString().substring(0, 6));
messageProducer.send(queue, textMessage);
JMSDeliveryMode: 持久模式和非持久模式。队列默认情况下消息是持久模式。
一条持久性的消息:应该被传送“一次仅仅一次”,这就意味着如果JMS提供者出现故障,该消息并不会丢失,它会在服务器恢复之后再次传递。
MessageProducer messageProducer = session.createProducer(queue);
messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
这种方式表示该队列中的消息是持久的。
启动Producer,查看控制台
关闭服务器(模拟服务器宕机)
启动服务器,查看控制台,消息还在
启动消费者,可以正常消费消息
一条非持久的消息:最多会传递一次,这意味着服务器出现故障,该消息将会永远丢失。
MessageProducer messageProducer = session.createProducer(queue);
messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
这种方式表示队列中的消息是非持久的。
启动Producer,查看控制台
关闭服务器(模拟服务器宕机)
启动服务器,查看控制台,消息丢失
这种方式是对这个Producer生产的消息都是用非持久模式,如果想要对某一条消息使用非持久模式怎么办呢?你可能会使用如下方式
TextMessage textMessage = session.createTextMessage("TextMessage : " + UUID.randomUUID().toString().substring(0, 6));
textMessage.setJMSDeliveryMode(DeliveryMode.NON_PERSISTENT);
很遗憾,我测试过,这种方式无效,至于为什么,我也没有搞清楚,知道的朋友可以在下方留言。当然正常情况下我们都是使用的持久模式,至于非持久模式,也不用纠结。
JMSExpiration:可以设置消息在一定时间后过期,默认是永不过期。
消息过期时间,等于Destination的send方法中的timeToLive值加上发送时刻的GMT时间值。
如果timeToLive值等于0,则JMSExpiration被设为0,表示该消息永不过期。
如果发送后,在消息过期时间之后还没有被发送到目的地,则该消息被清除
JMSPriority:消息优先级,从0-9十个级别,0-4是普通消息5-9是加急消息。
JMS不要求MQ严格按照这十个优先级发送消息,但必须保证加急消息要先于普通消息到达。默认是4级。
JMSMessageID:唯一标识每个消息的标识由MQ产生。
2、消息体
封装具体的消息数据。
有5中消息格式
TxtMessage:普通字符串消息,包含一个String
MapMessage:一个Map类型的消息,key为String类型,而值为Java基本类型或String类型。
BytesMessage:二进制数组消息,包含一个byte[]。
StreamMessage:Java数据流消息,用标准流操作来顺序填充和读取。
ObjectMessage:对象消息,包含一个可序列化的Java对象。
Producer代码
TextMessage textMessage = session.createTextMessage("TextMessage : " + UUID.randomUUID().toString().substring(0, 6));
messageProducer.send(textMessage);
MapMessage mapMessage = session.createMapMessage();
mapMessage.setBoolean("boolean", false);
mapMessage.setDouble("double", 2.0D);
mapMessage.setString("String", "activemq");
messageProducer.send(mapMessage);
Consumer代码
messageConsumer.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
if(message != null && message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("消费者收到消息:" + textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
if(message != null && message instanceof MapMessage) {
MapMessage mapMessage = (MapMessage) message;
try {
System.out.println("消费者收到消息:"
+ mapMessage.getBoolean("boolean")
+ "\n" + mapMessage.getDouble("double")
+ "\n" + mapMessage.getString("String"));
} catch (JMSException e) {
e.printStackTrace();
}
}
}
});
注意:发送和接收的消息体类型必须一致对应
3、消息属性
如果需要除消息字段以外的值,那么可以使用消息属性。
识别/去重/重点标注等操作非常有用的方法。
Producer代码
TextMessage textMessage = session.createTextMessage("TextMessage : " + UUID.randomUUID().toString().substring(0, 6));
textMessage.setStringProperty("client01", "vip");
textMessage.setBooleanProperty("is_vip", true);
messageProducer.send(textMessage);
Consuemr代码
messageConsumer.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
if(message != null && message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("消费者收到消息:" + textMessage.getText()
+ "\n" + textMessage.getStringProperty("client01")
+ "\n" + textMessage.getBooleanProperty("is_vip"));
} catch (JMSException e) {
e.printStackTrace();
}
}
}
});
4、JMS可靠性
可靠性从三个方面考虑:Persistent(持久性)、Tansaction(事务)、acknowledge(签收)。
1、Persistent(持久性)
队列的持久性在上面已经说过了,这里不在赘述。
下面说一下主题的持久性。
要先启动消费者订阅主题,再启动订阅的生产者。
Producer代码
package com.xd.topic.persist;
import javax.jms.Connection;
import javax.jms.DeliveryMode;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;
import org.apache.activemq.ActiveMQConnectionFactory;
public class Producer {
public static final String BROKER_URL = "tcp://192.168.64.3:61616";
public static final String TOPIC_NAME = "topic-persist";
public static void main(String[] args) throws JMSException {
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(BROKER_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();
TextMessage textMessage = session.createTextMessage("persist-TextMessage");
messageProducer.send(textMessage);
messageProducer.close();
session.close();
connection.close();
System.out.println("生产者发送消息到MQ成功");
}
}
Consumer代码
package com.xd.topic.persist;
import java.io.IOException;
import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;
import javax.jms.TopicSubscriber;
import org.apache.activemq.ActiveMQConnectionFactory;
public class Consumer {
public static final String BROKER_URL = "tcp://192.168.64.3:61616";
public static final String TOPIC_NAME = "topic-persist";
public static void main(String[] args) throws JMSException, IOException {
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(BROKER_URL);
Connection connection = activeMQConnectionFactory.createConnection();
connection.setClientID("client01");
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Topic topic = session.createTopic(TOPIC_NAME);
//创建主题的订阅者
TopicSubscriber topicSubscriber = session.createDurableSubscriber(topic, "张三");
MessageConsumer messageConsumer = session.createConsumer(topic);
//开启连接
connection.start();
topicSubscriber.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
if(message != null && message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("MessageListener---" + textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
});
System.in.read();
messageConsumer.close();
session.close();
connection.close();
}
}
启动消费者,消费者在线,已订阅主题
启动生产者,消费者(已订阅)正常消费消息
再次运行生产者,消费者(已订阅)正常消费消息
关闭消费者,消费者(已订阅)离线
运行生产者,离线(已订阅)消费者有一条消息待处理
运行消费者(已订阅),消费者可以正常消费消息
2、事务和签收(transaction)
第一个参数就是事务,false不开启事务,true开启事务。
当为false时,生产者发送的消息自动进入队列。
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
当为true时,必须提交事务,生产者发送的消息才能进入队列。
session.commit();
第二个参数就是签收方式,有4种签收方式,常用的两种。
//自动签收
static final int AUTO_ACKNOWLEDGE = 1;
//客户端手动签收
static final int CLIENT_ACKNOWLEDGE = 2;
//自动批量确认
static final int DUPS_OK_ACKNOWLEDGE = 3;
//事务提交并确认
static final int SESSION_TRANSACTED = 0;
在生产者当中
如果开启了事务,即第一个参数为true时,签收方式(第二个参数)是什么影响不大。只有提交了事务,消息才能进入队列,否则消息不能进入队列。既然是开启了事务,所以我们一般设置第二个参数为SESSION_TRANSACTED。
如果第二个参数设置为SESSION_TRANSACTED,那么必须开启事务,否则将报错。
如果第二个参数设置为除了SESSION_TRANSACTED以外其他的3个,事务可以不开启,也可以开启,如果开启事务,必须提交,消息才能进入队列。
在消费者当中
如果开启了事务,和生产者一样。
第二个参数设置为SESSION_TRANSACTED,那么必须开启事务,否则将报错。
如果第二个参数设置为CLIENT_ACKNOWLEDGE,必须手动签收,否则消息将一直存放在队列中,可能被重复消费。
messageConsumer.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
if(message != null && message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
try {
//手动签收消息
textMessage.acknowledge();
System.out.println("消费者收到消息:" + textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
if(message != null && message instanceof MapMessage) {
MapMessage mapMessage = (MapMessage) message;
try {
//System.out.println("消费者收到消息:" + mapMessage.getBoolean("client01"));
System.out.println("消费者收到消息:"
+ mapMessage.getBoolean("boolean")
+ "\n" + mapMessage.getDouble("double")
+ "\n" + mapMessage.getString("String"));
} catch (JMSException e) {
e.printStackTrace();
}
}
}
});
3、事务和签收的关系
- 在事务性会话中,事务被提交则消息自动签收;
- 如果事务回滚,则消息会被再次传送;
- 非事务性会话中,消息何时被确认,取决于创建会话时的应答方式(acknowledgement model)。
5、JMS点对点总结
- 点对点模型是基于队列的,生产者发送消息到队列,消费者从队列接收消息,队列的存在使得消息的异步传输成为可能。和我们平时给朋友发送短信类似。
- 如果在Session关闭时有部分消息被收到但还没有被签收(acknowledge),那当消费者下次连接到相同的队列时,这些消息还会被再次接收
- 队列可以长久的保存消息直到消费者收到消息。消费者不需要因为担心消息会丢失而时刻和队列保持激活的链接状态,充分体现了异步传输模式的优势
6、JMS的发布订阅总结 - JMS Pub/Sub 模型定义了如何向一个内容节点发布和订阅消息,这些节点被称作topic
- 主题可以被认为是消息的传输中介,发布者(publisher)发布消息到主题,订阅者(subscribe)从主题订阅消息。
- 主题使得消息订阅者和消息发布者保持互相独立不需要解除即可保证消息的传送
- 非持久订阅
只有当客户端处于激活状态,也就是和MQ保持连接状态才能收发到某个主题的消息。
如果消费者处于离线状态,生产者发送的主题消息将会丢失作废,消费者永远不会收到。
一句话:先订阅注册才能接受到发布,只给订阅者发布消息。 - 持久订阅
客户端首先向MQ注册一个自己的身份ID识别号,当这个客户端处于离线时,生产者会为这个ID保存所有发送到主题的消息,当客户再次连接到MQ的时候,会根据消费者的ID得到所有当自己处于离线时发送到主题的消息。
非持久订阅状态下,不能恢复或重新派送一个未签收的消息。
持久订阅才能恢复或重新派送一个未签收的消息。 - 持久订阅和非持久订阅用哪个?
当所有的消息必须被接收,则用持久订阅。
当消息丢失能够被容忍,则用非持久订阅。
7.Broker
1、什么是Broker
用ActiveMQ Broker作为独立的消息服务器来构建Java应用。
ActiveMQ也支持在vm中通信基于嵌入的broker,能够无缝的集成其他java应用。
相当于一个ActiveMQ服务器实例。
说白了,Broker其实就是实现了用代码的形式启动ActiveMQ将MQ嵌入到Java代码中,以便随时用随时启动,在用的时候再去启动这样能节省了资源,也保证了可用性。
2、嵌入式Broker
package com.xd.broker;
import org.apache.activemq.broker.BrokerService;
public class EmbedBroker {
public static void main(String[] args) throws Exception {
BrokerService brokerService = new BrokerService();
//启用监控
brokerService.setUseJmx(true);
//绑定地址
brokerService.addConnector("tcp://localhost:61616");
//启动
brokerService.start();
}
}
3、测试
- 先关闭Linux上的ActiveMQ
-修改生产者和消费者代码,使用Broker地址
public static final String BROKER_URL = "tcp://localhost:61616";
- 启动Broker
- 启动生产者
- 启动消费者
8.Spring整合ActiveMQ
1、创建Maven工程
2、pom.xml
依赖说明:
spring-core:Spring核心
activemq-all:Spring整合activemq
spring-jms:提供JmsTemplate
其实spring-core依赖不需要加入,因为使用了maven管理依赖,spring-jms自动将依赖的Spring的jar包引进来了。但是如果加入了spring-core那么和spring-jms的版本号一定要一致。我们可以点击spring-jms查看如下。
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.xd</groupId>
<artifactId>activemq-spring</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<!-- spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.23.RELEASE</version>
</dependency>
<!-- activemq依赖 -->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-all</artifactId>
<version>5.9.1</version>
</dependency>
<!-- spring-jms -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>4.3.23.RELEASE</version>
</dependency>
</dependencies>
</project>
3、spring配置文件applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/jms https://www.springframework.org/schema/jms/spring-jms-4.3.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 包扫描 -->
<context:component-scan base-package="com.xd.activemq"/>
<!-- 连接工厂 -->
<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://192.168.64.3:61616"/>
</bean>
<!-- 点对点队列 -->
<bean id="destinationQueue" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg index="0" value="spring-activemq-queue" />
</bean>
<!-- JmsTemplate -->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="connectionFactory" />
<property name="defaultDestination" ref="destinationQueue" />
<property name="messageConverter">
<bean class="org.springframework.jms.support.converter.SimpleMessageConverter"/>
</property>
</bean>
</beans>
4、生产者(队列)
package com.xd.activemq;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Component;
@Component
public class Producer {
@Autowired
private JmsTemplate jmsTemplate;
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
Producer producer = ac.getBean(Producer.class);
producer.jmsTemplate.convertAndSend("Srping整合ActiveMQ");
System.out.println("消息发送成功");
}
}
5、运行生产者,查看控制台
6、Consumer(队列)
使用receive,同步阻塞
package com.xd.activemq;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Component;
@Component
public class Consumer {
@Autowired
private JmsTemplate jmsTemplate;
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
Consumer consumer = ac.getBean(Consumer.class);
String message = (String) consumer.jmsTemplate.receiveAndConvert();
System.out.println(message);
}
}
7、运行消费者,查看控制台
使用监听器,异步非阻塞
自定义监听器,实现接口MessageListener
package com.xd.activemq.config;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
import org.springframework.stereotype.Component;
@Component
public class MyMessageListener implements MessageListener {
@Override
public void onMessage(Message message) {
if(message != null && message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("消费者收到消息:" + textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
在spring配置文件中配置监听器
<bean id="jmsMessageListenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destination" ref="destinationQueue"/>
<property name="messageListener" ref="myMessageListener"/>
</bean>
配置监听器后,不需要手动运行消费者,容器启动后,这个监听器就会运行,监听队列消息。
运行生产者,查看控制台
8、主题
只需要修改配置文件即可
添加一个主题bean
修改jmsTemplate的defaultDestination
修改jmsMessageListenerContainer的destination
<!-- 主题 -->
<bean id="destinationTopic" class="org.apache.activemq.command.ActiveMQTopic">
<constructor-arg index="0" value="spring-active-topic"/>
</bean>
<!-- JmsTemplate -->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="connectionFactory" />
<property name="defaultDestination" ref="destinationTopic" />
<property name="messageConverter">
<bean class="org.springframework.jms.support.converter.SimpleMessageConverter"/>
</property>
</bean>
<bean id="jmsMessageListenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destination" ref="destinationTopic"/>
<property name="messageListener" ref="myMessageListener"/>
</bean>
运行生产者,查看控制台
9.SpringBoot整合ActiveMQ
队列
生产者
1、创建maven工程
2、pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.xd</groupId>
<artifactId>activemq-producer-springboot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
<version>2.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.1.5.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<!--跳过测试类打包 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3、springboot配置文件application.yml
# 端口号
server:
port: 6666
# activemq 配置
spring:
activemq:
broker-url: tcp://192.168.64.3:61616
user: admin
password: admin
jms:
pub-sub-domain: false # false表示队列 true表示主题
# 定义队列名称
myqueue: springboot-activemq-queue
4、主启动类
package com.xd;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootAppRun {
public static void main(String[] args) {
SpringApplication.run(SpringBootAppRun.class, args);
}
}
5、配置类BeanConfig
package com.xd.activemq.config;
import javax.jms.Queue;
import org.apache.activemq.command.ActiveMQQueue;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class BeanConfig {
//读取配置文件中的自定义队列名称
@Value("${myqueue}")
private String queue;
/**
* 将队列加入Spring容器
* @return
*/
@Bean
public Queue queue() {
return new ActiveMQQueue(queue);
}
}
6、生产者
package com.xd.activemq.producer;
import java.util.UUID;
import javax.jms.Queue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.stereotype.Component;
@Component
public class Producer {
//JmsMessagingTemplate模板
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
//队列
@Autowired
private Queue queue;
/**
* 生产消息
*/
public void produceMessage() {
jmsMessagingTemplate.convertAndSend(queue, "SpringBoot整合ActiveMQ:"
+ UUID.randomUUID().toString().substring(0, 6));
System.out.println("消息发送成功");
}
}
7、测试类,测试生产者
package com.xd;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.xd.activemq.producer.Producer;
/**
* Springboot测试类
* @author LenovoSVIP
*
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class TestProducer {
@Autowired
private Producer producer;
@Test
public void test() {
//调用Producer的生产消息的方法
producer.produceMessage();
}
}
8、运行测试方法test(),查看控制台
9、生产者定时投递消息
在生产者中添加如下代码
@Scheduled(fixedDelay = 3000L)//每隔3秒执行一次
public void produceScheduledMessage() {
jmsMessagingTemplate.convertAndSend(queue, "SpringBoot整合ActiveMQ:"
+ UUID.randomUUID().toString().substring(0, 6));
System.out.println("消息发送成功");
}
主启动类添加注解 @EnableScheduling 开启定时任务
package com.xd;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling //开启定时任务
public class SpringBootAppRun {
public static void main(String[] args) {
SpringApplication.run(SpringBootAppRun.class, args);
}
}
10、运行主启动类,查看控制台
消费者
1、创建Maven工程
2、pom.xml
和生产者一致。
3、配置文件application.yml
注意端口号不要和生产者重复
# 端口号
server:
port: 6667
# activemq 配置
spring:
activemq:
broker-url: tcp://192.168.64.3:61616
user: admin
password: admin
jms:
pub-sub-domain: false # false表示队列 true表示主题
# 定义队列名称
myqueue: springboot-activemq-queue
4、主启动类
和生产者一致。
5、配置类BeanConfig
和生产者一致。
6、消费者
package com.xd.activemq.producer;
import javax.jms.JMSException;
import javax.jms.TextMessage;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
@Component
public class Consumer {
/**
* 消费消息
* @throws JMSException
*/
@JmsListener(destination = "${myqueue}")//自动监听队列
public void consumeMessage(TextMessage textMessage) throws JMSException {
System.out.println("消费者收到消息: " + textMessage.getText());
}
}
7、启动消费者和生产者,查看控制台
非持久订阅主题
生产者
1、创建Maven工程
2、pom.xml
同队列的生产者一致。
3、配置文件application.yml
注意修改端口号
将spring.jms.pub-sub-domain修改为true
定义主题名称
# 端口号
server:
port: 6668
# activemq 配置
spring:
activemq:
broker-url: tcp://192.168.64.3:61616
user: admin
password: admin
jms:
pub-sub-domain: true # false表示队列 true表示主题
# 定义主题名称
mytopic: springboot-activemq-topic
4、主启动类
package com.xd;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling //开启定时任务
public class SpringBootAppRun {
public static void main(String[] args) {
SpringApplication.run(SpringBootAppRun.class, args);
}
}
5、配置类BeanConfig
package com.xd.activemq.config;
import javax.jms.Topic;
import org.apache.activemq.command.ActiveMQTopic;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class BeanConfig {
//读取配置文件中的自定义主题名称
@Value("${mytopic}")
private String topic;
/**
* 将主题加入Spring容器
* @return
*/
@Bean
public Topic topic() {
return new ActiveMQTopic(topic);
}
}
6、生产者
package com.xd.activemq.producer;
import java.util.UUID;
import javax.jms.Topic;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class Producer {
//JmsMessagingTemplate模板
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
//队列
@Autowired
private Topic topic;
/**
* 生产消息
*/
@Scheduled(fixedDelay = 3000L)//每隔3秒执行一次
public void produceScheduledMessage() {
jmsMessagingTemplate.convertAndSend(topic, "SpringBoot整合ActiveMQ:"
+ UUID.randomUUID().toString().substring(0, 6));
System.out.println("消息发送成功");
}
}
注意: 先启动消费者,再启动生产者,否则生产者发送的消息就是一条废消息。
消费者
1、创建Maven工程
不再赘述。
2、pom.xml
不再赘述。
3、配置文件application.yml
修改端口号
# 端口号
server:
port: 6669
4、主启动类
不再赘述
5、配置类BeanConfig
和生产者一致。
6、消费者
package com.xd.activemq.consumer;
import javax.jms.JMSException;
import javax.jms.TextMessage;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
@Component
public class Consumer {
@JmsListener(destination = "${mytopic}")
public void consumeMessage(TextMessage textMessage) throws JMSException {
System.out.println("消费者收到消息:" + textMessage.getText());
}
}
7、运行,查看控制台
先启动消费者,再启动生产者
持久订阅主题
修改主题的消费者
BeanConfig
package com.xd.activemq.config;
import javax.jms.ConnectionFactory;
import javax.jms.Topic;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.command.ActiveMQTopic;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
@Configuration
public class BeanConfig {
//读取配置文件中的自定义队列名称
@Value("${mytopic}")
private String topic;
@Value("${spring.activemq.broker-url}")
private String brokerURL;
@Value("${spring.activemq.user}")
private String username;
@Value("${spring.activemq.password}")
private String password;
/**
* 将队列加入Spring容器
* @return
*/
@Bean
public Topic topic() {
return new ActiveMQTopic(topic);
}
/**
* activemq连接工厂
* @return
*/
@Bean
public ConnectionFactory connectionFactory() {
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(brokerURL);
activeMQConnectionFactory.setUserName(username);
activeMQConnectionFactory.setPassword(password);
return activeMQConnectionFactory;
}
/**
* JMS 队列的监听容器工厂
* @return
*/
@Bean(name = "jmsListenerContainerFactory")
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
DefaultJmsListenerContainerFactory defaultJmsListenerContainerFactory = new DefaultJmsListenerContainerFactory();
defaultJmsListenerContainerFactory.setConnectionFactory(connectionFactory());
defaultJmsListenerContainerFactory.setSubscriptionDurable(true);
defaultJmsListenerContainerFactory.setClientId("client01");
return defaultJmsListenerContainerFactory;
}
}
Consumer
package com.xd.activemq.consumer;
import javax.jms.JMSException;
import javax.jms.TextMessage;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
@Component
public class Consumer {
@JmsListener(destination = "${mytopic}",
containerFactory = "jmsListenerContainerFactory",//监听容器工厂
subscription = "张三")//持久订阅者名称
public void consumeMessage(TextMessage textMessage) throws JMSException {
System.out.println("消费者收到消息:" + textMessage.getText());
}
}
先启动消费者,查看控制台,client01张三已经持久订阅主题spring-activemq-topic
再启动生产者,查看控制台,消费者可以正常消费消息
关闭消费者,查看控制台,client01张三离线状态 ,依然能接收到消息,待处理
再启动消费者,client01张三上线,入队和出队数量一致,可以正常消费离线时收到的消息。
10.ActiveMQ的通讯协议
主要的协议
在ActiveMQ安装目录下的conf目录下配置文件activemq.xml中查看
默认的通讯协议和端口号
<transportConnector name="openwire" uri="tcp://0.0.0.0:61616?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
ActiveMQ支持的通讯协议有如下
<transportConnector name="openwire" uri="tcp://0.0.0.0:61616?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
<transportConnector name="amqp" uri="amqp://0.0.0.0:5672?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
<transportConnector name="stomp" uri="stomp://0.0.0.0:61613?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
<transportConnector name="mqtt" uri="mqtt://0.0.0.0:1883?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
<transportConnector name="ws" uri="ws://0.0.0.0:61614?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
在上文给出的配置信息中,
URI描述信息的头部都是采用协议名称:例如
描述amqp协议的监听端口时,采用的URI描述格式为“amqp://······”;
描述Stomp协议的监听端口时,采用URI描述格式为“stomp://······”;
唯独在进行openwire协议描述时,URI头却采用的“tcp://······”。这是因为ActiveMQ中默认的消息协议就是openwire。
1.TCP默认
Transmission Control Protocol(TCP)
1.这是默认的Broker配置,TCP的Client监听端口61616
2.在网络传输数据前,必须要先序列化数据,消息是通过一个叫wire protocol的来序列化成字节流,ActiveMQ将其称之为openwire
3.TCP连接的URI形式如:tcp://HostName:port?key=value&key=value,后面的参数是可选的。
4.TCP传输的的优点:
(4.1)TCP协议传输可靠性高,稳定性强
(4.2)高效率:字节流方式传递,效率很高
(4.3)有效性、可用性:应用广泛,支持任何平台
5.关于Transport协议的可选配置参数可以参考官网http://activemq.apache.org/configuring-version-5-transports.html
2.NIO
New I/O API Protocol(NIO)
1.NIO协议和TCP协议类似,但NIO更侧重于底层的访问操作。它允许开发人员对同一资源可有更多的client调用和服务器端有更多的负载。
2.适合使用NIO协议的场景:
(2.1)可能有大量的Client去连接到Broker上,一般情况下,大量的Client去连接Broker是被操作系统的线程所限制的。因此,NIO的实现比TCP需要更少的线程去运行,所以建议使用NIO协议。
(2.2)可能对于Broker有一个很迟钝的网络传输,NIO比TCP提供更好的性能。
3.NIO连接的URI形式:nio://hostname:port?key=value&key=value
4.关于Transport协议的可选配置参数可以参考官网http://activemq.apache.org/configuring-version-5-transports.html
3.AMQP
Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同开发语言等条件限制。
4.Stomp
STOMP,Streaming Text Orientation Message Protocol,是流文本定向消息协议,是一种为MOM(Message Oriented Middleware,面向消息中间件)设计的简单文本协议。
5.SSL
Secure Sockets Layer Protocol
6.MQTT
案例:
https://github.com/fusesource/mqtt-client
MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)是IBM开发的一个即时通讯协议,有可能成为物联网的重要组成部分。该协议支持所有平台,几乎可以把所有联网物品和外部连接起来,被用来当作传感器和致动器(比如通过Twitter让房屋联网)的通信协议。
7.WS协议(websocket)
总结
NIO案例
1.官网
http://activemq.apache.org/configuring-version-5-transports.html
2.修改配置文件activemq.xml
修改配置文件后需要重启activemq服务器
<transportConnectors>
<transportConnector name="nio" uri="nio://0.0.0.0:61618?trace=true" />
</transportConnectors>
如果你不特别指定ActiveMQ的网络监听端口,那么这些端口都讲使用BIO网络IO模型
所以为了首先提高单节点的网络吞吐性能,我们需要明确指定ActiveMQ网络IO模型。
如下所示:URI格式头以“nio”开头,表示这个端口使用以TCP协议为基础的NIO网络IO模型。
3.修改生产者和消费者代码
生产者
只需要修改地址就可以了
public static final String BROKER_URL = “nio://192.168.64.3:61618”;
package com.xd.queue;
import java.util.UUID;
import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnectionFactory;
public class Producer {
//使用以TCP协议为基础的NIO网络IO模型
public static final String BROKER_URL = "nio://192.168.64.3:61618";
public static final String QUEUE_NAME = "queue01";
public static void main(String[] args) throws JMSException {
//1.创建连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(BROKER_URL);
//2.创建连接,并开启连接
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//3.创建会话
/*
* createSession(boolean transacted, int acknowledgeMode)
* transacted 事务 true开启事务 false不开启事务
* acknowledgeMode 签收方式
*/
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4.创建目的地(队列)
Queue queue = session.createQueue(QUEUE_NAME);
//5.创建消息生产者
MessageProducer messageProducer = session.createProducer(queue);
//6.生产消息,并发送
/*
* 随机生成UUID,截取前6位。
*/
TextMessage textMessage = session.createTextMessage("TextMessage : " + UUID.randomUUID().toString().substring(0, 6));
messageProducer.send(textMessage);
//session.commit();
//7.关闭资源
messageProducer.close();
session.close();
connection.close();
System.out.println("消息发送成功");
}
}
消费者
只需要修改地址就可以了
public static final String BROKER_URL = “nio://192.168.64.3:61618”;
package com.xd.queue;
import java.io.IOException;
import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnectionFactory;
public class Consumer {
public static final String BROKER_URL = "nio://192.168.64.3:61618";
public static final String QUEUE_NAME = "queue01";
public static void main(String[] args) throws JMSException, IOException {
//1.创建连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(BROKER_URL);
//2.创建连接,并启动连接
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//3.创建会话
Session session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
//4.创建目的地(队列)
Queue queue = session.createQueue(QUEUE_NAME);
//5.创建消费者
MessageConsumer messageConsumer = session.createConsumer(queue);
//6.接收消息
messageConsumer.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
if(message != null && message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
try {
textMessage.acknowledge();
System.out.println("消费者收到消息:" + textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
});
System.in.read();
//7.关闭资源
messageConsumer.close();
session.close();
connection.close();
}
}
启动生产者和消费者,查看控制台
增强型NIO案例
URI格式以"nio"开头,代表这个端口使用TCP协议为基础的NIO网络模型。
但是这样的设置方式,只能使这个端口支持Openwire协议。我们怎么能够让这个端口既支持NIO网络模型,又让他支持多个协议呢?
1.官网:http://activemq.apache.org/auto
2.修改配置文件activemq.xml
在标签中添加如下配置(修改配置文件后重启activemq服务器)
<transportConnector name="auto+nio" uri="auto+nio://0.0.0.0:61680"/>
3.修改生产者和消费者代码
public static final String BROKER_URL = “nio://192.168.64.3:61680”;
启动生产者和消费者,查看控制台
public static final String BROKER_URL = “tcp://192.168.64.3:61680”;
启动生产者和消费者一样可以正产运行。
11.ActiveMQ的消息存储和持久化
待更新
12.ActiveMQ的多节点集群
待更新
13.高级特性和大厂常考重点
待更新