使用RabbitMQ死信队列和延迟队列模拟订单系统使用场景

场景

在日常购物平台中,如果某个订单超过30分钟还未支付成功,系统就会将该订单取消,并进行库存回滚,本实例就使用RabbitMQ提供的两大特性:延迟队列和死信队列 来模拟这一应用场景,但实际上RabbitMQ并没有提供真正意义上的延迟队列,这句话什么意思呢?简单来讲就是RabbitMQ任意队列都没有方法直接指定消息被消费的时间(一般消息到达队列时,就会立马被路由到与之绑定的消费者,而不是说该消息到队列之后,等待30分钟再路由到消费者,目前RabbitMQ是不支持这种功能的),但是我们可以通过设置消息的一个过期时间,来达到模拟效果。在这以前,需要先了解一下死信消息的概念

死信消息

顾名思义,死信消息就表示该条消息已经死掉了,无法继续被当前队列路由到消费者消费了,这时RabbitMQ就会将该条消息发送到死信队列里面,消息成为死信有三种情况:
1、队列消息长度到达限制;
2、消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;
3、原队列存在消息过期设置,消息到达超时时间未被消费;

直接来看代码演示:

定义死信队列

@Configuration
public class DeadConfig {

    /**
     * 参数明细
     * 1、name 队列名称
     * 2、durable 是否持久化,如果持久化,mq重启后队列还在
     * 3、exclusive 是否独占连接,队列只允许在该连接中访问,如果connection连接关闭队列则自动删除,如果将此参数设置true可用于临时队列的创建
     * 4、autoDelete 自动删除,队列不再使用时是否自动删除此队列,如果将此参数和exclusive参数设置为true就可以实现临时队列(队列不用了就自动删除)
     * 5、arguments 参数,可以设置一个队列的扩展参数,比如:可设置存活时间
     */
    @Bean
    public Queue deadQueue() {
        return new Queue("deadQueue", false);
    }


    /**
     * 这种声明一个直连交换机
     * 参数明细:
     * 1、name,交换机名称
     * 2、durable  是否持久化,如果持久化,mq重启后交换机还在
     * 3、autoDelete 自动删除,交换机不再使用时是否自动删除此交换机
     */
    @Bean
    public DirectExchange deadExchange() {
        return new DirectExchange("deadExchange", false, false);
    }


    /**
     * 将死信队列与交换机进行绑定,路由键为: dead_route_key
     */
    @Bean
    public Binding bindDeadQueue() {
        return BindingBuilder.bind(deadQueue()).to(deadExchange()).with("dead_route_key");
    }
}

从上述代码可以看出,死信队列本质其实就是一个普通的队列,只不过将其定义成死信队列,专门来接收那些死信消息

定义延迟队列

@Configuration
public class WorkConfig {

    @Bean
    public Queue workQueue() {
        Map<String, Object> map = new HashMap<>();
        /**
         * 绑定死信交换机
         * <p><p/>
         * x-dead-letter-exchange , value = '死信交换机的名字'
         * x-dead-letter-routing-key value='路由key'
         */
        map.put("x-dead-letter-exchange", "deadExchange");
        map.put("x-dead-letter-routing-key", "dead_route_key");

        Queue workQueue = new Queue("workQueue", false, false, false, map);
        return workQueue;
    }


    @Bean
    public DirectExchange workExchange() {
        return new DirectExchange("workExchange", false, false);
    }


    @Bean
    public Binding bindWorkQueue() {
        return BindingBuilder.bind(workQueue()).to(workExchange()).with("work_route_key");
    }
}

通过上述代码可以看出,跟死信队列定义相差不大,需要注意的是:这里需要为该延迟队列绑定一个死信队列,当该延迟队列中消息成为死信时,会将该条死信消息重新转发到死信队列。

发送消息

用户下单以后,就往工作队列发送一条订单消息,并设置该条订单消息过期时间为30分钟

@RestController
public class Publish {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @GetMapping("sendWorkMessage")
    public String sendWorkMessage() {

        //设置消息属性的处理函数
        MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
                //设置消息的过期时间,为了方便测试,这里设置为 5秒
                message.getMessageProperties().setExpiration("5000");
                return message;
            }
        };
        //用户下单消息
        Map<String, Object> map = new HashMap<>();
        map.put("用户tom","刚刚下单了一部手机");
        map.put("createTime",String.format("yyyy-MM-dd HH:mm:ss",new Date()));
        //往延迟队列发送一条订单消息
        rabbitTemplate.convertAndSend("workExchange", "work_route_key", map, messagePostProcessor);

        return "ok";
    }
}

监听死信队列

public class DeadConsumer {

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        factory.setPort(5672);
        factory.setVirtualHost("/");
        factory.setUsername("guest");
        factory.setPassword("guest");
        Connection connection = factory.newConnection();

        //创建信道
        Channel channel = connection.createChannel();
		
		//监听死信队列,如果死信队列中有消息,会立马
        channel.basicConsume("deadQueue",true,new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("监听到死信队列消息:" + new String(body,"utf8"));
                //这里接收到订单消息后,就可以根据订单信息去查询订单系统
                //如果用户未支付,再进行后续相应的业务逻辑
            }
        });
    }
}

至此,这里就模拟了订单30分钟如果还未支付,则可以进行相应业务需求处理

总结

以上业务流程大致为:先定义两个队列,一个作为死信队列,一个作为延迟队列,并将死信队列绑定到延迟队列上面,然后向延迟队列中发送一条订单消息,并设置消息的过期时间,最后为死信队列绑定一个消费者,当死信队列接收到消息时,说明该条订单消息已经到30分钟了,然后就可以去查询订单系统来判断用户是否下单成功。但是这里需要特别注意的是:延迟队列不能绑定消费者!延迟队列不能绑定消费者!延迟队列不能绑定消费者! 重要的事说三遍!!!,因为一旦为延迟队列绑定了消费者,那么消息到达延迟队列以后,会立马被消费掉,那么该条消息就无法成为死信消息了,也就意味着死信队列会永远接收不到消息,正确的做法是发送一条消息到延迟队列,然后让该条消息静静地等待过期时间,变成一条死信,RabbitMQ检测到死信以后,会将该条死信消息转发到绑定该延迟队列上的死信队列

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值