redis.setNX(ctx, key, "1")
defer redis.del(ctx, key)
redis.setNX(ctx, key, "1",expiration)
defer redis.del(ctx, key)
redis.SetNX(ctx, key, randomValue, expiration)
defer redis.del(ctx, key, randomValue)
// 以下为del的lua脚本
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
// 在公司的redis-v6包已经支持cad,因而lua脚本可以精简为以下代码
// 公司的cas/cad命令见 扩展命令 - CAS/CAD/XDECRBY Extended commands - CAS/CAD/XDECRBY
redis.cad(ctx, key, randomValue)
获得锁和删除锁是一个协程,避免程序运行时间长时删除别的协程的锁,做到一定程度的一致性。
func myFunc() (errCode *constant.ErrorCode) {
errCode := DistributedLock(ctx, key, randomValue, LockTime)
defer DelDistributedLock(ctx, key, randomValue)
if errCode != nil {
return errCode
}
// doSomeThing
}
// 注意,以下代码还不能用cas优化,因为公司的redis-v6还不支持oldvalue是nil
func DistributedLock(ctx context.Context, key, value string, expiration time.Duration) (errCode *constant.ErrorCode) {
ok, err := redis.SetNX(ctx, key, value, expiration)
if err == nil {
if !ok {
return constant.ERR_MISSION_GOT_LOCK
}
return nil
}
// 应对超时且成功场景,先get一下看看情况
time.Sleep(DistributedRetryTime)
v, err := redis.Get(ctx, key)
if err != nil {
return constant.ERR_CACHE
}
if v == value {
// 说明超时且成功
return nil
} else if v != "" {
// 说明被别人抢了
return constant.ERR_MISSION_GOT_LOCK
}
// 说明锁还没被别人抢,那就再抢一次
ok, err = redis.SetNX(ctx, key, value, expiration)
if err != nil {
return constant.ERR_CACHE
}
if !ok {
return constant.ERR_MISSION_GOT_LOCK
}
return nil
}
// 以下为del的lua脚本
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
// 在公司的redis-v6包已经支持cad,因而lua脚本可以精简为以下代码
// redis.cad(ctx, key, randomValue)
func DelDistributedLock(ctx context.Context, key, value string) (errCode *constant.ErrorCode) {
v, err := redis.Cad(ctx, key, value)
if err != nil {
return constant.ERR_CACHE
}
return nil
}
解决了超时且成功的问题,写入超时且成功是偶现的,灾难性的问题。
还存在的问题:
启动定时器,锁过期,但是还没完成流程时,续租,只能续当前协程抢占的锁。
// 以下为续租的lua脚本,实现CAS(compare and set)
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("expire",KEYS[1], ARGV[2])
else
return 0
end
// 在公司的redis-v6包已经支持cas,因而lua脚本可以精简为以下代码
redis.Cas(ctx, key, value, value)
能保障锁过期的一致性,但是解决不了单点问题 同时,可以发散思考一下,如果续租的方法失败怎么办?我们如何解决“为了保证高可用而使用的高可用方法的高可用问题”这种套娃问题?开源类库Redisson使用了看门狗的方式一定程度上解决了锁续租的问题,但是这里,个人建议不要做锁续租,更简洁优雅的方式是延长过期时间,由于我们分布式锁锁住代码块的最大执行时长是可控的(依赖于RPC、DB、中间件等调用都设定超时时间),因而我们可以把超时时间设得大于最大执行时长即可简洁优雅地保障锁过期的一致性
如果 Redis 发生主从切换,主从切换如果数据丢失,并且丢失的数据和分布式锁有关系,会导致锁机制出现问题,引起业务异常。
Redis的主从同步(replication)是异步进行的,如果向master发送请求修改了数据后master突然出现异常,发生高可用切换,缓冲区的数据可能无法同步到新的master(原replica)上,导致数据不一致。如果丢失的数据跟分布式锁有关,则会导致锁的机制出现问题,从而引起业务异常。针对这个问题介绍两种解法: