rabbitMQ 系列 之 死信

rabbitMQ 系列 之 死信

概念

​ 先说下什么是死信。这里边包含几个概念:

  • 死信交换器:Dead-Letter-Exchange,简称DLX。作用是干嘛的呢,当一条消息在普通队列中变为死信后,这条消息就能被重新发送到另一个交换器中,这个交换器就是死信交换器。

  • 死信队列:和死信交换器绑定的队列就是死信队列。当死信被重新发送到DLX后,被路由到此队列。

  • 死信:简而言之,可以概括为被丢弃的消息,就是死信。

    消息变为死信的几种情况:

    • 消息被拒绝(Basic.Reject/Basic.Nack),并设置requeue(重新入队)参数为false;
    • 消息设置了TTL,并且过期;
    • 队列达到最大长度

​ 其实在定义期间,死信交换器和死信队列都是普通的交换器和队列,只是在某个普通队列声明时使用x-dead-letter-exchange参数将此死信交换器赋予了DLX的职责而已。

示意图

死信队列示意图

延迟队列

​ 延迟队列存储的是延迟消息,比如某条消息不想被立即消费,而是等待到固定时间后才被消费者接收。此场景比较常见的便是订单下单30分钟后未支付,订单状态变更为已取消状态,如果使用定时任务定时扫描订单表,那么扫描间隔长,造成业务无法精准,扫描间隔短,会对数据库造成压力。此时便可使用延时队列。

​ rabbitmq本身不具有延迟队列的功能,但是通过DLX和TTL的设置,可以模拟出延迟队列的功能。

实例

下面使用活动到期开始改变状态的业务来演示怎样使用延时队列

交换器、队列以及路由key的定义:

public class MQKeyStatic {
 
    /**
     * 营销模块死信交换器
     */
    public static final String EXCHANGE_MARKETING_DLX = "exchange.marketing.dlx";
    public static final String EXCHANGE_MARKETING_DELAY = "exchange.marketing.delay";
    
     /**
     * 营销模块延时队列-活动处理
     */
    public static final String QUEUE_MARKETING_DELAY_ACTIVITY = "queue.good.delay.activity";
    public static final String ROUTING_MARKETING_DELAY_ACTIVITY = "routing.good.delay.activity";
    /**
     * 营销模块死信队列-活动处理
     */
    public static final String QUEUE_MARKETING_DLX_ACTIVITY = "queue.good.dlx.activity";
    public static final String ROUTING_MARKETING_DLX_ACTIVITY = "routing.good.dlx.activity";

}    

交换器,队列以及binding处理:

@Configuration
public class MQConfig {
    
    @Bean
    public MessageConverter messageConverter() {
        Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
        jackson2JsonMessageConverter.setCreateMessageIds(true);
        return jackson2JsonMessageConverter;
    }
    
	/**
     * @desc: 延迟队列处理死信交换器
     * @return: org.springframework.amqp.core.TopicExchange
     * @auther: Michael Wong
     * @email:  michael_wong@yunqihui.net
     * @date:   2020/9/8 18:52
     * @update:
     */
    @Bean
    public TopicExchange dlxExchange() {
        return new TopicExchange(MQKeyStatic.EXCHANGE_MARKETING_DLX,true,false,null);
    }
    
    /**
     * @desc: 延迟业务处理交换器
     * @return: org.springframework.amqp.core.TopicExchange
     * @auther: Michael Wong
     * @email:  michael_wong@yunqihui.net
     * @date:   2020/9/8 18:52
     * @update:
     */
    @Bean
    public TopicExchange delayExchange() {
        return new TopicExchange(MQKeyStatic.EXCHANGE_MARKETING_DELAY,true,false,null);
    }

     /**
     * @desc: 活动处理队列
     * @return: org.springframework.amqp.core.Queue
     * @auther: Michael Wong
     * @email:  michael_wong@yunqihui.net
     * @date:   2020/9/5 14:46
     * @update:
     */
    @Bean
    public Queue activityDlxQueue() {
        return new Queue(MQKeyStatic.QUEUE_MARKETING_DLX_ACTIVITY, true, false, false, null);
    }

