1. 锁必须有TTL
golang
// 错误
redis.Set("lock", 1) // 永远不能释放
// 正确
redis.SetNX("lock", uuid, 5 * time.Minute)
2. 长时间任务必须续期
golang
go refreshLock(ctx, lock, 1 * time.Minute)
defer canceRefresh()
3. 释放锁要校验身份
shell
-- 错误
DEL Lock
-- 正确
if GET(lock) == myuuid then DEL(locl)
4. 业务侧必须有幂等
golang
// 锁只是辅助,业务必须幂等
// 锁可能失效,业务不能依赖锁
func Pay(orderID string) error {
// 锁
if !tryLock(orderId) {
return errors.New("processing")
}
defer unlock(orderId)
// 业务幂等(正确性保证)
if isPaid(orderID) {
return nil // 已经支付过了
}
return doPay(orderID)
}
5. 别在锁里sleep
golang
// 错误 锁在sleep 10秒
lock.lock()
time.Sleep(10 * time.Second)
process()
lock.Unlock()
// 正确
time.Sleep(10 * time.Second)
lock.Lock()
process()
lock.Unlock()
6. 锁粒度要细
golang
// 错误全局锁
lock.Lock('"global"')
process(userID)
// 正确 per-key锁
lock.Lock(fmt.Sprintf("user:%d", UserID))
process(userID)
7. 误区
-
锁防重 = 锁安全
锁防重 != 数据安全
锁可能失效,业务必须幂等 -
锁粒度越粗越好
全局锁:所有用户串行
应该: per-user锁 -
锁的TTL越长越好
反例:TTL设置1个小时,中途崩溃,1个小时才能抢锁
应该:TTL略大于任务时间 + 续期 -
锁=性能杀手
反例:能不用锁就不用,
并发写共享资源,用锁比不用锁快10倍
不锁的话,DB锁更慢
- TTL必设--不能永久锁
- 续期必做--长任务必续
- 业务必幂等--锁失效业务要能恢复