并发解决方案之Redis实现分布式锁

分布式锁

- 为什么要使用分布式锁?
因为存在并发产生的数据安全问题。

在生产环境中,通常会出现并发的场景,多个用户请求同一个接口时,若接口不对并发进行处理,则会造成数据的不安全,传统单体架构的应用我们可以使用synchronized锁或者是lock锁进行解决,但是在现在,多数应用都是集群部署,使用synchronized只是在单应用层面对JVM进行加锁,对集群部署的应用几乎没用,所以需要使用分布式锁。
我的理解是,高并发实际上就是多个线程在争抢唯一的一块资源,需要使用锁来证明是哪个线程拿到了这块资源,谁拿到这块资源,谁就可以执行自己的业务逻辑代码,证明谁拿到这块资源需要将锁放在所有线程都能看到的地方(存放在Redis),所以使用synchronized就力不从心,使用synchronized相当于是在自己的JVM内锁住,可是相同的方法不值存在于一台JVM的方法栈中。
若以下代码是单体架构部署,则不会出现线程安全问题,若是集群部署,则会出现线程安全问题。
在这里插入图片描述

- 测试可能存在并发问题的接口。

使用Jmeter压测接口,可以发现上图代码在单体架构线程安全,可是在集群部署很容易出现线程间数据不安全的问题
如何使用Jmeter:我还没写(回头写,先挖个坑(逃))

- 如何解决上图代码集群部署线程不安全问题?

:使用分布式锁。
:如何实现分布式锁?
:借助Redis单线程以及setnx的特性实现分布式锁。
:什么是setnx?
setnx
---setnx key value
---setnx Grover 666 执行成功
---setnx Grover 777 执行失败(因为Grover的key已存在)

对上图线程不安全代码进行改进(入门级别分布式锁)

加锁Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey,v. "zhuge");
释放锁stringRedisTemplate.delete(lockkey);
在这里插入图片描述
:上述修改后的入门分布式锁有没有什么问题?
:死锁问题:当程序拿到锁在执行业务逻辑时,可能会出现抛异常的情况,这个时候程序代码就不能执行到释放锁的代码,此时别的线程也拿不到锁,就出现了死锁问题。
:那抛异常可以使用try-catch-finally解决,将释放锁的代码放在finally代码块中。
:那要是服务宕机呢?
:我可以给锁的key设置一个过期时间,只要超过过期时间自动释放锁。
像这样:
在这里插入图片描述
:那要是第一行执行成功后就宕机了呢?
:那就保证这个操作的原子性(不可分割的最小部分),这样写:
原子性加锁
:这个代码还存在什么问题吗?
:存在锁永久失效的可能。图解:线程一执行业务逻辑到了10s,线程一的锁被释放,此时线程二拿到锁,线程一继续执行剩下的5s的业务代码,执行完毕后该释放锁,此时锁已经是线程二的锁了,线程一释放了线程二的锁,以此类推 ,相当于锁的永久失效。
在这里插入图片描述
:如何解决锁永久失效的问题?
:现在的问题是线程一释放掉线程二的锁,线程二释放线程三的锁,只要保证当前线程只能释放自己的锁就好。
代码如下:给每个线程一个clientID,没次释放锁的时候通过clientId判断一下当前释放的锁锁是不是当前线程的锁。
在这里插入图片描述
:有的公司确实这样实现分布式锁屏,可是当前代码是否还存在什么问题?
:有,还是会可能出现锁失效的可能,假如线程一花费了9.9s已经执行完finally中的if判断,正准备去释放锁,此时出现了系统卡顿的情况 ,释放锁的代码并没有被执行,可是10s已经到了(系统卡顿代码未执行与redis无关,redis该到期就到期),此时线程一释放的就是线程二的锁了。
:你说的还是释放锁的代码原子性的问题,如何解决呢?
:我们先不探讨代码原子性问题,先试着可以将锁超时时间变得久一点看行不行。
:那也无法根本解决问题,业务逻辑的执行时间仍然不确定,而且锁的时间越久,出现问题后用户等待的时间也就越久,所以锁不宜设置的太久,设置的时间太久也没有意义,有没有什么其他方案保证线程安全呢?
:可以使用锁续期的解决方案,锁续期就是除了主线程之外,每条线程再配备一个B线程,B线程定时去判断当前主线程的锁是否还存在,若存在,就将锁进行续期,若不存在,就说明锁已经被释放,不需要续期。需要注意的是,锁的失效时间大于B线程定时任务判断锁是否存在的任务时间,若锁失效时间是10s,定时任务时间要小于10s.
:可是我给代码加定时任务,加超时时间,加那么多的控制线程安全的代码,我怕写错或者考虑欠缺,总是没有安全感,怎么办呢?
:可以使用Redisson.Redisson-Github
:什么是Redisson呢?
:可以理解为Java操作Redis的客户端,和Jedis性质一样。Redisson特别善于解决分布式问题。

