rabbitmq
一、简介
RabbitMQ是一个实现了AMQP(Advanced Message Queuing Protocol)高级消息队列协议的消息队列服务,用Erlang语言。是面向消息的中间件。
你可以把它想像成一个邮局:你把信件放入邮箱,邮递员就会把信件投递到你的收件人处。在这个比喻中,RabbitMQ是一个邮箱、邮局、邮递员。RabbitMQ和邮局的主要区别是,它处理的不是纸,而是接收、存储和发送二进制的数据——消息。
主要流程:生产者(Producer)与消费者(Consumer)和 RabbitMQ 服务(Broker)建立连接, 然后生产者发布消息(Message)同时需要携带交换机(Exchange) 名称以及路由规则(Routing Key),这样消息会到达指定的交换机,然后交换机根据路由规则匹配对应的 Binding,最终将消息发送到匹配的消息队列(Quene),最后 RabbitMQ 服务将队列中的消息投递给订阅了该队列的消费者(消费者也可以主动拉取消息)。
二、业务场景
1、异步
如: 用户注册发送,注册邮件、注册短信,
传统做法
:
1、串行 (先发送邮件、再发短信)。问题:持续时间长
2、并行(将注册信息写入数据库后,同时发送邮件、短信),速度快、但不能满足高吞吐需求。
消息队列做法
:
将数据写入数据库、同时发送消息给发送邮件和注册,异步处理
2、应用解耦
如:双十一购物节,用户下单后、订单系统通知库存系统。
传统做法
:
订单系统调用库存系统接口。问题:库存接口故障,订单就会失败,而损失大量订单
消息队列做法
订单系统:下单,订单系统完成持久化,将消息写入队列,返回下单成功给用户
库存系统:订阅下单的消息,获取下单消息,进行库操作,就算库存系统故障,消息队列也能保证消息可靠投递,不会导致消息丢失。
3、流量削峰
如:秒杀活动、一般会因为流量过大,导致应用挂掉,一般在应用前端加入消息队列。
作用:1、可以控制活动人数,超过一定阈值,订单直接丢弃
2、可以缓解短时间的高流量压垮应用(应用程序按自己的最大处理能力获取订单)
消息队列做法
1、用户的请求,服务器收到后,首先写入消息队列,加入消息队列长度最大值,则直接抛弃用户请求或跳转到错误页面
2、秒杀业务根据消息队列中的请求信息,再做后续处理
三、下载
1、docker 安装 rabbitmq
docker pull rabbitmq:3.7.7-management
2、启动镜像(用户名和密码设置为 guest guest)
docker run -dit --restart=always --name rabbitmq3.7.7 -e RABBITMQ_DEFAULT_USER=guest -e RABBITMQ_DEFAULT_PASS=guest -v /home/rabbitmq/data:/var/lib/rabbitmq -p 15672:15672 -p 5672:5672 rabbitmq:3.7.7-management
3、访问 rabbitmq 管理界面
http://127.0.0.1:15672
账号密码都是 guest
4、docker 安装 rabbitMQ 延时队列插件(delayed_message_exchange)
下载解压文件 链接:https://pan.baidu.com/s/1PpeOn8NJT4hgh7ZBP0J0OA?pwd=u2gu
提取码:u2gu
拷贝插件文件到 rabbitMQ 的 Docker 容器中
先解压
unzip rabbitmq_delayed_message_exchange-20171201-3.7.x.zip
拷贝插件
docker cp rabbitmq_delayed_message_exchange-20171201-3.7.x.ez rabbitmq3.7.7:/plugins
进入容器:
docker ps // 查看启动容器信息
docker exec -it 镜像ID /bin/bash //开启进入终端
查看插件列表
rabbitmq-plugins list
启用插件
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
四、界面认识
1、概要
2、连接
3、通道
4、交换机
交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列
Type | 解释 |
---|---|
direct | 它会把消息路由到那些 binding key 与 routing key 完全匹配的 Queue 中 |
fanout | 它会把所有发送到该 Exchange 的消息路由到所有与它绑定的 Queue 中 |
headers | headers 类型的 Exchange 不依赖于 routing key 与 binding key 的匹配规则来路由消息,而是根据发送的消息内容中的 headers 属性进行匹配。(headers 类型的交换器性能差,不实用,基本不会使用。) |
topic | 与direct模型相比,多了个可以使用通配符!,这种模型Routingkey一般都是由一个或多个单词组成,多个单词之间以"."分割,例如:item.insert ---------星号 匹配一个1词 , 例audit.* ------- #号匹配一个或多个词 audit.# |
x-delayed-message | 延迟交换机,可以延迟接收消息 |
Features | 解释 |
---|---|
D | d 是 durable 的缩写,代表这个队列中的消息支持持久化 |
AD | ad 是 autoDelete 的缩写。代表当前队列的最后一个消费者退订时被自动删除。注意:此时不管队列中是否还存在消息,队列都会删除。 |
excl | 是 exclusive 的缩写。代表这是一个排他队列。如果一个队列被声明为排他队列,该队列仅对首次声明它的连接可见,并在连接断开时自动删除。这里需要注意三点:其一,排他队列是基于连接可见的,同一连接的不同信道是可以同时访问同一个连接创建的排他队列的。其二,“首次”,如果一个连接已经声明了一个排他队列,其他连接是不允许建立同名的排他队列的,这个与普通队列不同。其三,即使该队列是持久化的,一旦连接关闭或者客户端退出,该排他队列都会被自动删除的。这种队列适用于只限于一个客户端发送读取消息的应用场景。 |
Args | 是 arguments 的缩写。代表该队列配置了 arguments 参数。 |
TTL | 是 x-message-ttl 的缩写。设置队列中的所有消息的生存周期(统一为整个队列的所有消息设置生命周期), 也可以在发布消息的时候单独为某个消息指定剩余生存时间,单位毫秒。 |
Exp | Auto Expire,是 x-expires 配置的缩写。当队列在指定的时间没有被访问(consume, basicGet, queueDeclare…)就会被删除,Features=Exp。注意这里是删除队列,不是队列中的消息。 |
Lim | 说明该队列配置了 x-max-length。限定队列的消息的最大值长度,超过指定长度将会把最早的几条删除掉。 |
Lim B | 说明队列配置了 x-max-length-bytes。限定队列最大占用的空间大小, 一般受限于内存、磁盘的大小。 |
DLX | 说明该队列配置了 x-dead-letter-exchange。当队列消息长度大于最大长度、或者过期的等,将从队列中删除的消息推送到指定的交换机中去而不是丢弃掉。 |
DLK | x-dead-letter-routing-key 的缩写,将删除的消息推送到指定交换机的指定路由键的队列中去。 |
Pri | x-max-priority 的缩写,优先级队列。表明该队列支持优先级,先定义最大优先级值(定义最大值一般不要太大),在发布消息的时候指定该消息的优先级, 优先级更高(数值更大的)的消息先被消费。 |
Ovfl | x-overflow 的缩写。队列中的消息溢出时,如何处理这些消息。要么丢弃队列头部的消息,要么拒绝接收后面生产者发送过来的所有消息。有两个配置项:drop-head,代表丢弃队列头部的消息,默认行为;reject-publish 设置队列中的消息溢出后,该队列的行为:”拒绝接收”(所有消息)。 |
ha-all | 镜像队列。all 表示镜像到集群上的所有节点,ha-params 参数忽略。 |
5、队列
点击名称进去,可以看到队列的详细信息
get Message可以看到消息的内容
arguments具体参数如下:
参数名 | 作用 |
---|---|
x-message-ttl | 发送到队列的消息在丢弃之前可以存活时间(毫秒) |
x-max-length | 队列最大长度 |
x-expires | 队列在被自动删除(毫秒)之前可以使用多长时间 |
x-max-length-bytes | 消息容量限制,该参数是非负整数值。该参数和x-max-length目的一样限制队列的容量,但是这个是靠队列大小(bytes)来达到限制。 |
x-dead-letter-exchange | 设置队列溢出行为。这决定了在达到队列的最大长度时消息会发生什么。有效值为drop-head或reject-publish。交换的可选名称,如果消息被拒绝或过期,将重新发布这些名称 |
x-dead-letter-routing-key | 可选的替换路由密钥,用于在消息以字母为单位时使用。如果未设置,叫使用消息的原始路由密钥 |
x-max-priority | 队列支持的最大优先级数;如果未设置,队列将不支持消息优先级 |
x-queue-mode | 将队列设置为延迟模式,在磁盘上保留尽可能多的消息以减少内存使用,如果未设置,队列将保留内存缓存以尽快传递消息 |
x-queue-master-locator | 将队列设置为主位置模式,确定在节点集群上声明时队列主机所在的规则 |
6、用户
就是添加用户和设置用户权限
7、开启日志:
进入容器,输入
rabbitmq-plugins enable rabbitmq_tracing
此时会多一个tracing标签,输入信息添加日志。
Name: 自定义,建议标准点容易区分
Format: 表示输出的消息日志格式,有Text和JSON两种,Text格式的日志方便人类阅读,JSON的方便程序解析。
JSON格式的payload(消息体)默认会采用Base64进行编码,如上面的“trace test payload.”会被编码成“dHJhY2Ug dGVzdCBwYXlsb2FkLg==”。
Max payload bytes: 表示每条消息的最大限制,单位为B。比如设置了了此值为10,那么当有超过10B的消息经过Rabbit MQ流转时,在记录到trace文件的时候会被截断。如上text日志格式中“trace test payload.”会被截断成“trace tes
Pattern:
#
追踪所有进入和离开MQ的消息
publish.#
追踪所有进入MQ的消息
publish.myExchage
追踪所有进入到myExchange的消息
deliver.#
跟踪所有离开MQ的消息
deliver.myQueue
追踪所有从myQueue离开的消息
#.myQueue
实测效果等同于deliver.myQueue
添加后,点击即可查看日志
如果出现错误,是因为插件默认是使用 guest 用户,是因为把 guest 用户删除了,或者在配置文件里面使用其他用户
解决: 配置/etc/rabbitmq/rabbitmq.config,添加配置(未尝试,可以或者不行或者有其他解决方法请留言)
{rabbitmq_tracing,
[
{directory, "/var/vcap/sys/log/rabbitmq-server/tracing"},
{username, <<"admin">>},
{password, <<"password">>}
]
},
五、五种模型示例
0、springboot依赖配置
依赖
<!-- amqp依赖,包含Rabbitmq-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
yml配置
spring:
application:
name: rabbitmq
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
virtual-host: /
1、Hello World简单模型
一对一消费,只有一个消费者能接收到
消费者
@Component
public class HolloWordListener {
// @RabbitListener(queues = ("simple.queue")) // queues需手动先创建队列
@RabbitListener(queuesToDeclare = @Queue("simple.queue")) // queuesToDeclare 自动声明队列
public void holloWordListener(String message){
System.out.println("message = " + message);
}
}
生产者
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testSimpleQueue() {
String queueName = "simple.queue"; // 队列名称
String message = "heel,simple.queue"; // 要发送的消息
rabbitTemplate.convertAndSend(queueName,message);
}
2、Work queues工作队列
多个消费者,你一个我一个分配消费消息,有预取机制,默认公平消费,可配置能者多劳模式,谁完成的快,谁多做一点
消费者
@Component
public class WoekWordListener {
@RabbitListener(queuesToDeclare = @Queue("workQueue")) // queuesToDeclare 自动声明队列
public void holloWordListener(String message) throws InterruptedException {
Thread.sleep(200);
System.out.println("message1 = " + message);
}
@RabbitListener(queuesToDeclare = @Queue("workQueue")) // queuesToDeclare 自动声明队列
public void holloWordListener1(String message) throws InterruptedException {
Thread.sleep(400);
System.out.println("message2 = " + message);
}
}
生产者
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testWorkQueue(){
String queueName = "workQueue";
String message = "hello,work.queue__";
for (int i = 0; i < 10; i++) {
rabbitTemplate.convertAndSend(queueName,message+i);
System.out.println("i = " + i);
}
}
取消预取机制,能者多劳配置
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
virtual-host: /
listener:
simple:
prefetch: 1 # 每次只能获取一条,处理完成才能获取下一条
3、Publish/Subscribe发布订阅模型
发布订阅模式与之前案例的区别就是允许将同一消息发送给多个消费者。
实现方式是加入了exchange(交换机),注意:交换机是不缓存消息的
使用fanout交换机,会将接收到的消息路由到每一个跟其绑定的queue
(队列)
消费者
// 消费者直接绑定交换机,指定类型为fanout
@Component
public class FanoutExchangeListener {
// 不指定队列,消息过了就没了
// @RabbitListener(bindings = {@QueueBinding(value = @Queue,exchange = @Exchange(value = "fanoutTest",type = ExchangeTypes.FANOUT))})
// 指定队列,可以接收缓存到队列里的消息
@RabbitListener(bindings = {@QueueBinding(value = @Queue(value ="test",durable = "true" ),exchange = @Exchange(value = "fanoutTest",type = ExchangeTypes.FANOUT))})
public void reveivel(String message){
System.out.println("message = " + message);
}
@RabbitListener(bindings = {@QueueBinding(value = @Queue,exchange = @Exchange(value = "fanoutTest",type = ExchangeTypes.FANOUT))})
public void reveivel2(String message){
System.out.println("message1 = " + message);
}
}
生产者
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void tesyPubSubQueue(){
// 参数1:交换机名称 , 参数2routingKey,(fanout类型可不写) , 参数3,消息内容
rabbitTemplate.convertAndSend("fanoutTest","","消息内容");
}
4、Routing路由模型
routing模型也是将消息发送到交换机
使用的是Direct类型的交换机,会将接收到的消息根据规则路由到指定的Queue
(队列),因此称为路由模式
消费者
// 消费者直接绑定交换机,指定类型为direct,并指定key表示能消费的key
@Component
public class RoutingExchangeListener {
// 不指定队列,消息过了就没了
// @RabbitListener(bindings = {@QueueBinding(value = @Queue,exchange = @Exchange(value = "direstTest",type = ExchangeTypes.DIRECT),key = {"info","error"})})
// 指定队列,可以接收缓存到队列里的消息
// key = {"info","error"} 表示我能接收到routingKey为 info和error的消息
@RabbitListener(bindings = {@QueueBinding(value = @Queue(value ="test1",durable = "true" ),exchange = @Exchange(value = "direstTest",type = ExchangeTypes.DIRECT),key = {"info","error"})})
public void receivel(String message){
System.out.println("message = " + message);
}
// key = {"error"} 表示我只能接收到routingKey为 error的消息
@RabbitListener(bindings = {@QueueBinding(value = @Queue,exchange = @Exchange(value = "direstTest",type = ExchangeTypes.DIRECT),key = {"error"})})
public void receivel1(String message){
System.out.println("message1 = " + message);
}
}
生产者
@Autowired
private RabbitTemplate rabbitTemplate;
// 路由模型
@Test
public void direstExchangeTest(){
rabbitTemplate.convertAndSend("direstTest","info","发送info的key的路由消息");
}
// 路由模型
@Test
public void direstExchangeTest1(){
rabbitTemplate.convertAndSend("direstTest","error","发送error的key的路由消息");
}
5、Topics主题模型
topicExchange与directExchange类型,区别在于routingKey必须是多个单词的列表,并且以 . 分隔
*(代表通配符,任意一个字段)
#(号代表一个或多个字段)
消费者
@Component
public class TopicsExchangeListener {
// 不指定队列,消息过了就没了
// @RabbitListener(bindings = {@QueueBinding(value = @Queue,exchange = @Exchange(name = "topicList",type = ExchangeTypes.TOPIC),key = {"user.save","user.*"})})
// 指定队列,可以接收缓存到队列里的消息
// key = {"user.save","user.*"} 表示能消费 routingkey为 user.save 和 user.任意一个字符 的消息
@RabbitListener(bindings = {@QueueBinding(value = @Queue(value ="test2",durable = "true" ),exchange = @Exchange(name = "topicList",type = ExchangeTypes.TOPIC),key = {"user.save","user.*"})})
public void recevicel(String message){
System.out.println("message = " + message);
}
// key = {"order.#","user.*"} 表示能消费 routingkey为 order.一个或多个字符 和 user.任意一个字符 的消息
@RabbitListener(bindings = {@QueueBinding(value = @Queue,exchange = @Exchange(name = "topicList",type = ExchangeTypes.TOPIC),key = {"order.#","user.*"})})
public void recevicel1(String message){
System.out.println("message1 = " + message);
}
}
生产者
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void topicTest(){
rabbitTemplate.convertAndSend("topicTest","user.save","topic路由消息,use.save");
}
@Test
public void topicTest1(){
rabbitTemplate.convertAndSend("topicTest","order.select.getone","topic路由消息,order.select.getone");
}
6、消息转换器
代码里直接发送对象
,虽然接收的到消息,但是rabbitmq的界面上看到的消息会是乱码
依赖
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.9.10</version>
</dependency>
配置
@Configuration
public class rabbitmqConfig {
// 消息转换配置
@Bean
public MessageConverter jsonMessageConverter(){
return new Jackson2JsonMessageConverter();
}
}
再次发送就会是转换好的消息
六、进阶
1、消费者并发消费
让消费者可以开启多个线程并发
去消费消息,可以配合上方工作队列
只需要加配置
spring:
rabbitmq:
addresses: 127.0.0.1:5672
# host: 127.0.0.1
# port: 5672
username: 你的账号
password: 你的密码
virtual-host: /
# 消费者配置
listener:
simple:
concurrency: 2 # 并发数
max-concurrency: 10 #最大并发数
生产者
正常发送消息,发送10个
// 并发消费
@GetMapping("/test13")
public void test13(){
for (int i = 0; i < 10; i++) {
rabbitTemplate.convertAndSend("concurrency","测试并发消费消息");
}
}
消费者
消费者也正常消费
@RabbitListener(queuesToDeclare = @Queue(value = "concurrency"))
public void concurrency(String msg) throws InterruptedException {
long l = System.currentTimeMillis();
// 打印线程名
String name = Thread.currentThread().getName();
System.out.println("name = " + name);
Thread.sleep(1000); // 休眠一秒,好看效果
long l2 = System.currentTimeMillis();
System.out.println("time" + (l2-l)/1000);
}
打印日志发现,并发消费生效
配合工作队列,消费速度就非常快了
多个消费者
@RabbitListener(queuesToDeclare = @Queue(value = "concurrency"))
public void concurrency(String msg) throws InterruptedException {
// 打印线程名
String name = Thread.currentThread().getName();
System.out.println("name = "+new Date() + name);
Thread.sleep(1000); // 休眠一秒,好看效果
}
@RabbitListener(queuesToDeclare = @Queue(value = "concurrency"))
public void concurrency1(String msg) throws InterruptedException {
// 打印线程名
String name = Thread.currentThread().getName();
System.out.println("name1 = "+new Date() + name);
Thread.sleep(1000); // 休眠一秒,好看效果
}
2、批量发送
实现多个消息批量发送,可配置每次发送几个,不足发送数时,等待超时后也继续发送
批量发送配置类
// 批量发送配置
@Configuration
public class RabbitBatchSendConfig {
@Resource
ConnectionFactory connectionFactory;
/**
* 注入一个批量 template
* Spring-AMQP 通过 BatchingRabbitTemplate 提供批量发送消息的功能。如下是三个条件,满足任一即会批量发送:
* <p>
* 【数量】batchSize :超过收集的消息数量的最大条数。
* 【空间】bufferLimit :超过收集的消息占用的最大内存。
* 【时间】timeout :超过收集的时间的最大等待时长,单位:毫秒。
* 不过要注意,这里的超时开始计时的时间,是以最后一次发送时间为起点。也就说,每调用一次发送消息,都以当前时刻开始计时,重新到达 timeout 毫秒才算超时。
*
* @return BatchingRabbitTemplate
*/
@Bean
public BatchingRabbitTemplate batchRabbitTemplate() {
// 创建 BatchingStrategy 对象,代表批量策略
// 超过收集的消息数量的最大条数。
int batchSize = 10; // 例:每次发送10条
// 每次批量发送消息的最大内存 b
int bufferLimit = 1024 * 1024;
// 超过收集的时间的最大等待时长,单位:毫秒
int timeout = 10 * 1000; // 例:不足10条时,等待10秒继续发送
BatchingStrategy batchingStrategy = new SimpleBatchingStrategy(batchSize, bufferLimit, timeout);
// 创建 TaskScheduler 对象,用于实现超时发送的定时器
TaskScheduler taskScheduler = new ConcurrentTaskScheduler();
// 创建 BatchingRabbitTemplate 对象
BatchingRabbitTemplate batchTemplate = new BatchingRabbitTemplate(batchingStrategy, taskScheduler);
batchTemplate.setConnectionFactory(connectionFactory);
return batchTemplate;
}
}
生产者
@Resource
private BatchingRabbitTemplate batchingRabbitTemplate;
// 批量发送
@GetMapping("/test14")
public void test14(){
for (int i = 0; i < 15; i++) {
batchingRabbitTemplate.convertAndSend("batchSend","批量发送");
}
}
消费者
@RabbitListener(queuesToDeclare = @Queue(value = "batchSend"))
public void batchSend(String msg){
System.out.println("msg = " +new Date() + msg);
}
可以看到消息批量发送已实现,不足10条的按配置等待10秒后发送
3、批量消费
实现多个消息批量消费,可配置每次消费几个,不足消费数时,等待超时后也继续消费
批量消费配置类
/**
* 批量消费
*/
@Configuration
public class RabbitBatchConsumerConfig {
@Resource
ConnectionFactory connectionFactory;
@Resource
SimpleRabbitListenerContainerFactoryConfigurer configurer;
/**
* 配置一个批量消费的 SimpleRabbitListenerContainerFactory
*/
@Bean(name = "consumer10BatchContainerFactory")
public SimpleRabbitListenerContainerFactory consumer10BatchContainerFactory() {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
configurer.configure(factory, connectionFactory);
// 这里是重点 配置消费者的监听器是批量消费消息的类型
factory.setBatchListener(true);
// 一批十个
factory.setBatchSize(10);
// 等待时间 毫秒 , 这里其实是单个消息的等待时间 指的是单个消息的等待时间
// 也就是说极端情况下,你会等待 BatchSize * ReceiveTimeout 的时间才会收到消息
factory.setReceiveTimeout(10 * 1000L);
factory.setConsumerBatchEnabled(true);
return factory;
}
}
生产者
@GetMapping("/test13")
public void test13(){
for (int i = 0; i < 16; i++) {
rabbitTemplate.convertAndSend("batchConsume","测试批量消费消息");
}
}
消费者
// 指定containerFactory
@RabbitListener(queuesToDeclare = @Queue(value = "batchConsume"),containerFactory = "consumer10BatchContainerFactory")
public void batchSend(List<Message> msg){ //接收换成List
for (int i = 0; i < msg.size(); i++) {
System.out.println("msg = " +new Date() + msg.get(i).getBody());
}
}
可以看到消息批量消费已实现,不足10条的按配置等待10秒后消费
4、基于插件延迟队列
延迟队列非常常用且好用,可以将消息发送后使消费者延迟接收
。
RabbitAdmin配置
RabbitAdmin是用于对交换机和队列进行管理,用于创建、绑定、删除队列与交换机,发送消息的组件。
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitAdminConfig {
@Value("${spring.rabbitmq.host}")
private String host;
@Value("${spring.rabbitmq.username}")
private String username;
@Value("${spring.rabbitmq.password}")
private String password;
@Value("${spring.rabbitmq.virtualhost}")
private String virtualhost;
@Bean
public ConnectionFactory connectionFactory(){
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
connectionFactory.setAddresses(host);
connectionFactory.setUsername(username);
connectionFactory.setPassword(password);
connectionFactory.setVirtualHost(virtualhost);
return connectionFactory;
}
@Bean
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
rabbitAdmin.setAutoStartup(true);
return rabbitAdmin;
}
}
封装发送延迟队列工具类
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.CustomExchange;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
@Component
public class DelayedQueue {
// routingKey
private static final String DELAYED_ROUTING_KEY = "delayed.routingkey";
// 延迟队列交换机
private static final String DELAYED_EXCHANGE = "delayed.exchange";
@Autowired
RabbitTemplate rabbitTemplate;
@Resource
RabbitAdmin rabbitAdmin;
/**
* 发送延迟队列
* @param queueName 队列名称
* @param params 消息内容
* @param expiration 延迟时间 毫秒
*/
public void sendDelayedQueue(String queueName, Object params, Integer expiration) {
// 先创建一个队列
Queue queue = new Queue(queueName);
rabbitAdmin.declareQueue(queue);
// 创建延迟队列交换机
CustomExchange customExchange = createCustomExchange();
rabbitAdmin.declareExchange(customExchange);
// 将队列和交换机绑定
Binding binding = BindingBuilder.bind(queue).to(customExchange).with(DELAYED_ROUTING_KEY).noargs();
rabbitAdmin.declareBinding(binding);
// 发送延迟消息
rabbitTemplate.convertAndSend(DELAYED_EXCHANGE, DELAYED_ROUTING_KEY, params, msg -> {
// 发送消息的时候 延迟时长
msg.getMessageProperties().setDelay(expiration);
return msg;
});
}
public CustomExchange createCustomExchange() {
Map<String, Object> arguments = new HashMap<>();
/**
* 参数说明:
* 1.交换机的名称
* 2.交换机的类型
* 3.是否需要持久化
* 4.是否自动删除
* 5.其它参数
*/
arguments.put("x-delayed-type", "direct");
return new CustomExchange(DELAYED_EXCHANGE,"x-delayed-message", true, false, arguments);
}
}
生产者
@Autowired
private DelayedQueue delayedQueue;
/**
* 发送延迟队列
* @param queueName 队列名称
* @param params 消息内容
* @param expiration 延迟时间 毫秒
*/
@GetMapping("/test9")
public void topicTest8() {
delayedQueue.sendDelayedQueue("delayTest2","这是消息",5000);
}
消费者
@RabbitListener(queuesToDeclare = @Queue(value = "delayTest2",durable = "true"))
public void declareExchange2(String message){
System.out.println("delayTest2 = " + message);
}
5、TTL队列
TTL是time to live的缩写,生存时间,RabbitMQ支持消息的过期时间,消息发送时可以指定,从消息入队列开始计算,只要超过队列的超时时间配置,消息没被接收,消息就会自动清除
封装发送TTL队列工具类
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
@Component
public class TtlQueue {
// routingKey
private static final String TTL_KEY = "ttl.routingkey";
private static final String TTL_EXCHANGE = "ttl.exchange";
@Autowired
RabbitTemplate rabbitTemplate;
@Resource
RabbitAdmin rabbitAdmin;
/**
* 发送TTL队列
* @param queueName 队列名称
* @param params 消息内容
* @param expiration 过期时间 毫秒
*/
public void sendTtlQueue(String queueName, Object params, Integer expiration) {
/**
* ----------------------------------先创建一个ttl队列--------------------------------------------
*/
Map<String, Object> map = new HashMap<>();
// 队列设置存活时间,单位ms,必须是整形数据。
map.put("x-message-ttl",expiration);
/*参数1:队列名称 参数2:持久化 参数3:是否排他 参数4:自动删除队列 参数5:队列参数*/
Queue queue = new Queue(queueName,true,false,false,map);
rabbitAdmin.declareQueue(queue);
/**
* ---------------------------------创建交换机---------------------------------------------
*/
DirectExchange directExchange = new DirectExchange(TTL_EXCHANGE, true, false);
rabbitAdmin.declareExchange(directExchange);
/**
* ---------------------------------队列绑定交换机---------------------------------------------
*/
// 将队列和交换机绑定
Binding binding = BindingBuilder.bind(queue).to(directExchange).with(TTL_KEY);
rabbitAdmin.declareBinding(binding);
// 发送消息
rabbitTemplate.convertAndSend(TTL_EXCHANGE,TTL_KEY,params);
}
}
生产者
@Autowired
private TtlQueue ttlQueue;
/**
* 发送TTL队列
* @param queueName 队列名称
* @param params 消息内容
* @param expiration 过期时间 毫秒
*/
@GetMapping("/test10")
public void topicTest10() {
ttlQueue.sendTtlQueue("ttlQueue","这是消息内容",5000);
}
消费者
@RabbitListener(queues = "ttlQueue" )
public void ttlQueue(String message){
System.out.println("message = " + message);
}
6、死信队列
DLX,全称为Dead-Letter-Exchange,可以称之为死信交换器。队列消息变成死信(deadmessage)之后,它能被重新被发送到另一个交换器中
,这个交换器就是DLX,绑定DLX的队列就称之为死信队列。
消息变成死信的几种情况:
1.消息被拒绝(basic.reject/ basic.nack)并且requeue=false
2. 消息TTL过期
3. 队列达到最大长度
流程:发送消息,消息过期后进入到另一个队列(这个队列设置持久化,不过期)的过程。
封装发送死信队列工具类
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
@Component
public class DLXQueue {
// routingKey
private static final String DEAD_ROUTING_KEY = "dead.routingkey";
private static final String ROUTING_KEY = "routingkey";
private static final String DEAD_EXCHANGE = "dead.exchange";
private static final String EXCHANGE = "common.exchange";
@Autowired
RabbitTemplate rabbitTemplate;
@Resource
RabbitAdmin rabbitAdmin;
/**
* 发送死信队列,过期后进入死信交换机,进入死信队列
* @param queueName 队列名称
* @param deadQueueName 死信队列名称
* @param params 消息内容
* @param expiration 过期时间 毫秒
*/
public void sendDLXQueue(String queueName, String deadQueueName,Object params, Integer expiration){
/**
* ----------------------------------先创建一个ttl队列和死信队列--------------------------------------------
*/
Map<String, Object> map = new HashMap<>();
// 队列设置存活时间,单位ms,必须是整形数据。
map.put("x-message-ttl",expiration);
// 设置死信交换机
map.put("x-dead-letter-exchange",DEAD_EXCHANGE);
// 设置死信交换器路由键
map.put("x-dead-letter-routing-key", DEAD_ROUTING_KEY);
/*参数1:队列名称 参数2:持久化 参数3:是否排他 参数4:自动删除队列 参数5:队列参数*/
Queue queue = new Queue(queueName,true,false,false,map);
rabbitAdmin.declareQueue(queue);
/**
* ---------------------------------创建交换机---------------------------------------------
*/
DirectExchange directExchange = new DirectExchange(EXCHANGE, true, false);
rabbitAdmin.declareExchange(directExchange);
/**
* ---------------------------------队列绑定交换机---------------------------------------------
*/
Binding binding = BindingBuilder.bind(queue).to(directExchange).with(ROUTING_KEY);
rabbitAdmin.declareBinding(binding);
/**
* ---------------------------------在创建一个死信交换机和队列,接收死信队列---------------------------------------------
*/
DirectExchange deadExchange = new DirectExchange(DEAD_EXCHANGE, true, false);
rabbitAdmin.declareExchange(deadExchange);
Queue deadQueue = new Queue(deadQueueName,true,false,false);
rabbitAdmin.declareQueue(deadQueue);
/**
* ---------------------------------队列绑定死信交换机---------------------------------------------
*/
// 将队列和交换机绑定
Binding deadbinding = BindingBuilder.bind(deadQueue).to(deadExchange).with(DEAD_ROUTING_KEY);
rabbitAdmin.declareBinding(deadbinding);
// 发送消息
rabbitTemplate.convertAndSend(EXCHANGE,ROUTING_KEY,params);
}
生产者
@Autowired
private DLXQueue dlxQueue;
/**
* 发送死信队列,过期后进入死信交换机,进入死信队列
* @param queueName 队列名称
* @param deadQueueName 死信队列名称
* @param params 消息内容
* @param expiration 过期时间 毫秒
*/
@GetMapping("/test11")
public void topicTest11() {
dlxQueue.sendDLXQueue("queue","deadQueue","这是消息内容",5000);
}
消费者
// 接收转移后的队列消息
@RabbitListener(queuesToDeclare = @Queue(value = "deadQueue",durable = "true"))
public void ttlQueue(String message){
System.out.println("message = " + message);
}
7、消息确认
1、发送消息确认机制
为确保消息发送有真的发送出去,设置发布时确认,确认消息是否到达 Broker 服务器
配置
spring:
rabbitmq:
host: 47.99.110.29
port: 5672
username: guest
password: guest
virtual-host: /
listener:
simple:
prefetch: 1 # 每次只能获取一条,处理完成才能获取下一条
publisher-confirm-type: correlated #确认消息已发送到交换机(Exchange)
publisher-returns: true #确认消息已发送到队列(Queue)
如果有使用rabbitAdmin配置的话,那里也需要加配置
修改RabbitAdmin配置
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitAdminConfig {
@Value("${spring.rabbitmq.host}")
private String host;
@Value("${spring.rabbitmq.username}")
private String username;
@Value("${spring.rabbitmq.password}")
private String password;
@Value("${spring.rabbitmq.virtualhost}")
private String virtualhost;
@Bean
public ConnectionFactory connectionFactory(){
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
connectionFactory.setAddresses(host);
connectionFactory.setUsername(username);
connectionFactory.setPassword(password);
connectionFactory.setVirtualHost(virtualhost);
// 配置发送确认回调时,次配置必须配置,否则即使在RabbitTemplate配置了ConfirmCallback也不会生效
connectionFactory.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.CORRELATED);
connectionFactory.setPublisherReturns(true);
return connectionFactory;
}
@Bean
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
rabbitAdmin.setAutoStartup(true);
return rabbitAdmin;
}
}
实现发送消息确认接口
消息只要被 rabbitmq broker 接收到就会触发 confirmCallback 回调 。
/**
* 消息发送确认配置
*/
@Component
public class ConfirmCallbackConfig implements RabbitTemplate.ConfirmCallback{
@Autowired
private RabbitTemplate rabbitTemplate;
@PostConstruct // @PostContruct是spring框架的注解,在⽅法上加该注解会在项⽬启动的时候执⾏该⽅法,也可以理解为在spring容器初始化的时候执
public void init(){
rabbitTemplate.setConfirmCallback(this);
}
/**
* 交换机不管是否收到消息的一个回调方法
* @param correlationData 消息相关数据
* @param ack 交换机是否收到消息
* @param cause 失败原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if (ack){ // 消息投递到broker 的状态,true表示成功
System.out.println("消息发送成功!");
}else { // 发送异常
System.out.println("发送异常原因 = " + cause);
}
}
}
实现发送消息回调接口
如果消息未能投递到目标queue
里将触发回调 returnCallback ,一旦向 queue 投递消息未成功,这里一般会记录下当前消息的详细投递数据,方便后续做重发或者补偿等操作。
@Component
public class ReturnCallbackConfig implements RabbitTemplate.ReturnsCallback {
@Autowired
private RabbitTemplate rabbitTemplate;
@PostConstruct // @PostContruct是spring框架的注解,在⽅法上加该注解会在项⽬启动的时候执⾏该⽅法,也可以理解为在spring容器初始化的时候执
public void init(){
rabbitTemplate.setReturnsCallback(this);
}
@Override
public void returnedMessage(ReturnedMessage returnedMessage) {
System.out.println("消息"+returnedMessage.getMessage().toString()+"被交换机"+returnedMessage.getExchange()+"回退!"
+"退回原因为:"+returnedMessage.getReplyText());
// 回退了所有的信息,可做补偿机制
}
}
2、消费者消息确认机制
为确保消息消费成功,需设置消费者消息确认机制,如果消费失败或异常了,可做补偿机制。
配置
spring:
rabbitmq:
host: 47.99.110.29
port: 5672
username: guest
password: guest
virtual-host: /
# 消费者配置
listener:
simple:
prefetch: 1 # 每次只能获取一条,处理完成才能获取下一条
acknowledge-mode: manual # 设置消费端手动ack确认
retry:
enabled: true # 是否支持重试
# 生产者配置
publisher-confirm-type: correlated #确认消息已发送到交换机(Exchange)
publisher-returns: true #确认消息已发送到队列(Queue)
channel.basicAck
消息确认
消费者修改,利用消费者参数Channel 进行消息确认操作
@RabbitListener(queuesToDeclare = @Queue(value = "simple.queue",durable = "true")) // queuesToDeclare 自动声明队列
public void holloWordListener(String msg, Channel channel, Message message) throws IOException {
// 消息
System.out.println("msg = " + msg);
/**
* 确认
* deliveryTag:表示消息投递序号,每次消费消息或者消息重新投递后,deliveryTag都会增加
* multiple:是否批量确认,值为 true 则会一次性 ack所有小于当前消息 deliveryTag 的消息。
*/
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}
channel.basicNack
消息回退
将消息重返队列
@RabbitListener(queuesToDeclare = @Queue(value = "simple.queue",durable = "true")) // queuesToDeclare 自动声明队列
public void holloWordListener(String msg, Channel channel, Message message) throws IOException {
try {
// 消息
System.out.println("msg = " + msg);
throw new RuntimeException("来个异常");
} catch (Exception e) {
e.printStackTrace();
System.out.println("消息消费异常,重回队列");
/**
* deliveryTag:表示消息投递序号。
* multiple:是否批量确认。
* requeue:值为 true 消息将重新入队列。
*/
channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,true);
}
// 确认
/**
* deliveryTag:表示消息投递序号,每次消费消息或者消息重新投递后,deliveryTag都会增加
* multiple:是否批量确认,值为 true 则会一次性 ack所有小于当前消息 deliveryTag 的消息。
*/
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}
channel.basicReject
消息拒绝
拒绝消息,与basicNack区别在于不能进行批量操作,其他用法很相似。
/**
* 消息拒绝
* deliveryTag:表示消息投递序号。
* requeue:值为 true 消息将重新入队列。
*/
channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);
封装消息确认处理类
链接: https://blog.csdn.net/qq_48721706/article/details/125709761
七、rabbitmq集群搭建
1、普通集群
1、新建三个docker容器
docker run -d --hostname rabbit1 --name myrabbit1 -v /home/rabbitmq/data:/var/lib/rabbitmq -p 15671:15672 -p 5671:5672 rabbitmq
docker run -d --hostname rabbit2 --name myrabbit2 -v /home/rabbitmq/data:/var/lib/rabbitmq -p 15672:15672 -p 5672:5672 --link myrabbit1:rabbit1 rabbitmq
docker run -d --hostname rabbit3 --name myrabbit3 -v /home/rabbitmq/data:/var/lib/rabbitmq -p 15673:15672 -p 5673:5672 --link myrabbit1:rabbit1 --link myrabbit2:rabbit2 rabbitmq
2、三个都进入容器下载可视化工具
3、进入第一个mq容器重启
docker exec -it ef4a1f0fade7 /bin/bash
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl start_app
exit
4、进入第二个 和 第三个 mq容器执行
docker exec -it e36d94d40008 /bin/bash
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl join_cluster --ram rabbit@rabbit1 //如遇到报错再执行上句、再继续执行
rabbitmqctl start_app
exit
5、进去mq可视化界面,overview面板中的Nodes可查看到节点信息。
6、测试,在mq上新建交换机、其余两个也出现新建的交换机
此时普通集群以构建完成
1、此种集群主节点down掉后,消费者也无法消费从节点的消息,不能做故障转移,只能当作备份。
2、主节点正常,从节点则可以消费消息
2、镜像集群(高可用)(推荐)
这种集群弥补第一种的缺陷,需在普通集群的基础下搭建(确保第一种集群可用)
镜像队列机制就是将队列在三个节点之间设置主从关系,消息会在三个节点之间进行自动同步,且如果其中一个节点不可用,并不会导致消息丢失或服务不可用的情况,提升mq集群的高可用性。
1、配置集群架构
2、进入任意节点配置策略
docker exec -it ef4a1f0fade7 /bin/bash
rabbitmqctl set_policy ha-all "^rabbitmq" '{"ha-mode":"all","ha-sync-mode":"automatic"}'
3、测试,新建一个rabbitmq开头的队列
此时某个节点down掉(包括主节点),其余节点也能消费
将主节点down掉,节点自动切换
4、清除策略
rabbitmqctl clear_policy ha-all