总结不易,如有不足不吝赐教,如有帮助请点个赞,谢谢~
延迟队列
公用配置
/* 生产者yml配置 */
spring:
rabbitmq:
host: 192.168.75.128
port: 5672
username: guest
password: guest
virtual-host: /
publisher-confirm-type: correlated # 开启确认回调 确定发送到交换机
publisher-returns: true # 开启退回回调
template:
mandatory: true #为true时,消息通过交换器无法匹配到队列会返回给生产者 并触发MessageReturn,为false时,匹配不到会直接被丢弃
/* 消费者yml配置 */
spring:
rabbitmq:
host: 192.168.75.128
port: 5672
username: guest
password: guest
virtual-host: /
listener:
simple:
acknowledge-mode: manual
prefetch: 1 # 每个消费者每次可以消费一个
延迟队列和ttl实现
交换机、队列以及绑定配置
@Configuration
public class MQDeadQueueConfig {
/**
* 配置普通队列
*/
@Bean
public DirectExchange direct_exchange(){
return ExchangeBuilder
.directExchange(MQExchangeEnum.TEST_DIRECT_EXCHANGE.getExchange())
.durable(true)
.build();
}
@Bean
public Queue direct_queue2(){
return QueueBuilder
.durable(MQQueueContants.TEST_DIRECT_QUEUE2)
.deadLetterExchange(MQExchangeEnum.TEST_DEAD_LETTER_EXCHANGE.getExchange())
.deadLetterRoutingKey(MQRoutingKeyEnum.TEST_DEAD_LETTER_ROUTINGKEY.getRoutingkey())
//队列设置过期时间10秒 注:死信队列接消费消息的时间取队列、消息过期时间小的那个过期时间
.ttl(10000)
.build();
}
@Bean
public Binding direct_binding2(){
return BindingBuilder
.bind(direct_queue2())
.to(direct_exchange())
.with(MQRoutingKeyEnum.TEST_DIRECT_ROUTINGKEY2.getRoutingkey());
}
/**
* 配置死信队列
*/
@Bean
public Queue dead_queue(){
return QueueBuilder
.durable(MQQueueContants.TEST_DEAD_LETTER_QUEUE)
.build();
}
@Bean
public Exchange dead_exchange(){
return ExchangeBuilder.directExchange(MQExchangeEnum.TEST_DEAD_LETTER_EXCHANGE.getExchange())
.durable(true)
.build();
}
@Bean
public Binding dead_bingding(){
return BindingBuilder
.bind(dead_queue())
.to(dead_exchange())
.with(MQRoutingKeyEnum.TEST_DEAD_LETTER_ROUTINGKEY.getRoutingkey())
.noargs();
}
}
生产者
@GetMapping("/direct2")
public String sendMsg_direct2(@RequestParam String msg) {
log.info("消息发送成功");
rabbitTemplate.convertAndSend(MQExchangeEnum.TEST_DIRECT_EXCHANGE.getExchange(),
MQRoutingKeyEnum.TEST_DIRECT_ROUTINGKEY2.getRoutingkey(),
msg,
message -> {
//队列设置过期时间8秒 注:死信队列接消费消息的时间取队列、消息过期时间小的那个过期时间
message.getMessageProperties().setExpiration(String.valueOf(8000L));
return message;
});
return "success";
}
消费者
// @RabbitListener(queues = MQQueueContants.TEST_DIRECT_QUEUE2)
// public void listen_direct2(Message message, Channel channel) throws IOException {
// long tag = message.getMessageProperties().getDeliveryTag();
//
// try {
// log.info("消费者2收到消息:【{}】",new String(message.getBody()));
// // ==>业务处理
// channel.basicAck(tag,false);
// }catch (Exception e){
// channel.basicNack(tag,false,false);
// }
// }
//切记不要对发送消息队列进行监听,否则消息直接被消费,就没有任何意义了,永远也到不了死信队列。
@RabbitListener(queues = MQQueueContants.TEST_DEAD_LETTER_QUEUE)
public void dead_queue_listener2(Message message,Channel channel) throws IOException {
long tag = message.getMessageProperties().getDeliveryTag();
try {
log.info("死信队列2收到消息:【{}】",new String(message.getBody()));
// ==>业务处理
channel.basicAck(tag,false);
}catch (Exception e){
channel.basicNack(tag,false,false);
}
}
实现效果
注:ttl方式实现延迟队列会出现这样的情况:
发送一条过期时间10s和一条过期时间5s的消息,会以第一条消息过期时间为准。如果影响需求,则推荐使用延迟插件。
延迟插件实现
linux安装插件(windows可参考其他博主)
a.下载
rabbit官方插件社区下载: https://www.rabbitmq.com/community-plugins.html
/* 下载完成之后将ez文件拷贝进容器plugins目录下 */
docker cp /home/mq/rabbitmq/plugins/rabbitmq_delayed_message_exchange-3.8.0.ez 容器id:/plugins
/* 进入容器 */
docker exec -it 容器id bash
/* 启用插件 */
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
b.校验
交换机、队列以及绑定配置
@Configuration
public class MQDeadQueueConfig {
@Bean
public DirectExchange direct_exchange(){
return ExchangeBuilder
.directExchange(MQExchangeEnum.TEST_DIRECT_EXCHANGE.getExchange())
.durable(true)
.delayed()
.build();
}
@Bean
public Queue direct_queue1(){
return QueueBuilder
.durable(MQQueueContants.TEST_DIRECT_QUEUE1)
.build();
}
@Bean
public Binding direct_binding1(){
return BindingBuilder
.bind(direct_queue1())
.to(direct_exchange())
.with(MQRoutingKeyEnum.TEST_DIRECT_ROUTINGKEY1.getRoutingkey());
}
}
生产者
@GetMapping("/direct1")
public String sendMsg_direct1(@RequestParam String msg) {
Message message = MessageBuilder
.withBody(msg.getBytes())
.setDeliveryMode(MessageDeliveryMode.PERSISTENT)
// 设置消息延迟时间5s
.setHeader("x-delay", 5000)
.build();
log.info("消息发送");
rabbitTemplate.convertAndSend(MQExchangeEnum.TEST_DIRECT_EXCHANGE.getExchange(),
MQRoutingKeyEnum.TEST_DIRECT_ROUTINGKEY1.getRoutingkey(),
message);
return "success";
}
消费者
@RabbitListener(queues = MQQueueContants.TEST_DIRECT_QUEUE1)
public void listen_direct1(Message message, Channel channel) throws IOException {
log.info("消费者1收到消息:【{}】",new String(message.getBody()));
try {
//业务处理
channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
}catch (Exception e){
// 记录日志
log.info("出现异常:{}", e.getMessage());
channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,false);
}
}
实现效果
注: 这里要注意一个点,在实现效果黄色区域其实是我之前在生产者那边实现RabbitTemplate.ReturnsCallback接口重写回调函数打印的日志,这里也是我在学习时踩的一个坑。在正常流程下生产者这边发送成功后,也路由到了消费者队列消费,不应该会进returnedMessage()这个回调函数。后面在网上找了一会也没有找到类似的说明,后面看了某位博主的流程图也就是上面那副终于搞明白了。
这个回调函数的话是交换机没有路由到指定队列而进行回调的方法,而使用延迟插件之后,所有的消息第一时间都没有直接发送给队列而是暂存在交换机,等待消息过期了才会路由到指定队列进行消费。而原生的也就是基于ttl与延迟队列实现的话,它所发送的消息会第一时间路由到指定队列而不会在交换机停留。
// 个人提供的解决方案:(个人思路,如有不足请大佬不吝赐教,或有更好的方法请大佬告知)
// 1.修改yml配置不使用returnedMessage()回调函数
spring:
rabbitmq:
template:
mandatory: false //为true时,消息通过交换器无法匹配到队列会返回给生产者 并触发MessageReturn,为false时,匹配不到会直接被丢弃
//2.在returnedMessage()方法中对使用延迟插件的交换机特殊处理
以下是原本的回调函数代码:
/**
* 生产者层面消费确认配置类
*/
@Component
public class MQInit implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnsCallback {
@Resource
private RabbitTemplate rabbitTemplate;
@PostConstruct
public void init(){
this.rabbitTemplate.setReturnsCallback(this);
this.rabbitTemplate.setConfirmCallback(this);
}
/**
*
* @param correlationData correlationData是发送消息时候携带的消息
* @param ack 如果为true,表示交换机接收到消息了
* @param cause 异常消息
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if (ack){
System.out.println("消息发送到rabbit broker成功");
}else {
System.out.println("消息发送到rabbit broker失败,原因:"+cause);
//TODO ===>做业务处理
}
}
/**
* 当routingkey不存在的时候,会触发该方法
* @param returnedMessage
*/
@Override
public void returnedMessage(ReturnedMessage returnedMessage) {
System.out.println("消息对象===>:" + returnedMessage.getMessage());
System.out.println("错误码===>:" + returnedMessage.getReplyCode());
System.out.println("错误信息===>:" + returnedMessage.getReplyText());
System.out.println("消息使用的交换器===>:" + returnedMessage.getExchange());
System.out.println("消息使用的路由key===>:" + returnedMessage.getRoutingKey());
//TODO ===>做业务处理
}
}