在分布式锁的应用中,常常通过redis 实现分布式锁,但是遇到一些问题,记录一下。
- 锁释放问题
1
2
3
4
5
6
|
// 获取锁操作
if redis.setnx(lockKey, "lock") != 1 { // 获取锁成功后会继续执行
return errors.New("获取锁失败")
}
// business logic ...
|
问题: 业务逻辑执行完之后锁没有释放,导致后续请求都获取不到锁,导致业务逻辑执行失败。
解决: 在业务逻辑执行完之后,再释放锁。
- 死锁问题
1
2
3
4
5
6
7
8
|
// 获取锁操作
if redis.setnx(lockKey, "lock") != 1 { // 获取锁成功后会继续执行
return errors.New("获取锁失败")
}
// business logic ...
redis.del(lockKey) // 释放锁操作失败,导致所没有释放
|
问题: 获取锁成功后,业务逻辑执行完之后,释放锁失败,导致锁没有释放,导致后续请求都获取不到锁,导致业务逻辑执行失败。
解决方案:获取锁之后,设置锁的过期时间,防止锁没有释放,导致锁无法释放。
1
2
3
4
5
6
7
8
9
10
|
// 获取锁操作
if redis.setnx(lockKey, "lock") != 1 { // 获取锁成功后会继续执行
return errors.New("获取锁失败")
}
redis.expire(lockKey, 30) // 设置过期时间,若释放锁失败,则锁会自动释放
// business logic ...
redis.del(lockKey)
|
- 锁的生命周期设置失败
1
2
3
4
5
6
|
// 获取锁操作
if redis.setnx(lockKey, "lock") != 1 { // 获取锁成功后会继续执行
return errors.New("获取锁失败")
}
redis.expire(lockKey, 30) // 设置失败
|
问题:设置有效期时 redis 出现宕机,锁无法释放,导致锁变成死锁。
解决方案:使用 lua 脚本,可以保证 获取锁和设置有效期的原子性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// 加锁
key := "lock"
value := uuid.New()
cmd := redis.NewScript(`
if (redis.call("setnx", KEYS[1], ARGV[1]) == 1) then
return redis.call("expire", KEYS[1], ARGV[2]);
else
return 0;
end
`).Run(ctx, rds.rdb, []string{key}, value, exp)
if ok, _ := cmd.Int64(); ok != 1 {
return errors.New("set lock failed")
}
// business logic
|
- 并发锁被其他进程释放
问题:当进程A业务逻辑还没有执行完,锁已经结束生命周期(expire已过期),此时操作锁释放,其他进程B可以得到锁,在B还没有执行完时,A先执行完,并手动释放锁,此时A进程释放的是B进程的锁。
解决方案:释放锁时,要判断锁是否属于自己,若不是,则不释放,可以将锁的value 设置为uuid,如果是当前进程的uuid,则可以释放。否则不释放。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
key := "lock"
value := uuid.New()
// 加锁 ...
// business logic ...
// 释放锁
cmd := redis.NewScript(`
if (redis.call("get", KEYS[1] ) == ARGV[1]) then
return redis.call("del", KEYS[1]);
else
return 0;
end
`).Run(ctx, rds.rdb, []string{key}, value)
if ok, _ := cmd.Int64(); ok != 1 {
fmt.Printf("del lock error, err: %v\n", cmd.Err())
return errors.New("del lock failed")
}
return nil
|