Redis分布式锁的实现

Redis分布式锁的实现

    • 分布式锁初级
      • 存在问题
      • 解决方案
      • Redission解决方案优化
      • 单机秒杀系统Bug
      • 单机版加synchronized
      • 单机版架构升级为分布式微服务setnx
      • 部署微服务jar包宕机问题
      • 判断加锁与解锁不是一个客户端?
      • 集群环境下锁丢失问题
      • Redis分布式锁如何实现续期?
      • 补充

分布式锁初级

Redis分布式锁的实现_第1张图片

存在问题

模拟场景:线程A进入到锁的业务区域,设置锁的存活时间为10s,但是线程A执行完业务的总时间需要15s,当10s过后线程A只执行一半锁就消失了,此时线程B就可以来获取锁了,当线程B加锁后,假设线程B的执行时间需要8s,但此时线程A还在接着运行,问题就在这里,当A执行完15s后,释放的锁是线程B加的锁,而线程B此时才运行了5s,还没有运行完毕,此时就会有线程C等等等线程来加锁,那么重复刚才线程AB的运行过程,最后造成锁的永久失效。
问题的根本
当前线程加的锁被前一个线程释放掉了

解决方案

设置一个UUID,当线程执行这个方法时,将这个UUID记录到setnx的value中,执行完业务解锁时,判断当前线程的UUID是否等于setnx中的value
Redis分布式锁的实现_第2张图片

Redission解决方案优化

Redission分布式锁原理
Redis分布式锁的实现_第3张图片
当线程1加锁成功后会开启一个子线程,子线程通常会定时(大约为锁存活时间的1/3)的查看锁是否过期,如果锁没有过期,那么以当前时间点开始,重新设置锁的存活时间为30,防止还没有执行完业务锁提前消失。

  1. RLock redissonLock = redission.getLock();
  2. redissionLock.lock(30,TmieUnit.SECONDS);加锁并设置锁的存活时间
  3. redissionLock.unLock();解锁
    代码:
    Redis分布式锁的实现_第4张图片
    这种分布式锁解决高并发问题只是将并行的线程改为串行,会降低程序的效率,增加程序的执行时间。
    **解决思路:**比如想要秒杀一件商品,秒杀商品的库存为1000,如果我们直接操纵这个库存的话每次只能串行的-1。因此我们可以采用分段机制,将这个商品的库存分为10份,每份为100件,这样就可以同时有10个线程对库存进行操作,提升了程序的效率。

单机秒杀系统Bug

单机版加synchronized

Redis分布式锁的实现_第5张图片
缺点:造成线程的积压,大量的线程堆在sync锁的外部,程序会变得非常的慢。最重要的是在分布式架构中仍然不能解决超卖问题。

单机版架构升级为分布式微服务setnx

分布式架构部署后,单机锁仍然会出现超卖的情况
解决方法: redis分布式锁setnx
Redis分布式锁的实现_第6张图片
缺点:如果在加锁后的业务中报异常了,也就执行不到解锁的命令。需要在代码层面加一个finally释放锁。

部署微服务jar包宕机问题

部署微服务jar包的机器宕机了,代码层面根本没机会走到finally,没办法删除这个key解锁,此时需要对这个key设置一个过期时间。

缺点:线程A进入到锁的业务区域,设置锁的存活时间为10s,但是线程A执行完业务的总时间需要15s,当10s过后线程A只执行一半锁就消失了,此时线程B就可以来获取锁了,当线程B加锁后,假设线程B的执行时间需要8s,但此时线程A还在接着运行,问题就在这里,当A执行完15s后,释放的锁是线程B加的锁,而线程B此时才运行了5s,还没有运行完毕,此时就会有线程C等等等线程来加锁,那么重复刚才线程AB的运行过程,最后造成锁的永久失效。

Redis分布式锁的实现_第7张图片
解决方式:在解锁处判断当前的UUID是否为锁对应的value
在这里插入图片描述

判断加锁与解锁不是一个客户端?

在Fianlly代码块中的判断UUID和删除锁操作不是原子的,因此有可能会造成客户端A加锁,客户端B解锁的问题。
解决一:使用LUA脚本
Redis分布式锁的实现_第8张图片
解决二:redis自身的事务
使用Watch监控+事务
Redis分布式锁的实现_第9张图片

集群环境下锁丢失问题

集群环境:
Redis分布式锁的实现_第10张图片
模拟场景:假设Redis主机加锁成功后,返回加锁成功信息,但是由于主从复制是异步操作,当主机加锁成功后还未同步到从机上,主机就宕机了。那么会由哨兵模式在从机中进行选举新的主机。但是此时的新主机没被加锁,就会出现异常。redis异步复制导致锁的丢失

另外可以使用zookeeper实现,zookeeper和redis的不同点是zookeeper会在主从复制结束后再返回信息,因此保证的主从的一致性,但是这个过程是消耗时间的。数据的完整性和效率是不能共存的,可以根据实际情况选择。

Redis分布式锁如何实现续期?

RedLock落地实现续期
在线程加锁进入到业务代码时,会生成一个子线程,子线程会定期的查看业务是否执行完毕,如果执行完毕就按照主线程的逻辑走,如果没执行完毕,那么子线程会将当前key的以当前时间点设置存活时间,存活的时间时间仍为之前的时间。定期查看的时间可以设置为key存活时间的1/3。
Redis分布式锁的实现_第11张图片
使用Redisson实现上面的思想

  1. RLock redissonLock = redission.getLock();
  2. redissionLock.lock(30,TmieUnit.SECONDS);加锁并设置锁的存活时间
  3. redissionLock.unLock();解锁
    Redis分布式锁的实现_第12张图片

补充

使用Redission在超高并发的情况下会抛出这样一个异常:
在这里插入图片描述
意思就是加锁和解锁不是同一个线程操作,就相当于我们使用setnx时,在解锁前需要判断一次线程UUID。
判断当前是否还是锁着的状态、锁是当前线程持有的,才进行解锁操作
Redis分布式锁的实现_第13张图片

你可能感兴趣的:(redis,分布式)