Redis分布式锁原理

分布式锁一般用于解决事务问题

背景

在这里插入图片描述
现在有两个订单服务,同时访问数据库并写入数据,但由于写入的数据不同,就会导致产生脏写,假如库存为5,订单服务1库存减少2,并写数据库,此时为3,由于服务1,2同时访问数据库,所以订单服务2读到的数据也为5,并减少1,此时为4,这个时候就产生了脏写,实际我们想要的效果应该是5-2-1=2,这就需要redis的setnx命令来实现,setnx命令的原理是,key不存在才可以新增成功,利用这个特性来做到实现事务的控制问题

setnx命令

在这里插入图片描述

  1. 同时读数据库没有关系,因为读数据不会对后续有影响,所以不需要控制,问题在于写数据,要保证数据的一致性,否则回产生脏数据
  2. Redis是单线程的,且基于内存的操作,这个请求的顺序由redis自己内部去控制,我们不需要了解,所以才会有2和3的setnx
  3. 假设订单服务1先去执行setnx命令,此时商品id这个key在redis中不存在,那么新增key-value并返回1,说明获取到了锁,可以进行写操作,库存-2;而订单服务2去redis里面执行setnx时返回0,发现商品id这个key已经存在了,那么获取锁失败,也就不能进行库存减操作了,如果想继续执行写操作,就得等订单服务1执行完写操作,把锁删除掉,也就是del命令,把key删除,订单服务2才可以执行写操作

以上流程看似解决了分布式系统下的事务问题,但存在以下问题

原子操作

事务控制需要严格遵守原子操作,而我们可以发现,加锁setnx和解锁del命令并不是原子操作,而是两个命令,我们假设一个场景,订单服务1加锁成功之后,在去写数据库的时候,意外宕机了,那么也就说明,后续的del删除锁的操作也就无法进行了,那么就造成了死锁问题,这个商品id的key在redis中一直都存在,后续谁都无法操作了,解决办法如下:set命令增加参数NX可以替换setnx命令

setnx 指令本身是不支持传入超时时间的,set 指令增加了可选参数
set(商品ID,当前线程id,30,NX)

误删锁

set命令解决了原子操作的问题,但是如果value值设置不当,还会造成误删锁的问题。假设服务1获得锁,并给key设置的失效时间是30秒,然后去执行写操作,但是30秒到了,我还没有执行完,然后服务2此时由于key失效了,所以通过set命令获取到了锁,这个时候服务1写操作完成,准备删除锁,但这个时候,删除的却是服务2的锁,这就造成了误删锁,解决办法:value值设置的是当前线程id,每次删除前判断一下

String threadId = Thread.currentThread().getId()
set(key,threadId ,30,NX)
// do  something
if(threadId .equals(redisTemplate.get(key)){
    del(key)
}

守护线程续命

误删锁解决了,但是误删锁的前提是因为,服务1没有在30秒(key的失效时间)内执行完任务,锁自动失效,导致其他的线程获得到了锁,那么不就又回到最开始脏写的问题上了嘛!这个时候就需要守护线程来帮忙了。我们在设置key的失效时间为30秒后,可以启动一个守护线程,在29秒的时候去为当前线程去续命20秒,也就是守护线程从第 29 秒开始执行,每 20 秒执行一次。 当前线程执行完任务之后,也就关掉守护线程并删除锁。

你可能会想,若当前线程和守护线程都挂了怎么办,没关系,还有key的失效时间,redis会删除锁,不会让死锁产生。

总结:

  1. 如何解决分布式系统下的事务问题——redis的setnx命令
  2. setnx命令加锁,删除锁不是原子操作——set命令替换setnx命令
  3. 误删锁怎么办——set命令中value值不要随便设置,要设置为当前线程id,每次删除前判断
  4. 服务没有宕机的情况下,key失效——启动守护线程为当前key续命

资料来源:
https://www.bilibili.com/video/av34115846/
https://www.bilibili.com/video/av34116094/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值