Springboot 整合 RabbitMQ(二)

承接上文

Springboot 整合RabbitMQ_Maplelhw的博客-CSDN博客

开启消息手动确认

yaml文件:

spring:
  rabbitmq:
    host: 服务器地址
    port: 5672
    username: admin
    password: 123
    listener:
      direct:
        acknowledge-mode: manual # 开启手动确认
    publisher-confirm-type: correlated # 开启异步确认机制

生产者代码:

 // 开启消息确认
     @GetMapping("/confirmMsg/{message}")
     public void confirmMsg(@PathVariable String message){
         // 配置confirm机制
         RabbitTemplate.ConfirmCallback confirmCallback = (correlationData,b,error) -> {
             if (b){
                 System.out.println("confirm 消息发送成功...消息ID为:" + correlationData.getId());
             }else {
                 System.out.println("confirm 消息发送失败...消息ID为:" + correlationData.getId() + "原因:"+ error);
             }
         };
         // 设置confirm
         rabbitTemplate.setConfirmCallback(confirmCallback);
         // 参数为 交换机 路由键 消息 唯一消息id
         rabbitTemplate.convertAndSend(DirectConfig.DIRECT_EXCHANGE_NAME,DirectConfig.DIRECT_B_ROUTING_KEY,
                    message,new CorrelationData(""+System.currentTimeMillis()));
     }

消费者代码

// 消费消息
    @RabbitListener(queues = DirectConfig.DIRECT_QUEUEQ_B_NAME)
    public void receiveConfirmMessageB(Message message, Channel channel) throws IOException {

        String content = new String(message.getBody());
        try {
            log.info("接收到的队列消息为:{}",content);
// 手动确认消息是否消费
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
        }catch (Exception e){
            log.info("接收到的队列消息为:{} 出现异常,返还队列",content);
            // 第二个布尔值为true表示消息返还队列,false表示丢弃消息
            channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,true);
        }

结果:

模拟异常

 结果:

开启高级发布确认

在生产环境中由于一些不明原因,导致 rabbitmq 重启,在 RabbitMQ 重启期间生产者消息投递失败, 导致消息丢失,需要手动处理和恢复.而普通确认如果交换机不存在,控制台会报错无法找到交换机,因为普通确认有弊端因而有了高级发布确认。
高级发布确认:即使交换机不存在也会执行回调方法。
新增回调方法
package com.example.myselfmq.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ReturnedMessage;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

/**
 * @author Maple
 * @description: TODO
 * @date 2022/8/13 01:25
 * @email 576235959@qq.com
 */
