目录
ActiveMQ简介
消息中间件应用场景
异步处理应用解耦流量削锋
异步处理
场景说明:用户注册,需要执行三个业务逻辑,分别为写入用户表,发注册邮件以及注册短信。
串行方式
将注册信息写入数据库成功后,发送注册邮件,再发送注册短信。以上三个任务全部完成后,返回给客户端。
![](https://img-blog.csdnimg.cn/882bc6276d714022813e6fc8c8832dae.png)
并行方式
将注册信息写入数据库成功后,发送注册邮件的同时,发送注册短信。以上三个任务完成后,返回给客户端。与串行的差别是,并行的方式可以提高处理的时间
![](https://img-blog.csdnimg.cn/922f910c905e4027b564315468300e7b.png)
异步处理
引入消息中间件,将部分的业务逻辑,进行异步处理。改造后的架构如下:
![](https://img-blog.csdnimg.cn/af20db86a636495389fe74b96fd54951.png)
按照以上约定,用户的响应时间相当于是注册信息写入数据库的时间,也就是
50
毫秒。注册邮件,发送短信写入消息队列后,直接返回,因此写入消息队列的速度很快,基本可以忽略,因此用户的响应时间可能是50
毫秒。因此架构改变后,系统的吞吐量提高啦,比串行提高了
3
倍,比并行提高了两倍。
应用解耦
场景说明:用户下单后,订单系统需要通知库存系统。传统的做法是,订单系统调用库存系统的接口。如下图
![](https://img-blog.csdnimg.cn/823929a32b2943fda1c8556848f9a055.png)
传统模式的缺点:假如库存系统无法访问,则订单减库存将失败,从而导致订单失败,订单系统与库存系统耦合。如何解决以上问题呢?引入应用消息队列后的方案,如下图:
![](https://img-blog.csdnimg.cn/26b7148b2d6f4b61bc1143f667d27201.png)
订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功库存系统:订阅下单的消息,采用拉/
推的方式,获取下单信息,库存系统根据下单信息,进行库存操作假 如:在下单时库存系统不能正常使用。也不影响正常下单,因为下单后,订单系统写入消息队列就不再关心其他的后续操作了。实现订单系统与库存系统的应用解耦。
流量消峰
流量削锋也是消息队列中的常用场景,一般在秒杀或团抢活动中使用广泛。应用场景:秒杀活动,一般会因为流量过大,导致流量暴增,应用挂掉。为解决这个问题,一般需要在应用前端加入消息队列。通过加入消息队列完成如下功能:
a
、可以控制活动的人数
b
、可以缓解短时间内高流量压垮应用
![](https://img-blog.csdnimg.cn/8500b4df0ae54a2e852516759847445b.png)
用户的请求,服务器接收后,首先写入消息队列。假如消息队列长度超过最大数量,则直接抛弃用户请求或跳转到错误页面。秒杀业务根据消息队列中的请求信息,再做后续处理
常见的消息中间件产品对比
![](https://img-blog.csdnimg.cn/14f867e00a5744de982cfd21dabbfc53.png)
ActiveMQ
简介及
JMS
什么是
ActiveMQ
?
官网:
http://activemq.apache.org/
ActiveMQ
是
Apache
出品,最流行的,能力强劲的开源消息总线。
ActiveMQ
是一个完全支持
JMS1.1
和J2EE 1.4规范的
JMS Provider
实现。我们在本次课程中介绍
ActiveMQ的使用。
什么是
JMS
?
JMS
消息模型
消息中间件一般有两种传递模式:点对点模式
(P2P)
和发布
-
订阅模式
(Pub/Sub)
。
(1) P2P (Point to Point)
点对点模型(
Queue
队列模型)
(2) Publish/Subscribe(Pub/Sub)
发布
/
订阅模型
(Topic
主题模型
)
点对点模型
点对点模型(
Pointer-to-Pointer
):即生产者和消费者之间的消息往来。
![](https://img-blog.csdnimg.cn/67e9bd74eb124187a8a6ddfbeb2afce5.png)
每个消息都被发送到特定的消息队列,接收者从队列中获取消息。队列保留着消息,直到他们被消费或超时。
点对点模型的特点:
- 每个消息只有一个消费者(Consumer)(即一旦被消费,消息就不再在消息队列中);
- 发送者和接收者之间在时间上没有依赖性,也就是说当发送者发送了消息之后,不管接收者有没有正在运行,它不会影响到消息被发送到队列;
- 接收者在成功接收消息之后需向队列应答成功
发布
/
订阅模型
发布
/
订阅(
Publish-Subscribe
)
包含三个角色:主题(
Topic
),发布者(
Publisher
),订阅者(
Subscriber
),多个发布者将消息发送到topic
,系统将这些消息投递到订阅此
topic
的订阅者
![](https://img-blog.csdnimg.cn/37b7b60fa8054c9bb73295bea39dc3e5.png)
发布者发送到
topic
的消息,只有订阅了
topic
的订阅者才会收到消息。
topic
实现了发布和订阅,当你发布一个消息,所有订阅这个topic
的服务都能得到这个消息,所以从
1
到
N
个订阅者都能得到这个消息的拷贝。
发布
/
订阅模型的特点:
每个消息可以有多个消费者;
发布者和订阅者之间有时间上的依赖性(先订阅主题,再来发送消息)。
订阅者必须保持运行的状态,才能接受发布者发布的消息;
JMS
编程
API
![](https://img-blog.csdnimg.cn/cfefd0e3b3364f088f4155ab11d550d4.png)
(
1
)
ConnectionFactory
创建
Connection
对象的工厂,针对两种不同的
jms
消息模型,分别有
QueueConnectionFactory
和TopicConnectionFactory两种。
(
2
)
Destination
Destination
的意思是消息生产者的消息发送目标或者说消息消费者的消息来源。对于消息生产者来说,它的Destination
是某个队列(
Queue
)或某个主题(
Topic
)
;
对于消息消费者来说,它的Destination也是某个队列或主题(即消息来源)。所以,
Destination
实际上就是两种类型的对象:Queue、
Topic
(
3
)
Connection
Connection
表示在客户端和
JMS
系统之间建立的链接(对
TCP/IP socket
的包装)。
Connection
可以产生一个或多个Session
(
4
)
Session
Session
是我们对消息进行操作的接口,可以通过
session
创建生产者、消费者、消息等。
Session
提供了事务的功能,如果需要使用session
发送
/
接收多个消息时,可以将这些发送
/
接收动作放到一个事务 中。
(
5
)
Producter
Producter
(消息生产者):消息生产者由
Session
创建,并用于将消息发送到
Destination
。同样,消息生产者分两种类型:QueueSender
和
TopicPublisher
。可以调用消息生产者的方法(
send
或
publish方法)发送消息。
(
6
)
Consumer
Consumer
(消息消费者):消息消费者由
Session
创建,用于接收被发送到
Destination
的消息。两种 类型:QueueReceiver
和
TopicSubscriber
。可分别通过
session
的
createReceiver(Queue)
或 createSubscriber(Topic)来创建。当然,也可以
session
的
creatDurableSubscriber
方法来创建持久化的订阅者。
(
7
)
MessageListener
消息监听器。如果注册了消息监听器,一旦消息到达,将自动调用监听器的
onMessage
方法。
EJB
中的MDB(
Message-Driven Bean
)就是一种
MessageListener
。
![](https://img-blog.csdnimg.cn/c37168e1db4a4e6fae263569d41cf3f9.png)
ActiveMQ安装
第一步:安装 jdk (略)第二步:把 activemq 的压缩包( apache-activemq-5.14.5-bin.tar.gz )上传到 linux 系统第三步:解压缩压缩包 tar -zxvf apache-activemq-5.14.5-bin.tar.gz第四步:进入 apache-activemq-5.14.5 的 bin 目录 cd apache-activemq-5.14.5/bin第五步:启动 activemq ./activemq start (执行 2 次:第一次:生成配置信息;第二次:启动 )第六步:停止 activemq : ./activemq stop
访问
http://192.168.12.132:8161
页面控制台:
http
:
//ip:8161 (
监控
)
请求地址:
tcp
:
//ip:61616
(
java
代码访问消息中间件)
账号:
admin
密码:
admin
图
1
:登陆:
![](https://img-blog.csdnimg.cn/6d329fea9a274e6ba0391c5a14d2321c.png)
图
2
:点击
Queues
队列或者
Topics
主题消息
![](https://img-blog.csdnimg.cn/21296eed17584c769dc46c073655421b.png)
列表各列信息含义如下:
Number Of Pending Messages :等待消费的消息 这个是当前未出队列的数量。Number Of Consumers :消费者 这个是消费者端的消费者数量Messages Enqueued :进入队列的消息 进入队列的总数量 , 包括出队列的。Messages Dequeued :出了队列的消息 可以理解为是消费这消费掉的数量。
原生JMS API操作ActiveMQ
PTP
模式
(
生产者
)
(
1
)引入坐标
<dependencies>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-all</artifactId>
<version>5.11.2</version>
</dependency>
</dependencies>
(
2
)编写生产消息的测试类
QueueProducer
步骤:
1. 创建连接工厂2. 创建连接3. 打开连接4. 创建 session5. 创建目标地址( Queue: 点对点消息, Topic :发布订阅消息)6. 创建消息生产者7. 创建消息8. 发送消息9. 释放资源
package com.itheima.producer;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* 演示点对点模式 -- 消息生产者
*/
public class PTP_Producer {
public static void main(String[] args) throws JMSException {
//1.创建连接工厂
ConnectionFactoryfactory=newActiveMQConnectionFactory("tcp://192.168.66.133:61616");
//2.创建连接
Connection connection = factory.createConnection();
//3.打开连接
connection.start();
//4.创建session
/**
* 参数一:是否开启事务操作
* 参数二:消息确认机制
*/
Session session = connection.createSession(false,
Session.AUTO_ACKNOWLEDGE);
//5.创建目标地址(Queue:点对点消息,Topic:发布订阅消息)
Queue queue = session.createQueue("queue01");
//6.创建消息生产者
MessageProducer producer = session.createProducer(queue);
//7.创建消息
//createTextMessage: 文本类型
TextMessage textMessage = session.createTextMessage("test message");
//8.发送消息
producer.send(textMessage);
System.out.println("消息发送完成");
//9.释放资源
session.close();
connection.close();
}
}
观察发送消息的结果:
![](https://img-blog.csdnimg.cn/7f6439b01182484b97df88720d62da0a.png)
PTP
模式
(
消费者
)
步骤:
1. 创建连接工厂2. 创建连接3. 打开连接4. 创建 session5. 指定目标地址6. 创建消息的消费者7. 配置消息监听器
package com.itheima.consumer;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* 演示点对点模式- 消息消费者(第二种方案) -- 更加推荐
*/
public class PTP_Consumer2 {
public static void main(String[] args) throws JMSException {
//1.创建连接工厂
ConnectionFactory factory
= new ActiveMQConnectionFactory("tcp://192.168.66.133:61616");
//2.创建连接
Connection connection = factory.createConnection();
//3.打开连接
connection.start();
//4.创建session
Session session = connection.createSession(false,
Session.AUTO_ACKNOWLEDGE);
//5.指定目标地址
Queue queue = session.createQueue("queue01");
//6.创建消息的消费者
MessageConsumer consumer = session.createConsumer(queue);
//7.设置消息监听器来接收消息
consumer.setMessageListener(new MessageListener() {
//处理消息
@Override
public void onMessage(Message message) {
if(message instanceof TextMessage){
TextMessage textMessage = (TextMessage)message;
try {
System.out.println("接收的消息(2):"+textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
});
//注意:在监听器的模式下千万不要关闭连接,一旦关闭,消息无法接收
}
}
观察消费消息的结果:
![](https://img-blog.csdnimg.cn/09a482378b8147a5921e8abcbfaeb849.png)
Pub/Sub
模式
(
生成者
)
1. 创建连接工厂2. 创建连接3. 打开连接4. 创建 session5. 创建目标地址( Queue: 点对点消息, Topic :发布订阅消息)6. 创建消息生产者7. 创建消息8. 发送消息9. 释放资源
package cn.itcast.activemq;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* 主题消息,消息的发送方
*/
public class TopicProducer {
public static void main(String[] args) throws Exception {
//1.创建连接工厂
ConnectionFactory factory = new
ActiveMQConnectionFactory("tcp://192.168.12.132:61616");
//2.创建连接
Connection connection = factory.createConnection();
//3.打开连接
connection.start();
//4.创建session
Session session = connection.createSession(false,
Session.AUTO_ACKNOWLEDGE);
//5.创建目标地址(Queue:点对点消息,Topic:发布订阅消息)
Topic topic = session.createTopic("sms");
//6.创建消息生产者
MessageProducer producer = session.createProducer(topic);
//7.创建消息
TextMessage message = session.createTextMessage("发短信...");
//8.发送消息
producer.send(message);
System.out.println("发送消息:发短信...");
session.close();;
connection.close();
}
}
查看主题消息:
![](https://img-blog.csdnimg.cn/dc7ef23629ca47dd86ebe1aa1a4c7985.png)
Pub/Sub
模式
(
消费者
)
1. 创建连接工厂2. 创建连接3. 打开连接4. 创建 session5 指定目标地址6. 创建消息的消费者7. 配置消息监听器
package cn.itcast.activemq;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* 主题消息,消息的消费方
*/
public class TopicConsumer {
public static void main(String[] args) throws Exception {
//1.创建连接工厂
ConnectionFactory factory = new
ActiveMQConnectionFactory("tcp://192.168.12.132:61616");
//2.创建连接
Connection connection = factory.createConnection();
//3.打开连接
connection.start();
//4.创建session
Session session = connection.createSession(false,
Session.AUTO_ACKNOWLEDGE);
//5.创建目标地址(Queue:点对点消息,Topic:发布订阅消息)
Topic topic = session.createTopic("sms");
//6.创建消息的消费者
MessageConsumer consumer = session.createConsumer(topic);
//7.配置消息监听器
consumer.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("消费消息:" + textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
});
}
}
SpringBoot与ActiveMQ整合
生产者
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
配置:
server:
port: 9001 #端口
spring:
application:
name: activemq-producer # 服务名称
# springboot与activemq整合配置
activemq:
broker-url: tcp://192.168.66.133:61616 # 连接地址
user: admin # activemq用户名
password: admin # activemq密码
# 指定发送模式 (点对点 false , 发布订阅 true)
jms:
pub-sub-domain: false
public class SpringBootProducer {
//JmsMessagingTemplate: 用于工具类发送消息
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
@Test
public void ptpSender(){
/**
* 参数一:队列的名称或主题名称
* 参数二:消息内容
*/
jmsMessagingTemplate.convertAndSend("springboot_queue","spring boot
message");
}
}
消费者
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
配置:
server:
port: 9002 #端口
spring:
application:
name: activemq-consumer # 服务名称
# springboot与activemq整合配置
activemq:
broker-url: tcp://192.168.66.133:61616 # 连接地址
user: admin # activemq用户名
password: admin # activemq密码
# 指定发送模式 (点对点 false , 发布订阅 true)
jms:
pub-sub-domain: false
activemq:
name: springboot_queue
/**
* 用于监听消息类(既可以用于队列的监听,也可以用于主题监听)
*/
@Component // 放入IOC容器
public class MsgListener {
/**
* 用于接收消息的方法
* destination: 队列的名称或主题的名称
*/
@JmsListener(destination = "${activemq.name}")
public void receiveMessage(Message message){
if(message instanceof TextMessage){
TextMessage textMessage = (TextMessage)message;
try {
System.out.println("接收消息:"+textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
ActiveMQ消息组成与高级特性
JMS消息组成详解
JMS
消息组成格式
整个
JMS
协议组成结构如下:
![](https://img-blog.csdnimg.cn/0c8aa9d275384bf290cb17081596d9b2.png)
JMS Message
消息由三部分组成:
1 )消息头2 )消息体3 )消息属性
JMS
消息头
JMS
消息头预定义了若干字段用于客户端与
JMS
提供者之间识别和发送消息,预编译头如下:
红色
为重要的消息头
名称
|
描述
|
JMSDestination
|
消息发送的
Destination
,在发送过程中由提供者设置
|
JMSMessageID
|
唯一标识提供者发送的每一条消息。这个字段是在发送过程中由提供者设置的,客户机只能在消息发送后才能确定消息的 JMSMessageID
|
JMSDeliveryMode
|
消息持久化。包含值
DeliveryMode.PERSISTENT
或者
DeliveryMode.NON_PERSISTENT
。
|
JMSTimestamp
|
提供者发送消息的时间,由提供者在发送过程中设置
|
JMSExpiration
|
消息失效的时间,毫秒,值
0
表明消息不会过期,默认值为
0
|
JMSPriority
| 消息的优先级,由提供者在发送过程中设置。优先级 0 的优先级最低,优先级 9 的优先级最高。0-4为普通消息,5-9为加急消息。ActiveMQ不保证优先级高就一定先发送,只保证了加急消息必须先于普通消息发送。默认值为4 |
JMSCorrelationID
|
通常用来链接响应消息与请求消息,由发送消息的
JMS
程序设置。
|
JMSReplyTo
|
请求程序用它来指出回复消息应发送的地方,由发送消息的
JMS
程序设置
|
JMSType
|
JMS
程序用它来指出消息的类型。
|
JMSRedelivered
|
消息的重发标志,
false
,代表该消息是第一次发生,
true
,代表该消息为重发消息
|
不过需要注意的是,在传送消息时,消息头的值由
JMS
提供者来设置,
因此开发者使用以上
setJMSXXX()
方法分配的值就被忽略了
,只有以下几个值是可以由开发者设置的:
JMSCorrelationID
,
JMSReplyTo
,
JMSType
JMS
消息体
在消息体中,
JMS API
定义了五种类型的消息格式,让我们可以以不同的形式发送和接受消息,并提供了对已有消息格式的兼容。不同的消息类型如下:
JMS
定义了五种不同的消息正文格式,以及调用的消息类型,允许你发送并接收一些不同形式的数据,提供现有消息格式的一些级别的兼容性。
· TextMessage-- 一个字符串对象 *· MapMessage-- 一套名称 - 值对· ObjectMessage-- 一个序列化的 Java 对象 *· BytesMessage-- 一个字节的数据流 *· StreamMessage -- Java 原始值的数据流
TextMessage:
写出:
/**
* 发送TextMessage消息
*/
@Test
public void testMessage(){
jmsTemplate.send(name, new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
TextMessage textMessage = session.createTextMessage("文本消息");
return textMessage;
}
});
}
读取:
/**
* 接收TextMessage的方法
*/
@JmsListener(destination = "${activemq.name}")
public void receiveMessage(Message message){
if(message instanceof TextMessage){
TextMessage textMessage = (TextMessage)message;
try {
System.out.println("接收消息:"+textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
MapMessage:
发送:
/**
* 发送MapMessage消息
*/
@Test
public void mapMessage(){
jmsTemplate.send(name, new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
MapMessage mapMessage = session.createMapMessage();
mapMessage.setString("name","张三");
mapMessage.setInt("age",20);
return mapMessage;
}
});
}
接收:
@JmsListener(destination = "${activemq.name}")
public void receiveMessage(Message message){
if(message instanceof MapMessage){
MapMessage mapMessage = (MapMessage)message;
try {
System.out.println("名称:"+mapMessage.getString("name"));
System.out.println("年龄:"+mapMessage.getString("age"));
} catch (JMSException e) {
e.printStackTrace();
}
}
}
ObjectMessage:
发送:
//发送ObjectMessage消息
@Test
public void test2(){
jmsTemplate.send(name, new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
User user = new User();
user.setName("小苍");
user.setAge(18);
ObjectMessage objectMessage = session.createObjectMessage(user);
return objectMessage;
}
});
}
接收:
@JmsListener(destination = "${activemq.name}")
public void receiveMessage(Message message){
if(message instanceof ObjectMessage){
ObjectMessage objectMessage = (ObjectMessage)message;
try {
User user = (User)objectMessage.getObject();
System.out.println(user.getUsername());
System.out.println(user.getPassword());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
注意:
ActiveMQ5.12
后 ,为了安全考虑,
ActiveMQ
默认不接受自定义的序列化对象,需要将自定义的加入到受信任的列表。
spring:
activemq:
broker-url: tcp://192.168.66.133:61616
user: admin
password: admin
packages:
trust-all: true # 添加所有包到信任列表
BytesMessage:
写出:
//发送BytesMessage消息
@Test
public void test3(){
jmsTemplate.send(name, new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
BytesMessage bytesMessage = session.createBytesMessage();
try {
File file = new File("d:/spring.jpg");
FileInputStream in = new FileInputStream(file);
byte[] bytes = new byte[(int)file.length()];
in.read(bytes);
bytesMessage.writeBytes(bytes);
} catch (Exception e) {
e.printStackTrace();
}
return bytesMessage;
}
});
}
读取:
@JmsListener(destination="${activemq.name}")
public void receiveMessage(Message message) throws Exception {
BytesMessage bytesMessage = (BytesMessage)message;
FileOutputStream out = new FileOutputStream("d:/abc.jpg");
byte[] buf = new byte[(int)bytesMessage.getBodyLength()];
bytesMessage.readBytes(buf);
out.write(buf);
out.close();
}
StreamMessage:
写出:
//发送StreamMessage消息
@Test
public void test4(){
jmsTemplate.send(name, new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
StreamMessage streamMessage = session.createStreamMessage();
streamMessage.writeString("你好,ActiveMQ");
streamMessage.writeInt(20);
return streamMessage;
}
});
}
读取:
@JmsListener(destination="${activemq.name}")
public void receiveMessage(Message message) throws Exception {
StreamMessage streamMessage = (StreamMessage)message;
String str = streamMessage.readString();
int i = streamMessage.readInt();
System.out.println(str);
System.out.println(i);
}
JMS
消息属性
我们可以给消息设置自定义属性,这些属性主要是提供给应用程序的。对于实现消息过滤功能,消息属性非常有用JMS API定义了一些标准属性,这些属性在所有JMS实现中都是可用的。这些标准属性包括:
- JMSMessageID:消息的唯一标识符
- JMSDestination:消息的目标目的地
- JMSDeliveryMode:消息的传递模式(持久化或非持久化)
- JMSPriority:消息的优先级
- JMSExpiration:消息的过期时间
- JMSTimestamp:消息的创建时间戳
- JMSCorrelationID:用于关联消息的唯一标识符
- JMSReplyTo:回复消息应该发送到的目的地
- JMSRedelivered:表示消息是否被重新传递的布尔值
- JMSType:消息的类型
message . setStringProperty ( "Property" , Property ); // 自定义属性
消息持久化
消息持久化是保证消息不丢失的重要方式!!! ActiveMQ提供了以下三种的消息存储方式:
- Memory 消息存储-基于内存的消息存储。
- 基于日志消息存储方式,KahaDB是ActiveMQ的默认日志存储方式,它提供了容量的提升和恢复能力。
- 基于JDBC的消息存储方式-数据存储于数据库(例如:MySQL)中。
ActiveMQ
持久化机制流程图:
![](https://img-blog.csdnimg.cn/dbf063791f6149ecb911c08eec6eedf2.png)
![](https://img-blog.csdnimg.cn/1a407f4c9bcc497f8b68f3440d98fab0.png)
1
)生产者:
application.yml
![](https://img-blog.csdnimg.cn/f90f682647c14d1a8735fc090b1700c9.png)
2
)broker:修改
activemq.xml
<!--配置数据库连接池-->
<bean name="mysql-ds" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url"
value="jdbc:mysql://192.168.66.133:3306/db_activemq" />
<property name="username" value="root" />
<property name="password" value="123456"/>
</bean>
<!--JDBC Jdbc用于master/slave模式的数据库分享 -->
<persistenceAdapter>
<jdbcPersistenceAdapter dataSource="#mysql-ds"/>
</persistenceAdapter>
3
)拷贝
mysql
及
durid
数据源的
jar
包到
activemq
的
lib
目录下
4
)重启
activemq
消息事务
消息事务,是保证消息传递原子性的一个重要特征,和
JDBC
的事务特征类似。
一个事务性发送,其中一组消息要么能够全部保证到达服务器,要么都不到达服务器。 生产者、消费者与消息服务器直接都支持事务性; ActionMQ的事务主要偏向在生产者的应用。
ActionMQ
消息事务流程图:
![](https://img-blog.csdnimg.cn/6c3a2e40eba1427ab5fa73321849f846.png)
一、生产者事务:
方式一:
/**
* 事务性发送--方案一
*/
@Test
public void sendMessageTx(){
//获取连接工厂
ConnectionFactory connectionFactory =
jmsMessagingTemplate.getConnectionFactory();
Session session = null;
try {
//创建连接
Connection connection = connectionFactory.createConnection();
/**
* 参数一:是否开启消息事务
*/
session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
//创建生产者
MessageProducer producer =
session.createProducer(session.createQueue(name));
for(int i=1;i<=10;i++){
//模拟异常
if(i==4){
int a = 10/0;
}
TextMessage textMessage = session.createTextMessage("消息--" +
i);
producer.send(textMessage);
}
//注意:一旦开启事务发送,那么就必须使用commit方法进行事务提交,否则消息无法到达
MQ服务器
session.commit();
} catch (JMSException e) {
e.printStackTrace();
//消息事务回滚
try {
session.rollback();
} catch (JMSException e1) {
e1.printStackTrace();
}
}
}
方式二:
配置类:
/**
*
*/
@Configuration
public class ActiveMqConfig {
@Bean
public PlatformTransactionManager transactionManager(ConnectionFactory
connectionFactory) {
return new JmsTransactionManager(connectionFactory);
}
}
/**
* 消息发送的业务类
*/
@Service
public class MessageService {
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
@Value("${activemq.name}")
private String name;
@Transactional // 对消息发送加入事务管理(同时也对JDBC数据库的事务生效)
public void sendMessage(){
for(int i=1;i<=10;i++) {
//模拟异常
if(i==4){
int a = 10/0;
}
jmsMessagingTemplate.convertAndSend(name, "消息---"+i);
}
}
}
测试发送方法:
@Autowired
private MessageService messageService;
/**
* 事务性发送--方案二: Spring的JmsTransactionManager功能
*/
@Test
public void sendMessageTx2(){
messageService.sendMessage();
}
二、消费者事务
/**
* 消息消费者
*/
@Component
public class Consumer {
/**
* 接收消息的方法
*/
@JmsListener(destination="${activemq.name}",containerFactory =
"jmsQueryListenerFactory")
public void receiveMessage(TextMessage textMessage,Session session) throws
JMSException {
try {
System.out.println("消息内容:" + textMessage.getText() + ",是否重发:"
+ textMessage.getJMSRedelivered());
int i = 100/0; //模拟异常
session.commit();//提交事务
} catch (JMSException e) {
try {
session.rollback();//回滚事务
} catch (JMSException e1) {
}
e.printStackTrace();
}
}
}
消息确认机制
值 | 描述 |
Session.AUTO_ACKNOWLEDGE |
当客户成功的从
receive
方法返回的时候,或者从MessageListener.onMessage方法成功返回的时候,会话自动确认客户收到的消息
|
Session.CLIENT_ACKNOWLEDGE |
客户通过消息的
acknowledge
方法确认消息。需要注意的 是,在这种模式中,确认是在会话层上进行:确认一个被消费的消息将自动确认所有已被会话消费的消息。例如,如果
一个消息消费者消费了
10
个消息,然后确认第
5
个消息,那么所有10
个消息都被确认
|
Session.DUPS_ACKNOWLEDGE |
该选择只是会话迟钝确认消息的提交。如果
JMS provider
失败,那么可能会导致一些重复的消息。如果是重复的消息,那么JMS provider
必须把消息头的
JMSRedelivered
字段设置为true
|
注意:消息确认机制与事务机制是冲突的,只能选其中一种。所以演示消息确认前,先关闭事务
![](https://img-blog.csdnimg.cn/7bcb80aa006d4b388b4fe311e0c4e7f6.png)
1
)
auto_acknowledge
自动确认
添加配置类:
/**
*
*/
@Configuration
public class ActiveMqConfig {
@Bean(name="jmsQueryListenerFactory")
public DefaultJmsListenerContainerFactory
jmsListenerContainerFactory(ConnectionFactory connectionFactory){
DefaultJmsListenerContainerFactory factory=new
DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setSessionTransacted(false); // 不开启事务操作
factory.setSessionAcknowledgeMode(1); //自动确认
return factory;
}
}
消费者:
/**
* 消息消费者
*/
@Component
public class Consumer {
/**
* 接收消息的方法
*/
@JmsListener(destination="${activemq.name}",containerFactory =
"jmsQueryListenerFactory")
public void receiveMessage(TextMessage textMessage){
try {
System.out.println("消息内容:" + textMessage.getText() + ",是否重发:"
+ textMessage.getJMSRedelivered());
throw new RuntimeException("test");
} catch (JMSException e) {
e.printStackTrace();
}
}
}
如果消费方接收消息失败,
JMS
服务器会重发消息,默认重发
6
次。
2
)
dups_ok_acknowledge
类似于
auto_acknowledge
确认机制,为自动批量确认而生,而且具有
“
延迟
”
确认的特点
ActiveMQ 会根据内部算法,在收到一定数量的消息自动进行确认。 在此模式下,可能会出现重复消息,如果消费方不允许重复消费,不建议使用!
3
)
client_acknowledge
手动确认
配置类:
/**
*
*/
@Configuration
public class ActiveMqConfig {
@Bean(name="jmsQueryListenerFactory")
public DefaultJmsListenerContainerFactory
jmsListenerContainerFactory(ConnectionFactory connectionFactory){
DefaultJmsListenerContainerFactory factory=new
DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setSessionTransacted(false); // 不开启事务操作
factory.setSessionAcknowledgeMode(4); //手动确认
return factory;
}
}
消费者:
/**
* 消息消费者
*/
@Component
public class Consumer {
/**
* 接收消息的方法
*/
@JmsListener(destination="${activemq.name}",containerFactory =
"jmsQueryListenerFactory")
public void receiveMessage(TextMessage textMessage){
try {
System.out.println("消息内容:" + textMessage.getText() + ",是否重发:"
+ textMessage.getJMSRedelivered());
textMessage.acknowledge(); // 确认收到消息,一旦消息确认,消息不会重新发送
throw new RuntimeException("test");
} catch (JMSException e) {
e.printStackTrace();
}
}
}
消息投递方式
异步投递
1
、异步投递
vs
同步投递
同步发送:
消息生产者使用持久(
Persistent
)传递模式发送消息的时候,
Producer.send()
方法会被阻塞,直到 broker 发送一个确认消息给生产者
(ProducerAck)
,这个确认消息暗示
broker
已经成功接收到消息并把消息保存到二级存储中。
异步发送
:
如果应用程序能够容忍一些消息的丢失,那么可以使用异步发送。异步发送不会在受到
broker
的确认之前一直阻塞 Producer.send
方法。
1
)如果设置了
alwaysSyncSend=true
系统将会忽略
useAsyncSend
设置的值都采用同步
2
)当 alwaysSyncSend=false时,
“NON_PERSISTENT”(
非持久化
)
、事务中的消息将使用
“
异步发送
”
3
)当 alwaysSyncSend=false时,如果指定了
useAsyncSend=true
,
“PERSISTENT”
类型的消息使用异步发送。如果useAsyncSend=false
,
“PERSISTENT”
类型的消息使用同步发送。
总结: 默认情况
(alwaysSyncSend=false,useAsyncSend=false)
,非持久化消息、事务内的消息均采用异步发送;对于持久化消息采用同步发送!!!
2
、配置异步投递的方式
1. 在连接上配置new ActiveMQConnectionFactory ( "tcp://locahost:61616?jms.useAsyncSend=true" );2. 通过 ConnectionFactory(( ActiveMQConnectionFactory ) connectionFactory ). setUseAsyncSend ( true );3. 通过 connection(( ActiveMQConnection ) connection ). setUseAsyncSend ( true );
注意:如果是
Spring
或
SpringBoot
项目,通过修改
JmsTemplate
的默认参数实现异步或同步投递
@Configuration
public class ActiveConfig {
/**
* 配置用于异步发送的非持久化JmsTemplate
*/
@Autowired
@Bean
public JmsTemplate asynJmsTemplate(PooledConnectionFactory
pooledConnectionFactory) {
JmsTemplate template = new JmsTemplate(pooledConnectionFactory);
template.setExplicitQosEnabled(true);
template.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
return template;
}
/**
* 配置用于同步发送的持久化JmsTemplate
*/
@Autowired
@Bean
public JmsTemplate synJmsTemplate(PooledConnectionFactory
pooledConnectionFactory) {
JmsTemplate template = new JmsTemplate(pooledConnectionFactory);
return template;
}
异步投递如何确认发送成功?
异步投递丢失消息的场景是:生产者设置
UseAsyncSend=true
,使用
producer.send
(
msg
)持续发 送消息。 由于消息不阻塞,生产者会认为所有 send
的消息均被成功发送至
MQ
。如果
MQ
突然宕机,此时生产 者端内存中尚未被发送至 MQ
的消息都会丢失。 这时,可以给异步投递方法接收回调,以确认消息是否发送成功!
/**
* 异步投递,回调函数
* @return
*/
@RequestMapping("/send")
public String sendQueue(){
Connection connection = null;
Session session = null;
ActiveMQMessageProducer producer = null;
// 获取连接工厂
ConnectionFactory connectionFactory =
jmsMessagingTemplate.getConnectionFactory();
try {
connection = connectionFactory.createConnection();
session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
Queue queue = session.createQueue(name);
int count = 10;
producer = session.createProducer(queue);
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
long start = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
//创建需要发送的消息
TextMessage textMessage = session.createTextMessage("Hello");
//设置消息唯一ID
String msgid = UUID.randomUUID().toString();
textMessage.setJMSMessageID(msgid);
producer.send(textMessage, new AsyncCallback() {
@Override
public void onSuccess() {
// 使用msgid标识来进行消息发送成功的处理
System.out.println(msgid+" 消息发送成功");
}
@Override
public void onException(JMSException exception) {
// 使用msgid表示进行消息发送失败的处理
System.out.println(msgid+" 消息发送失败");
exception.printStackTrace();
}
});
}
session.commit();
} catch (Exception e) {
e.printStackTrace();
}
return "ok";
}
延迟投递
生产者提供两个发送消息的方法,一个是即时发送消息,一个是延时发送消息
1
、broker修改
activemq.xml
<broker xmlns="http://activemq.apache.org/schema/core" ...
schedulerSupport="true" >
......
</broker>
注意:添加
schedulerSupport="true"
配置
2
、在代码中设置延迟时长
/**
* 延时投递
*
* @return
*/
@Test
public String sendQueue() {
Connection connection = null;
Session session = null;
ActiveMQMessageProducer producer = null;
// 获取连接工厂
ConnectionFactory connectionFactory =
jmsMessagingTemplate.getConnectionFactory();
try {
connection = connectionFactory.createConnection();
session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
Queue queue = session.createQueue(name);
int count = 10;
producer = (ActiveMQMessageProducer) session.createProducer(queue);
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
//创建需要发送的消息
TextMessage textMessage = session.createTextMessage("Hello");
//设置延时时长(延时10秒)
textMessage.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY,
10000);
producer.send(textMessage);
session.commit();
} catch (Exception e) {
e.printStackTrace();
}
return "ok";
}
定时投递
1
、启动类添加定时注解
@SpringBootApplication
@EnableScheduling // 开启定时功能
public class MyActiveMQApplication {
public static void main(String[] args) {
SpringApplication.run(MyActiveMQApplication.class,args);
}
}
2
、在生产者添加
@Scheduled
设置定时
/**
* 消息生产者
*/
@Component
public class ProducerController3 {
@Value("${activemq.name}")
private String name;
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
/**
* 延时投递
*
* @return
*/
//每隔3秒定投
@Scheduled(fixedDelay = 3000)
public void sendQueue() {
jmsMessagingTemplate.convertAndSend(name, "消息ID:" +
UUID.randomUUID().toString().substring(0,6));
System.out.println("消息发送成功...");
}
}
死信队列
DLQ-Dead Letter Queue
,死信队列,用来保存处理失败或者过期的消息
出现以下情况时,消息会被重发:A transacted session is used and rollback() is called.A transacted session is closed before commit is called.A session is using CLIENT_ACKNOWLEDGE and Session.recover() is called.当一个消息被重发超过 6( 缺省为 6 次 ) 次数时,会给 broker 发送一个 "Poison ack" ,这个消息被认为是 apoison pill,这时 broker 会将这个消息发送到死信队列,以便后续处理。注意两点:1 )缺省持久消息过期,会被送到 DLQ ,非持久消息不会送到 DLQ2 )缺省的死信队列是 ActiveMQ.DLQ ,如果没有特别指定,死信都会被发送到这个队列。可以通过配置文件 (activemq.xml) 来调整死信发送策略。
1
、修改
activemq.xml
为每个队列建立独立的死信队列
<destinationPolicy>
<policyMap>
<policyEntries>
<policyEntry queue=">">
<deadLetterStrategy>
<individualDeadLetterStrategy queuePrefix="DLQ."
useQueueForQueueMessages="true" />
</deadLetterStrategy>
</policyEntry>
<policyEntry topic=">" >
<pendingMessageLimitStrategy>
<constantPendingMessageLimitStrategy limit="1000"/>
</pendingMessageLimitStrategy>
</policyEntry>
</policyEntries>
</policyMap>
</destinationPolicy>
2
、
RedeliveryPolicy
重发策略设置
修改配置类
/**
*
*/
@Configuration
public class ActiveMqConfig {
//RedeliveryPolicy重发策略设置
@Bean
public RedeliveryPolicy redeliveryPolicy(){
RedeliveryPolicy redeliveryPolicy= new RedeliveryPolicy();
//是否在每次尝试重新发送失败后,增长这个等待时间
redeliveryPolicy.setUseExponentialBackOff(true);
//重发次数,默认为6次 这里设置为10次
redeliveryPolicy.setMaximumRedeliveries(10);
//重发时间间隔,默认为1秒
redeliveryPolicy.setInitialRedeliveryDelay(1);
//第一次失败后重新发送之前等待500毫秒,第二次失败再等待500 * 2毫秒,这里的2就是value
redeliveryPolicy.setBackOffMultiplier(2);
//是否避免消息碰撞
redeliveryPolicy.setUseCollisionAvoidance(false);
//设置重发最大拖延时间-1 表示没有拖延只有UseExponentialBackOff(true)为true时生
效
redeliveryPolicy.setMaximumRedeliveryDelay(-1);
return redeliveryPolicy;
}
@Bean
public ActiveMQConnectionFactory activeMQConnectionFactory
(@Value("${spring.activemq.broker-url}")String url,RedeliveryPolicy
redeliveryPolicy){
ActiveMQConnectionFactory activeMQConnectionFactory =
new ActiveMQConnectionFactory(
"admin",
"admin",
url);
activeMQConnectionFactory.setRedeliveryPolicy(redeliveryPolicy);
return activeMQConnectionFactory;
}
@Bean
public PlatformTransactionManager transactionManager(ConnectionFactory
connectionFactory) {
return new JmsTransactionManager(connectionFactory);
}
@Bean(name="jmsQueryListenerFactory")
public DefaultJmsListenerContainerFactory
jmsListenerContainerFactory(ConnectionFactory
connectionFactory,PlatformTransactionManager transactionManager){
DefaultJmsListenerContainerFactory factory=new
DefaultJmsListenerContainerFactory ();
factory.setTransactionManager(transactionManager);
factory.setConnectionFactory(connectionFactory);
factory.setSessionTransacted(true); // 开启事务
factory.setSessionAcknowledgeMode(1);
return factory;
}
}