基于redis的分布式锁逐步解析

一、分布式锁的使用场景

  • 互联网秒杀
  • 抢优惠券

   1、普通锁的使用与弊端

       先看一下普通的下单操作,可见下图的 代码:
在这里插入图片描述
       上图代码中:从redis中获取库存数量,当库存数量大于0时,就进行扣减,再重新保存至redis
       这是一个很简单的下单操作;但需要考虑到,当有 多个人同时 进来下单时,就有可能出现同时获取库存,进行判断;这样就会导致 超卖现象
       这时候,都会想到使用同步代码块,如下:
在这里插入图片描述
       上图代码中:当有多人同时进来下单时,就 只有一个人获取锁 ,能够进行获取、判断、扣减库存的操作;其他人只能等待
       这就是最简单的一个锁操作;但上述代码中,只适用于单体应用因为synchronized是针对于同一个JVM的,无法做到跨JVM环境 );而现在很多的应用都是 分布式集群的应用 ,也就意味着有多个tomcat,也就意味着有多个JVM,也就是说一旦多人同时进来下单时,访问的是不同的集群节点 ,就一样会出现 超卖现象

   2、逐步进阶的解析基于redis的分布式锁

     1)相关的redis知识点

       (1)redis是 单线程 的,即使并发请求redis,也会被自动排序成单线程队列;但是这个 单线程是针对于同一个key
       (2)setnx方法:
         与redis的 set方法 用法一样;
         唯一不同的是:
           set方法不会分辨是否存在对应的key(存在的话,就进行覆盖);
           而setnx方法,会分辨是否存在对应的key(如果存在,不覆盖,直接返回false;如果不存在,就进行赋值,并返回true)

     2)最初级的redis分布式锁

       如下图所示:
在这里插入图片描述
       (1)解析: 在进入下单操作前,先要 使用setnx方法 创建一个redis键值对;再加上redis的 单线程模式 ;就可以保证每次只有一个线程得到redis键值对,而其他人得不到就会 降级返回异常 ;等到得到redis键值对的将其释放,其他人才能重新进来进行下单操作
       (2)弊端: 有两种情况;一个是下单操作中 抛了异常 ;一个是 服务宕机(比如程序在运行过程中,运营人员或运维人员进行服务重启等);以上两种情况出现,就会造成redis键值对就一直 无法被释放

     3)第二级的redis分布式锁

       如下图所示:
在这里插入图片描述
       (1)解析: 通过 捕捉 异常处理异常,来解决抛异常问题;再通过设置键值对 超时时间 ,来解决服务宕机问题
       (2)弊端: 有两个弊端;一个是由于设置键值对和设置键值对超时时间不是一个原子性操作,就 有可能是在设置超时时间的时候服务宕机 了,导致没设置成功;一个是下单操作如果 运行时间超过10秒 ,那么就说明在第一个线程还没执行完成的时候,就有第二个线程进来,就有可能超卖

     4)第三级的redis分布式锁

       如下图所示:
在这里插入图片描述
       (1)解析: 将设置键值对,和设置键值对超时时间;设置成 一个原子性操作
       (2)弊端: 但是没有解决 运行时间超过10秒 的问题
       (3)解析弊端: 如图
在这里插入图片描述

     5)第四级的redis分布式锁

       如下图所示:
在这里插入图片描述
       (1)解析: 将键值对中的值,设置成每个线程特有的值(UUID);从而在释放锁的时候判断是否是自己这个线程的锁
       (2)问题: 但是这个超时时间(10秒)设置得到底 是不是合理 的,设置的太短,担心出现多个线程同时运行,超卖;设置太长,一旦出现宕机,就会被锁住太长;但是这种分布式锁,基本可以满足低并发的场景了 ,一般的软件公司和外包公司(对并发要求不是很高的),就可以使用这种分布式锁了

     6)第五级的redis分布式锁

       使用redisson工具类
       在pom.xml中引入redisson的依赖:

<!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.12.5</version>
</dependency>

       在配置类(可以是启动类)中注册redisson的Bean组件:
在这里插入图片描述

       在使用到的类中注入redisson组件:在这里插入图片描述
       修改主逻辑代码:
在这里插入图片描述
       (1)解析: 可使用简简单单的三行代码,实现分布式锁

RLock redissonLock = redisson.getLock(lockKey);

redissonLock.lock();

redissonLock.unlock();

         具体流程如下图:
在这里插入图片描述
         唯一的线程获取到键值对锁,设置超时时间(默认30秒);同时,另外开启一个独立的线程,实现 时钟效果 ;具体就是计算运行时长,运行时长达到超时时间的1/3时,判断这个键值对锁 是否还存在 ,存在的话,说明对 应的线程还没运行完 ,则 重新设置键值对的超时时长 (默认是加回原来的超时时长,相当于续命);如果不存在,就不操作,重新执行第二个线程
       (2)问题: 上图中,一旦redis(Master)崩了,就会启用redis(Slave);那么就存在两种情况;一个是两个redis之间已经同步完成,那么就不会有问题;还有一种是还没同步完成,那么第一线程还没运行完时,其他线程进入就是请求redis(Slave),但这时由于没有同步完成,也就是redis(Slave)没有第一线程的键值对锁,那么其他线程就可以再设置获取一个键值对锁,这样就会存在问题
         但是每种方案都不可能完美,而且出现上述情况的概率非常之小,是可以接受并且后期人工处理修复造成后果的;所以不严重的高并发场景也是可以使用这种分布式锁的。当然,要避免redis的主从问题,可以使用zookeeper技术实现分布式锁,但是同样的,每种方案都不可能完美,zookeeper的性能是不如redis的

         redis毕竟是单线程模型的,那么难免会降低一些性能;可以使用分段锁进行提高性能,也就是可以把库存分开存储到redis,比如:100个库存,分成10段,每段10个库存;分10个key存储到redis,再进行下单操作,也就可以提高redis性能;这 利用的就是redis不同的key,可以同时被不同线程访问 (不同的key情况下,redis不是单线程模型)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值