ActiveMQ
一 、入门概述
二、ActiveMQ安装
官网下载 http://activemq.apache.org/
启动方式
将安装上传到Linux系统后,无需进行任何操作,直接通过命令启动即可
功能 | 命令 |
---|---|
普通启动 | ./activemq start |
普通停止 | ./activemq stop |
带日志方式启动 | ./activemq start > activemq.log |
端口情况
功能 | 端口 |
---|---|
后台管理系统图形化界面 | 8161 |
默认进程端口号 | 61616 |
启动情况
Linux中查看启动状况
功能 | 命令 |
---|---|
查看MQ是否使用 | ps -ef | grep activemq |
查看端口是否使用 | netstat -anp | grep 61616 |
Win中通过浏览器查看,通过IP地址 + 端口号 进入, 账号和密码默认都是admin
登录成功界面
三、Queue队列入门Demo
1 环境配置
1) IEDA2020.1
2) JDK1.8版本
3) Springboot功能
4) 导入依赖
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-all</artifactId>
<version>5.16.0</version>
</dependency>
2 队列测试(同步阻塞方式receive())
1)消息生产者编码
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
public class JmsProduce {
//ActiveMQ地址
public static final String ACTIVEMQ_URL = "tcp://192.168.146.131:61616";
//队列名
public static final String QUEUE_NAME = "queue01";
public static void main(String[] args) throws JMSException {
//1 创建连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2 通过连接工程,获取连接connection并启动访问
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//3 创建会话seesion
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4 创建目的地(具体是队列还是主题topic)
Queue queue = session.createQueue(QUEUE_NAME);
//5 创建消息的生产者
MessageProducer messageProducer = session.createProducer(queue);
//6 生产3条消息发送到消息队列里面
for (int i = 0; i < 3; i++) {
// 7 创建消息
TextMessage textMessage = session.createTextMessage("发送队列测试消息" + i);
// 8 通过消息生产者发布
messageProducer.send(textMessage);
}
//9 关闭资源
messageProducer.close();
session.close();
connection.close();
System.out.println("消息发送到MQ完成");
}
}
启动类之后,查看网页
连接不成功解决问题,参考文章: https://blog.csdn.net/weixin_44051038/article/details/111188636
2) 消息消费者代码
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
public class JmsConsumer {
// 消息接收
public static final String ACTIVEMQ_URL = "tcp://192.168.146.132:61616";
public static final String QUEUE_NAME = "test_queue01";
public static void main(String[] args) throws JMSException {
//1 创建连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2 通过连接工程,获取连接connection并启动访问
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//3 创建会话seesion
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4 创建目的地(具体是队列还是主题topic)
Queue queue = session.createQueue(QUEUE_NAME);
//5 创建消息的消费者
MessageConsumer consumer = session.createConsumer(queue);
//6 循环获取消息
while (true){
TextMessage textMessage = (TextMessage) consumer.receive();
if (textMessage != null){
System.out.println("消费消息----->" + textMessage.getText());
}else {
break;
}
}
//7 关闭资源
consumer.close();
session.close();
connection.close();
System.out.println("消费MQ消息成功");
}
}
消费者receive()方法
功能 | 方法 |
---|---|
接收不到消息一直等待 | receive() |
没有消息则在固定时间后停止 | receive(long var1) |
3)测试结果
控制台显示生产者发送的消息,并且页面发送改变
3 队列测试(异步非阻塞方式方式listener)
消息生产者编码同上
消息消费者编码
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
import java.io.IOException;
public class JmsConsumer {
public static final String ACTIVEMQ_URL = "tcp://192.168.146.131:61616";
public static final String QUEUE_NAME = "queue01";
public static void main(String[] args) throws JMSException, IOException {
System.out.println("我是2号消费者");
//1 创建连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2 通过连接工程,获取连接connection并启动访问
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//3 创建会话seesion
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4 创建目的地
Queue queue = session.createQueue(QUEUE_NAME);
//5 创建消息的消费者
MessageConsumer consumer = session.createConsumer(queue);
//6 创建消息监听器
consumer.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();
}
}
}
});
// 一定要写 不然没有输出
// 此代码要保证MQ连接成功并且被消费之后,才关闭连接,不然还未执行就已经关闭了
System.in.read();
consumer.close();
session.close();
connection.close();
System.out.println("消息接收到MQ完成");
}
}
4 消费者三大消费情况
序号 | 启动方式 | 结果 |
---|---|---|
1 | 先启动生产者,然后在启动1个消费者 | 消费者全部消费消息 |
2 | 先启动生产者,然后启动1号消费者,成功后在启动2号消费者 | 1号消费者全部消费,2号消费者未消费任何消息 |
3 | 先分别启动1号消费者和2号消费者,然后在启动生产者 | 全部的消息1号消费者和2号消费者每个一半,而不是每个全都消费一遍 |
5 JMS开发的基本步骤
- 创建1个connection factory工厂
- 通过connection factory来创建JMS connection
- 启动JMS connection
- 通过connection创建JMS session
- 创建JMS destination
- 创建JMS producer 或者 创建JMS message并设置destination
- 创建JMS consumer或者是注册一个JMS message listener
- 发送或接收JMS message
- 关闭所有的JMS资源
6 两种消费方式
方式 | 描述 |
---|---|
同步阻塞方式receive() | 订阅者或接收者调用MessageConsumer的receive()方法来接收消息,receive方法在能够接收到消息之前(或超时之前)将一直阻塞 |
异步非阻塞方式(监听器onMessage) | 订阅者或接收者通过MessageConsumer的setMessageListener(MessageListener listener)注册一个消息监听器, |
7 点对点消息传递的特点
- 每个消息只能有1个消费者,类似于1对1的关系,好比个人快递自己领取自己的
- 消息的生产者和消费者之间没有时间上的相关性,无论消费者在生产者发送消息的时候是否处于运行状态,消费者都可以提取消息,好比我们发送短信,发送者发送以后不见得接收者会即收即看
- 消息被消费后队列中不会在存储,所有消费者不会消费到已经被消费的消息
四、Topic主题入门Demo
1 发送/订阅消息传递域的特点如下:
- 生产者将消息发送到topic中,每个消息可以有多个消费者,属于1:N的关系
- 生产者和消费者之间有时间上的相关性,订阅某一个主题的消费者只能消费自他订阅之后发布的消息
- 生产者生产时,topic不保存消息,它是不保存消息的不落地,假如无人订阅就去生产,那就是一条废消息,所以一般先启动消费者在启动生产者
2 主题测试
1) 消费者代码
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
import java.io.IOException;
public class JmsConsumer {
// 消息接收
public static final String ACTIVEMQ_URL = "tcp://192.168.146.132:61616";
public static final String TOPIC_NAME = "topic-test";
public static void main(String[] args) throws JMSException, IOException {
System.out.println("我是1号消费者");
//1 创建连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2 通过连接工程,获取连接connection并启动访问
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//3 创建会话seesion
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4 创建目的地
Topic topic = session.createTopic(TOPIC_NAME);
//5 创建消息的消费者
MessageConsumer consumer = session.createConsumer(topic);
//6 消息
consumer.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();
consumer.close();
session.close();
connection.close();
}
}
2) 生产者代码
package com.lin.activemqdemo.demo03_topic;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
public class JmsProduce {
public static final String ACTIVEMQ_URL = "tcp://192.168.146.132:61616";
public static final String TOPIC_NAME = "topic-test";
public static void main(String[] args) throws JMSException {
//1 创建连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2 通过连接工程,获取连接connection并启动访问
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//3 创建会话seesion
// 两个参数 事务 / 签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4 创建目的地(具体是队列还是主题topic)
Topic topic = session.createTopic(TOPIC_NAME);
//5 创建消息的生产者
MessageProducer messageProducer = session.createProducer(topic);
messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
//6 生产3条消息发送到消息队列里面
for (int i = 0; i < 3; i++) {
// 7 创建消息
TextMessage textMessage = session.createTextMessage("topic测试消息" + i);
// 8 通过消息生产者发布
messageProducer.send(textMessage);
}
//9 关闭资源
messageProducer.close();
session.close();
connection.close();
System.out.println("TOPIC消息发送到MQ完成");
}
}
3) 测试步骤
- 先启动消费者代码,为1号消费者
- 修改消费者代码中的输出文字,然后启动,为2号消费者,这个通过IDEA设置代码可以启动2个进程
- 启动生产者代码,
- 发现启动之后,1号消费者和2号消费者,接收的消息都是生产者发送的全部消息,而不是一人一半
- 当我们通过生产者再次启动发送消息时,发送1号消费者和2号消费者依然能够接收到消息
3 队列VS主题
比较项目 | Topic模式对队列 | Queue模式队列 |
---|---|---|
工作模式 | ”订阅-发布“模式,如果当前没有订阅者,消息将会被丢弃,如果有多个订阅者,那么这些订阅者都会收到消息 | “负载均衡”模式,如果当前没有消费者,消息也不会丢弃,如果有多个消费者,那么1条消息只能发送给其中一个消费者,并且要求消费者ack消息 |
有无状态 | 无状态 | Queue数据默认会在mq服务器上以文字形式保存,比如ActiveMQ一般保存在$AMQ_HOME/data/kr-store/data下面,也可以配置成DB存储 |
传递完整性 | 如果没有订阅者,消息会被丢弃 | 消息不回丢弃 |
处理效率 | 由于消息要按照订阅者的数量进行复制,所以处理性能会随着订阅者的增加而明显降低,并且还要结合不同消息协议自身的性能差异 | 由于1条消息只能发送给1个消费者,所以就算消费者再多,性能也就不会有明显降低,当然不同消息协议的具体性能也是有差异的 |
五、 JMS规范和落地产品
1 是什么?
Java Message Service(Java消息服务是JavaEE中的一个技术)
Java消息服务指的是两个应用程序之间进行异步通信的API,它为标准消息协议和消息服务提供了一组通用接口,包括创建、发送、读取消息等,用于支持Java应用程序开发。在JavaEE中,当两个应用程序使用JMS进行通信时,它们之间斌不是之间相连的,而是通过一个共同的消息收发服务组件关联起来以达到解耦/异步削峰的效果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7RUg1Jqc-1612112511098)(/…/…/Images/image-20201215221144986.png)]
2 MQ消息中间件落地产品
特性 | ActiveMQ | RabbitMQ | Kafka | RockerMQ |
---|---|---|---|---|
PRODUCER-COMSUMER | 支持 | 支持 | 支持 | 支持 |
PUBLISH-SUBSCRIBE | 支持 | 支持 | 支持 | 支持 |
REQUEST-REPLY | 支持 | 支持 | - | 支持 |
API完整性 | 高 | 高 | 高 | 低(静态配置) |
多语言支持 | 支持,Java优先 | 语言无关 | 支持,Java有限 | 支持 |
单机吞吐量 | 万级 | 万级 | 十万级 | 单机万级 |
消息延迟 | - | 微秒级 | 毫秒级 | - |
可用性 | 高(主从) | 高(主从) | 非常高(分布式) | 高 |
消息丢失 | - | 低 | 理论上不会丢失 | - |
3 JMS的组成结构和特点
消息设置示例代码
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
public class JmsProducer {
public static final String ACTIVEMQ_URL = "tcp://192.168.146.132:61616";
public static final String TOPIC_NAME = "topic-test2";
public static void main(String[] args) throws JMSException {
//1 创建连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_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 producer = session.createProducer(topic);
// 消息头设置
// 1)可以统一设置消息为非持久化
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
// 2)可以统一设置消息优先级
producer.setPriority(4);
// 3)可以统一设置过期时间
//producer.setTimeToLive(1000);
for (int i = 0; i < 3; i++) {
//文本消息
TextMessage textMessage = session.createTextMessage("文本消息" + i);
//单独设置某条消息为队列/主题模式
//textMessage.setJMSDestination(topic);
//单独设置某条消息的持久化策略
//textMessage.setJMSDeliveryMode(DeliveryMode.PERSISTENT);
//单独设置某条消息的过期时间
//textMessage.setJMSExpiration(1000);
//单独设置某条消息的优先级
//textMessage.setJMSPriority(5);
//单独设置某条消息的id
//textMessage.setJMSMessageID("1000");
producer.send(textMessage);
//map类型
MapMessage mapMessage = session.createMapMessage();
mapMessage.setString("hello","world");
//其它类型....
}
// 6 关闭资源
producer.close();
session.close();
connection.close();
System.out.println("消息发送成功");
}
}
4 JMS的可靠性
持久的Queue
持久的Topic
1) 消费者代码
// 主要代码有和上面不一样的
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
import java.io.IOException;
public class JmsConsumer_Topic_Persist {
public static final String ACTIVEMQ_URL = "tcp://192.168.146.132:61616";
public static final String TOPIC_NAME = "topic-persist";
public static void main(String[] args) throws JMSException, IOException {
System.out.println("我是1号消费者");
//1 创建连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2 通过连接工程,获取连接connection并启动访问
Connection connection = activeMQConnectionFactory.createConnection();
// 设置订阅者id
connection.setClientID("1号消费者");
//3 创建会话seesion
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4 创建目的地(具体是队列还是主题topic)
Topic topic = session.createTopic(TOPIC_NAME);
TopicSubscriber topicSubscriber = session.createDurableSubscriber(topic,"remark");
//5 启动
connection.start();;
//6 订阅者接收
Message message = topicSubscriber.receive();
while (message != null){
TextMessage textMessage = (TextMessage) message;
System.out.println("收到的持久化topic:" + textMessage.getText());
//接收消息之后一直监听服务
message = topicSubscriber.receive();
//第一次接收到消息之后,4秒之内没有接收到新消息,服务自动停止
//message = topicSubscriber.receive(4000);
}
session.close();
connection.close();
}
}
2) 启动者代码
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
public class JmvProduce_Topic_Persist {
public static final String ACTIVEMQ_URL = "tcp://192.168.146.132:61616";
public static final String TOPIC_NAME = "topic-persist";
public static void main(String[] args) throws JMSException {
//1 创建连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2 通过连接工程,获取连接connection并启动访问
Connection connection = activeMQConnectionFactory.createConnection();
//3 创建会话seesion
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4 创建目的地(具体是队列还是主题topic)
Topic topic = session.createTopic(TOPIC_NAME);
//5 创建消息的生产者
MessageProducer messageProducer = session.createProducer(topic);
messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
//6 这里在上面设置完持久话在启动
connection.start();
//7 生产3条消息发送到消息队列里面
for (int i = 0; i < 3; i++) {
TextMessage textMessage = session.createTextMessage("topic消息" + i);
messageProducer.send(textMessage);
}
//8 关闭资源
messageProducer.close();
session.close();
connection.close();
System.out.println("TOPIC消息发送到MQ完成");
}
}
3) 测试方式
序号 | 启动方式 | 结果 |
---|---|---|
1 | 先启动消费者,订阅主题,然后启动生产者 | 消息可以正常接收 |
2 | 先启动1号消费者,然后启动2号消费者,然后启动生产者 | 消息可以正常接收 |
3 | 先启动1号消费者,订阅之后在关闭,然后启动生产者发送消息,再次启动消费者看是否能收到消息? | 1号消费者启动之后之前的消息正常接收 |
4) 结论
- 一定要先运行一次消费者,等于向MQ注册,类似我订阅了这个主题
- 然后在运行生产者发送信息
- 此时无论消费者是否在线,都会接收到,不在线的话,下次连接的时候,会把没有收到的消息都接收过来
需要注意页面订阅者数量变化,可以查看到当前主题的在线人数和离线人数
事务transcation
步骤
- 开启消息事务, 在创建session的时候开启
- 当消息通过send()方法发送之后,必须要通过 session.commit(); 才能提交成功
- 当出现异常时,可以通过 session.rollback(); 进行回滚
生产者开启事务代码示例
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
public class JmsProduce {
public static final String ACTIVEMQ_URL = "tcp://192.168.146.132:61616";
public static final String QUEUE_NAME = "test_queue01";
public static void main(String[] args) throws JMSException {
//1 创建连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2 通过连接工程,获取连接connection并启动访问
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//3 创建会话seesion
// !!!!!!!!!!!!!!!这是设置事务为 true 开启状态
Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
//4 创建目的地(具体是队列还是主题topic)
Queue queue = session.createQueue(QUEUE_NAME);
//5 创建消息的生产者
MessageProducer messageProducer = session.createProducer(queue);
//6 生产3条消息发送到消息队列里面
for (int i = 0; i < 3; i++) {
// 7 创建消息
TextMessage textMessage = session.createTextMessage("发送队列测试消息----" + i);
// 8 通过消息生产者发布
messageProducer.send(textMessage);
}
//9 关闭资源
messageProducer.close();
// 当上面开启事务以后,这里一定要通过commit提交
session.commit();
session.close();
connection.close();
System.out.println("消息发送到MQ完成");
try {
}catch (Exception e){
// 如果代码如果任何异常,可以使用rollback进行回滚
session.rollback();
}
}
}
消费者开启事务代码示例
注意: 当消费者开启事务以后,也必须要commit提交,不然这条消息则没有被消费,下次运行消费者代码会再次获取相同的消息,造成非常常见的问题: 消息重复消费
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
public class JmsConsumer {
// 消息接收
public static final String ACTIVEMQ_URL = "tcp://192.168.146.132:61616";
public static final String QUEUE_NAME = "test_queue01";
public static void main(String[] args) throws JMSException {
//1 创建连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2 通过连接工程,获取连接connection并启动访问
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//3 创建会话seesion
Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
//4 创建目的地(具体是队列还是主题topic)
Queue queue = session.createQueue(QUEUE_NAME);
//5 创建消息的消费者
MessageConsumer consumer = session.createConsumer(queue);
//6 循环获取消息
while (true){
TextMessage textMessage = (TextMessage) consumer.receive(1000);
if (textMessage != null){
System.out.println("消费消息----->" + textMessage.getText());
}else {
break;
}
}
//7 关闭资源
consumer.close();
//这里一定要commit,不然则消息没有被消费,下次运行还会获取相同的消息
//会造成消息重复消费
session.commit();
session.close();
connection.close();
System.out.println("消费MQ消息成功");
}
}
签收acknowledge
描述 | 代码 | |
---|---|---|
常用 | 自动签收 | Session.AUTO_ACKNOWLEDGE=1 |
常用 | 手动签收 | Session.CLIENT_ACKNOWLEDGE=2 |
允许部分重复签收 | Session.DUPS_OK_ACKNOWLEDGE=3 | |
事务 | Session.SESSION_TRANSACTED=0 |
非事务状态下消费者开启自动签收示例代码
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
public class JmsConsumer {
// 消息接收
public static final String ACTIVEMQ_URL = "tcp://192.168.146.132:61616";
public static final String QUEUE_NAME = "test_queue01";
public static void main(String[] args) throws JMSException {
//1 创建连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2 通过连接工程,获取连接connection并启动访问
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//3 创建会话seesion
// 设置签收为自动签收
Session session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
//4 创建目的地(具体是队列还是主题topic)
Queue queue = session.createQueue(QUEUE_NAME);
//5 创建消息的消费者
MessageConsumer consumer = session.createConsumer(queue);
//6 循环获取消息
while (true){
TextMessage textMessage = (TextMessage) consumer.receive(1000);
if (textMessage != null){
System.out.println("消费消息----->" + textMessage.getText());
// 消息一定要确定签收,不然会造成重复消费
textMessage.acknowledge();
}else {
break;
}
}
//7 关闭资源
consumer.close();
session.close();
connection.close();
System.out.println("消费MQ消息成功");
}
}
事务状态下消费者开启手动签收
测试情况:
事务状态 | 签收状态 | 是否commit() | 是否调用acknowledge() | 结果 | 结论 |
---|---|---|---|---|---|
开启 | 自动签收 | 是 | 否 | 消息不会重复消费 | 事务会帮我们进行手动签收 |
开启 | 自动签收 | 否 | 是 | 消息会重复消费 | 没有commit则签收不管事 |
5 点对点模式的总结
点对点模式是基于队列的,生产者发消息队列,消费者从队列接收消息,队列的存在使得消息的异步传输成为可能,和我们平常给朋友发送短信类似
1 如果在Session关闭时有部分消息已被接收到但还没有被签收(ack),那当消费者下次连接到相同的队列时,这些消息还会被再次接收
2 队列可以长久地保存消息知道消费者收到消息,消费者不需要因为担心消息会丢失而时刻和队列保持激活的连接状态,充分体现了异步传输模式的优势
6 发布/订阅模式总结
非持久订阅
JMS Pub/Sub模型定义了如何向一个内容节点发送和订阅消息,这些节点被称作topic
主题可以被认为是消息的传输中介,发布者(publisher)发布消费到主题,订阅者(subscribe)从主题订阅消息
主题使得消息订阅者和消息发布者保持互相独立,不需要接触即可保证消息的传送
持久订阅
客户端首先向MQ注册一个自己的身份ID识别号,当这个客户端处理离线时,生产者会为这个ID保存所有发送到主题的消息,当客户再次连接到MQ时会根据消费者的ID得到所有当自己处理离线时发送到主题的消息
非持久订阅状态下,不能恢复或重新派送一个未签收的消息
持久订阅才能恢复或重新派送一个未签收的消息
六、ActiveMQ的Broker
1 是什么
相当于一个ActiveMQ服务器示例
说白了,Broker其实就是实现了用代码的形式启动ActiveMQ将MQ嵌入到Java代码中,以便随时用随时启动,在用的时候再去启动,这样能节省了资源,也保证了可靠性
2 不同配置文件启动ActiveMQ
启动命令
./bin/activemq start xbean:file:/usr/local/apache-activemq-5.16.0/conf/activemq02.xml
启动示例
3 嵌入式Broker
import org.apache.activemq.broker.BrokerService;
public class EmbedBroker {
public static void main(String[] args) throws Exception {
// 在本机启动一个迷你般的ActiveMQ
BrokerService brokerService = new BrokerService();
brokerService.setUseJmx(true);
// 因为要在本机启动,所以地址为本机
brokerService.addConnector("tcp://localhost:61616");
brokerService.start();
}
}
启动之后可以通过JPS查看进程,然后通过消费者和生产者测试
七、SpringBoot整合ActiveMQ
八、ActiveMQ的传输协议
1) 面试题
- 默认的61616端口如何修改
- 你生产上的链接协议如何配置的? 使用tcp吗?
2) 官网
http://activemq.apache.org/configuring-version-5-transports.html 官网传输协议文章
3)协议有哪些
不同的传输协议,对应的配置和编码也不相同,不单单只是把地址修改而已
序号 | 名称 | 使用频率 | 性能 |
---|---|---|---|
1 | TCP协议 | 默认 | 默认的协议,性能相对可以 |
2 | New I/0协议 | 常用 | 基于TCP协议之上的,进行了扩展和优化,具有更好的扩展性 |
3 | AMQP协议 | ||
4 | stomp协议 | ||
5 | mqtt协议 | ||
6 | ws协议 | ||
7 | … |
4) NIO案例演示
1 配置
(1)修改conf/activemq.xml文件,将官网上开启NIO的配置添加到配置文件中,为了TCP默认的61616端口区分,可以将端口修改为其它,我这里设置为61618
修改之后,重启ActiveMQ服务,然后可以通过官网查看修改结果
(2)测试
将上面地址修改为 nio://192.168.146.132:61618 然后测试即可
5) NIO增强
上述的NIO性能不错了,如何进进一步优化?
问题: URL格式以“nio"开头,表示这个端口使用已TCP协议为基础的NIO网络IO模型,但是这样的设置方式,只能使这个端口支持Openwire协议
那么我们怎么既让这个端口支持NIO网络IO模型,又让它支持多个协议呢?
解决:
1 使用auto关键字
2 适应”+“符号来为端口设置多种特性
3 如果我们既需要某一个端口支持NIO网络IO模型,又需要它支持多个协议
(1)修改配置文件
<transportConnector name="auto+nio" uri="auto+nio://192.168.146.132:61618?maximumConnections=1000&wireFormat.maxFrameSize=104857600&org.apache.activemq.transport.nio.SelectorManager.corePoolSize=20&org.apache.activemq.transport.nio.SelectorManager.maximumPoolSize=50" />
(2)重启ActiveMQ
(3)测试
九、ActiveMQ的消息存储和持久化
1)官网
http://activemq.apache.org/persistence
2)是什么
为了避免意外宕机以后丢失信息,需要做到重启后可以恢复消息队列,消息系统一半都会采用持久化机制
ActiveMQ的消息持久化机制有:JDBC,AMQ,KahaDB和LevelDB,无论使用哪种持久化方式,消息的存储逻辑都是一致的
就是发送者将消息发送出去后,消息中心首先将消息存储到本地数据文件、内存数据库或远程数据库等在试图将消息发送给接收者,成功则将消息从存储中删除,失败则继续发送
消息中心启动以后,首先要检查指定的存储位置,如果有未发送成功的消息,则需要把消息发送出去
3)有哪些
序号 | 名称 | 情况 | 解释 |
---|---|---|---|
1 | AMQ Message Store | 了解 | 基于文件的存储方式,是以前的默认消息存储,现在不用了,使用于5.3版本之前 |
2 | KahaDB消息存储 | 默认 | |
3 | JDBC消息存储 | ||
4 | LevelDB消息存储 | 了解 | |
5 | JDBC Message store with ActiveMQ Journal |
4)KahaDB(默认)
基于日志文件,从ActiveMQ5.4开始默认的持久化插件,可以从配置文件中看到声明,类似于Redis中的AOF持久化机制
KahaDB持久化机制目录
KahaDB是目前默认的存储方式,可用于任何场景,提高了性能和恢复能力,消息存储使用一个事务日志和仅仅一个索引文件来存储它所有的地址,
KahaDB是一个专门针对消息持久化的解决方案,它对典型的消息使用模式进行了优化.数据被追加到data logs中。当不在需要log文件中的数据的时候.log文件会被丢弃
KahaDB文件中目录结构
KahaDB在消息保存目录中只有4类文件和一个lock,跟ActiveMQ的其它几种文件存储引擎相比这就非常简洁了
1 db-.log KahaDB存储消息到预定义大小的数据记录文件中,文件命名为db-.log 当数据文件已满时.一个新的文件会随之创建,number数值会随之递增,它随着消息数量的增多,如每32M一个文件,文件名按照数字进行编号,如db-1.log、db-2.log、db-3.log… 当不再有引用到数据文件中的任何消息时,文件会被删除或归档
2 db-data 该文件包含了持久化的BTree索引,索引了消息数据记录中的消息,它是消息的索引文件,本质上时B-Tree(B树),使用B-Tree作为索引指向db-.log里面存储的消息
3 db-free 当前db.data 文件里面哪些页面是空闲的,文件具体内容是所有空闲页的ID,当消息存储的较少时,该文件可能不存在
4 db.redo 用于进行消息恢复,如果KahaDB消息存储在强制退出后启动,用于恢复BTree索引
5 lock文件锁,表示当前获得KahaDB读写权限的broker
5) LevelDB消息存储
LevleDB性能在理论上会比KahaDB要好,因为它使用的是自己的索引,但是官方关于LevelDB还没有确定好,所以目前推荐的还是KahaDB
这种文件系统是从ActiveMQ5.8之后引进的,它和KahaDB非常相似,也是基于文件的本地数据库存储形式,但是它提供比KahaDB更快的持久性,
但它不使用自定义B-Tree来实现索引预写日志,而是使用基于LevelDB的索引
默认配置如下:
<persistenceAdapter>
<levelDB directory="activemq-data"/>
</persistenceAdapter>
6) JDBC 持久化机制
1 MQ+Mysql
2 添加mysql数据库的驱动包到lib文件夹
3 jdbcPersistenceAdapter配置
<persistenceAdapter>
<jdbcPersistenceAdapter dataSource="#mysql-ds" />
</persistenceAdapter>
4 数据库连接池配置
按照官网说明修改配置文件
<bean id="mysql-ds" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://192.168.146.1:3306/activemq?relaxAutoCommit=true"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
<property name="poolPreparedStatements" value="true"/>
</bean>
5 建仓SQL和建表说明
(1)建一个名为activemq的数据库
(2)默认创建三张表的说明:
如果新建数据库OK+上述配置OK+代码运行OK,3张表会自动生成
序列 | 表名 | 说明 |
---|---|---|
1 | ACTIVEMQ_MSGS | |
2 | ACTIVEMQ_ACKS | |
3 | ACTIVEMQ_LOCK |
CREATE TABLE `ACTIVEMQ_MSGS` (
`ID` bigint(20) NOT NULL COMMENT '主键id',
`CONTAINER` varchar(250) NOT NULL COMMENT '消息的Destination(目的地,队列或主题的名字)',
`MSGID_PROD` varchar(250) DEFAULT NULL COMMENT '消息发送者的主键',
`MSGID_SEQ` bigint(20) DEFAULT NULL COMMENT '是发送消息的顺序',
`EXPIRATION` bigint(20) DEFAULT NULL COMMENT '消息的过期时间,0为永久,存储是从1970-01-01到现在的毫秒数',
`MSG` longblob COMMENT '消息本体的Java序列化对象的二进制数据',
`PRIORITY` bigint(20) DEFAULT NULL COMMENT '优先级,从0-9 数值越大,优先级越高',
`XID` varchar(250) DEFAULT NULL,
PRIMARY KEY (`ID`),
KEY `ACTIVEMQ_MSGS_MIDX` (`MSGID_PROD`,`MSGID_SEQ`),
KEY `ACTIVEMQ_MSGS_CIDX` (`CONTAINER`),
KEY `ACTIVEMQ_MSGS_EIDX` (`EXPIRATION`),
KEY `ACTIVEMQ_MSGS_PIDX` (`PRIORITY`),
KEY `ACTIVEMQ_MSGS_XIDX` (`XID`),
KEY `ACTIVEMQ_MSGS_IIDX` (`ID`,`XID`,`CONTAINER`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
用于存储订阅关系,如果是持久化Topic,订阅者和服务器的订阅关系在这个表保存
CREATE TABLE `ACTIVEMQ_ACKS` (
`CONTAINER` varchar(250) NOT NULL COMMENT '消息的Destination(目的地,队列或主题的名字)',
`SUB_DEST` varchar(250) DEFAULT NULL COMMENT '如果是使用Static集群,这个字段会有集群其他系统的信息',
`CLIENT_ID` varchar(250) NOT NULL COMMENT '每个订阅者都必须有一个唯一的客户端ID用以区分',
`SUB_NAME` varchar(250) NOT NULL '订阅者名称',
`SELECTOR` varchar(250) DEFAULT NULL '选择器,可以选择只消费满足条件的消息,条件可以用自定义属性实现,可支持多属性AND和OR操作',
`LAST_ACKED_ID` bigint(20) DEFAULT NULL COMMENT '记录消费过的消息的ID',
`PRIORITY` bigint(20) NOT NULL DEFAULT '5',
`XID` varchar(250) DEFAULT NULL,
PRIMARY KEY (`CONTAINER`,`CLIENT_ID`,`SUB_NAME`,`PRIORITY`),
KEY `ACTIVEMQ_ACKS_XIDX` (`XID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
表ActiveMQ_lokc在集群环境中才有用,只有一个Broker可以获得消息,成为Master Broker,其它的只能作为备份等待的Master Broker不可用
才可能成为下一个Masker Broker 这个表用于记录哪个Broker是当前的Master Broker
CREATE TABLE `ACTIVEMQ_LOCK` (
`ID` bigint(20) NOT NULL,
`TIME` bigint(20) DEFAULT NULL,
`BROKER_NAME` varchar(250) DEFAULT NULL,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
6 代码运行验证
(1)一定要开启持久化
(2)队列测试,生产者发送消息以后,数据表会添加对应的记录,当消费者消费以后,数据库记录会消失
在点对点类型中,
当DeliveryMode设置为NON_PERSISTENCE非持久化时,消息被保存在内存中,数据库是没有记录的
当DeliveryMode设置为PERSISTENCE持久化时,消息保存在broker的相应的文件或数据库中
而且点对点类型中,消息一旦被Comsumer消费就从broker中删除
(3)订阅测试
在订阅者模式测试中
我们开启持久化以后,启动消费者订阅消息,这个时候activemq_acks这个表会保存订阅记录,然后当启动生产者以后,发送消息,消费者立刻接收到消息
这个时候在activemq_msgs同样会保存消息记录
(4)队列和订阅在发送消息以后,都会在activemq_msgs里面存储消息记录,但是队列模式中,消息一旦被消费,记录就被删除了,但是订阅者模式中,是永久保存的,而且在activemq_acks里面还会保存订阅者的关系
7 数据库情况
8 开发有坑
在配置关系型数据库作为ActiveMQ的持久化存储方案时,有坑
数据库jar包,
记得需要使用到的相关jar包放置到ActiveMQ安装路径下的lib目录.mysql-jdbc驱动的jar包和对应的数据库连接池jar包
createTablesOnStartup属性
在jdbcPersistenceAdapter标签中设置了createTablesOnStartup属性为true时在第一次启动ActiveMQ时,ActiveMQ服务节点会自动创建所需要的数据表,启动完成后可以去掉这个属性,或者更改createTablesOnStartup属性为false
下划线坑爹
"java.lang.llegalStateException:BeanFactory not initialized or already closed"
这是因为你的操作系统的机器名中有"_"符号,请更改机器名并且重启后即可解决问题
7) JDBC Message store with ActiveMQ Journal
(1)是什么
这种方式客服了JDBC Store的不足 JDBC每次消息过来,都需要区写库和读库
ActiveMQ Journal 使用高速缓存写入技术,大大提高了性能
当消费者的消费速度能够及时跟上生产者消息的生产速度时,journal文件能够大大减少需要写入到DB中的消息
举个例子:
生产者生产了1000条消息,这1000条消息会保存到journal文件,如果消费者的消费速度很快的情况下,在journal文件还没有同步到DB之前,消费者已经消费了90%的以上的消息,那么这个时候只需要同步剩余的10%的消息到DB,如果消费者的消费速度很慢,这个时候journal文件可以使消息以批量方式写到DB
(2)配置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1UVhesae-1612112511114)(/…/…/Images/image-20210112222345017.png)]
<persistenceFactory>
<journalPersistenceAdapterFactory
journalLogFiles="4"
journalLogFileSize="32768"
useJournal="true"
useQuickJournal="true"
dataSource="#mysql-ds"
dataDirectory="activemq-data" />
</persistenceFactory>
- 持久化机制总结
持久化消息主要是指:
MQ所在服务器down了消息不会丢失的机制
持久化机制演化过程:
从最初的AMQ Message Store方案到ActiveMQ V4版本退出的High performance journal(高性能事务支持)附件,并且同步推出了关于关系型数据库的存储方案,ActiveMQ5.3版本中又推出了对KahaDB的支持,(V5.4版本后成为ActiveMQ默认的持久化方案),后来ActiveMQ V5.8版本开始支持LevelDB,到现在,V5.9+版本提供了标准的Zookeeper+LevelDB集群化方案,我们重点介绍了KahaDB、LevelDB和mysql数据库这三种持久化存储方案
ActiveMQ的消息持久化机制有:
AMQ 基于日志文件
KahaDB 基于日志文件,从ActiveMQ5.4 开始默认的持久化插件
JDBC 基于第三方数据库
LevelDB 基于文件的本地数据库储存,从ActiveMQ5.8版本之后又推出了LevelDB的持久化引擎性能高于KahaDB
Replicated LevelDB Store 从ActiveMQ 5.9 提供了基于LevelDB和Zookeeper的数据复制方式,用于Master-slave方式的首选数据复制方案
无论使用哪种持久化方式,消息的存储逻辑都是一致的
十、ActiveMQ多节点集群
1) 面试题:
引入消息队列之后该如何保证其高可用性
2) 是什么?
基于Zookeeper和LevelDB搭建ActiveMQ集群,集群仅提供主备方式的高可用集群功能,避免单点故障
3) 三种集群方式的区别
4) Zookeeper +Replicated LevelDB Store 的主从集群
原理说明:
使用Zookeeper集群注册所有的ActiveMQ Broker但只有其中的一个Broker可以提供服务它将被视为Master,其他的Broker处于待机状态被视为Slave
如果Master因故障而不能提供服务Zookeeper会从Slave中选择出一个Broker充当Master
Slave连接Master并同步他们的存储状态,Slave不接受客户端连接,所有的存储操作都将被复制到连接至Master的Slaves.
如果Master宕机得到了最新更新的Slave会变成Master。故障节点在恢复后会重新加入到集群中并连接Master进入Slave模式
所有需要同步的消息操作都将等待存储状态被复制到其它法定节点的操作完成才能完成
所以,如果你配置了replicas=3,那么法定大小是(3/2)+1 = 2. Master将会存储并更新然后等待(2-1)=1 个Slave存储和更新完成,才汇报success
有一个node要作为观察者存在,当一个新的Master被选中,你需要至少保障一个法定node在线以能够找到拥有最新状态的node。这个node才可以成为新的Master
因为,推荐运行至少3个replica nodes以防止一个node失败后服务中断
配置
(1)环境和版本:
Centos7 + JDK1.8 + Zookeeper + apache-activemq
(2)关闭防火墙并保证win可以ping通ActiveMQ服务器
(3)要求具备ZK集群并可以成功启动
(4)集群部署规划列表
(5)创建3台集群目录
(6)修改管理控制台端口
(7)hostname名字映射
(8)ActiveMQ集群配置
(9)修改各节点的消息端口
(10)按顺序启动3个ActiveMQ节点,到这步前提是ZK集群已经成功启动运行
(11)zk集群的节点状态说明
十一、高级特性和大厂常考重点
1 引入消息队列之后该如何保证其高可用性
2 异步投递Async Sends
ActiveMQ支持同步、异步两种发送的模式将消息发送到Broker,模式的选择对发送延迟时有巨大的影响.Producer能达到怎样的产出率(产出率=发送消息总量/时间)主要受发送延迟的影响,使用异步发送可以显著的提供发送的性能
ActiveMQ默认使用异步发送的模式: 除非明确指定使用同步发送的方式或者在未使用事务的前提下发送持久化的消息,这两种情况都是同步发送的
如果你没有使用事务期呃发送的西时持久化的消息,每一次发送都是同步发送的且会阻塞Producer知道Broker返回一个确认,表示消息已经被安全的持久化到磁盘.确认机制提供了消息安全的保障,但同时会阻塞客户端带来了很大的延时
很多高性能的应用,允许在失败的情况下有少量的数据丢失,如果你的应用满足这个特点,你可以使用异步发送来提高生产率,即使发送的是持久化的消息
异步发送
它可以最大化Producer端的发送效率。我们通常在发送消息量比较密集的情况下使用异步发送,它可以很大的提升Producer性能;不过这也带来了额外的问题
就是需要消耗较多的Client端内存同时也会导致Broker端性能消耗增加
此外它不能有效地确保消息地发送成功,在useAsyncSend=true的情况下客户端需要容忍消息丢失的可能
如何开启异步投送?
开启异步同步投送以后,如何确认发送成功?
异步发送丢失消息的场景是: 生产者设置UseAsyncSend=true 使用producer,send(msg) 持续发送消息
由于消息不阻塞,生产者会认为所有send的消息均被成功发送至MQ
如果MQ突然宕机,此时生产者端内存中尚未被发送至MQ的消息都会丢失
所以,正确的异步发送方法是 需要接受回调的
同步发送和异步发送的区别就在此
同步发送等send不阻塞了就表示一定发送成功了
异步发送需要接收回执并由客户端在判断一次是否发送成功
异步发送回调函数代码示例
public String ACTIVEMQ_URL = "tcp://192.168.146.132:61616";
public String QUEUE_NAME = "queue01";
@Test
public void producer() throws JMSException {
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
activeMQConnectionFactory.setUseAsyncSend(true);
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Queue queue = session.createQueue(QUEUE_NAME);
ActiveMQMessageProducer producer = (ActiveMQMessageProducer) session.createProducer(queue);
for (int i = 0; i < 3; i++) {
TextMessage textMessage = session.createTextMessage("发送消息队列:" + i);
// 设置消息id
textMessage.setJMSMessageID(UUID.randomUUID().toString() + "--wang");
String msgID = textMessage.getJMSMessageID();
// 消息发送回调函数
producer.send(textMessage, new AsyncCallback() {
@Override
public void onSuccess() {
System.out.println(msgID + "消息发送成功");
}
@Override
public void onException(JMSException e) {
System.out.println(msgID + "消息发送失败");
}
});
}
producer.close();
session.close();
connection.close();
System.out.println("消息发送完成");
}
3 延迟投递和定时投递
四大属性
使用:
(1) 要在activemq.xml中配置schedulerSupport属性为true,然后重启服务
schedulerSupport=“true”
(2) Java代码里面封装的辅助消息类型:ScheduledMessage
启动消费者以后,然后启动生产者,消费者则定时重复接收数据
@Test
public void producer() throws JMSException {
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Queue queue = session.createQueue(QUEUE_NAME);
MessageProducer producer = session.createProducer(queue);
// 延迟投递
long delay = 3 * 1000;
// 重复投递时间间隔
long period = 4 * 1000;
// 重复投递次数
int repeat = 5;
for (int i = 0; i < 3; i++) {
TextMessage textMessage = session.createTextMessage("发送消息队列:" + i);
textMessage.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY,delay);
textMessage.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_PERIOD,period);
// 注意这里repeat是int类型,所以要使用setIntProperty()方法
textMessage.setIntProperty(ScheduledMessage.AMQ_SCHEDULED_REPEAT,repeat);
producer.send(textMessage);
}
producer.close();
session.close();
connection.close();
System.out.println("消息发送完成");
}
4 分发策略
5 ActiveMQ消息重试机制
(1)生产案例面试题:
1 具体哪些情况会引起消息重发?
1)Client用了transactions且在session中调用了rollback()
2)Client用了transactions且在调用commit()之前关闭或者没有commit
3)Client在CLIENT_ACKNOWLEDGE的传递模式下,在Session中调用了recover()
2 请说说消息重发时间间隔和重发次数吗?
间隔:1秒
次数:6
3 有毒消息Poison ACK谈谈你的理解?
一个消息被redelivedred超过默认的最大重发次数(默认6次)时,消费端会给MQ发送一个'poison ack'表示这个消息有毒,告诉broker不要再发了,这个时候broker会把这个消息放到DLQ(死信队列)
(2)消息重发机制官网: http://activemq.apache.org/redelivery-policy
Property | Default Value | Description | 中文翻译 | 使用频率 |
---|---|---|---|---|
backOffMultiplier | 5 | The back-off multiplie | 重连时间间隔递增倍数,只有值大于1和启动useExponentialBackOff参数时才生效,默认是5 | |
collisionAvoidanceFactor | 0.15 | The percentage of range of collision avoidance if enabled. | 设置防止冲突范围的正负百分比,只有启用useCollisionAvoidance参数时才生效,也就是在延迟时间上在加一个时间波动范围,默认值为0.15 | |
initialRedeliveryDelay | 1000L | The initial redelivery delay in milliseconds. | 初始重发延迟时间 | 重要 |
maximumRedeliveries | 6 | Sets the maximum number of times a message will be redelivered before it is considered a poisoned pill and returned to the broker so it can go to a Dead Letter Queue. Set to -1 for unlimited redeliveries. | 最大重连次数,达到最大重连次数后抛出异常,为-1时不限制次数,为0时表示不进行重传,默认值为6 | 重要 |
maximumRedeliveryDelay | -1 | Sets the maximum delivery delay that will be applied if the useExponentialBackOff option is set. (use value -1 to define that no maximum be applied) (v5.5). | 最大传送延迟,只在useExponentialBackOf为true时有效,假设首次重连间隔为10ms,倍数为2,那么第二次重连时间间隔为20ms,第三次重连间隔为40ms,当重连时间间隔大的最大重连时间间隔时,以后每次重连时间间隔都为最大重连时间间隔,默认为-1 | |
redeliveryDelay | 1000L | The delivery delay if initialRedeliveryDelay=0 (v5.4). | 重发延迟时间,当initialRedeliveryDelay=0时生效,默认1000L | |
useCollisionAvoidance | false | Should the redelivery policy use collision avoidance. | 启用防止冲突功能,默认false | |
useExponentialBackOff | false | Should exponential back-off be used, i.e., to exponentially increase the timeout. | 启用指数倍数递增的方式增加延迟时间,默认false |
6 死信队列
7 如何保证消息不被重复消费呢? 幂等性问题你谈谈
网络延迟传输中,会造成进行MQ重试中,在重试过程中,可能会造成重复消费
如果消息是做数据库的插入操作,给这个消息做一个唯一主键,那么就算出现重复消费的情况,就会导致主键冲突,避免数据库出现脏数据
如果上面两种情况还不行,准备一个第三方服务来做消费记录,以Redis为例,给消息分配一个全局id,只要消费过该消息,将<id,message>以K-V形式写入redis,那消费者开始消费前,先去redis中查询有没消费记录即可
ill** and returned to the broker so it can go to a Dead Letter Queue. Set to -1
for unlimited redeliveries. | 最大重连次数,达到最大重连次数后抛出异常,为-1时不限制次数,为0时表示不进行重传,默认值为6 | 重要 |
| maximumRedeliveryDelay
| -1
| Sets the maximum delivery delay that will be applied if the useExponentialBackOff
option is set. (use value -1
to define that no maximum be applied) (v5.5). | 最大传送延迟,只在useExponentialBackOf为true时有效,假设首次重连间隔为10ms,倍数为2,那么第二次重连时间间隔为20ms,第三次重连间隔为40ms,当重连时间间隔大的最大重连时间间隔时,以后每次重连时间间隔都为最大重连时间间隔,默认为-1 | |
| redeliveryDelay
| 1000L
| The delivery delay if initialRedeliveryDelay=0
(v5.4). | 重发延迟时间,当initialRedeliveryDelay=0时生效,默认1000L | |
| useCollisionAvoidance
| false
| Should the redelivery policy use collision avoidance. | 启用防止冲突功能,默认false | |
| useExponentialBackOff
| false
| Should exponential back-off be used, i.e., to exponentially increase the timeout. | 启用指数倍数递增的方式增加延迟时间,默认false | |
6 死信队列
7 如何保证消息不被重复消费呢? 幂等性问题你谈谈
网络延迟传输中,会造成进行MQ重试中,在重试过程中,可能会造成重复消费
如果消息是做数据库的插入操作,给这个消息做一个唯一主键,那么就算出现重复消费的情况,就会导致主键冲突,避免数据库出现脏数据
如果上面两种情况还不行,准备一个第三方服务来做消费记录,以Redis为例,给消息分配一个全局id,只要消费过该消息,将<id,message>以K-V形式写入redis,那消费者开始消费前,先去redis中查询有没消费记录即可