锁机制

为什么需要分布式锁?

在分布式环境下,多个消费者可能同时获取同一个任务。

如果这个任务是“30 分钟未支付自动取消订单”,重复执行可能只是多写一条日志;但如果任务是“优惠券过期退款”“会员到期扣费”“发送到期提醒”,重复执行就会变成真实事故。延时队列的锁机制,本质上是在回答一个问题:同一时刻,谁有资格处理这条到期任务?

当前架构
分布式调度
多台调度器同时工作时,重点是分片、互斥、故障接管和重复执行控制。
调度集群
分片规则
领导者选举
心跳
互斥控制
任务锁
租约
幂等键
故障恢复
超时释放
任务迁移
补偿扫描

Redis 分布式锁

基本实现

Redis 锁适合短时间、低延迟的互斥控制。调度器领取任务前先拿锁,拿到锁才允许把任务从等待状态推进到执行状态。锁必须带过期时间,否则调度器崩溃后任务会永远卡住。

这类锁有三个关键边界:

  • 锁的粒度不能太粗,否则所有任务都在排队。
  • 锁的过期时间不能太短,否则任务没处理完锁就释放。
  • 解锁时必须确认自己仍然是持有者,避免误删别人的锁。

使用示例

读者不需要记具体命令,只要理解它的状态变化:任务从“可领取”变成“处理中”,锁就是这段状态变化的保护栏。保护栏的作用不是让系统永远不出错,而是把重复执行概率降到可接受范围。


数据库悲观锁

SKIP LOCKED

数据设计要点

  • 查询目标是快速定位状态、任务或资源,避免在关键路径上做大范围扫描。

数据库悲观锁适合任务本来就存在数据库里的场景。多个调度器同时扫描到期任务时,谁先锁住一批行,谁就处理这批任务;其他调度器跳过已锁定任务,继续找下一批。

它的优势是状态和锁都在数据库里,理解成本低。缺点是高并发下数据库压力会变大,因此更适合中低吞吐或中长延时任务,不适合把所有秒级任务都压到数据库里。

锁之外:幂等才是最后防线

锁只能减少重复执行,不能承诺永远没有重复。网络抖动、锁过期、主从延迟、消费者重启,都可能让同一任务被看到两次。因此真正可靠的延时队列,还要让业务处理本身具备幂等能力。

可以把锁理解为第一道门,把幂等理解为最后一道门。第一道门尽量不让重复任务进来,最后一道门保证就算重复进来了,也不会造成重复扣款、重复退款或重复通知。