APP下载

基于RabbitMQ的延迟队列实现及应用

2020-12-06黄可王盛义胡兵李朝阳易勇

科学导报·学术 2020年49期

黄可 王盛义 胡兵 李朝阳 易勇

摘 要:延迟队列是一种延迟处理消息的特殊队列,在实际应用系统中,存在大量需要延迟处理业务的场景。常规的轮询检测实现方式,存在诸多弊端。本文结合具体业务场景,利用RabbitMQ消息中间件的死信机制,设计实现了一种简单优雅的延迟队列。

关键词:延迟队列;RabbitMQ;死信

1.RabbitMQ和延迟队列

1.1RabbitMQ中间件

RabbitMQ是当下非常流行的开源消息队中间件,使用erlang语言开发,实现了AMQP协议。由于其性能稳定,维护更新快,社区活跃,广泛应用于众多企业信息系统中。

RabbitMQ的工作原理[1]如圖1所示。其中P表示消息的生产者(Producer),X表示交换机(Exchange),Q表示队列(Queue),C表示消息的消费者(Consumer)。

AMQP协议规定的消息系统应包括消息生产者、消息消费者和消息服务器三个核心功能组件。其中RabbitMQ服务器上可以划分为多个虚拟主机(vhost),每个虚拟机都可以创建交换机和队列,虚拟机之间的交换机和队列数据是互相隔离的。

交换机接收消息生产者发布的消息,并根据规则将消息路由给符合条件的队列。交换机是匹配和路由消息的实体。

队列是存储和转发消息的实体,队列中的消息会被顺序传递给一个或多个消费者。

绑定(binding)用于表示交换机和消息队列(或交换机)之间的关系。消息生产者发送消息时提供了消息的路由参数(routing key),交换机收到消息后匹配绑定规则,然后将消息投递到指定的队列。

1.2延迟队列

延迟队列是一种特殊的队列,和一般的队列不同,延迟队列中的消息不会被立即消费,往往需要延迟一定的时间后才会被消费。延迟队列中的消息有别于定时任务,定时任务通常是有明确的触发时间和触发周期的。而延迟队列中的消息没有固定的开始时间,消息进入延迟队列后间隔指定的时间执行。

在应用系统中,延迟队列的应用场景很常见的。例如:春运12306抢票,顾客45分钟内没有完成支付,系统自动取消订单。用户一周内没有活跃记录时,系统向不活跃的用户发送邮件等。

延迟队列有多种实现方式,可以使用Java自带的DealayQueue,使用定时任务轮询,使用Redis[2]等。当然,也可以通过RabbitMQ的死信机制实现。

2.延迟队列的设计

2.1RabbitMQ的死信机制

死信(Dead Letter)是RabbitMQ中的一种消息机制,在消费消息时,如果队列中的消息出现以下三种情形:

1、消息或者队列的TTL过期;

2、消息被消费端拒绝(basic.reject or basic.nack)并且消息不重新进入队列(requeue =false);

3、队列中的消息数量已达到最大值。

那么该消息即为死信。RabbitMQ会对死信进行特殊处理,如果配置了死信交换机和死信队列信息,死信将会路由到死信队列中,如果没有配置,该死信消息将被丢弃。

2.2基于死信机制实现延迟队列

RabbitMQ消息中间件[3]中没有延迟队列类型,但可以通过死信机制,使普通队列具备延迟队列的特性。死信机制的核心是配置队列中消息的生存时间(TTL,Time To Live),死信交换机(DLX,Dead Letter Exchange)和死信路由(DLK,Dead Letter Key)。基于死信机制实现的延迟队列基本结构如图2所示。

可以看到,配置一个延迟队列,基本分为以下三个步骤:

1、声明业务队列Q,并绑定到业务交换机X上;

2、配置业务队列Q的x-message-ttl,x-dead-letter-exchange和x-dead-letter-routing-key属性;

3、声明死信队列DLQ,并绑定到死信交换机DLX上。

和图1不同,除了声明正常的业务队列Q之外,还声明了一个死信队列DLQ。和RabbitMQ中的普通队列相同,死信队列DLQ只是和死信交换机DLX建立了绑定。业务队列Q中的消息超过了设置的生存时间x-message-ttl后,会根据队列上设置的x-dead-letter-exchange和x-dead-letter-routing-key属性,将超时的消息发送到设定的死信交换机中。其中x-message-ttl用于设置队列中消息的生存时间,x-dead-letter-exchange用于指定死信交换机的名称,x-dead-letter-routing-key用于设定死信消息的路由键。死信交换机根据路由键将死信消息路由到符合匹配规则的死信队列DLQ中。最后由消费者C消费已经延迟了指定的时间的消息,从而实现了延迟队列的功能。

3.延迟队列的应用

3.1应用场景介绍

电商平台促销抢购时,订单超时关闭是延迟队列的一种典型应用场景。其不仅有延迟处理业务的基本要求,还有低资源消耗,高可用的潜在要求。传统方式一般是通过开启定时器的方式轮询扫描符合条件的业务数据,然后再进行延迟处理。由于定时任务有固定的触发周期,可能存在很多订单已经超时,但还没到处理订单任务的触发时刻,无法及时处理的情况。另一方面,促销抢购是一个大数据量、高并发的业务场景,使用定时器频繁扫描数据库中大量未付款的订单,将给数据库和应用服务器带来巨大的压力。而使用RabbitMQ的延迟队列,则可以优雅地应对该场景。

3.2应用场景分析实践

实际的业务场景中,订单超时关闭涉及到多种业务处理逻辑,例如商品出库,退回库存的处理。简单起见,暂不讨论过程中的库存处理逻辑。以订单45分钟内未支付,订单自动关闭为例,用户下单时,需要将用户的下单记录存储至数据库中,同时也需要将用户订单id作为消息发送到队列中,并设置队列中消息的生存时间为45分钟,再配置队列的x-dead-letter-exchange和x-dead-letter-routing-key属性信息,指明队列中的订单45分钟后需要投递到指定的死信交换机,并路由到指定的死信队列中处理。消费端监听到死信队列中的到期的订单信息后,根据订单的id在用户下单记录表中查询该订单的支付状态。如果用户在45分钟已完成支付或者取消了该订单,则直接向用户返回相应的提示信息。如果用户未支付也没有取消订单,表明用户未在指定时间内完成支付,则需要将订单状态更新为关闭状态。

和定时轮询相比,使用延迟队列后,订单的检查时间精确,而且不会进行全表查询。在抢购场景下,45分钟内可能产生数万条订单,使用延迟队列可以避免定时频繁查询数据库,有效减轻内存、CPU、网络和数据库服务器的负载。

4.总结

本文介绍了延迟队列的基本概念和常见的实现方式。重点介绍了RabbitMQ的工作原理和RabbitMQ延迟队列的构造过程。并以订单支付超时自动关闭这一具体场景说明如何使用RabbitMQ构造的延迟队列进行订单检查处理。

目前该延迟队列的功能比较简单,也没有做成公共服务提供给开发人员使用。需进一步增强队列的故障恢复,消息履历追踪等功能,并进行封装简化。

参考文献

[1] 王冬雪. 基于AMQP的异构信息转换/传输机制的研究与实现[D]. 杭州:浙江工业大学,2013.

[2] 刘子辰. 基于 Tair/Redis 的延迟队列系统的设计与实现[D]. 大连:大连理工大学,2017.

[3] 马巍,武欣嵘,郑翔,等.RabbitMQ在实时监控系统中的应用[J]. 军事通信技术,2017,38(1):83.