拜托,面试官不要在问我Redis分布式锁原理了

  • ⼀、写在前⾯
  • ⼆、Redisson 实现 Redis 分布式锁的底层原理
    (1)加锁机制
    (2)锁互斥机制
    (3)watch dog ⾃动延期机制
    (4)可重⼊加锁机制
    (5)锁释放机制
    (6)此种⽅案 Redis 分布式锁的缺陷

⼀、写在前面

现在⾯试,⼀般都会聊聊分布式系统这块的东⻄。通常⾯试官都会从服务框架(Spring Cloud、Dubbo)聊起,⼀路聊到分布式事务、分布式锁、ZooKeeper 等知识。

所以咱们这篇⽂章就来聊聊分布式锁这块知识,具体的来看看 Redis 分布式锁的实现原理。

说实话,如果在公司⾥落地⽣产环境⽤分布式锁的时候,⼀定是会⽤开源类库的,⽐如 Redis分布式锁,⼀般就是⽤ Redisson 框架就好了,⾮常的简便易⽤。
⼤家如果有兴趣,可以去看看 Redisson 的官⽹,看看如何在项⽬中引⼊ Redisson 的依赖,然后基于 Redis 实现分布式锁的加锁与释放锁。

下⾯给⼤家看⼀段简单的使⽤代码⽚段,先直观的感受⼀下:


```java
RLock rlock = redission.getLock("mylock");
rlock.lock();
rlock.unlock();

怎么样,上⾯那段代码,是不是感觉简单的不⾏!
此外,⼈家还⽀持 redis 单实例、redis 哨兵、redis cluster、redis master-slave 等各种部署架构,都可以给你完美实现。

⼆、Redisson 实现 Redis 分布式锁的底层原理

拜托,面试官不要在问我Redis分布式锁原理了_第1张图片

  • (1)加锁机制
    咱们来看上⾯那张图,现在某个客户端要加锁。如果该客户端⾯对的是⼀个 redis cluster 集群,他⾸先会根据 hash 节点选择⼀台机器。
    这⾥注意,仅仅只是选择⼀台机器!这点很关键!
    紧接着,就会发送⼀段 lua 脚本到 redis 上

为啥要⽤ lua 脚本呢?
因为⼀⼤坨复杂的业务逻辑,可以通过封装在 lua 脚本中发送给 redis,保证这段复杂业务逻辑⾏的原⼦性。
那么,这段 lua 脚本是什么意思呢?

KEYS[1] 代表的是你加锁的那个 key,⽐如说:
RLock lock = redisson.getLock(“myLock”);

这⾥你⾃⼰设置了加锁的那个锁 key 就是 “myLock”。

ARGV[1] 代表的就是锁 key 的默认⽣存时间,默认 30 秒。
ARGV[2] 代表的是加锁的客户端的 ID,类似于下⾯这样:

8743c9c0-0795-4907-87fd-6c719a6b4586:1
给⼤家解释⼀下,第⼀段 if 判断语句,就是⽤ “exists myLock” 命令判断⼀下,如果你要加锁的那个锁 key 不存在的话,你就进⾏加锁。
如何加锁呢?很简单,⽤下⾯的命令:

hset myLock 8743c9c0-0795-4907-87fd-6c719a6b4586:1 1

上述就代表 “8743c9c0-0795-4907-87fd-6c719a6b4586:1” 这个客户端对 “myLock” 这个锁key 完成了加锁。

接着会执⾏ “pexpire myLock 30000” 命令,设置 myLock 这个锁 key 的⽣存时间是 30 秒

  • (2)锁互斥机制
    那么在这个时候,如果客户端 2 来尝试加锁,执⾏了同样的⼀段 lua 脚本,会咋样呢?
    很简单,第⼀个 if 判断会执⾏ “exists myLock” ,发现 myLock 这个锁 key 已经存在了。
    接着第⼆个 if 判断,判断⼀下,myLock 锁 key 的 hash 数据结构中,是否包含客户端 2 的 ID,
    但是明显不是的,因为那⾥包含的是客户端 1 的 ID。
    所以,客户端 2 会获取到 pttl myLock 返回的⼀个数字,这个数字代表了 myLock 这个锁
    key 的剩余⽣存时间。⽐如还剩 15000 毫秒的⽣存时间。
    此时客户端 2 会进⼊⼀个 while 循环,不停的尝试加锁。

  • (3)watch dog ⾃动延期机制
    客户端 1 加锁的锁 key 默认⽣存时间才 30 秒,如果超过了 30 秒,客户端 1 还想⼀直持有这把锁,怎么办呢?
    简单!只要客户端 1 ⼀旦加锁成功,就会启动⼀个 watch dog 看⻔狗,他是⼀个后台线程,会每隔 10 秒检查⼀下,如果客户端 1 还持有锁 key,那么就会不断的延⻓锁 key 的⽣存时间。

(4)可重⼊加锁机制
那如果客户端 1 都已经持有了这把锁了,结果可重⼊的加锁会怎么样呢?

第⼀个 if 判断肯定不成⽴, “exists myLock” 会显示锁 key 已经存在了。
第⼆个 if 判断会成⽴,因为 myLock 的 hash 数据结构中包含的那个 ID,就是客户端 1 的那个ID,也就是 “8743c9c0-0795-4907-87fd-6c719a6b4586:1”此时就会执⾏可重⼊加锁的逻辑,他会⽤:incrby myLock 8743c9c0-0795-4907-87fd-6c71a6b4586:1 1通过这个命令,对客户端 1 的加锁次数,累加 1。

  • (5)锁释放机制
    如果执⾏ lock.unlock() ,就可以释放分布式锁,此时的业务逻辑也是⾮常简单的。
    其实说⽩了,就是每次都对 myLock 数据结构中的那个加锁次数减 1。
    如果发现加锁次数是 0 了,说明这个客户端已经不再持有锁了,此时就会⽤:
    “del myLock” 命令,从 redis ⾥删除这个 key。
    然后呢,另外的客户端 2 就可以尝试完成加锁了。
    这就是所谓的分布式锁的开源 Redisson 框架的实现机制。
    ⼀般我们在⽣产系统中,可以⽤ Redisson 框架提供的这个类库来基于 redis 进⾏分布式锁的加锁与释放锁。

  • (6)此种⽅案 Redis 分布式锁的缺陷
    其实上⾯那种⽅案最⼤的问题,就是如果你对某个 redis master 实例,写⼊了 myLock 这种锁key 的 value,此时会异步复制给对应的 master slave 实例。
    但是这个过程中⼀旦发⽣ redis master 宕机,主备切换,redis slave 变为了 redis master。
    接着就会导致,客户端 2 来尝试加锁的时候,在新的 redis master 上完成了加锁,⽽客户端 1也以为⾃⼰成功加了锁。
    此时就会导致多个客户端对⼀个分布式锁完成了加锁。
    这时系统在业务语义上⼀定会出现问题,导致各种脏数据的产⽣。
    所以这个就是 redis cluster,或者是 redis master-slave 架构的主从异步复制导致的 redis 分布式
    锁的最⼤缺陷:在 redis master 实例宕机的时候,可能导致多个客户端同时完成加锁。

下一篇我们聊聊 zookeeper实现分布式锁

你可能感兴趣的:(BAT大厂面试必问系列,Redis原理专栏,分布式,redis)