Kafka 原生不支持延迟队列功能。而RabbitMQ、RocketMQ及Redis等其他消息队列原生支持延迟队列。
RabbitMQ | RocketMQ | Redis | |
实现方式 | 通过插件实现,消息进入延迟队列后根据配置时间过滤转发。 | 原生支持,发送消息时设置延迟级别,定时任务处理到期消息。 | 通过sorted set实现,消息按延迟时间存储。 |
特点 | 优点:易于部署使用,支持消息重试和顺序处理。 缺点:性能较低,不适合高并发场景。 | 优点:高性能、支持分布式和消息持久化。 缺点:不支持动态添加/删除队列。 | 优点:性能高,支持高并发。 缺点:消息未经持久化,存在丢失风险。 |
适用场景 | 中小型任务调度和消息通知。 | 大规模数据处理,高性能要求场景。 | 轻量级任务调度和短期延迟任务。 |
表 RabbitMQ、RocketMQ及Redis实现延迟方案对比
延迟队列的其他实现方式:
1) 数据库 + 定时任务。
实现:将消息存储到数据库并记录目标执行时间,定时任务轮询数据库,将到期的消息取出并消费。
优点:延迟时间精准控制,可靠性高。
缺点:依赖数据库,性能受限。
适用场景:延迟时间不固定,消息量不大。
2) Redis ZSet方案
实现:利用Redis有序集合(ZSet),以消息的执行时间作为score,消费者定时轮询到期消息。
优点:基于内存,性能高。
缺点:消息可能丢失。
1 Kafka + 时间轮 + 数据库实现延迟队列
实现:创建一个延迟队列Topic,将需要延迟的消息发送到这里。该Topic由一个专门的消费者处理。通过时间轮将读取的延迟队列消息存储并在消息的期望执行时间将消息再发送到目标Topic。
图 Kafka + 时间轮实现延迟队列示例图
1.1 时间轮可靠性保证
方案的关键在于时间轮,时间轮是在内存中的数据结构,除了需要保证消息的准时性,还需要可靠性,即当项目重启后,原先在时间轮中的消息不能丢失。
当消息进入时间轮时,同步将这个消息存储到数据库中,状态为待完成,当这条消息被处理后,标注为已完成。
当系统重启时,从数据库中获取待处理的任务,并把它们放入到时间轮中。
1.2 角色职责及数据结构
表 Kafka + 时间轮 + 数据库实现延迟队列
延迟消息:指定执行日期的消息,包含字段:id、执行日期、目标topic、状态(待执行、执行完成、执行失败)、目标参数。
延迟消息生产者:业务代码中延迟任务的产出源头。负责将延迟消息发送给Kafka的延迟队列Topic。
延迟队列Topic消费者:消费Kafka 延迟队列Topic的消息,将待执行的消息放入到时间轮中,同时将其持久化到数据库中。
时间轮:负责延迟消息的调度,将要执行的消息发送到Kafka的目标Topic中。
目标Topic消费者:消费Kafka目标Topic的消息,并根据消息的id,来更新其在数据库中的状态。