@Slf4j
@Component
public class MyCallBack implements RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnsCallback {
    // 注入
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @PostConstruct
    public void init(){
        rabbitTemplate.setConfirmCallback(this);
        rabbitTemplate.setReturnsCallback(this);
    }
    /**
     * 交换机确认回调方法
     * 发消息 交换机收到了就回调
     * 1.发消息 交换机收到了 回调
     * @param correlationData 保存回调消息的ID以及相关信息
     * @param ack true:交换机收到消息 false:交换机接收失败
     * @param cause 失败原因
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        String id = correlationData != null ? correlationData.getId() : "";
        if (ack){
            log.info("交换机已经收到Id为:{}的消息",id);
        }else {
            log.info("交换机未收到Id为:{}的消息,原因是:{}",id,cause);
        }
    }

    
}

新增生产者接口

@GetMapping("/confirmMsgPlus/{message}")
    public void confirmMsgPlus(@PathVariable String message){
        String a = ""+System.currentTimeMillis();
        String b = ""+System.currentTimeMillis()+1;
        // 参数为 交换机 路由键 消息 唯一消息id
        rabbitTemplate.convertAndSend(DirectConfig.DIRECT_EXCHANGE_NAME,DirectConfig.DIRECT_B_ROUTING_KEY,
                message,new CorrelationData(a));
        log.info("发送消息内容:{}",a+" "+message);
        rabbitTemplate.convertAndSend(DirectConfig.DIRECT_EXCHANGE_NAME,"123"+DirectConfig.DIRECT_B_ROUTING_KEY,
                message,new CorrelationData(b));
        log.info("发送消息内容:{}",b+" "+message);
    }

测试结果

消息都到达了交换机,但是由于第二条消息routingkey错误,导致第二条消息被丢弃。但是我们要自己处理这条消息,于是设置回退消息。

MyCallBack代码优化

package com.example.myselfmq.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ReturnedMessage;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

/**
 * @author Maple
 * @description: TODO
 * @date 2022/8/13 01:25
 * @email 576235959@qq.com
 */
@Slf4j
@Component
public class MyCallBack implements RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnsCallback {
    // 注入
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @PostConstruct
    public void init(){
        rabbitTemplate.setConfirmCallback(this);
        rabbitTemplate.setReturnsCallback(this);
    }
    /**
     * 交换机确认回调方法
     * 发消息 交换机收到了就回调
     * 1.发消息 交换机收到了 回调
     * @param correlationData 保存回调消息的ID以及相关信息
     * @param ack true:交换机收到消息 false:交换机接收失败
     * @param cause 失败原因
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        String id = correlationData != null ? correlationData.getId() : "";
        if (ack){
            log.info("交换机已经收到Id为:{}的消息",id);
        }else {
            log.info("交换机未收到Id为:{}的消息,原因是:{}",id,cause);
        }
    }

    /**
     * 返回到达不了目的地的消息
     * @param returnedMessage
     */
    @Override
    public void returnedMessage(ReturnedMessage returnedMessage) {
        log.error("消息{},被交换机{}退回,退回原因:{},路由Key:{}",
                new String(returnedMessage.getMessage().getBody()),
                returnedMessage.getExchange(),returnedMessage.getReplyText(),returnedMessage.getRoutingKey());
    }
}

yaml文件新增

spring:
  rabbitmq:
    host: 121.40.235.153
    port: 5672
    username: admin
    password: 123
    listener:
      direct:
        acknowledge-mode: manual # 开启手动确认
    publisher-confirm-type: correlated # 开启异步确认机制
    publisher-returns: true # 开启回退

结果:

 设置备份交换机

如果既不想丢失消息,又不想增加生产者的复杂性,该怎么做呢?死信队列可以为队列设置死信交换机来存储那些处理失败的消息,可是这些不可路由消息根本没有机会进入到队列,因此无法使用死信队列来保存消息。在 RabbitMQ 中,有一种备份交换机的机制存在,可以很好的应对这个问题。
修改DirectConfig代码:
新增:
声明交换机时绑定备份交换机

声明备份交换机队列以及绑定

 

整体:

package com.example.myselfmq.config;

import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

/**
 * @author Maple
 * @description: TODO
 * @date 2022/8/12 20:31
 * @email 576235959@qq.com
 */
@Configuration
public class DirectConfig {
    // 交换机名称
    public static final String DIRECT_EXCHANGE_NAME = "direct_exchange";
    // 队列名称
    public static final String DIRECT_QUEUEQ_NAME = "direct_queue";
    // 队列b
    public static final String DIRECT_QUEUEQ_B_NAME = "direct_b_queue";
    // RoutingKey
    public static final String DIRECT_ROUTING_KEY = "key1";

