ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

分布式还有这个坑

2022-03-20 21:03:15  阅读:200  来源: 互联网

标签:这个 删除 lock redis value 还有 我们 分布式


这是一篇3分钟就能阅读完的文章,相信对你有极大的实战帮战

hi,大家好,我是康师傅,最近看到一个比较有意思的问题,关于分布式锁的,自己平时在工作中也有用到分布式锁,但是确实也没注意到一些临界值的问题,说白了就是没有进行深度思考。关于这个标题,你可能会比较诧异,锁还能怎么优雅删除?直接一个 delete 不就完了,不然还怎么删除。

首先啊,我们先简单说下为什么要分布式锁,现在基本上都是分布式系统,应该没有什么系统是部署的单节点吧,因为单节点风险比较大,如果节点宕机,那么整个应用就起不来,如果是多节点,那么好处就多多了,可以做到负载均衡,高可用,即使一个节点挂了,还有其他节点可用,服务整体依然可用。

分布式虽好,但是也带来很多问题,比如对于临界资源的保护,在分布式系统下,自然而然离不开分布式锁的使用,分布锁的实现可以用 redis、zookeeper 等,但这些不是我们今天讨论的重点。

以 redis 为例,我们可以使用 redis 的 setnx key value来简单实现一个分布式锁,它是原子性的,只有当 value 不存在的时候,才会设置成功,因此用它来实现分布式锁再好不过。

但是啊,这里有个问题,代码的业务逻辑复杂,很多地方有 return ,那么对应的我们是不是要在所有 return 的地方也解锁。

setnx lock 1
if xx {
  delete(lock)
 return 
}
if xx {
  delete(lock)
  return 
}
...
doSomething()
delete(lock)
return 

如果我们不小心在某个地方忘记了删除(解锁),那么这个锁就永远无法解开了,这就会导致线上事故了。于是我们可以对这个锁加个过期时间:setex key timeout value,这样就可以类似做个保底的操作,即使忘了删除锁,也可以通过过期时间来降低风险。

因此问题来了,如果锁时间到了,但是还没执行完逻辑,最后处理完逻辑再正常删除会导致什么问题?

  1. 刚开始 A、B 节点都去争抢锁 setex lock 1 a

  2. A 成功获得锁,并且锁的有效时间是 1s,然后 B 可能自旋等待获取锁

  3. 这时 A 由于一些网络状况,导致本来应该很快结束的逻辑,超过了1s才完成,这时锁自动失效,B获得锁(A、B 同时在临界区)

  4. 由于 A 执行完毕逻辑,然后执行删除锁 del lock,这时导致删除了 B 的锁,然后 C 获得锁 (B、C 同时在临界区)

这时是不是发现了问题所在,造成这个问题的根本原因就是节点 A 删错了锁,把 B 的锁给删了,那如何避免呢?

延长

我们可以这样想,A 的业务逻辑还没走完,就不要放 B 进来,哪怕 A 花了很长时间,所以我们的锁可以不加过期时间,这样的话锁就不会自动消失,但是你要承担异常带来的风险:比如我们上面说到的在某个分支判断处忘了删除,或者程序还没走到解锁的时候异常退出,这些风险还是挺高的,那有什么办法让锁有过期时间,也不会在业务逻辑还没走完的时候自动失效呢?答案就是自动延长,比如起一个监听线程,这个线程干两件事:

  1. 监听锁的剩余时间

  2. 如果锁的剩余时间没多少了,但是业务的处理的进度还剩的比较多,尝试延长时间

当然这个只是个想法,真正实现起来,我觉得相对还是比较不好把握这个“度”的,比如剩余多少时间开始尝试延长,每次延长多少是个问题,如果延长的时间比较短,那么可能还要几次延长,延长的比较长可能还比较好,因为可以自己删除。

唯一

我们再来看看另一个更简单的方法,我们这次不考虑延长锁的时间了,失效就失效了,只不过我们要确认要删除的锁是不是一开始我们上的那一把?那如何确认呢?我们只要在一开始上锁的时候设置一个唯一 ID 来替代呆板的“a” (setex lock 1 a),这样下次准备删除的时候我们先 check 下这个 value 是不是我们一开始设置的唯一 ID,如果是的话,说明是我们自己上的那一把,如果不是的话,那么说明锁在我们执行期间失效了,然后给别人上了,我们忽略就行,不需要删除。

val = uuid()
set lock 1 val
dosomething()
if redis.get("lock") == val {
 del("lock")
}

看着好像没毛病,但是我们千万不要忘记原子性这个东西,我们知道 redis 本身处理命令是个单线程,单个指令不可分割,可以保证原子性,但是在此例子中,发现没有~我们先 get 判断了下值,然后再删除,这整个过程其实不是原子性的,我们看下下面的例子:

  1. 用户 A 生成 uuid

  2. 用户 A 获得锁,并且锁的 value 就是 A 的 uuid

  3. 然后 A 处理完自己的逻辑,获取锁的 value,发现是自己的 value

  4. 用户 B 进来 生成 uuid

  5. 正好锁的时间到了,锁自动失效

  6. 用户 B 上锁成功

  7. 用户 A 删除锁,尴尬了,还是删错了锁

造成这个问题的根本原因,就是用户 A 在读取 lock 和删除 lock 这个时间期间被用户 B 正好插入了进来,从而造成了误删,那如何解决这个问题呢?其实也很简单,redis 提供了 lua 脚本,lua 脚本会被 redis 当成一个整体,从而保证原子性,我们只需要把 if get 和 del 用 lua 实现即可,具体 lua 怎么编写,这里就不细说了。

ok,这次要说的就这么多,现在回想起来我以前用的很多分布式锁他们的 value 都是简单的 1,既没有考虑到误删也没有考虑到原子的问题,不知道你们有没有踩过同样的坑。

最后如果你喜欢我的文章,觉得我的文章对你有帮助的话,小手一动点个赞再走~

往期精彩:

 

标签:这个,删除,lock,redis,value,还有,我们,分布式
来源: https://blog.csdn.net/shanwu1/article/details/123621225

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有