大厂kafka实现延迟消息原理

192 阅读2分钟

业务背景与需求场景

在分布式系统中,延迟消息是支撑业务逻辑的重要能力,典型应用场景包括:

  • 订单管理:30分钟未支付自动关闭订单
  • 资金处理:红包24小时未领取自动退款
  • 物流系统:N天后自动确认收货

技术背景与挑战

Kafka作为高吞吐、分布式的消息系统,被广泛应用于互联网企业。然而其原生设计并未提供延迟消息支持,本文介绍了一种kafka过期缓存+定时扫描的延迟队列实现方案。

整体流程

image.png

  1. 消息发送阶段
    • Producer发送消息到延迟消息发送服务,携带延迟相对时间
    • 延迟消息发送服务计算到期时间:当前时间 + 延迟时间
    • 到期时间作为主key,消息体作为value,写入Tair缓存中
  2. 消息调度阶段
    • 延迟消息发送服务 Schedule定期扫描Tair:
      • 使用 range操作按当前时间戳获取到期消息
    • 集群节点取出消息发送给Broker

Tair的Key设计

缓存的主key为过期时间,副key是消息的唯一表示。通过range操作,扫描主key,可以很快查询出过期的消息。

Key类型组成要素作用描述
主Keytimestamp按时间戳进行range范围查询
副Keyuuid_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的数据。

参数含义如下:

参数类型说明
nsshort数据分区命名空间
pkeybyte[]主键前缀
beginbyte[]扫描起始位置(null表示首条)
endbyte[]扫描结束位置(null表示末条)
maxCountint单次最大获取数量

方案优势分析

高性能:时间分桶+批量扫描减少IO压力

高可靠:Tair持久化保证消息不丢失

精准控制:100ms级时间精度满足多数业务场景