rabbitmq实现延迟队列

总结不易,如有不足不吝赐教,如有帮助请点个赞,谢谢~

延迟队列

公用配置

/* 生产者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 ===>做业务处理
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值