「面试」Redis实现分布式锁这一篇就够了

每日一问

灵魂拷问之☞用电鳗的电去电电鳗会电到电鳗吗?
各位巨佬们把答案留在评论区吧
上车上车

分布式锁简介

是一种用来解决多个执行线程,访问共享资源时出现错误或数据不一致问题的工具。
在JAVA中可以使用synchronized关键字或者使用java.util.concurrent.locks.Lock的实现类,例如java.util.concurrent.locks.ReentrantLock
在分布式、微服务的架构体系下,不能再使用相关的关键字或者锁的实现类来达到串行执行,相关的关键字或者锁的实现类只能适用于单机的情况下,需要在一个JVM体系内才能实现锁,这个时候他就出现了,不知道分布式锁这个名字是谁命名的。好家伙,那么该怎么样实现分布式锁呢?

应用场景

1.避免不同节点重复的执行某一块逻辑,比如说我们的定时任务,不加锁的情况下在某一时间点这个定时任务可能会执行多次
2.防止表单重复提交
3.避免破坏数据的正确性,当多个线程同时某一块逻辑时,可能会出现数据的错乱或者不一致

JAVA实现

1.基于Redis,不熟悉Redis的可以看「摩擦面试官」Redis这一篇就够了,也是今天的主题
2.基于数据库,一般数据都有实现悲观锁,当然也可以用版本号或者其他乐观锁来实现,悲观锁的话主要还是for update 关键字
3.基于Zookeeper,原理就是利用了Zookeeper的临时节点来实现。
推荐的话使用Zookeeper来实现分布式锁,主要是因为Zookeeper相关的特性非常适合用来做分布式锁,后续再来讲这一块的东西。当然,前提是项目中有使用到Zookeeper,不然就光为了实现一个分布式锁而搭建一套Zookeeper的集群那就得不偿失了。redis则不一样,基本上的项目都会使用,当时使用redis会有一些问题。下面就进入正题吧

Redis实现原理

使用Redis实现分布式锁主要是运用了Redis的SETNX(SET if Not eXists) 指令和lua脚本来实现,那为什么要用到lua脚本呢?
原因只要是我们要给锁加一个过期时间并没有一个API能提供,一般都需要拆分成两个步骤,第一个将key存入Redis,第二部给key设置过期时间,这个时候就会导致线程不安全性。因为 Lua 脚本可以保证多个指令的原子性执行(单线程基于I\O多了复用和对列执行命令)。
RedisLockRegistry lua脚本
KEYS[1]代表当前锁的key值,ARGV[1]代表当前的客户端标识,ARGV[2]代表过期时间。
基本逻辑就是拿着KEYS[1]去redis中获取值,如果值等于ARGV[1]就表示这条数据已经被上锁了,并且延长锁的过期时间,如果想要获取锁锁就要等待拿到锁的进程释放锁;如果这个键KEYS[1]不存在,那么设置KEYS[1]的值为ARGV[1],并且设置过期时间为ARGV[2],即当前进程就获取到这个数据的锁,并设置过期时间。从而达到原子命令的效果

问题点

锁超时

假设现在我们有的服务A和B,其中 A 服务在 获取锁之后由于某种原因挂了,那么 B 服务就永远无法获取到锁了,这个时候就需要加一个超时时间来释放锁,但是加了超时时间后又出现一个新的问题就是如果在加锁和释放锁之间的逻辑执行得太长,以至于超出了锁的超时限制,这时锁已经被释放了,但是实际上执行逻辑还没有处理完,可能出现多个线程同时执行的情况。
解决方案:
  1.不解决,不要打我(手动狗头),解决不了我就加入他, 也就是尽量避免。Redis 分布式锁不要用于较长时间的任务。如果真的偶尔出现了问题,造成的数据小错乱可能就需要人工的干预。
  2.安全点的方式手动释放锁,将锁的 value 值设置为一个随机数,释放锁时先匹配随机数是否一致,然后再删除 key,这是为了确保当前线程占有的锁不会被其他线程释放,除非这个锁是因为过期了而被服务器自动释放的。这种方式也会存在问题 ,因为匹配value和删除key在 Redis 中并不是一个原子性的操作,所以也是不安全的,当然可以使用lua脚本来保证。
  3.使用Redisson框架,原理我们下面会提到,好哥哥们不用着急

单点/多点问题

如果 Redis 采用单机部署模式,那就意味着当 Redis 故障了,就会导致整个服务不可用。而如果采用主从模式部署,我们想象一个这样的场景:服务 A 申请到一把锁之后,如果作为主机的 Redis 宕机了,那么 服务 B 在申请锁的时候就会从从机那里获取到这把锁,为了解决这个问题,Redis 作者提出了一种 RedLock 红锁 的算法

Redisson实现原理

1.加锁机制:就是上面提到的SETNX(SET if Not eXists)指令和lua脚本来实现
2.watch dog自动延期机制,就是会启动一个后台线程,定时比如说10秒检测当前线程是否还持有了锁,是的话就延期。
3.可重入性,Redis存储锁的数据类型是 Hash类型并且Hash数据类型的key值包含了当前线程信息。
4.互斥性,通过Redis数据结构来保证分布式锁的唯一性
5.避免死锁,通过Redis的超时时间保证
redisson执行原理

Redisson的运用

Redisson实战

Redisson存在的问题

1.当Redis做的是Cluster集群的情况下,如果客户端对某个master节点写入了Redisson锁,此时会异步复制给对应的 slave节点。但是这个过程中一旦发生 master节点宕机,主备切换,slave节点从变为了 master节点。这时客户端2来尝试加锁的时候,由于客户端1已经宕机的master节点加锁成功,而客户端2在新的master节点上也能加锁,此时就会导致多个客户端对同一个分布式锁完成了加锁。
2.在哨兵模式或者主从模式下,如果 master实例宕机的时候,可能导致多个客户端同时完成加锁。
解决方案:
  1.不解决,不要打我(手动狗头),解决不了我就加入他,没想到吧,又是我
  2.使用Zookeeper实现,抛弃它(你个渣男)。 这也是我推荐使用Zookeeper来实现分布式锁的原因,没有那么多问题。 后续再来讲这个玩意(彩蛋)

本期到这里啦,写的不对的地方巨佬们多多指点,喜欢的话来一个一键三连吧

猜你喜欢

转载自blog.csdn.net/qq_34090008/article/details/110387373