业务背景与需求场景
在分布式系统中,延迟消息是支撑业务逻辑的重要能力,典型应用场景包括:
- 订单管理:30分钟未支付自动关闭订单
- 资金处理:红包24小时未领取自动退款
- 物流系统:N天后自动确认收货
技术背景与挑战
Kafka作为高吞吐、分布式的消息系统,被广泛应用于互联网企业。然而其原生设计并未提供延迟消息支持,本文介绍了一种kafka过期缓存+定时扫描的延迟队列实现方案。
整体流程
- 消息发送阶段
- Producer发送消息到延迟消息发送服务,携带延迟相对时间
- 延迟消息发送服务计算到期时间:
当前时间 + 延迟时间 - 将
到期时间作为主key,消息体作为value,写入Tair缓存中
- 消息调度阶段
- 延迟消息发送服务 Schedule定期扫描Tair:
- 使用
range操作按当前时间戳获取到期消息
- 使用
- 集群节点取出消息发送给Broker
- 延迟消息发送服务 Schedule定期扫描Tair:
Tair的Key设计
缓存的主key为过期时间,副key是消息的唯一表示。通过range操作,扫描主key,可以很快查询出过期的消息。
| Key类型 | 组成要素 | 作用描述 |
|---|---|---|
| 主Key | timestamp | 按时间戳进行range范围查询 |
| 副Key | uuid_index | 消息唯一性标识 |
消息的到期发送
延迟任务已经按到期时间、对应的任务列表存好了,那怎么知道有没有到期呢?没错,答案就是轮询了。 由单线程不断轮询,每100ms递增时间戳为一个桶,具体获取桶中的任务和执行由另一个线程池进行。
获取任务与执行
通过轮询确定timestamp后,即拿到pkey,可通过tair getRange接口将该key下的全部value取出,取出对应任务后,通过mafka的producer发送到broker集群。
Result<List<Pair<byte[], Result<byte[]>>>> getRange(short ns, byte[] pkey, byte[] begin, byte[] end, int offset, int maxCount, TairOption opt)
获取prefix为前缀的数据: 获取以prefix为前缀,子key大于等于key_start, 小于等于key_end范围中从offset个开始最多maxCount个KV的数据。
参数含义如下:
| 参数 | 类型 | 说明 |
|---|---|---|
| ns | short | 数据分区命名空间 |
| pkey | byte[] | 主键前缀 |
| begin | byte[] | 扫描起始位置(null表示首条) |
| end | byte[] | 扫描结束位置(null表示末条) |
| maxCount | int | 单次最大获取数量 |
方案优势分析
高性能:时间分桶+批量扫描减少IO压力
高可靠:Tair持久化保证消息不丢失
精准控制:100ms级时间精度满足多数业务场景