    @Bean
    public Binding dlxExchangeBindingActivityDlx() {
        return BindingBuilder.bind(activityDlxQueue()).to(dlxExchange()).with(MQKeyStatic.ROUTING_MARKETING_DLX_ACTIVITY);
    }


    @Bean
    public Queue activityDelayQueue() {
        // 注意要在延时队列上设置ttl和dlx属性
        Map<String, Object> args = new HashMap<>(2);
        args.put("x-dead-letter-exchange", MQKeyStatic.EXCHANGE_MARKETING_DLX);
        args.put("x-dead-letter-routing-key", MQKeyStatic.ROUTING_MARKETING_DLX_ACTIVITY);
        return new Queue(MQKeyStatic.QUEUE_MARKETING_DELAY_ACTIVITY, true, false, false, args);
    }
    @Bean
    public Binding delayExchangeBindingActivityDelay() {
        return BindingBuilder.bind(activityDelayQueue()).to(delayExchange()).with(MQKeyStatic.ROUTING_MARKETING_DELAY_ACTIVITY);
    }

}

监听队列:

@Component
public class MQListener {
    
    @Autowired
    private RedisTemplate redisTemplate;
    
    /**
     * @desc: 活动商品上下架处理队列监听
     * @param message
     * @param channel
     * @return: void
     * @auther: Michael Wong
     * @email:  michael_wong@yunqihui.net
     * @date:   2020/9/9 15:59
     * @update:
     */
    @RabbitListener(queuesToDeclare =
    @Queue(name = MQKeyStatic.QUEUE_MARKETING_DLX_ACTIVITY, durable = "true", exclusive = "false", autoDelete = "false"))
    public void activityListener(Message message, Channel channel) throws IOException {
        if (idempotentMessage(message, channel)) {
            return;
        }

        JSONObject messageJson = JSONObject.parseObject(new String(message.getBody()));
        Integer activityId = messageJson.getInteger("activityId");
        String activityType = messageJson.getString("activityType");
        try {
            // 此处使用策略模式
            String executorName = activityType + "Service";
            IActivityStrategy strategy = (IActivityStrategy) SpringContextHolder.getBean(executorName);
            strategy.start(activityId);
            ackMessage(message,channel);
        } catch (Exception e) {
            channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);
        }

    }
    
    
    /**
     * @param message
     * @param channel
     * @desc: 确认消息公共方法
     * @return: void
     * @auther: Michael Wong
     * @email: michael_wong@yunqihui.net
     * @date: 2020/9/5 16:51
     * @update:
     */
    private void ackMessage(Message message, Channel channel) throws IOException {
        try {
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            String rediskey = RedisKeyStatic.IDEMPOTENT_MESSAGE + message.getMessageProperties().getMessageId();
            stringRedisTemplate.opsForValue().set(rediskey, "1", 3, TimeUnit.HOURS);
        } catch (Exception e) {
            log.error("接收消息失败,重新放回队列,message:{}", message);
            // 解决方案,剔除此消息,然后记录到db中去补偿
            channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
        }
    }
    
    /**
     * @param message
     * @param channel
     * @desc: 幂等消息
     * @return: boolean
     * @auther: Michael Wong
     * @email: michael_wong@yunqihui.net
     * @date: 2020/9/7 10:43
     * @update:
     */
    private boolean idempotentMessage(Message message, Channel channel) throws IOException {
        String messageId = message.getMessageProperties().getMessageId();
        String rediskey = RedisKeyStatic.IDEMPOTENT_MESSAGE + messageId;
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        if (stringRedisTemplate.hasKey(rediskey)) {
            channel.basicReject(deliveryTag, false);
            return true;
        }
        return false;
    }
}    

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值