GaussDB(DWS)中的分布式死锁问题实践
本文分享自华为云社区《GaussDB(DWS)中的分布式死锁问题实践》,作者: 他强由他强 。
1、什么是分布式死锁
分布式死锁是相对于单机死锁而言,一个事务块中的语句,可能会分散在集群里多个节点(CN/DN)执行,在不同节点上可能都会持有锁,当并发事务进行时可能会导致分布式(全局)死锁,如下图所示,会话SESSION1持有了DN1上的lock1资源后再去请求DN2上的lock2,会话SESSION2持有了DN2上的lock2资源后再去请求DN1上的lock1,两个会话形成互相等待。出现分布式死锁现象后,如果没有外部干预,通常是一方等待锁超时报错后,事务回滚清理持有锁资源,另一方可继续执行。
2、常见的分布式死锁场景
一般来说,分布式死锁的产生与在不同节点上的并发时序或持锁顺序有关,所以现网实际发生概率较低,分布式死锁通常都是RegularLock类型,下面是几种常见的分布式死锁场景,举例说明两个并发事务产生的分布式死锁:
1)锁升级
# 集群两个CN,两个DN create table mytable(a int, b int); insert into mytable values(1,1),(2,2);
其中sessionA与sessionB由不同CN发起(sessionA:CN1,session2:CN2),执行时序如下:
session A(CN1) | session B(CN2) |
begin; | begin; |
select * from mytable; // CN1上拿1级表锁 |
|
| select * from mytable; // CN2上拿1级表锁 |
truncate table mytable; // CN1上拿8级表锁 // CN2上拿8级表锁,waiting |
|
| truncate table mytable; // CN1上拿8级表锁,waiting |
可以看到sessionA里select会持有本地1级锁,truncate会持有8级锁,出现锁升级现象,导致sessionA在CN2上等锁,sessionB在CN1上等锁,形成相互等待。
2)行更新冲突
# 集群两个CN,两个DN create table mytable(a int, b int); insert into mytable values(1,1),(2,2);
行存表发生行更新冲突是比较常见的分布式死锁场景。因为表是round robin分布,所以行a = 1 与 a = 2数据可以保证分别分布在DN1和DN2节点。
一个事务在更新数据时需要在对应DN节点持有本xid事务锁的Exclusive锁,当发生行更新冲突时(写写冲突),一个事务需要阻塞等待另一个事务提交(等待获取对方事务锁ShareLock),形成相互等待时造成分布式死锁。
其中sessionA与sessionB可由相同或者不同CN发起,执行时序如下:
session A(xid1) | session B(xid2) |
begin; | begin; |
update mytable set b = 1 where a = 1; // DN1上拿xid1的事务锁 |
|
| update mytable set b = 2 where a = 2; // DN2上拿xid2的事务锁 |
update mytable set b = 1 where a = 2; // DN2上拿xid2的事务锁,waiting |
|
| update mytable set b = 2 where a = 1; // DN1上拿xid1的事务锁,waiting |
3)CU更新冲突
# 集群两个CN,两个DN create table mytable(a int, b int) with (orientation = column); insert into mytable values(1,1),(2,2),(3,3),(4,4);
其中sessionA与sessionB可由相同或者不同CN发起,执行时序如下:
session A(xid1) | session B(xid2) |
begin; | begin; |
update mytable set b = 1 where a = 1; // DN1上拿xid1的事务锁 |
|
| update mytable set b = 2 where a = 2; // DN2上拿xid2的事务锁 |
update mytable set b = 1 where a = 3; // DN2上拿xid2的事务锁,waiting |
|
| update mytable set b = 2 where a = 4; // DN1上拿xid1的事务锁,waiting |
当出现更新冲突时,对于行存表来说是对一行数据加锁(如场景2所述),但对于列存表来说是对一个CU加锁。所以一个事务里的更新语句如果涉及到不同的CU,也会拿事务锁,可能就会产生分布式死锁。
我们可以通过如下语句观察ctid信息判断数据是否分布在同一个CU上,如下图:可以看到a = 1 与 a = 3分布在DN1上,且在同一个CU;a = 2 与 a = 4分布在DN2上,且在同一个CU。所以这也能解释为什么看上去列存表更新不同的“行数据”也会产生锁阻塞和分布式死锁现象。
4)单语句出现分布式死锁
前面几种场景都是事务块里涉及到多条SQL语句,可能会到不同节点上去交错拿锁导致的分布式死锁,但有时候某些单语句场景可能也会出现分布式死锁,如下:
session A(xid1) | session B(xid2) |
update/delete mytable set b = 1 where a = 1; // waiting | update/delete mytable set b = 2 where a = 2; // waiting |
此类问题与数据分布有关,如下场景都可能会导致这个现象:
1)若表是复制表,每个DN节点上都有数据,更新时会去所有DN并发执行
2)表是普通行存表或列存表,但有行数据(如a=1)同时分布在了多个DN节点上,如round robin分布下插入两条相同a=1的数据
insert into mytable values(1,1); insert into mytable values(1,2);
此场景需要具体去排查数据分布是否会造成此情况。
3、规避分布式死锁的方法1)控制锁级别,减少锁升级
按照各类操作的锁级别建议规则使用,开发时不要盲目提高锁级别,造成可能发生的不必要的锁等待
2)控制锁粒度
合理控制锁使用范围,及时释放
3)控制拿锁顺序
尽量控制对资源操作的顺序,比如对各分区表的操作顺序,避免乱序造成的死锁。但全局各节点的拿锁情况或顺序一般无法提前预测,往往为了考虑提高性能,请求会在节点间并发执行,但我们可以在某个节点上控制并发互斥以规避分布式死锁问题,如操作某个表时先去FirstCN上请求持锁,持锁成功后再对其他CN和DN并行拿锁。GaussDB(DWS)内核的很多地方的设计中会有这种思想,如DDL语句,autoanalyze等。
4)主动设置较短的锁超时时间
一般用在非关键的用户路径操作上,如用户语句在runtime analyze子事务的流程里会主动设置锁超时时间为2秒,发生阻塞后可及时放锁,避免出现长时间锁等待,也能规避潜在的分布式死锁场景
4、如何排查系统是否产生了分布式死锁
本质上是发现集群中是否有全局的死锁环等待关系,内核中提供有许多视图可以辅助观察持锁等待情况,但需要注意的是,因为查询到的锁等待关系可能只是暂时的瞬间状态,只有持续存在的锁等待才会造成分布式死锁,需要判断锁是否稍后会主动释放(事务提交前),还是只能等到事务提交后释放。如何判断系统是否产生了分布式死锁,有以下方法:
1)查询pgxc_deadlock视图,会输出全局死锁环信息,如果信息为空,则代表无分布式死锁,但需要注意在某些复杂的场景可能会出现误报,即输出有死锁环信息,但可能并没有形成分布式死锁;
当有分布式死锁时,直到等待锁超时后,某一方事务会出现“Lock wait timeout...”,打印具体的锁信息及锁语句,报错后释放锁,另一方解除阻塞。相关的锁超时参数是lockwait_timeout或update_lockwait_timeout。
2)在GaussDB(DWS)的8.3.0版本及以后,内核已经支持了自动化地分布式死锁检测,当检测到系统中存在分布式死锁等待关系后,会自动报错和挑选事务进行cancel,具体原理下一节中会详细介绍。
如下图所示,若用户出现“cancelled by global deadlock detector”报错,代表检测到分布式死锁并被查杀,此时可以去检测CN上(FirstCN或者CCN)上去找相关日志信息,会输出具体死锁和session查杀信息,需要注意用户语句执行CN和检测CN可能并不是一个,此时检测CN会向执行CN发起事务cancel。
5、分布式死锁检测原理
分布式死锁检测的目标原则是做到不误报,争取不漏报,尽量及时报。
我们使用了中心化的收集检测思想,如流程图所示:首先挑选一个CN作为检测CN(类似master角色),CN上新增后台线程启动GlobalDeadlockDetector模块,周期性向集群所有节点收集锁等待关系,计算等待者和持有者信息,然后构造全局有向图(WFG),依赖定义的规则对图的顶点和边进行消除,判断是否能消除完成。如果无法消除完成,则出现了死锁环,并进行二次DoubleCheck,如果两次的死锁环信息有交集,则报告死锁信息。当发现死锁后,按照事务时间戳挑选最年轻的事务(youngest)进行中断,并会对用户报错。
我们在设计上主要参考了Greenplum的思路,由于与GaussDB(DWS)架构和应用场景上的差异性,也针对做了一些改造和优化,主要包括:
1、检测节点的选择:在FirstCN或CCN上启动后台检测线程,依赖外部OM模块做高可用切换;
2、等待关系图节点的标识:由检测CN构造全局唯一global_session下发,格式为:timestamp.pid.node_name(timestamp为事务开始的时间戳,pid为执行CN上的线程号,node_name为执行CN名称);
3、虚实边关系定义:支持定义线程级别虚实边,过滤掉不必要的死锁误报;- 实边:锁等待关系的变化,需要等到持有者事务会话commit或abort
- 虚边:锁等待关系的变化,不需要等到持有者事务会话commit或abort
4、死锁结果的合法性检查:增加DoubleCheck机制,提高检测结果准确性,结果以连续两次检测到的死锁环交集为准;
5、死锁消除:执行CN与检测CN可能不同,可能存在跨CN发起的事务中断;
6、与单机死锁检测算法互补:当分布式死锁检测算法如果发现检测到单机的死锁环路等待关系后,则忽略,与单机死锁检测算法处理不冲突;
分布式死锁检测相关参数:
-
enable_global_deadlock_detector:分布式死锁检测功能是否开启,默认off
-
global_deadlock_detector_period:分布式死锁检测周期,默认5秒

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
run-llm.sh,一键在本地跨平台运行大语言模型
由 Second State 开发的 run-llm.sh 脚本是一个命令行工具,用于让你快速在本地设备使用 CLI 和与 OpenAI 兼容的 API 服务器运行开源大型语言模型(LLMs)。这个命令行应用程序会自动下载并安装 WasmEdge runtime、模型文件以及用于推理的可移植 Wasm 应用程序。用户只需按照命令行提示选择所需的选项即可。 运行 run-llm.sh bash <(curl -sSfL 'https://code.flows.network/webhook/iwYN1SdN3AmPgR5ao5Gt/run-llm.sh') 按照提示安装 WasmEdge Runtime 并下载你喜欢的开源大模型。然后,你将被询问是否希望通过命令行界面或通过 web 界面与模型进行交流。 命令行界面:只需留在终端。当你看到一个 [USER] 提示符,就可以提问了! Web UI:在安装本地 web 应用程序和本地 web 服务器(用 Rust 编写,并在 WasmEdge 中运行)后,你将被要求从浏览器打开 http://127.0.0.1:8080。 点击查看We...
- 下一篇
记一次kubernetes获取internal Ip错误流程
本文分享自华为云社区《记一次kubernetes获取internal Ip错误流程》,作者:张俭。 偶尔也回首一下处理的棘手问题吧。问题的现象是,通过kubernetes get node输出的ip不是期望的ip地址。大概如下所示 ip addr eth0 ip1 eth0:xxx ip2 最终输出的不是预期的ip1地址,而是ip2地址。 按藤摸瓜,kubernetes把节点信息保存在/registry/minions/$node-name中的InternalIp字段。 InternalIp是如何确定的呢,这段代码位于pkg/kubelet/nodestatus/setters.go中 // 1) Use nodeIP if set (and not "0.0.0.0"/"::") // 2) If the user has specified an IP to HostnameOverride, use it // 3) Lookup the IP from node name by DNS // 4) Try to get the IP from the networ...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- MySQL8.0.19开启GTID主从同步CentOS8
- Mario游戏-低调大师作品
- Linux系统CentOS6、CentOS7手动修改IP地址
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS7安装Docker,走上虚拟化容器引擎之路
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题