序言
hi,大家好~刚看完s10比赛,恭喜SN3:1战胜JDG,今天我们聊一下分布式锁,希望大家可以点个赞支持一下O(∩_∩)O
分布式锁的产生:随着互联网的发展,单体架构无法满足需求,从而扩展成分布式架构后解决分布式情况下同步问题所产生的技术。
为什么分布式情况下要使用分布式锁来解决同步问题呢?
- 我们知道在程序中是离不开线程安全的,在单体架构下我们可选择的解决线程安全的方案有很多,最常见的就是使用互斥锁来解决,但在分布式集群环境下,使用常规的互斥锁例如:synchronized,lock是无法控制多台机器的,它们只能控制单台机器,因此在分布式集群下,单单使用常规的互斥锁是无法保证线程安全的。
举个例子:
- 现在有两台扣减库存的机器A和B,数据库中库存只剩下1个,这时两个请求同时到达,通过nginx分别负载到了两台不同的机器上,两台机器都使用了synchronized来修饰代码块,两个请求分别获取到了A机器和B机器上的锁,就相当于他俩同时进入了同步代码快,做参数校验库存此时>0,于是两台机器都做了减库存的操作,这就最终导致了超卖的情况发生。
分布式锁
分布式锁就是用来解决上述所发生的的问题的。
分布式锁可以通过redis和zookeeper来实现,今天我们就先聊一下redis的实现。
- redis中有一个setnx命令,它代表的意思是,如果设置的key不存在则设置成功返回true,如果存在则设置不成功返回false,redis的分布式锁就是基于这个命令的,无论集群有多少台,最终只会有一台机器可以返回true,来实现“唯一的锁”。
常见的问题
- 如果程序发生异常,锁释放不了怎么办?
答:释放锁的代码需放到finally中执行。
- 如果机器宕机,锁释放不了怎么办?
答:这就需要我们为锁设置过期时间了,保证宕机后锁还能被释放。
(注意,设置锁和设置过期时间的命令需通过一条指令实现,保证原子性,否则设置完锁宕机了还没来得及设置过期时间,锁就释放不了了,可以通过spring封装的StringRedisTemplate的setIfAbsent方法实现)
- 如果A线程释放了B线程的锁怎么办?
场景描述:A线程加锁成功,但是业务执行时长超过了锁的存活时间,锁自动释放,B线程加锁成功后,A线程业务终于执行完了,此时A线程要释放掉锁,它就会把B线程的锁给释放掉了。
解决方法:每次加锁时,可以通过UUID生成一个随机的VALUE,在释放锁的时候判断一下锁当中的VALUE和随机生成的VALUE是否相同,相同的话说明是自己的锁然后进行释放。
- 如果业务执行时间大于锁的释放时间怎么办?
答:我们可以再启动一个线程,定时为我们的锁增加存活时间,直到释放锁时就可以关闭该线程
- 在主从架构下,主库宕机了,但锁的信息还没同步给从库,此时一个从库当选成为新的主库,就可能会出现两个请求同时执行的情况
答:这时我们就需要设置,从库延迟重启了,延迟的时间可以设置为锁的存活时间,牺牲掉一些请求从而保证程序的安全性,避免发生两个请求同时执行的情况。
优化
我们知道锁这个东西有利有弊,在保证了并发安全的情况下降低了程序的执行效率,所以我们可以针对不同的业务场景做优化。
eg:有这么一个业务,对某一商品进行库存扣减时用到了分布式锁。
我们可以采用分段锁的思想来进行优化,比如现在有20件库存,我们将它拆分到4个库中,每个库存放5件库存,这样我们的并发就能足足提升4倍了。
分布式锁的封装
现有市面上有很多基于redis的分布式锁框架,最常见的就是Redisson了,使用Redisson可以很方便的使用分布式锁,它的底层基本和上述差不多。
- 使用方法:
- 第一步:导入Redission依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.6.5</version>
</dependency>
- 第二步:直接注入Redisson
@Autowired
private Redisson redisson;
- 第三步:添加锁,释放锁
String key="分布式锁";
RLock lock = redisson.getLock(key);//获取锁
lock.lock(30, TimeUnit.SECONDS);//添加锁
lock.unlock();//释放锁
- redis集群环境下,Redisson的使用
使用宏锁(传入的锁有一半加锁成功,则整体加锁成功),优化redis集群情况下,数据延迟同步问题导致的同时2个用户加锁成功
RLock lock1 = redisson.getLock(key);//获取锁
RLock lock2 = redisson.getLock(key);//获取锁
RedissonRedLock redissonRedLock=new RedissonRedLock(lock1,lock2);//设置宏锁
redissonRedLock.lock();//添加锁
redissonRedLock.unlock();//释放锁
结束语
以上就是redis分布式锁的实现和一些注意事项啦,如果您觉得哪里可以优化的话,可留言一起讨论哦,哈哈我是一个努力向上的小白,您的点赞支持就是对我持续创作的最大动力!