分布式锁
关于分布式锁,是指在分布式应用下多个客户端同时访问共享资源时候
保证 有且只能 有一个客户端持有一个锁(排他,独占)
比如在秒杀,抢购,红包场景下,多个用户需要对单一资源进行获取。
Redis 的 SETNX
锁:有上锁和解锁两个功能
在客户端访问时候,检查有没有上锁,没有先上锁。防止其他客户端进入。
if(redis->get("lock")) {
redis->set("lock", "locked")
}
那这样是否能保证唯一性呢 ? 不能
从 get
到 set
并没用保证原子性操作,这个期间内可以导致多个客户端进入。同时 :
set 如果 key 已经持有其他值, SET 就覆写旧值,无视类型
如果已经有多个客端进入,然后都进行 set
操作,结果都会是成功的,显然这样无法保证唯一性
SETNX
SETNX key value
将 key 的值设为 value ,当且仅当 key 不存在。
若给定的 key 已经存在,则 SETNX 不做任何动作。
SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。
SETNX 只会在当 key 不存在的时候进行操作,通过 SETNX 很好的解决了的痛点,把上面的代码修改一下:
if(redis->SETNX("lock", "locked")) {
//上锁成功
dosomething()
...
}
为了保证原子性,丢弃了 get
操作
解锁
解锁,如何解锁:
if(redis->SETNX("lock", "locked")) {
//上锁成功
dosomething()
...
redis->del("lock")
}
在处理完业务后执行 del
解锁
防止死锁
通过 del
手动解锁,一般情况下没什么问题,但是如果在 dosomething()
的过程中出现了异常,无法执行 del
。那么会导致 锁 一直存在,无法释放。所以我们希望的是在异常情况下,锁也能自动释放,保证后面的客户端再一次上锁。
再次回到 SET 命令
从 Redis 2.6.12 版本开始, SET 命令的行为可以通过一系列参数来修改:
- EX second :设置键的过期时间为 second 秒。 SET key value EX second 效果等同于 SETEX key second value 。
- NX :只在键不存在时,才对键进行设置操作。 SET key value NX 效果等同于 SETNX key value 。
…
通过 NX 保证了原子性, EX 过期时间来做自动释放。杜绝了死锁。
// 假设过期时间 3秒,可以根据自身业务设定
if(redis->SET("lock","locked","nx", "ex",3)) {
//上锁成功
dosomething()
...
redis->del("lock")
}
解锁的原子性和唯一
我们需要给锁生成唯一的值,而不是固定的(如上面的 locked
),防止非上锁客户端也能解锁。
// 可以通过 uuid 来给每一个锁生成唯一值
$locked = {uuid}
通过 $locked
来判断当前是否为 上锁客户端,是否有权限解锁
同时通过 redis 的 eval
函数来执行 lua 脚本,保证 “先对比后删除” 两个操作的原子性
$luaScript = "
if redis.call("get",KEYS[1]) == ARGV[1]
then
return redis.call("del",KEYS[1])
else
return 0
end
";
// 解锁
redis->eval($luaScript, 1, "lock", $locked);