当前位置:首页 > 关于 Redis 的分布式锁

关于 Redis 的分布式锁

发布于 2018-12-28 阅读 2164 次 Redis

分布式锁

关于分布式锁,是指在分布式应用下多个客户端同时访问共享资源时候
保证 有且只能 有一个客户端持有一个锁(排他,独占)

比如在秒杀,抢购,红包场景下,多个用户需要对单一资源进行获取。

Redis 的 SETNX

锁:有上锁和解锁两个功能

在客户端访问时候,检查有没有上锁,没有先上锁。防止其他客户端进入。

  1. if(redis->get("lock")) {
  2. redis->set("lock", "locked")
  3. }

那这样是否能保证唯一性呢 ? 不能
getset 并没用保证原子性操作,这个期间内可以导致多个客户端进入。同时 :

set 如果 key 已经持有其他值, SET 就覆写旧值,无视类型

如果已经有多个客端进入,然后都进行 set 操作,结果都会是成功的,显然这样无法保证唯一性

SETNX

SETNX key value

将 key 的值设为 value ,当且仅当 key 不存在。

若给定的 key 已经存在,则 SETNX 不做任何动作。

SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。

SETNX 只会在当 key 不存在的时候进行操作,通过 SETNX 很好的解决了的痛点,把上面的代码修改一下:

  1. if(redis->SETNX("lock" "locked")) {
  2. //上锁成功
  3. dosomething()
  4. ...
  5. }

为了保证原子性,丢弃了 get 操作

解锁

解锁,如何解锁:

  1. if(redis->SETNX("lock" "locked")) {
  2. //上锁成功
  3. dosomething()
  4. ...
  5. redis->del("lock")
  6. }

在处理完业务后执行 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 过期时间来做自动释放。杜绝了死锁。

  1. // 假设过期时间 3秒,可以根据自身业务设定
  2. if(redis->SET("lock""locked""nx", "ex"3)) {
  3. //上锁成功
  4. dosomething()
  5. ...
  6. redis->del("lock")
  7. }

解锁的原子性和唯一

我们需要给锁生成唯一的值,而不是固定的(如上面的 locked),防止非上锁客户端也能解锁。

  1. // 可以通过 uuid 来给每一个锁生成唯一值
  2. $locked = {uuid}

通过 $locked 来判断当前是否为 上锁客户端,是否有权限解锁

同时通过 redis 的 eval 函数来执行 lua 脚本,保证 “先对比后删除” 两个操作的原子性

  1. $luaScript = "
  2. if redis.call("get",KEYS[1]) == ARGV[1]
  3. then
  4. return redis.call("del",KEYS[1])
  5. else
  6. return 0
  7. end
  8. ";
  9. // 解锁
  10. redis->eval($luaScript, 1, "lock", $locked);