Redis 分布式锁
多实例部署下跨进程互斥(定时任务防重跑、库存扣减串行化等)。实现要点:
- 获取:
SET ace:lock:<key> <token> NX PX <ttlMs>——原子的"不存在才写 + 带过期" - 释放:compare-and-del Lua 脚本原子执行"值等于本次 token 才删除",只有持有者能释放,不会误删他人锁
- token:每次获取生成 16 字节随机数的 32 位 hex,作为持有凭证
- TTL 兜底:持有者崩溃不释放时锁随 TTL 自动过期,不会死锁
用法
cangjie
@Inject
var locks: RedisLockManagerwithLock(推荐)
获取锁后执行 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 | 轮询等待,超时返回 None;waitMs <= 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 多数派):主从切换的极端窗口内可能出现双持有,涉及资金等强一致场景需在业务层加幂等/对账兜底。