Skip to content

Redis 分布式锁

多实例部署下跨进程互斥(定时任务防重跑、库存扣减串行化等)。实现要点:

  • 获取SET ace:lock:<key> <token> NX PX <ttlMs>——原子的"不存在才写 + 带过期"
  • 释放:compare-and-del Lua 脚本原子执行"值等于本次 token 才删除",只有持有者能释放,不会误删他人锁
  • token:每次获取生成 16 字节随机数的 32 位 hex,作为持有凭证
  • TTL 兜底:持有者崩溃不释放时锁随 TTL 自动过期,不会死锁

用法

cangjie
@Inject
var locks: RedisLockManager

withLock(推荐)

获取锁后执行 body,无论 body 是否抛异常都在 finally 中释放:

cangjie
let ok = locks.withLock("job:daily-report", 30000, {=>
    generateReport()
    ()   // body 类型为 () -> Unit,末尾表达式非 Unit 时补 ()
})
if (!ok) {
    // 锁被其它实例持有,body 未执行
}

tryLock / lock / unlock

cangjie
// 尝试一次:成功 Some(RedisLock),被占 None
match (locks.tryLock("inventory:sku42", 5000)) {
    case Some(l) =>
        try { deduct() } finally { locks.unlock(l) }
    case None => ()   // 抢锁失败
}

// 阻塞获取:轮询(50ms 步进)直到拿到或累计等待超 waitMs
match (locks.lock("inventory:sku42", 5000, 2000)) {
    case Some(l) => ...
    case None => ...  // 2 秒内没等到
}

unlock(lock): Bool——true 释放成功;false 表示锁已 TTL 过期或已被他人持有(token 不匹配),此时无需也无法补救。

API 参考

方法签名说明
tryLock(key: String, ttlMs: Int64): ?RedisLock非阻塞尝试一次
lock(key: String, ttlMs: Int64, waitMs: Int64): ?RedisLock轮询等待,超时返回 NonewaitMs <= 0 等效 tryLock
unlock(lock: RedisLock): Bool原子释放(compare-and-del)
withLock(key: String, ttlMs: Int64, body: () -> Unit): Bool拿到执行 + finally 释放;拿不到返回 false 不执行

RedisLock 携带 key(含 ace:lock: 前缀的完整键)与 token,仅由 Manager 构造。

使用注意

  • TTL 要大于临界区耗时:ace-redis 不做 watchdog 自动续期,body 执行超过 TTL 时锁会提前过期、他人可进入。宁可放大 TTL(崩溃场景多等一会),不要压着执行时间设。
  • 锁键即业务互斥粒度"inventory:" + skuId 按商品互斥;全局串行才用固定键。
  • 这是单节点 Redis 锁(非 RedLock 多数派):主从切换的极端窗口内可能出现双持有,涉及资金等强一致场景需在业务层加幂等/对账兜底。

基于 Apache-2.0 许可证发布