使用RabbitMQ实现延迟队列

场景:

在项目中

1.用户确认一个订单,若30分钟之类没有支付,则需要取消订单,若用定时任务去扫描订单表,第一,定时任务时间如何定义,有存在漏扫的风险,第二,订单表数据庞大,扫描表非常消耗性能,这时候该功能可以引入RabbitMQ延迟队列来做

2.某条活动通知在指定的一天推送给用户,可以用延迟队列

 

定义:

延迟队列即发送一条消息给目标队列,并非让目标队列立即接受到消息,而是让消息等待一段延迟时间才到达目标队列让它消费

RabbitMQ本身没有直接支持延迟队列功能,我们可以利用它的DXL特性来扩展

rabbitMQ的queue可配置x-dead-letter-exchange和x-dead-letter-routing-key两个参数,意为如果队列中出现了dead letter,则按照这两个参数重新路由转发到指定队列

 

x-dead-letter-exchange : 出现dead letter之后,将dead letter重新发送到exchange

 

x-dead-letter-routing-key : 出现dead letter之后,将dead letter重新按照指定的routing-key发送

 

队列出现dead letter情况有:

  • 消息或队列的TTL过期
  • 队列达到最大长度
  • 消息被消费端拒绝,并且requeue = false

 

 

更多rabbitMQ延迟队列知识,推荐文章

由此可知,设置TTL规则之后,当消息在一个队列中变成死信之后,利用DLX特性,它能重新被转发到另一exchange或routing-key,从而被重新消费

 

撸码:

在之前的文章<RabbiMQ工作原理及简单使用>中,我们说过,rabbitMQ的消息生成器是不直接将消息发送到队列,而是发送到交换器,交换器可以转发到单个或多个队列,由此,我们需要两个队列、两个交换机

queue1: 死信队列,将消息发送到死信exchange,绑定此队列,让消息进入该队列成为死信,设置TTL过期时间,该队列没有消费者,等待时间过期进入dead letter,当queue1队列有死信产生时,会转发到交换器x-dead-letter-exchange,以路由键x-dead-letter-rouuting-key转发到指定queue

queue2: 转发队列,也是消息最终消费的目标队列,此队列需要从死信队列接收消息,所以需要绑定死信转发到的交换器x-dead-letter-exchange

fanout sender代码集成, MessagePostProcessor消息处理器是个functional接口

/**
     * 延迟发送消息队列,客户端发目标队列发送一条延迟消息 : {"queue":"queueName","body":"message"}
     * 此时,将消息发送到DXL死信队列,而非直接发送到queueName队列,并设置延迟时间 times 秒
     * <p>
     * 死信队列没有消费者,它用来存储超时的消息,并转发到另一队列,转发队列等待消息延迟之后接收到消息,
     *  已过了times 秒,处理业务逻辑
     *
     * @param payload 消息体
     */
    public void sendDelayMessage(DelayMessagePayload payload) {
        CorrelationData correlation = new CorrelationData(UUID.randomUUID().toString());
        rabbitTemplate.convertAndSend(payload.getDeadExchange(), payload, message -> {
            message.getMessageProperties().setExpiration(payload.getDelayTime() + "");
            return message;
        }, correlation);
        log.info("fanout send delay message success exchange:{}, message:{}, correlationId:{}",payload.getDeadExchange(),
                JsonUtils.obj2json(payload), correlation.getId());
    }

消息体:

@AllArgsConstructor
@NoArgsConstructor
@Data
public class DelayMessagePayload implements Serializable{

    private String forWardExchange;

    private String forWardQueue;

    private String deadExchange;

    private String deadQueue;

    private Object message;

    private long delayTime;

}

客户端:

 DelayMessagePayload payload = new DelayMessagePayload(RabbitConstant.FanoutExchange.SECKILL_DEAD_LETTER,
                RabbitConstant.FanoutQueue.SECKILL_FORWARD, RabbitConstant.FanoutExchange.SECKILL_DEAD_LETTER,
                RabbitConstant.FanoutQueue.SECKILL_DEAD_LETTER,
                "123456789",10 * 1000);
        fanoutSender.sendDelayMessage(payload);

初始化队列和交换机:

@Configuration
public class SeckillTimeOutPayConfig {
    
    /**
     * 初始化死信队列
     */
    @Bean
    public Queue initSeckillDead(){
        Map<String,Object> arguments = new HashMap<>(2);
        arguments.put("x-dead-letter-exchange", RabbitConstant.FanoutExchange.SECKILL_DEAD_LETTER);
        arguments.put("x-dead-letter-routing-key",RabbitConstant.FanoutQueue.SECKILL_FORWARD);
        return new Queue(RabbitConstant.FanoutQueue.SECKILL_DEAD_LETTER,true,false,false,arguments);
    }
    @Bean
    public FanoutExchange initDeadExchange(){
        return new FanoutExchange(RabbitConstant.FanoutExchange.SECKILL_DEAD_LETTER);
    }
    @Bean
    public Binding bindDead(){
        return BindingBuilder.bind(initSeckillDead()).to(initDeadExchange());
    }
    
    /**
     * 初始化转发队列
     */

    @Bean
    public Queue initForwardQueue(){
        return new Queue(RabbitConstant.FanoutQueue.SECKILL_FORWARD);
    }
    @Bean
    public Binding bindForwardExchange(){
        return BindingBuilder.bind(initForwardQueue()).to(initDeadExchange());
    }
}

消费监听:

@RabbitListener(queues = RabbitConstant.FanoutQueue.SECKILL_FORWARD)
@Component
@Slf4j
public class SeckillTimeOutPayReceive {

    @RabbitHandler
    public void test(DelayMessagePayload payload){
        try {
        log.info("收到消息:{}", JsonUtils.obj2json(payload));

        }catch (Exception e){

        }
    }

 

注意: 消息队列是先进先出的栈列结构,也就意味着,如果先进去的延迟时间比后进去的延迟时间要大,那么会一直阻塞等待先进去的消费,后面的就消费不到了,需要将延迟时间一致的业务放入同一队列即可

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值