- 如何使用Redisson?
  1. 在项目中加入Redisson的依赖
    Redisson依赖
  2. 配置Redis,创建Bean
    创建Bean
    此处创建的是Redis单机模式(只有一台Redis),在生产环境中有很多模式,如下图的哨兵模式,主从模式,集群等等。
    在这里插入图片描述
  3. 使用Redisson解决高并发问题(三行代码搞定)
    在这里插入图片描述
- Redisson底层 .lock()实现原理

在这里插入图片描述

redisson.lock();最核心的加锁代码如下:
在这里插入图片描述
:上面带那种script是什么东西呀?
:是Lua脚本
:什么是Lua脚本?
:Lua脚本就是一段运行在服务端的脚本语言,可以被多个语言调用,Redis2.6之后推出了Lua脚本功能,也就是说,可以在Redis中运行一段Lua脚本,而在Redis中运行Lua脚本的好处有以下几点。
Redis中使用Lua脚本
:那上面那段Lua脚本是什么意思呢?
:上面那段Lua代表,设置一个hset key value;
key为当前锁的名称(redisson.getLock(锁名称));
value为当前主线程ID。
并设置一个默认过期时间30s;
:那锁续期在哪里实现呢?
:在以下定时任务中这段Lua脚本中实现,判断当前线程锁是否存在,若存在,则续期。(这个定时任务循环调用自己,并且是延时执行,相当于是30/3=10s秒钟执行一次)在这里插入图片描述

以下代码还存在其他问题吗?

在这里插入图片描述
大多数互联网公司使用Redisson直接这样使用就可以解决并发问题,还需要注意的就是redis的集群问题,因为多数公司的redis都不止一台。

还存在哪些问题?

问题一:当线程一使用Redis的主节点加锁成功后,线程一继续执行自己的业务代码,这时,Redis的主节点挂了,由于Redis主从复制是异步操作,加锁信息还没有同步到子节点,这时候线程二访问Redis,发现没有加锁信息,于是线程二也开始执行业务代码,就出现了Redis在主从切换时,key丢失导致锁失效的问题

如何解决Redis在主从切换时,key丢失导致锁失效的问题?
使用zk替换redis作为分布式锁

  • 首先要知道的是:Redis遵循CAP原则中的AP原则-可用性(Availability)、分区容错性(Partition tolerance),在加锁成功之后,redis直接将加锁成功的信息返回给业务逻辑主线程,这时候业务逻辑线程开始执行。
  • 再了解一下Zookeeper的机制:Zookeeper遵循CAP中的CP-一致性(Consistency)、分区容错性(Partition tolerance),他的机制是在加锁成功后,先将加锁信息传递给半数以上子节点后,再将加锁成功的信息返回给业务逻辑主线程,这时候业务逻辑线程开始执行。若zk的leader节点挂了之后,zk中的选举机制会保证同步成功的子节点变为leader节点。而且zk有主节点才允许被写入key。使用Zookeeper就不会出现主从切换key丢失的问题。

使用Redlock红锁解决不推荐,红锁中还存在很多小问题)

  • 每次加锁的时候也给半数以上节点加锁,才能算是加锁成功,才能继续执行业务逻辑代码。在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

问题二性能问题,分布式锁其实就是将原本应该并行执行的场景进行串行化,只能等待前一个线程执行完毕释放锁之后,之后等待的线程才有可能执行。

如何解决加锁后的性能问题?

  • 将加锁的粒度降低:将加锁代码的范围减少,只对处理可能产生并发的代码段进行加锁。
  • 使用分段锁的机制提高锁的性能(ConcurrentHashMap中使用的也是分段锁)
  • 分段锁:
    现在要对Product_101的商品进行秒杀100份
    我们将Product_101分为若干个小份
    Product_101_0 20份
    Product_101_1 20份
    Product_101_2 20份
    … … . … … … .20份
    此时就有5个线程可以并发执行,以Product_101_n为加锁的key.
    在高并发场景下,例如12306抢票或者双11秒杀,一定有redis集群,我们还可以将Product_101_n通过某些方式放在不同的redis上提高性能。

完结撒花~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

难打的仗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值