RabbitMQ系列4:延迟订单的实现和死信队列

1.背景

平时在购物网站经常有一个场景:超过30min未付款的订单自动关闭,这个功能怎么实现。
一个思路是发一条跟订单有关的消息,30min以后被消费,在消费者的代码中查询订单数据,如果状态为未付款,就关闭订单。那么该如何实现在指定的时候之后才发给消费者呢?
RabbitMQ本身不支持延迟投递,总的来说有2种实现方案:
1.先存储到数据库,用定时任务扫描
2.利用RabbitMQ的死信队列实现
定时任务比较容易实现,按照一定的间隔扫描数据库,查出30min之前未付款的订单,把状态改成关闭,但是数据量大时,会带来比较大处理压力。

2.死信队列

什么是死信队列?
队列有一个消息过期的属性,x-mesage-ttl,所有队列中的消息超过时间未被消费的,都会过期。但是灵活性不够,为此每个消息都可以再设置一个过期时间,如果两个都设置了,那么就小的生效。
过期的消息会被放在一个单独的容器里,就是死信队列。这样就可以实现延迟消费了。
队列创建的时候可以指定一个死信交换机DLX,死信交换机绑定的队列是死信队列DLQ,DLX实际上也是普通的交换机,DLQ也是普通的队列。
此时数据的流向过程为:
在这里插入图片描述
也就是:
生产者->原交换机->原队列->死信交换机->死信队列->最终消费者

实现方法:
生产者:

public class DlxProducer {

    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setUri(ResourceUtil.getKey("rabbitmq.uri"));

        // 建立连接
        Connection conn = factory.newConnection();
        // 创建消息通道
        Channel channel = conn.createChannel();

        String msg = "Hello world, Rabbit MQ, DLX MSG";

        // 设置属性,消息10秒钟过期
        AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
                .deliveryMode(2) // 持久化消息
                .contentEncoding("UTF-8")
                .expiration("10000") // TTL
                .build();

        // 发送消息
        channel.basicPublish("", "ORI_USE_QUEUE", properties, msg.getBytes());

        channel.close();
        conn.close();
    }
}

消费者:

public class DlxConsumer {

    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setUri(ResourceUtil.getKey("rabbitmq.uri"));
        // 建立连接
        Connection conn = factory.newConnection();
        // 创建消息通道
        Channel channel = conn.createChannel();

        // 指定队列的死信交换机
        Map<String,Object> arguments = new HashMap<String,Object>();
        arguments.put("x-dead-letter-exchange","DEAD_LETTER_EXCHANGE");
//         arguments.put("x-expires",2000L); // 设置队列的TTL
        // arguments.put("x-max-length", 4); // 如果设置了队列的最大长度,超过长度时,先入队的消息会被发送到DLX

        // 声明队列(默认交换机AMQP default,Direct)
        // String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
        channel.queueDeclare("ORI_USE_QUEUE", false, false, false, arguments);

        // 声明死信交换机
        channel.exchangeDeclare("DEAD_LETTER_EXCHANGE","topic", false, false, false, null);
        // 声明死信队列
        channel.queueDeclare("DEAD_LETTER_QUEUE", false, false, false, null);
        // 绑定,此处 Dead letter routing key 设置为 #
        channel.queueBind("DEAD_LETTER_QUEUE","DEAD_LETTER_EXCHANGE","#");
        System.out.println(" Waiting for message....");

        // 创建消费者
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                String msg = new String(body, "UTF-8");
                System.out.println("Received message : '" + msg + "'");
            }
        };

        // 开始获取消息
        // String queue, boolean autoAck, Consumer callback
        channel.basicConsume("DEAD_LETTER_QUEUE", true, consumer);
    }
}

此时消息会在过期之后移动到死信队列,此时才能被消费。

除了消息过期,还有什么情况消息会变成死信?
(1)消息被拒绝,并且未设置重回队列
(2)队列达到最大长度,消息数或者字节数超过了设置值,最先入队的消息会被发送到DLX

题:
使用死信队列的不足之处也很明显:数据量大的时候会导致mq拥塞;
另外如果定时种类很多,还需要很多很多交换机和队列,非常不灵活。
其实这中问题更多交给业务通过定时任务处理,mq的死信队列主要处理一个因为服务异常等临时出现的超时消息的管理更好。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

纵横千里,捭阖四方

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值