Redis分布式锁原理(一)——redis分布式锁需要注意的问题

在分布式场景下,我们不同的业务功能放在不同的服务器上,而这些不同的业务可能会去操作同一个数据库资源,如果这时候大并发进来,就可能会出现同时操作共享资源的情况,为了避免这种情况发生,我们想到的是加锁,如果这时候是在各自服务器的代码实现上加本地锁能够解决这种问题吗?

答案当然是不能,来分析一下上述场景:

整个系统的业务1、业务2、业务3放在不同的服务器上构成了分布式场景,这三个业务底层都会去操作同一个数据库,现在对这三个业务的代码实现上加上本地锁,即synchronized、ReentrantLock等,此时100个并发请求进入这个系统,假设到达这三个服务器的请求量分别为30、30、40,由于三个业务是在不同的服务器上,所以对于它们而言使用本地锁锁住的不会是同一个对象,因此能进入业务1的请求只有1条,进入业务2的请求也有1条,同理进入业务3的请求也是1条,最终数据库会同时接收到3条访问请求,出现了同时操作共享资源的情况。

分布式锁是怎么解决这种问题的呢?就好比在这三个业务外边放一把大锁,这把锁也就脱离了“本地”的概念,三个服务器都可以去这个公共的地方抢这把锁,谁抢到了这把锁谁就可以去执行业务操作数据库,其他业务只能等待。

二、redis为什么可以实现分布式锁?

上面所提到的公共的地方可以用redis代替,也就是说所有的服务器都连接上同一个redis,redis是一个缓存数据库,我们通过往这个缓存中存取标记的手段达到锁的获取和释放的效果,说到这里就不得不引出这个关键的命令了:set NX,它是一个原子性的命令,它能保证如果缓存中如果没有这个key时才会对其进行设置,如果这个key已经存在了那么这个命令就会执行失败。

下面可以用xshell连接虚拟机后进行进行一个简单的测试,需要提前在虚拟机中安装好redis。首先复制多份会话模拟多个用户去抢redis锁,在确保redis在虚拟中正常运行的前提下输入命令“docker exec -it redis redis-cli”并发送到全部会话,使所有的会话都进入到redis容器中,接着输入命令“set locktest 123 NX”并同时发送到全部会话,模拟同一时刻多个会话去抢同一把锁。结果如下,会话1抢占锁成功,而会话2和会话3抢锁失败,而打开redis可以看到的确只有一条锁记录插入成功。

这样看来,redis的确是可以用来实现分布式锁,原理就是使用set nx命令进行设值,若返回成功则代表拿锁成功,返回失败则代表拿锁失败。

三、实现redis分布式锁需要注意哪些问题?

首先来看这样一段代码。

@Override

public List<Map<String, Object>> findList() {

//尝试获取分布式锁,步骤一

Boolean lock = redisTemplate.opsForValue().setIfAbsent(“lock”, “齐天小圣”);

if (lock) {

//加锁成功,执行业务,步骤二

List<Map<String, Object>> resultList = findListByDB();

//删除锁,步骤三

redisTemplate.delete(“lock”);

return resultList;

} else {

//加锁失败,自旋重试,即重新调用本方法。

try {

Thread.sleep(300);

} catch (Exception e) {

e.printStackTrace();

}

return findList();

}

}

乍一看用redis完成一个分布式锁的整体逻辑好像也不是很复杂,无非就是先去获取分布式锁,如果拿到锁了就去执行相应的业务,没有拿到锁就自旋重试。但其实上面这段代码漏洞百出,实际想要完成一个完美的分布式锁是很复杂的,我们需要注意以下问题:

**1、死锁问题:**如果某服务器拿锁成功,执行到步骤二时突然宕机没能正常的执行步骤三释放锁,那么其他正在等待锁的服务器则永远也拿不到锁了,这就是死锁问题。

解决这个问题很简单,只需要在拿锁之后给锁上一个超时时间即可,即使业务过程中出现问题导致不能释放锁,到了过期时间redis也会自动帮我们去把这个锁删除。需要注意的是,千万不能把“获取锁”和“设置超时时间”在代码中分成两步执行,如下:

原因在于这两个步骤分开执行没有保证原子性,拿锁到设置过期时间之间是存在时间差的,如果在这之间机器宕机了还是会存在上述问题,解决办法就是在占锁的同时设置过期时间。

**2、业务时间 > 超时时间:**假设这样一个场景,业务1拿锁成功并设置过期时间30s,但业务1比较复杂需要花费40s,那么到了30s后业务1的锁就已经失效了,此时业务2抢到了锁也进来执行相应的逻辑,那么此时业务1和业务2都在执行各自的业务逻辑,可能会操作相同的数据资源造成违反资源互斥的现象。问题还没完,又过去了10s,业务1执行完了,理所当然的就去删锁,那么自然就会把业务2手里的锁删掉,业务2一脸懵逼。。。这就造成了锁误删的现象。

先解决锁误删的问题,其实很简单,只需要保证谁拿的锁谁就有资格删就可以了,我们在获取锁的时候设置了一个value,此时就派上了用场,把每个业务的value都设置uuid,最后删锁的时候先去redis获取锁对应的值,如果这个值等于uuid才有资格执行删锁命令。需要注意的是这里也需要保证原子性,因为去远程redis获取锁对应的值再返回来也是有时间差的,如果业务1去远程获取到锁的value为“1111”,回来的过程中锁过期了,此时业务2拿到了锁开心的去执行它的业务去了,好景不长,业务1回来判断出锁的value是等于自己的uuid,于是又理所当然的把锁删掉了,业务2又一脸懵逼。。。。这里就需要使用lua脚本解锁,lua脚本就是为了保证这两段操作的原子性,解锁脚本内容如下:

if redis.call(“get”,KEYS[1]) == ARGV[1]

then

return redis.call(“del”,KEYS[1])

else

return 0

end

最后优化后的代码如下:

@Override

public List<Map<String, Object>> findList() {

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

由于篇幅限制,小编在此截出几张知识讲解的图解

P8级大佬整理在Github上45K+star手册,吃透消化,面试跳槽不心慌

P8级大佬整理在Github上45K+star手册,吃透消化,面试跳槽不心慌

P8级大佬整理在Github上45K+star手册,吃透消化,面试跳槽不心慌

P8级大佬整理在Github上45K+star手册,吃透消化,面试跳槽不心慌

P8级大佬整理在Github上45K+star手册,吃透消化,面试跳槽不心慌

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!
[外链图片转存中…(img-C01N0Chf-1712185862174)]

[外链图片转存中…(img-pFPVvzSp-1712185862175)]

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!

  • 22
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值