RabbitMQ(六)死信队列

06、SpringBoot + RabbitMQ 死信队列实战Topic/manual/Aop/安全性(做冗余和备份)/ 定时器

死信队列: 接盘侠,

在开发中,延时和延迟处理一些指定的业务逻辑是非常常见的事情,

  • 比如商城平台订单处理超过30分钟的订单未支付将自动关闭
  • 商城订单完成后,用户一直未评价,5天后自动好评
  • 会员到期前15天,到期前3天发送短信提醒
  • 或者12306的抢票也是如此,10分钟支付时间,如果你没支付就自动取消,让其他的用户的去抢票
  • 或者银行取钱给你180s取钱时间,如果你没取钱当前视为放弃,银行卡退出。。。

这些场景都可以使用中间件RabbitMQ提供的死信队列,进行得以处理。

01、死信队列 — 春运 12306 抢票

死信队列(ex - routing - queue)又称之为”延迟队列“或者”延时队列”,也是RabbitMQ队列的一种,指的是进入队列的消息会被延迟消费的队列,这种队列根普通的队列相比,最大的差异在于消息一旦进入普通队列将会立即被消费处理,而进入死信队列则会过一定的时间在被消费处理。

在传统企业级的应用系统中实现消息,业务数据的延迟处理一般是通过开启定时器的方式,轮询扫描并获取数据表中满足条件的业务数据记录,比较数据记录的业务时间和当前时间,如果当前时间大于记录中的业务时间,则说明数据记录已经超过了指定的时间而未被处理,此时需要执行相应的业务逻辑,比如:失效该数据记录,发送通知信息给指定用户等。

这种处理方式中,定时器每隔一定的时间不间断地去扫描数据库表,并不断地获取满足业务条件的数据,直到手动关闭定时器,如果不关闭,定时器开启的线程会一直执行下去。

思考:

假设如果这个时候我们开发的是淘宝,京东或者12306这样的网站使用定时器这种方式可取吗?以12306为例,相g都用12306抢票过,在抢票的过程中一般会提醒用户:“请在30分钟内付款”。一般用户会点击“立即付款”,然后输入相应的支付密码支付车票的费用。扣款成功后,12306官网会发送邮件或者短信通知用户抢票成功等信息。

然后实际情况却存在一些特殊情况,比如用户抢到了火车票,由于各种因素迟迟没有付款,过了30分钟以后仍然没有支付车票的费用,最后导致系统自动取消该笔订单。

如果这个时候采用的是定时器的方式进行开发,并判断用户下单实际距离当前实际是否已经超过30分钟,如果是,则表示用户在30分钟内仍然没有付款,系统将自动失效该笔订单并回收该车票,整个业务流程如下:
在这里插入图片描述
众所周知,抢票完全是一个非常庞大的数据量,高并发场景(全国几乎上千万,上亿的)人都在抢票,在某一时刻车票开抢之后,正常情况下将陆续会又用户抢到火车票,但是距离车票付款成功是有一定的时间间隔的,在这段时间内,如果定时器频繁地从数据库中获取:“未付款”状态的订单,其数据量之大难以想象,而且如果大批量的用户在30分钟内迟迟不付款,那从数据库中获取的数据量将一直增长,当达到一定程度时,将给数据库服务器和应用服务器带来巨大的压力,甚至之间压垮服务器,导致抢票等业务全线崩溃,带来的直接后果不堪设想。

所以在早期的很多抢票软件每当赶上春运高峰期的时候,经常会出现“网站崩溃”,点击购买车票却一直没响应等状况。某种程度上可能是因为某一时刻产生的高并发请求,或者频繁访问数据得到的数据过大导致内存,CPU,网络和数据库服务器等负载过高引起的。

而死信队列的引入,不管是从业务层面还是应用的性能层面来看,都大大地改善了原有的处理流程,下面是“抢票成功后30分钟内未付款的处理流程”:

在这里插入图片描述
优化后的处理流程中可以看出RabbitMQ的引入主要替代了传统处理的“定时器”处理逻辑,采用RabbitMQ的死信队列进行处理

死信队列是指:是一种可以延迟一定时间的在处理相应的业务逻辑,而这也可以看做是死信队列的作用,即死信队列可以实现特定的消息、业务数据等待一定的时间后再被消费者监听消费处理

02、死信队列 — 商城用户购买商品未付款订单

和抢票一样,在一些电商或者有支付的场景中,也会存在这种类似的情况。用户在选购商品后点击“去付款”后,商城将会引导用户跳转到支付页面,此时系统会为用户生成一笔对应购物车中商品的订单,并将该订单的付款状态设置为0。即代表未付款,同时该订单id或者订单编号加入RabbitMQ的死信队列中,并设置延迟时间为30分钟。

如果用户在30分钟内选择了某种付款方式进行付款,则系统将更新该订单ID对应的订单的付款状态为1,即已付款状态。同时在商品表的库存表中更新订单中所包含的商品对应的库存,

如果用在30分钟内未付款,则RabbitMQ的死信队列对应的消费者将会在30分钟后监听到该订单id或编号,根据订单id或编号查询数据库的商品订单表,如果该订单的付款状态仍然为0,即未付款,则表示用户在30分钟内仍然未付款,此时需要失效该笔订单,同时更新回退商品库存,这以业务场景的整体流程如下图所示:
在这里插入图片描述
从上图可以看出,RabbitMQ死信队列的引入主要是用于延迟一定时间在处理特定的业务逻辑,而这种延迟在RabbitMQ中是一种“自动化”的,无需人为进行干预,即只需要指定延迟队列中绑定的交换机所对应处理业务逻辑的“真正队列”,并开发这个“真正队列”对应的消费者的监听消费功能即可。

