高性能与强一致性:Redis + Databus 构建分布式任务系统


在分布式架构中,如何高效且可靠地分发、处理海量异步任务一直是核心难题。单纯依赖数据库会产生性能瓶颈,而单纯依赖内存队列又容易丢失数据。RedisDatabus(如 LinkedIn Databus, Maxwell 或 Canal 等基于 CDC 的组件)的结合,为这一问题提供了一个“性能”与“可靠”并重的黄金方案。


1. 核心角色:速度与记录的平衡

在这一架构中,Redis 和 Databus 分别扮演了不同的角色:

  • Redis(速度层/调度中心)
    • 利用 ListStream 作为任务队列,提供微秒级的入队/出队性能。
    • 利用 ZSet 实现延迟任务(如订单超时取消)。
    • 利用分布式锁(Redlock)确保同一个任务不会被多个节点同时领取。
  • Databus(可靠层/变更追踪)
    • CDC (Change Data Capture):监听主数据库(如 MySQL)的 Binlog。
    • 负责将数据库的每一个变更实时同步到任务执行器或缓存中。
    • 即使 Redis 发生崩溃,Databus 也可以通过重播(Replay)日志来恢复任务状态。

2. 典型架构流程

一个典型的“订单处理”任务系统流程如下:

  1. 持久化入库:业务请求进入,首先在 MySQL 中创建一条“待处理”订单记录(状态为 PENDING)。
  2. Databus 捕获:Databus 实时监测到这条 INSERT 操作,将其封装为一个“创建任务”事件。
  3. 推送到 Redis
    • Databus 消费者接收到事件,将任务 ID 推入 Redis 的 List 队列。
    • 或者将该任务放入 Redis ZSet,Score 设置为“当前时间 + 延迟时间”。
  4. 分布式处理
    • 多个 Worker 节点并发从 Redis 中 RPOPXREAD 任务。
    • Worker 领取任务后,先在 Redis 设置一个“处理中”标记(带过期时间),防止死锁。
  5. 状态回写
    • 任务完成后,Worker 更新数据库中该记录的状态为 SUCCESS
    • Databus 再次捕获到该 UPDATE,并清理 Redis 中对应的任务缓存。

3. 为什么需要 Databus?(对比传统方案)

很多开发者会问:“为什么不直接由业务代码写完 DB 后,顺便把任务发到 Redis?”

这种“双写”方案存在严重风险:

  • 非原子性:如果 DB 写成功了,但 Redis 写入失败(网络抖动或 Redis 满载),这个任务就彻底“丢了”。
  • 事务不一致:如果 DB 事务最后回滚了,但 Redis 里的任务已经发出去了,会导致业务逻辑错误。

Databus 的优势在于它基于数据库日志(Binlog)。只要数据库事务提交了,Binlog 就一定存在,任务也就一定会被捕获并最终送达 Redis,实现了最终一致性


4. 关键技术细节

4.1 解决 Redis 任务丢失:Ack 机制

使用 Redis Stream 时,利用 XREADGROUPPEL (Pending Entries List) 机制。Worker 处理完后显式调用 XACK。如果 Worker 崩溃,监控脚本可以将 PEL 中超时的任务重新分配给其他 Worker。

4.2 应对大流量:分片与多租户

当任务量达到千万级,单机 Redis 会成为瓶颈。可以根据业务维度(如 tenant_iduser_id)进行 Hash,将任务分散到不同的 Redis 集群分片中。

4.3 幂等性设计

由于分布式环境下任务可能因为重试而多次触发,Worker 必须具备幂等性

  • 做法:在执行业务逻辑前,检查数据库状态。如果订单已是 PROCESSED 状态,直接跳过。

5. 总结:性能 vs. 柔性

Redis + Databus 的组合本质上是将“高性能队列”作为前端执行器,将“数据库日志”作为兜底存储

  • Redis 解决了“快”的问题,让 Worker 能够低延迟地获取任务。
  • Databus 解决了“准”的问题,确保了任务流转与业务数据状态的绝对同步。

这种架构非常适合于订单系统、支付异步通知、大规模站内信推送以及复杂的供应链状态流转等对可靠性要求极高的场景。


延伸阅读

  • LinkedIn Databus 官方白皮书
  • Redis Streams 入门指南
  • MySQL Binlog 与 CDC 最佳实践