ActiveMQ

ActiveMQ简介

消息中间件应用场景
异步处理
应用解耦
流量削锋
异步处理
场景说明:用户注册,需要执行三个业务逻辑,分别为写入用户表,发注册邮件以及注册短信。
串行方式
将注册信息写入数据库成功后,发送注册邮件,再发送注册短信。以上三个任务全部完成后,返回给客户端。
并行方式
将注册信息写入数据库成功后,发送注册邮件的同时,发送注册短信。以上三个任务完成后,返回给客户端。与串行的差别是,并行的方式可以提高处理的时间
异步处理
引入消息中间件,将部分的业务逻辑,进行异步处理。改造后的架构如下:
按照以上约定,用户的响应时间相当于是注册信息写入数据库的时间,也就是 50 毫秒。注册邮件,发送短信写入消息队列后,直接返回,因此写入消息队列的速度很快,基本可以忽略,因此用户的响应时间可能是50 毫秒。因此架构改变后,系统的吞吐量提高啦,比串行提高了 3 倍,比并行提高了两倍。
应用解耦
场景说明:用户下单后,订单系统需要通知库存系统。传统的做法是,订单系统调用库存系统的接口。如下图
传统模式的缺点:假如库存系统无法访问,则订单减库存将失败,从而导致订单失败,订单系统与库存系统耦合。如何解决以上问题呢?引入应用消息队列后的方案,如下图:
订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功库存系统:订阅下单的消息,采用拉/ 推的方式,获取下单信息,库存系统根据下单信息,进行库存操作假 如:在下单时库存系统不能正常使用。也不影响正常下单,因为下单后,订单系统写入消息队列就不再关心其他的后续操作了。实现订单系统与库存系统的应用解耦。
流量消峰
流量削锋也是消息队列中的常用场景,一般在秒杀或团抢活动中使用广泛。应用场景:秒杀活动,一般会因为流量过大,导致流量暴增,应用挂掉。为解决这个问题,一般需要在应用前端加入消息队列。通过加入消息队列完成如下功能:
a 、可以控制活动的人数
b 、可以缓解短时间内高流量压垮应用
用户的请求,服务器接收后,首先写入消息队列。假如消息队列长度超过最大数量,则直接抛弃用户请求或跳转到错误页面。秒杀业务根据消息队列中的请求信息,再做后续处理
常见的消息中间件产品对比
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 ):即生产者和消费者之间的消息往来。
每个消息都被发送到特定的消息队列,接收者从队列中获取消息。队列保留着消息,直到他们被消费或超时。
点对点模型的特点:
  • 每个消息只有一个消费者(Consumer(即一旦被消费,消息就不再在消息队列中)
  • 发送者和接收者之间在时间上没有依赖性,也就是说当发送者发送了消息之后,不管接收者有没有正在运行,它不会影响到消息被发送到队列;
  • 接收者在成功接收消息之后需向队列应答成功
发布 / 订阅模型
发布 / 订阅( Publish-Subscribe
包含三个角色:主题( Topic ),发布者( Publisher ),订阅者( Subscriber ),多个发布者将消息发送到topic ,系统将这些消息投递到订阅此 topic 的订阅者
发布者发送到 topic 的消息,只有订阅了 topic 的订阅者才会收到消息。 topic 实现了发布和订阅,当你发布一个消息,所有订阅这个topic 的服务都能得到这个消息,所以从 1 N 个订阅者都能得到这个消息的拷贝。
发布 / 订阅模型的特点:
每个消息可以有多个消费者;
发布者和订阅者之间有时间上的依赖性(先订阅主题,再来发送消息)。
订阅者必须保持运行的状态,才能接受发布者发布的消息;
JMS 编程 API
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

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 :登陆:
2 :点击 Queues 队列或者 Topics 主题消息
列表各列信息含义如下:
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. 创建 session
5. 创建目标地址( 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();
}
}
观察发送消息的结果:
PTP 模式 ( 消费者 )
步骤:
1. 创建连接工厂
2. 创建连接
3. 打开连接
4. 创建 session
5. 指定目标地址
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();
            }
        }
    }
});
//注意:在监听器的模式下千万不要关闭连接,一旦关闭,消息无法接收
}
}
观察消费消息的结果:
Pub/Sub 模式 ( 生成者 )
1. 创建连接工厂
2. 创建连接
3. 打开连接
4. 创建 session
5. 创建目标地址( 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();
}
}
查看主题消息:
Pub/Sub 模式 ( 消费者 )
1. 创建连接工厂
2. 创建连接
3. 打开连接
4. 创建 session
5 指定目标地址
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();
}
}
});
}
}

SpringBootActiveMQ整合

生产者

<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 协议组成结构如下:
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提供了以下三种的消息存储方式:
  1. Memory 消息存储-基于内存的消息存储。
  2. 基于日志消息存储方式,KahaDBActiveMQ的默认日志存储方式,它提供了容量的提升和恢复能力。
  3. 基于JDBC的消息存储方式-数据存储于数据库(例如:MySQL)中。
ActiveMQ 持久化机制流程图:
1 )生产者: application.yml
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 消息事务流程图:
一、生产者事务:
方式一:
/**
* 事务性发送--方案一
*/
@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
注意:消息确认机制与事务机制是冲突的,只能选其中一种。所以演示消息确认前,先关闭事务
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 ,非持久消息不会送到 DLQ
2 )缺省的死信队列是 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;
}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值