03、RabbitMQ死信队列实战

RabbitMQ的死信队列,相对于传统的定时器轮询的处理方式,死信队列具有占有系统资源少,(比如不需要轮询数据库获取数据,减少DB层面资源的消耗),人为很少干预,只需要搭建好死信队列消息模型,就可以不需要认为去进行干预了,以及自动处理消息和数据,不夸张的讲,在实际项目中,任何需要延迟,延时处理的业务都可以使用死信队列这个强大的组件。

死信队列的专有术语和词汇

与普通的队列相比,死信队列同样具有三个核心成员:

  • 交换机
  • 路由
  • 队列

只不过死信队列,增加了另外三个成员即:

  • DLX:死信交换机:即Dead -Letter-Exchange。是一种特殊的交换机。
  • DLK:死信路由:即Dead-Letter-Routing-Key。主要和DLX一起使用,
  • TTL:存活时间,即Time-To-Live,指的是进入死信队列的消息可以存活的时间,一旦达到TTL,讲以为这该消息“死了”,从而进入下一个中转站,等待被正在的消息队列监听消费。

什么样子的消息会进入死信DLX —死信队列 —DLX–DLK

  • 消息被拒绝,比如通过调用basic.reject或者basic.nack方法的时候,会进入到死信中,并且不在重新投递,即requeue参数的取值是false.
  • 消息超过指定的存活时间(比如通过调用messageProperties.setExpiration()设置消息的TTL时间即可实现)。
  • 队列达到最大长度。

当发生上述的情况时,将会出现死信的情况,而之后的消费讲被重新投递到另一个交换机,此时该交换机就是死信队列交换机。由于该死信队列交换机和死信路由绑定在一起对应真正的队列。导致消息讲被分发到真正的队列。最终被该队列对应的消费者所监听消费,简单说:就是没有被死信队列消费的消息,讲换个地方重新被消费从而实现消息:“延迟”,“延时”消费,而这个地方就是消息的下一个中转站,即死信交换机。
在这里插入图片描述
在前面我们得知,RabbitMQ的基础消息模型是由:交换机,路由和队列及其绑定所组成。生产者生产消息后,将消息发送到消费模型的交换机中,又由交换机与绑定的路由key找到对应的队列,然后监听该队列的消费者进行消费和处理,如下图:
在这里插入图片描述

死信队列消息模式实战

在这里插入图片描述

消息的载体
package com.pug.mq.dead;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class DeadInfo implements java.io.Serializable{
    private Integer id;
    private String message;
}

关系绑定配置类
package com.pug.mq.dead;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class DeadRabbitmqConfiguration {

    public static final String DEAD_ORDER_EXCHANGE_NAME = "pug.dead.order.exchange";
    public static final String DEAD_ORDER_ROUTING_KEY = "pug.dead.order.routing.key";
    public static final String DEAD_ORDER_QUEUE = "pug.dead.order.queue";


    // 面向生产者的交换机
    public static final String PRODUCER_ORDER_EXCHANGE = "pug.producer.order.exchange";
    public static final String PRODUCER_ORDER_ROUTING_KEY = "pug.producer.order.routing.key";

    /**
     * 创建死信队列
     *
     * @return
     */
    @Bean
    public Queue basicDeadQueue() {
        // 使用Map存放死信队列的三个核心组成部分
        Map<String, Object> args = new HashMap<>();
        // 创建死信队列交换机
        args.put("x-dead-letter-exchange", DEAD_ORDER_EXCHANGE_NAME);
        // 创建死信队列路由
        args.put("x-dead-letter-routing-key", DEAD_ORDER_ROUTING_KEY);
        // 设定TTL,单位是ms,下面的单位是10s
        args.put("x-message-ttl", 10000);
        // 创建队列并返回死信队列实例
        return QueueBuilder.durable(DEAD_ORDER_QUEUE).withArguments(args).build();
    }


    /**
     * 创建 基本消息模型的基础交换机,面向生产者
     * @return
     */
    @Bean
    public TopicExchange basicProducerOrderExchange(){
        return ExchangeBuilder.topicExchange(PRODUCER_ORDER_EXCHANGE).durable(true).build();
    }

    /**
     * 创建基本绑定,(基础交换机+基础路由),面向生产者
     * @return
     */
    @Bean
    public Binding basicProducerOrderBinding(){
        return BindingBuilder.bind(basicDeadQueue())
                .to(basicProducerOrderExchange())
                .with(PRODUCER_ORDER_ROUTING_KEY);
    }


    /*************************************面向消费者*********************************/
    public static final String CONSUMER_ORDER_QUEUE = "pug.consumer.order.queue";
    /**
     * 真正的队列,面向消费者
     * @return
     */
    @Bean
    public Queue consumerOrderQueue(){
        return QueueBuilder.durable(CONSUMER_ORDER_QUEUE).build();
    }

    /**
     * 创建死信队列交换机
     * @return
     */
    @Bean
    public TopicExchange basicDeadExchange(){
        return ExchangeBuilder.topicExchange(DEAD_ORDER_EXCHANGE_NAME).durable(true).build();
    }

    /**
     * 创建死信路由并绑定
     * @return
     */
    @Bean
    public Binding basicDeadBinding(){
        return BindingBuilder.bind(consumerOrderQueue()).to(basicDeadExchange()).with(DEAD_ORDER_ROUTING_KEY);
    }

}

生产者发送消息

消费者等者死信过期消息消费

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值