IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> 分布式还有这个坑 -> 正文阅读

[大数据]分布式还有这个坑

这是一篇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,既没有考虑到误删也没有考虑到原子的问题,不知道你们有没有踩过同样的坑。

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

往期精彩:

?

  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2022-03-21 20:57:41  更:2022-03-21 20:58:43 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 6:15:31-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码