作者简介
Pin,关注 RPC、Service Mesh、Serverless 等云原生技术。
一、背景
随着上云项目的不断推进,大量的应用需要部署到 aws 上,其中有很多应用都依赖延迟队列的功能。而在 aws 上,我们选择以 Kafka 作为消息队列,但是 Kafka 本身不支持延迟队列,这就需要思考如何基于 Kafka 来实现延迟队列。
二、需求
统计了一下所有需要使用到延迟队列的场景,有以下几大特点:
延迟时间不固定。有的 topic 需要支持 5 分钟的延迟,有的却要求支持 7 天的延迟。
延迟消息数量小。所有的场景中涉及到的每天延迟消息的数量不超过 1 亿条,每条消息的大小不超过 1MB。
延迟消息不能丢失,可以不保证有序。
延迟误差小。延迟误差是指实际消费消息的时间和希望消费消息之间的时间差值。根据统计的业务场景来看,要求延迟误差在 2s 以内。
生产延迟消息的峰值比较高。很多情况下,业务会一次性创建 1000 万条延迟消息,并且这些延迟消息的延迟时长都是一致的。
三、目标
由于实现延迟队列的方式有很多,我们在满足需求的前提下,制定了几个目标:云上成本低、运维成本低、开发成本低、稳定性高和延迟误差小。
四、产品选型
在 aws 上支持消息队列的产品有 RabbitMQ、Apache ActiveMQ 和 SQS。其中 RabbitMQ 和 Apache ActiveMQ aws 主要是托管其安装部署,并非是以 Serverless 的方式对外提供服务。另外,我们当前已经选择使用 Kafka 作为消息队列,若仅仅为了满足延迟队列的功能而去更换消息队列,成本显然是巨大的。
除此之外,aws 还提供了 SQS 来支持延迟队列,虽然 SQS 是 Serverless 的,但是 SQS 有他自身的局限性:SQS 最多支持 15 分钟以内的延迟,明显无法满足我们的需求。
可见,仅仅基于云上已有的产品已无法满足我们的需求,基于这个原因,我们开始调研延时消息的实现方案,看看能否通过少量的开发来实现我们的需求。
五、方案调研
业界实现延时队列功能的方案比较多,我们对其进行了简单的分析,具体如下:
5.1 RabbitMQ
RabbitMQ 是基于 TTL+ 死信队列的方式来实现的。具体来说,通过设置消息的 TTL,当达到 TTL 时消息还没有被消费,此时会投递到死信队列。TTL 分两种:
Queue 级别的 TTL:所有消息统一的 TTL
Message 级别的 TTL:每条消息可以是不同的 TTL,但是存在队头阻塞问题
该方案的优点是实现简单,但是延迟误差不确定。
5.2 Apache ActiveMQ
Apache ActiveMQ 是基于定时调度的方式来实现的。具体来说,配置延迟时间或者 cron 表达式表示消息的投递策略,基于 Java 的 Timer 实现,将消息分级存储在文件和内存中。
该方案的优点是实现简单,延迟误差可控,但是可能会占用大量内存。
5.3 RocketMQ
RocketMQ 是基于定时调度+延迟等级的方式来实现的。具体来说,将延时消息发送到指定的延时等级队列(一共有 18 个等级),然后通过一个定时器进行轮询这些 ConsumeQueue 实现延时的效果。具体实现如下:
修改消息 topic 名称和队列信息投递到对应等级的延时消息的 ConsumeQueue 中
ScheduleMessageService消费ConsumeQueue中的消息再重新投递到 CommitLog 中
将 CommitLog 中的消息投递到目标 topic 中,消费者消费目标 topic 中的消息
该方案的优点是延迟误差可控,但是实现复杂。
5.4 Redis
基于 Redis 实现延迟队列的方式有很多,在这里简单描述两种:
1)定时轮询
该方案的大致步骤如下:
将消息的