redis-lock
redis lock
既然是锁,首先想到的一个作用就是:防重复点击,在一个时间点只有一个请求产生效果。
而既然是 redis
,就得具有排他性,同时也具有锁的一些共性:
- 高性能
- 不能出现死锁
- 不能出现节点down掉后加锁失败
go-zero
中利用 redis set key nx
可以保证key不存在时写入成功,px
可以让key超时后自动删除「最坏情况也就是超时自动删除key,从而也不会出现死锁」
example
redisLockKey := fmt.Sprintf("%v%v", redisTpl, headId)
// 1. New redislock
redisLock := redis.NewRedisLock(redisConn, redisLockKey)
// 2. 可选操作,设置 redislock 过期时间
redisLock.SetExpire(redisLockExpireSeconds)
if ok, err := redisLock.Acquire(); !ok || err != nil {
return nil, errors.New("当前有其他用户正在进行操作,请稍后重试")
}
defer func() {
recover()
// 3. 释放锁
redisLock.Release()
}()
和你在使用 sync.Mutex
的方式时一致的。加锁解锁,执行你的业务操作。
获取锁
lockCommand = `if redis.call("GET", KEYS[1]) == ARGV[1] then
redis.call("SET", KEYS[1], ARGV[1], "PX", ARGV[2])
return "OK"
else
return redis.call("SET", KEYS[1], ARGV[1], "NX", "PX", ARGV[2])
end`
func (rl *RedisLock) Acquire() (bool, error) {
seconds := atomic.LoadUint32(&rl.seconds)
// execute luascript
resp, err := rl.store.Eval(lockCommand, []string{rl.key}, []string{
rl.id, strconv.Itoa(int(seconds)*millisPerSecond + tolerance)})
if err == red.Nil {
return false, nil
} else if err != nil {
logx.Errorf("Error on acquiring lock for %s, %s", rl.key, err.Error())
return false, err
} else if resp == nil {
return false, nil
}
reply, ok := resp.(string)
if ok && reply == "OK" {
return true, nil
} else {
logx.Errorf("Unknown reply when acquiring lock for %s: %v", rl.key, resp)
return false, nil
}
}
先介绍几个 redis
的命令选项,以下是为 set
命令增加的选项:
ex seconds
:设置key过期时间,单位spx milliseconds
:设置key过期时间,单位毫秒nx
:key不存在时,设置key的值xx
:key存在时,才会去设置key的值
其中 lua script
涉及的入参:
args | 示例 | 含义 |
---|---|---|
KEYS[1] | key$20201026 | redis key |
ARGV[1] | lmnopqrstuvwxyzABCD | 唯一标识:随机字符串 |
ARGV[2] | 30000 | 设置锁的过期时间 |
然后来说说代码特性:
Lua
脚本保证原子性「当然,把多个操作在 Redis 中实现成一个操作,也就是单命令操作」- 使用了
set key value px milliseconds nx
value
具有唯一性- 加锁时首先判断
key
的value
是否和之前设置的一致,一致则修改过期时间
释放锁
delCommand = `if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end`
func (rl *RedisLock) Release() (bool, error) {
resp, err := rl.store.Eval(delCommand, []string{rl.key}, []string{rl.id})
if err != nil {
return false, err
}
if reply, ok := resp.(int64); !ok {
return false, nil
} else {
return reply == 1, nil
}
}
释放锁的时候只需要关注一点:
不能释放别人的锁,不能释放别人的锁,不能释放别人的锁
所以需要先 get(key) == value「key」
,为 true 才会去 delete