文章目录
RabbitMQ整合SpringBoot
导入Maven依赖
<!-- RabbitMQ场景启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<!-- RabbitMQ测试依赖 -->
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
<scope>test</scope>
</dependency>
配置文件
# RabbitMQ
spring:
rabbitmq:
port: 5672
addresses: 116.62.113.241
virtual-host: /
username: guest
password: guest
# 确认消息已发送大宋交换机(Exchange)选中确认模式为交互
publisher-confirm-type: correlated
# 开启发送端消息抵达队列的确认
publisher-returns: true
# 只要抵达队列,以异步发送优先回调我们这个returnsfirm
template:
mandatory: true
# 手动ack消息
listener:
direct:
acknowledge-mode: manual
simple:
acknowledge-mode: auto
# 并发消费者初始化值
concurrency: 1
# 并发消费者的最大值
max-concurrency: 10
# 每个消费者每次监听时可拉取处理的消息数量
# 在单个请求中处理的消息个数,他应该大于等于事务数量(unAck的最大数量)
prefetch: 1
# 是否支持重试
retry:
enabled: true
- spring.rabbitmq.publisher-confirm-type
- NONE 禁用发布确认模式,是默认值
- CORRELATED 发布消息成功到交换器后会触发回调方法
- SIMPLE
- 经测试有两种效果,其一效果和 CORRELATED 值一样会触发回调方法
- 其二在发布消息成功后使用 rabbitTemplate 调用 waitForConfirms 或 waitForConfirmsOrDie 方法 等待 broker 节点返回发送结果,根据返回结果来判定下一步的逻辑,要注意的点是 waitForConfirmsOrDie 方法如果返回 false 则会关闭 channel,则接下来无法发送消息到 broker
队列与死信队列
创建两个队列 QA 和 QB,两者队列 TTL 分别设置为 10S 和 40S,然后在创建一个交换机 X 和死信交 换机 Y,它们的类型都是 direct,创建一个死信队列 QD,在这里新增了一个队列 QC,绑定关系如下,该队列不设置 TTL 时间 由生产者指定时间
通过配置文件 添加Bean创建交换机以及队列
config
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
* @Program: RabbitMQ
* @ClassName: CreateMQConfig
* @Description: RabbitMQ的配置类
* @Author: Mr.Lu
* @Email: chnllcong@gmail.com
* @Create: 2021-11-06 17:46
* @Version 1.0
**/
@Configuration
public class CreateMQConfig {
/**
* 发送消息给交换机X 通过不同的路由 XA 和 XB 路由给不同时间的TTL的队列,消息转为死信
* 将消息丢入死信交换机 在通过路由 QD 到死信队列中 等待消息被消费
*/
// 普通交换机的名称
public static final String ORDINARY_EXCHANGE_X = "X";
// 普通队列名称
public static final String ORDINARY_QUEUE_A = "QA";
public static final String ORDINARY_QUEUE_B = "QB";
// 死信交换机的名称
public static final String DEAD_LETTER_EXCHANGE_Y = "Y";
// 死信队列名称
public static final String DEAD_LETTER_QUEUE = "QD";
/**
* 创建交换机X
*
* @return: org.springframework.amqp.core.DirectExchange
* @author: Mr.Lu
*/
@Bean
public DirectExchange XExchange() {
return new DirectExchange(ORDINARY_EXCHANGE_X);
}
/**
* 创建死信交换机Y
*
* @return: org.springframework.amqp.core.DirectExchange
* @author: Mr.Lu
*/
@Bean
public DirectExchange YExchange() {
return new DirectExchange(DEAD_LETTER_EXCHANGE_Y);
}
/**
* 创建普通队列A 并设置过去时间 TTL 10s
*
* @return: org.springframework.amqp.core.Queue
* @author: Mr.Lu
*/
@Bean
public Queue queueA() {
/*
* 设置参数
* key
* x-dead-letter-exchange - 死信交换机名 | 死信之后路由指定交换机处理
* x-dead-letter-routing-key - RotingKey | 通过指定交换机路由到指定队列处理
* x-message-ttl - 毫秒值 | 队列死信时间TTL
*/
Map<String, Object> arguments = new HashMap<>();
arguments.put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE_Y);
arguments.put("x-dead-letter-routing-key", "YD");
arguments.put("x-message-ttl", 10000);
return QueueBuilder.durable(ORDINARY_QUEUE_A).withArguments(arguments).build();
}
/**
* 创建普通队列B 并设置过去时间 TTL 40s
*
* @return: org.springframework.amqp.core.Queue
* @author: Mr.Lu
*/
@Bean
public Queue queueB() {
/*
* 设置参数
* key
* x-dead-letter-exchange - 死信交换机名 | 死信之后路由指定交换机处理
* x-dead-letter-routing-key - RotingKey | 通过指定交换机路由到指定队列处理
* x-message-ttl - 毫秒值 | 队列死信时间TTL
*/
Map<String, Object> arguments = new HashMap<>();
arguments.put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE_Y);
arguments.put("x-dead-letter-routing-key", "YD");
arguments.put("x-message-ttl", 40000);
return QueueBuilder.durable(ORDINARY_QUEUE_B).withArguments(arguments).build();
}
/**
* 创建死信队列QD
*
* @return: org.springframework.amqp.core.Queue
* @author: Mr.Lu
*/
@Bean
public Queue queueQD() {
return QueueBuilder.durable(DEAD_LETTER_QUEUE).build();
}
/**
* 绑定 队列A绑定X交换机
*
* @return: org.springframework.amqp.core.Binding
* @author: Mr.Lu
*/
@Bean
public Binding queueABindingX(Queue queueA, DirectExchange XExchange) {
return BindingBuilder.bind(queueA).to(XExchange).with("XA");
}
/**
* 绑定 队列B绑定X交换机
*
* @return: org.springframework.amqp.core.Binding
* @author: Mr.Lu
*/
@Bean
public Binding queueBBindingX(Queue queueB, DirectExchange XExchange) {
return BindingBuilder.bind(queueB).to(XExchange).with("XB");
}
/**
* 绑定 死信队列QD绑定死信交换机Y
*
* @return: org.springframework.amqp.core.Binding
* @author: Mr.Lu
*/
@Bean
public Binding queueQDBindingY(Queue queueQD, DirectExchange YExchange) {
return BindingBuilder.bind(queueQD).to(YExchange).with("YD");
}
// ----------增加一个队列 不设置过期时间 过期时间由生产者指定 绑定 X 交换机 通过路由 XC 到QC队列----
// 普通队列名称
public static final String ORDINARY_QUEUE_QC = "QC";
/**
* 创建普通队列AC 不设置时间
*
* @return: org.springframework.amqp.core.Queue
* @author: Mr.Lu
*/
@Bean
public Queue queueQC() {
/*
* 设置参数
* key
* x-dead-letter-exchange - 死信交换机名 | 死信之后路由指定交换机处理
* x-dead-letter-routing-key - RotingKey | 通过指定交换机路由到指定队列处理
* x-message-ttl - 毫秒值 | 队列死信时间TTL
*/
Map<String, Object> arguments = new HashMap<>();
arguments.put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE_Y);
arguments.put("x-dead-letter-routing-key", "YD");
return QueueBuilder.durable(ORDINARY_QUEUE_QC).withArguments(arguments).build();
}
/**
* 绑定 队列QC绑定X交换机
*
* @return: org.springframework.amqp.core.Binding
* @author: Mr.Lu
*/
@Bean
public Binding queueQCBindingX(Queue queueQC, DirectExchange XExchange) {
return BindingBuilder.bind(queueQC).to(XExchange).with("XC");
}
}
controller
sendMessage 向X交换机发送消息 分别通过不同的路由 到指定队列中
sendMessageAssignTTL 由生产者指定消息的发送时间有弊端 因为RabbitMQ只会检查第一个消息是否过期 可以通过插件延迟队列进行弥补
import com.llc.rabbitmq.common.api.R;
import com.llc.rabbitmq.config.CreatePlugInDelayedMQConfig;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
/**
* @Program: RabbitMQ
* @ClassName: SendMsgController
* @Description: 发送延迟消息API
* @Author: Mr.Lu
* @Email: chnllcong@gmail.com
* @Create: 2021-11-07 22:44
* @Version 1.0
**/
@Api(tags = "RabbitMQ消息发送API")
@Slf4j
@RestController
@RequestMapping("/v1/rabbitmq")
public class SendMsgController {
private final
RabbitTemplate rabbitTemplate;
public SendMsgController(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
}
@ApiOperation(value = "向RabbitMQ中发送消息")
@ApiImplicitParam(name = "message", value = "消息内容", required = true, dataType = "String", dataTypeClass = String.class, paramType = "path")
@GetMapping("/send/{message}")
public R<String> sendMessage(@PathVariable String message) {
log.info("当前时间:{} --- 发送一条消息给两个TTL队列 | {}", new Date(), message);
rabbitTemplate.convertAndSend("X", "XA", "消息来自TTL为10S的队列" + message);
rabbitTemplate.convertAndSend("X", "XB", "消息来自TTL为40S的队列" + message);
return R.ok("消息发送成功");
}
/**
* 弊端 如果第一条消息时间长 第二条消息时间短 会导致消息不会按照正常时间死亡
* 因为RabbitMQ只会检查第一个消息是否过期 可以通过插件延迟队列进行弥补
*/
@ApiOperation(value = "向RabbitMQ中发送消息,指定消息的过期时间TTL")
@ApiImplicitParams({
@ApiImplicitParam(name = "message", value = "消息内容", required = true, dataType = "String", dataTypeClass = String.class, paramType = "path"),
@ApiImplicitParam(name = "ttl_time", value = "过期时间", required = true, dataType = "String", dataTypeClass = String.class, paramType = "path")
})
@GetMapping("/send/{message}/{ttl_time}")
public R<String> sendMessageAssignTTL(@PathVariable String message, @PathVariable String ttl_time) {
log.info("当前时间:{} --- 发送一条消息给QC队列 | {} | 过期时间 - {}MS", new Date(), message, ttl_time);
rabbitTemplate.convertAndSend("X", "XC", "消息自定义过期时间为" + ttl_time + "的队列" + message, (msg) -> {
// 设置消息消费的延迟时间
msg.getMessageProperties().setExpiration(ttl_time);
return msg;
});
return R.ok("消息发送成功");
}
}
consumer
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* @Program: RabbitMQ
* @ClassName: DeadLetterQueueConsumer
* @Description: 消费者监听器
* @Author: Mr.Lu
* @Email: chnllcong@gmail.com
* @Create: 2021-11-07 23:04
* @Version 1.0
**/
@Slf4j
@Component
public class DeadLetterQueueConsumer {
// 指定监听的队列名
@RabbitListener(queues = {"QD"})
public void receiveD(Message message, Channel channel) {
String msg = new String(message.getBody());
log.info("当前时间:{} --- 收到死信队列的消息 | {}", new Date(), msg);
}
}
手动ACK应达
-
配置开启
# 手动应答 spring.listener.simple.acknowledge-mode=manual
消费者案例
在消费者中手动进行消息应答
channel.basicAck 签收
channel.basicNack 拒签
import com.llc.rabbitmq.config.PriorityMQConfig;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Slf4j
@Component
public class PriorityQueueConsumer {
@RabbitListener(queues = {PriorityMQConfig.PRIORITY_QUEUE_NAME})
public void receivePriorityQueue(Message message, String body, Channel channel) {
log.info("《优先级》队列消费者 message==>[{}],body==>[{}]", message, body);
//获取交货标签 内容安顺序自增
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
channel.basicAck(deliveryTag/*交货标签*/, false/*是否批量签收*/);
/*
*退货,拒签
*/
if (deliveryTag % 2 == 0) {
channel.basicNack(deliveryTag/*交货标签*/, false/*是否批量签收*/, true/*是否重新入队列*/);
// channel.basicReject(); //另外一种拒签方法
}
} catch (IOException e) {
log.error("网络终端了", e);
}
}
}
RabbitMQ的交换机插件
使用RabbitMQ中的插件来解决以上的生产者指定过期时间的弊端
插件下载以及Docker安装
解决队列先进先出的情况,比如第一个进到队列的到期时间是30分钟,第二个进入队列的到期时间是20分钟,但是由于队列的先进先出原则,第二个进入的会被阻塞了,等到第一个到期了才会被一起延迟处理
原理:之前的的延迟是在队列中做的通过设置队列的TTL来延迟推送给消费者
而现在的延迟是在交换机中做,交换机延迟推送给队列,队列接收到消息直接推送给消费者,来弥补队列做延迟的弊端
-
插件 rabbitmq_delayed_message_exchange
Docker安装
docker run --name rabbitmq --restart=always \
-p 5671:5671 -p 5672:5672 -p 4369:4369 -p 25672:25672 -p 15671:15671 -p 15672:15672 \
-d rabbitmq:management
# 4369,25672 (Erlang发现&集群端口) | 5672,5671(AMQP端口) | 15672(web管理后台端口) | 61613,61614( STOMP协议端口) | 1883,8883(MQTT协议端口)
# 默认密码 guest
# 访问地址 http://127.0.0.1:15672
# 安装插件
## 将插件上传到服务器中
docker cp /dockeres/rabbitmq_delayed_message_exchange-3.9.0.ez rabbitmq:/plugins
## 进入容器启动插件
docker exec -it rabbitmq bash
## 启动插件
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
## 禁用插件
rabbitmq-plugins disable rabbitmq_delayed_message_exchange
## 重启容器
docker restart rabbitmq
创建插件类型的交换机
config
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
* @Program: RabbitMQ
* @ClassName: CreatePlugInDelayedMQConfig
* @Description: 通过插件实现生产者指定时间延迟队列 解决队列先进先出
* @Author: Mr.Lu
* @Email: chnllcong@gmail.com
* @Create: 2021-11-08 00:33
* @Version 1.0
**/
@Configuration
public class CreatePlugInDelayedMQConfig {
/**
* 通过插件在交换机上实现指定过期时间的先进先出的问题
*/
public static final String DEFAULT_EXCHANGE_NAME = "delayed.exchange";
public static final String DEFAULT_QUEUE_NAME = "delayed.queue";
public static final String DEFAULT_ROUTING_KEY = "delayed.routingKey";
/**
* 插件自定义交换机
*
* @return: org.springframework.amqp.core.CustomExchange
* @author: Mr.Lu
*/
@Bean
public CustomExchange delayedExchange() {
Map<String, Object> arguments = new HashMap<>();
arguments.put("x-delayed-type", "direct");
/*
* 1. 交换机名
* 2. 交换机类型
* 3. 是否需要持久化
* 4. 是否需要自动删除
* 5. 其他参数
*/
return new CustomExchange(DEFAULT_EXCHANGE_NAME, "x-delayed-message", true, false, arguments);
}
/**
* 创建普通队列
*
* @return: org.springframework.amqp.core.Queue
* @author: Mr.Lu
*/
@Bean
public Queue delayedQueue() {
return QueueBuilder.durable(DEFAULT_QUEUE_NAME).build();
}
/**
* 将队列和插件交换机绑定
*
* @param delayedQueue
* @param delayedExchange
* @return: org.springframework.amqp.core.Binding
* @author: Mr.Lu
*/
@Bean
public Binding delayedBinding(Queue delayedQueue, CustomExchange delayedExchange) {
return BindingBuilder.bind(delayedQueue).to(delayedExchange).with(DEFAULT_ROUTING_KEY).noargs();
}
}
controller
import com.llc.rabbitmq.common.api.R;
import com.llc.rabbitmq.config.CreatePlugInDelayedMQConfig;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
/**
* @Program: RabbitMQ
* @ClassName: SendMsgController
* @Description: 发送延迟消息API
* @Author: Mr.Lu
* @Email: chnllcong@gmail.com
* @Create: 2021-11-07 22:44
* @Version 1.0
**/
@Api(tags = "RabbitMQ消息发送API")
@Slf4j
@RestController
@RequestMapping("/v1/rabbitmq")
public class SendMsgController {
private final
RabbitTemplate rabbitTemplate;
public SendMsgController(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
}
/**
* 使用插件交换机解决延迟队列动态指定时间的弊端
* 使用延迟交换机 消息通过交换机 延迟推送给队列 ,从而达到延迟队列的效果
*/
@ApiOperation(value = "向RabbitMQ中发送消息,基于插件的延迟对列")
@ApiImplicitParams({
@ApiImplicitParam(name = "message", value = "消息内容", required = true, dataType = "String", dataTypeClass = String.class, paramType = "path"),
@ApiImplicitParam(name = "delay_time", value = "延迟时间", required = true, dataType = "Integer", dataTypeClass = Integer.class, paramType = "path")
})
@GetMapping("/send/plug_in/{message}/{delay_time}")
public R<String> sendMessagePlugInAssignTTL(@PathVariable String message, @PathVariable Integer delay_time) {
log.info("当前时间:{} --- 发送一条消息给QC队列 | {} | 过期时间 - {}MS", new Date(), message, delay_time);
rabbitTemplate.convertAndSend(CreatePlugInDelayedMQConfig.DEFAULT_EXCHANGE_NAME, CreatePlugInDelayedMQConfig.DEFAULT_ROUTING_KEY, message, msg -> {
// 设置延迟时间
msg.getMessageProperties().setDelay(delay_time);
return msg;
});
return R.ok("消息发送成功");
}
}
sonsumer
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* @Program: RabbitMQ
* @ClassName: DelayQueueConsumer
* @Description: 延迟队列
* @Author: Mr.Lu
* @Email: chnllcong@gmail.com
* @Create: 2021-11-08 01:10
* @Version 1.0
**/
@Slf4j
@Component
public class DelayQueueConsumer {
@RabbitListener(queues = {"delayed.queue"})
public void delayQueueConsumer(Message message, Channel channel) {
String msg = new String(message.getBody());
log.info("当前时间:{} --- 收到延迟队列的消息 | {}", new Date(), msg);
}
}
配置回调
-
消息准确发送到交换机的确认回调
-
消息未抵达队列的确认回调
callback
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
/**
* @Program: RabbitMQ
* @ClassName: MessageSendCallback
* @Description: 消息准确发送到RabbitMQ中的确认回调
* @Author: Mr.Lu
* @Email: chnllcong@gmail.com
* @Create: 2021-11-09 00:00
* @Version 1.0
**/
@Slf4j
@Component
public class MessageSendExchangeCallback {
private final RabbitTemplate rabbitTemplate;
public MessageSendExchangeCallback(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
}
/**
* 自制RabbitTemplate
* 1. 服务收到消息就回调
* 1. spring.rabbitmq.publisher-confirm-type=correlated
* 2. 设置确认回调
* 2. 消息正确抵达队列进行回调
* spring.rabbitmq.publisher-returns=true
* spring.rabbitmq.template.mandatory=true
* 3. 消费端确认(保证每个消息被正确消费,此时才可以broker删除这个消息)
* spring.rabbitmq.listener.simple.acknowledge-mode=manual
* 1. 默认是自动确认,只要消息接收到,客户端会自动确认,服务端就会移除这个消息
* 只要我们没有明确告诉MQ,货物被签收,没有Ack
* 消息就一直unacked状态,即使consumer消费者宕机也不会丢失,会从新变成ready状态
* 2. 如何签收
* channel.basicAck 签收
* channel.basicNack 拒签
*/
//@PostConstruct--RabbitMqConfig方法创建完成后,执行这个方法
@PostConstruct
public void initRabbitTemplate() {
//设置确认回调
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
/**
* 只要消息抵达Broker ack=true
*
* @param correlationData 当前消息的唯一关联数据(消息的唯一ID)
* @param ack 消息是否成功收到
* @param cause 失败的原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
log.warn("[[[交换机消息抵达确认]]] confirm...[{}]===>ack[{}]==>cause[{}]]", correlationData, ack, cause);
}
});
//设置消息未抵达队列的确认回调
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
/**
* 只要消息没有投递给指定的队列,就触发这个失败回调
*
* @param message 投递失败的消息详细信息
* @param replyCode 回复的状态码
* @param replyText 回复的内容
* @param exchange 当时这个消息发给哪个交换机
* @param routingKey 当时这个消息用的哪个路由键
*/
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
System.out.println();
log.warn("[[[消息未抵达队列]]] message...[{}]===>replyCode[{}]==>replyText[{}]==>exchange[{}]==>routingKey[{}]", message, replyCode, replyText, exchange, routingKey);
}
});
}
}
备份交换机
消息推送失败后将消息投递给备份交换机,通过备份交换机来进行对应处理
⚠️mandatory 参数与备份交换机可以一起使用的时候,如果两者同时开启,消息究竟何去何从?谁优先 级高,经过上面结果显示答案是备份交换机优先级高。
Config
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Program: RabbitMQ
* @ClassName: ConfirmConfig
* @Description: 发布确认配置类
* @Author: Mr.Lu
* @Email: chnllcong@gmail.com
* @Create: 2021-11-08 17:45
* @Version 1.0
**/
@Configuration
public class ConfirmConfig {
/**
* 消息发送 防止消息丢失 消息确认处理
*/
public static final String CONFIRM_EXCHANGE_NAME = "confirm.exchange";
public static final String CONFIRM_QUEUE_NAME = "confirm.queue";
public static final String CONFIRM_ROUTING_KEY = "confirm.routingKey";
/**
* 直接交换机
* 设置消息投递失败后转发给备份交换机
*
* @return: org.springframework.amqp.core.DirectExchange
* @author: Mr.Lu
*/
@Bean
public DirectExchange confirmExchange() {
return ExchangeBuilder.directExchange(CONFIRM_EXCHANGE_NAME)
.durable(true)
//设置该交换机的备份交换机
.withArgument("alternate-exchange", BACKUP_EXCHANGE_NAME)
.build();
}
/**
* 队列
*
* @return: org.springframework.amqp.core.DirectExchange
* @author: Mr.Lu
*/
@Bean
public Queue confirmQueue() {
return QueueBuilder.durable(CONFIRM_QUEUE_NAME).build();
}
/**
* 绑定
*
* @return: org.springframework.amqp.core.DirectExchange
* @author: Mr.Lu
*/
@Bean
public Binding queueBindingExchange(Queue confirmQueue, DirectExchange confirmExchange) {
return BindingBuilder.bind(confirmQueue).to(confirmExchange).with(CONFIRM_ROUTING_KEY);
}
/**
* 为正确抵达交换机或者队列的消息会被回调所记录
* 创建备份交换机使用Fanout扇出Type,将为正确抵达的消息放入备份交换机
* 并路由到不同的队列中处理
*/
public static final String BACKUP_EXCHANGE_NAME = "backup.exchange";
// 备份队列
public static final String BACKUP_QUEUE_NAME = "backup.queue";
// 警告队列
public static final String WARNING_QUEUE_NAME = "warning.queue";
/**
* 扇出类型 的 备份交换机
*
* @return: org.springframework.amqp.core.FanoutExchange
* @author: Mr.Lu
*/
@Bean
public FanoutExchange backupExchange() {
return ExchangeBuilder.fanoutExchange(BACKUP_EXCHANGE_NAME).build();
}
/**
* 备份队列
*
* @return: org.springframework.amqp.core.Queue
* @author: Mr.Lu
*/
@Bean
public Queue backupQueue() {
return QueueBuilder.durable(BACKUP_QUEUE_NAME).build();
}
/**
* 警告队列
*
* @return: org.springframework.amqp.core.Queue
* @author: Mr.Lu
*/
@Bean
public Queue warningQueue() {
return QueueBuilder.durable(WARNING_QUEUE_NAME).build();
}
/**
* 绑定
*
* @param backupQueue 备份队列
* @param backupExchange 备份交换机
* @return: org.springframework.amqp.core.Binding
* @author: Mr.Lu
*/
@Bean
public Binding backupQueueBindingBackupExchange(Queue backupQueue, FanoutExchange backupExchange) {
return BindingBuilder.bind(backupQueue).to(backupExchange);
}
/**
* 绑定
*
* @param warningQueue 警告队列
* @param backupExchange 备份交换机
* @return: org.springframework.amqp.core.Binding
* @author: Mr.Lu
*/
@Bean
public Binding warningQueueBindingBackupExchange(Queue warningQueue, FanoutExchange backupExchange) {
return BindingBuilder.bind(warningQueue).to(backupExchange);
}
}
controller
import com.llc.rabbitmq.common.api.R;
import com.llc.rabbitmq.config.ConfirmConfig;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
/**
* @Program: RabbitMQ
* @ClassName: ProducerController
* @Description: 消息发送并确认API
* @Author: Mr.Lu
* @Email: chnllcong@gmail.com
* @Create: 2021-11-08 19:05
* @Version 1.0
**/
@Api(tags = "RabbitMQ消息发送并确认API")
@Slf4j
@RestController
@RequestMapping("/v1/rabbitmq/confirm")
public class ProducerController {
private final RabbitTemplate rabbitTemplate;
public ProducerController(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
}
@ApiOperation(value = "向RabbitMQ中发送消息,并确认消息发送")
@ApiImplicitParams({
@ApiImplicitParam(name = "message", value = "消息内容", required = true, dataType = "String", dataTypeClass = String.class, paramType = "path")
})
@GetMapping("/send/{message}")
public R<String> sendMessageConfirm(@PathVariable String message) {
// 构造一个消息确认回调的相关数据
String msgId = UUID.randomUUID().toString();
CorrelationData correlationData = new CorrelationData();
correlationData.setId(msgId);
correlationData.setReturnedMessage(
MessageBuilder.withBody(message.getBytes())
.setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN)
.setCorrelationId(msgId)
.build()
);
rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE_NAME, ConfirmConfig.CONFIRM_ROUTING_KEY+"1", message, correlationData);
log.info("发送消息的内容 | {}", message);
return R.ok("消息发送成功");
}
}
consumer
import com.llc.rabbitmq.config.ConfirmConfig;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @Program: RabbitMQ
* @ClassName: ConfirmExchangeConsumer
* @Description: 消息确认消费者
* @Author: Mr.Lu
* @Email: chnllcong@gmail.com
* @Create: 2021-11-08 19:17
* @Version 1.0
**/
@Slf4j
@Component
public class ConfirmExchangeConsumer {
/**
* 确认队列的消费者
*
* @param message 消息
* @param body 消息体
* @param channel 信道
* @return: void
* @author: Mr.Lu
*/
@RabbitListener(queues = {ConfirmConfig.CONFIRM_QUEUE_NAME})
public void receiveConfirmQueue(Message message, String body, Channel channel) {
log.info("确认队列消费者 message==>[{}],body==>[{}]", message, body);
}
/**
* 备份交换机 中的 备份队列的消费者
*
* @param message 消息
* @param body 消息体
* @param channel 信道
* @return: void
* @author: Mr.Lu
*/
@RabbitListener(queues = {ConfirmConfig.BACKUP_QUEUE_NAME})
public void receiveBackupQueue(Message message, String body, Channel channel) {
log.info("备份交换机 《备份》队列消费者 message==>[{}],body==>[{}]", message, body);
}
/**
* 备份交换机 中的 警告队列的消费者
*
* @param message 消息
* @param body 消息体
* @param channel 信道
* @return: void
* @author: Mr.Lu
*/
@RabbitListener(queues = {ConfirmConfig.WARNING_QUEUE_NAME})
public void receiveWarningQueue(Message message, String body, Channel channel) {
log.info("备份交换机 《警告》队列消费者 message==>[{}],body==>[{}]", message, body);
}
}
优先级队列
在我们系统中有一个订单催付的场景,我们的客户在天猫下的订单,淘宝会及时将订单推送给我们,如 果在用户设定的时间内未付款那么就会给用户推送一条短信提醒,很简单的一个功能对吧,但是,tmall 商家对我们来说,肯定是要分大客户和小客户的对吧,比如像苹果,小米这样大商家一年起码能给我们创 造很大的利润,所以理应当然,他们的订单必须得到优先处理,而曾经我们的后端系统是使用 redis 来存 放的定时轮询,大家都知道 redis 只能用 List 做一个简简单单的消息队列,并不能实现一个优先级的场景,
所以订单量大了后采用 RabbitMQ 进行改造和优化,如果发现是大客户的订单给一个相对比较高的优先级, 否则就是默认优先级。
⚠️要让队列实现优先级需要做的事情有如下事情:队列需要设置为优先级队列,消息需要设置消息的优先级,消费者需要等待消息已经发送到队列中才去消费因为,这样才有机会对消息进行排序
config
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
* @Program: RabbitMQ
* @ClassName: priorityMQConfig
* @Description: 优先级
* @Author: Mr.Lu
* @Email: chnllcong@gmail.com
* @Create: 2021-11-09 17:29
* @Version 1.0
**/
@Configuration
public class PriorityMQConfig {
public static final String PRIORITY_EXCHANGE_NAME = "priority.exchange";
public static final String PRIORITY_QUEUE_NAME = "priority.queue";
public static final String PRIORITY_ROUTING_KEY = "priority.routingKey";
/**
* 创建交换机
*
* @return: org.springframework.amqp.core.DirectExchange
* @author: Mr.Lu
*/
@Bean
public DirectExchange priorityExchanger() {
return ExchangeBuilder.directExchange(PRIORITY_EXCHANGE_NAME).durable(true).build();
}
/**
* 创建队列
*
* @return: org.springframework.amqp.core.Queue
* @author: Mr.Lu
*/
@Bean
public Queue priorityQueue() {
Map<String, Object> arguments = new HashMap<>();
// 官方运行的范围优先级为0-255,此处设置10
arguments.put("x-max-priority", 10);
return QueueBuilder.durable(PRIORITY_QUEUE_NAME).withArguments(arguments).build();
}
/**
* 绑定
*
* @param priorityQueue 队列
* @param priorityExchanger 交换机
* @return: org.springframework.amqp.core.Binding
* @author: Mr.Lu
*/
@Bean
public Binding priorityQueueBindingExchange(Queue priorityQueue, DirectExchange priorityExchanger) {
return BindingBuilder.bind(priorityQueue).to(priorityExchanger).with(PRIORITY_ROUTING_KEY);
}
}
controller
import com.llc.rabbitmq.common.api.R;
import com.llc.rabbitmq.config.PriorityMQConfig;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Program: RabbitMQ
* @ClassName: PriorityController
* @Description: 优先级消息发送
* @Author: Mr.Lu
* @Email: chnllcong@gmail.com
* @Create: 2021-11-09 17:38
* @Version 1.0
**/
@Api(tags = "RabbitMQ优先级消息API")
@Slf4j
@RestController
@RequestMapping("/v1/rabbitmq/priority")
public class PriorityController {
private final
RabbitTemplate rabbitTemplate;
public PriorityController(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
}
@ApiOperation(value = "向RabbitMQ中发送消息10条消息")
@ApiImplicitParam(name = "message", value = "消息内容", required = true, dataType = "String", dataTypeClass = String.class, paramType = "path")
@GetMapping("/send/{message}")
public R<String> sendMessage(@PathVariable String message) {
for (int i = 0; i < 10; i++) {
// 随机数生产
int level = (int) (Math.random() * 10);
log.info("消息==>[{}-{}],优先级==>[{}]", message, i, level);
rabbitTemplate.convertAndSend(
PriorityMQConfig.PRIORITY_EXCHANGE_NAME,
PriorityMQConfig.PRIORITY_ROUTING_KEY,
"优先级消息==>[" + message + "]" + i + "优先级===>[" + level + "]",
mes -> {
mes.getMessageProperties().setPriority(level);
return mes;
});
}
return R.ok("消息发送成功");
}
}
consumer
import com.llc.rabbitmq.config.PriorityMQConfig;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @Program: RabbitMQ
* @ClassName: PriorityQueueConsumer
* @Description: 优先级队列的消费者
* @Author: Mr.Lu
* @Email: chnllcong@gmail.com
* @Create: 2021-11-09 17:50
* @Version 1.0
**/
@Slf4j
@Component
public class PriorityQueueConsumer {
@RabbitListener(queues = {PriorityMQConfig.PRIORITY_QUEUE_NAME})
public void receivePriorityQueue(Message message, String body, Channel channel) {
log.info("《优先级》队列消费者 message==>[{}],body==>[{}]", message, body);
}
}