    public static final String DIRECT_B_ROUTING_KEY = "key2";
    // 死信交换机
    public static final String DEAD_LETTER_EXCHANGE = "dead_exchange";
    // 死信队列
    public static final String DEAD_LETTER_QUEUE = "dead_queue";
    // 死信RoutingKey
    public static final String DEAD_LETTER_ROUTING_KEY = "dead_queue";
    // 备份交换机
    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";
    // 声明交换机
    @Bean
    public DirectExchange directExchange(){
        return ExchangeBuilder.directExchange(DIRECT_EXCHANGE_NAME).durable(true).
                // 设置该交换机的备份交换机
                alternate(BACKUP_EXCHANGE_NAME).build();
    }
    // 声明队列
    @Bean
    public Queue directQueue(){
        Map<String,Object> arguments = new HashMap<>();
        // 设置死信交换机
        arguments.put("x-dead-letter-exchange",DEAD_LETTER_EXCHANGE);
//        设置死信RoutingKey
        arguments.put("x-dead-letter-routing-key",DEAD_LETTER_ROUTING_KEY);
        // 设置TTL
        arguments.put("x-message-ttl",10000);
        // 业务队列绑定死信交换机
        return QueueBuilder.durable(DIRECT_QUEUEQ_NAME).withArguments(arguments).build();
    }
    @Bean
    public Queue directBQueue(){
        Map<String,Object> arguments = new HashMap<>();
        // 设置死信交换机
        arguments.put("x-dead-letter-exchange",DEAD_LETTER_EXCHANGE);
//        设置死信RoutingKey
        arguments.put("x-dead-letter-routing-key",DEAD_LETTER_ROUTING_KEY);
        // 设置TTL
//        arguments.put("x-message-ttl",40000);
        // 业务队列绑定死信交换机
        return QueueBuilder.durable(DIRECT_QUEUEQ_B_NAME).withArguments(arguments).build();
    }
    // 绑定交换机和队列
    @Bean
    public Binding queueBindingExchange(@Qualifier("directExchange")DirectExchange directExchange,
                                        @Qualifier("directQueue")Queue directQueue){
        return BindingBuilder.bind(directQueue).to(directExchange).with(DIRECT_ROUTING_KEY);
    }
    @Bean
    public Binding queueBBindingExchange(@Qualifier("directExchange")DirectExchange directExchange,
                                        @Qualifier("directBQueue")Queue directBQueue){
        return BindingBuilder.bind(directBQueue).to(directExchange).with(DIRECT_B_ROUTING_KEY);
    }
    // 声明死信交换机
    @Bean
    public DirectExchange deadExchange(){
        return new DirectExchange(DEAD_LETTER_EXCHANGE);
    }
    // 声明死信队列
    @Bean
    public Queue deadQueue(){
        return new Queue(DEAD_LETTER_QUEUE);
    }
    // 绑定死信交换机和队列
    @Bean
    public Binding deadBindingExchange(@Qualifier("deadExchange")DirectExchange deadExchange,
                                        @Qualifier("deadQueue")Queue deadQueue){
        return BindingBuilder.bind(deadQueue).to(deadExchange).with(DEAD_LETTER_ROUTING_KEY);
    }
    // 备份交换机
    @Bean("backupExchange")
    public FanoutExchange backupExchange(){
        return new FanoutExchange(BACKUP_EXCHANGE_NAME);
    }
    // 备份队列
    @Bean
    public Queue backupQueue(){
        return new Queue(BACKUP_QUEUE_NAME);
    }
    // 报警队列
    @Bean
    public Queue warningQueue(){
        return new Queue(WARNING_QUEUE_NAME);
    }
    // 绑定
    @Bean
    public Binding backupQueueBindingbackupExchange(@Qualifier("backupQueue") Queue backupQueue,
                                                    @Qualifier("backupExchange") FanoutExchange backupExchange){
        return BindingBuilder.bind(backupQueue).to(backupExchange);
    }
    @Bean
    public Binding backupQueueBindingwarningExchange(@Qualifier("warningQueue") Queue warningQueue,
                                                     @Qualifier("backupExchange") FanoutExchange backupExchange){
        return BindingBuilder.bind(warningQueue).to(backupExchange);
    }
}

新增报警消费

package com.example.myselfmq.consumer;

import com.example.myselfmq.config.DirectConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

/**
 * @author Maple
 * @description: TODO
 * @date 2022/8/12 17:10
 * @email 576235959@qq.com
 * 报警消费者
 */
@Slf4j
@Component
public class WarningConsumer {
    // 接收报警消息
    @RabbitListener(queues = DirectConfig.WARNING_QUEUE_NAME)
    public void receiveWarningMsg(Message message){
        String msg = new String(message.getBody());
        log.error("发现不可路由消息:{}",msg);
    }
}

 测试结果:

 回退消息和备份交换机可以一起使用,但是同时使用的话备份交换机优先级更高。

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值