分布式锁避坑

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锁更慢

  1. TTL必设--不能永久锁
  2. 续期必做--长任务必续
  3. 业务必幂等--锁失效业务要能恢复