内容简介:对于分布式场景下的spider集群主备切换 , flush tables with read lock已经不能保证数据的一致性了,为此需要实现一种事务级的锁,保证分布式场景下的主备安全切换。本文依次通过三种思路实现事务锁,不断优(踩)化(坑)。最终根据选型3的思路,实现MDL事务锁。spider是IEG DBA提供的MySQL分布式关系型数据库解决方案,主要特点包括:透明分库分表、高可用的MySQL集群服务,透明及在线的扩容及缩容;使得开发者可以仅专注于业务逻辑的开发及运营,无需编写数据分片逻辑,在海量用户
对于分布式场景下的spider集群主备切换 , flush tables with read lock已经不能保证数据的一致性了,为此需要实现一种事务级的锁,保证分布式场景下的主备安全切换。本文依次通过三种思路实现事务锁,不断优(踩)化(坑)。最终根据选型3的思路,实现MDL事务锁。
一. 痛点
1. spider集群简介
spider是IEG DBA提供的 MySQL 分布式关系型数据库解决方案,主要特点包括:透明分库分表、高可用的MySQL集群服务,透明及在线的扩容及缩容;使得开发者可以仅专注于业务逻辑的开发及运营,无需编写数据分片逻辑,在海量用户并发情况下,也无须关心DB存储层的负载压力。spider集群架构图如下:
2. spider集群主备切换
目前业务主要使用 flush table with read lock(ftwrl)
来实现 spider
集群的主备切换。具体流程为
1.update mysql.server 2.ftwrl后flush privileges 3.unlock tables
对于不开启事务的场景,上述步骤是没有问题的。如果是事务场景呢?
session A: begin; insert into table_1(a) values(1); update_1 : update table_1 set a=a+1; session B: update mysql.servers set Port=Port_dr; flush table with read lock; flush privileges; 此时A如果进行DML操作,也不能commit。 session B: unlock tables; session A: update_2 : update table_1 set a=a+1; commit;
上述场景 update_1
在 gamedb_1
上执行, update_2
在 dr_1
上执行。
对于不开启事务, update_1
执行完, gamedb_1
已经将 binlog
数据同步给 dr_1
,没有问题,最后 a=2
对于普通事务, update_1
执行完,没有提交,数据不会同步到 dr_1
,最后 a=1
对于分布式事务, 无法正常的提交
二. 技术选型
在上述事务切换场景中, ftwrl
的作用是阻止事务的 提交 。而我们的需求是实现一种操作: flush table with write lock
,阻止事务的 开启 。即:保证其他事务结束后,才能加锁成功。加锁成功后,阻止新的事务开启。经过实践与比较,最终采用选型3的思路,实现了事务锁。
选型1--基于互斥锁实现事务锁
a.所有事务在开始,都需要获得S锁; b.在主备切换前,执行flush table with write lock,尝试加X锁。如果没加成功,通过条件变量和互斥锁维护一种状态,阻止其他线程获得S锁,避免X锁饿死 c.主备互切结束,执行unlock tables,释放写锁。
不足:
S锁释放后,通知各个等待的线程是广播行为,并不能保证先到先得。
改进:
可以借鉴MDL的实现原理,保证锁资源先到先得
选型2--基于MDL锁,改变DML select语义
关于MDL锁的基础知识可以了解上一篇文章: 浅析MYSQL MDL锁 。 首先思考一个问题:为什么 update
执行后可以 ftwrl
呢? 跟踪源码发现, update
操作刚开始加的是 语句级 的 MDL IX锁 ,语句执行完就释放,后面加的事务级的锁并不会阻塞 ftwrl
的 MDL S锁 。如果将 update
的 MDL IX锁 ,改为 事物级的 ,那是不是就可以阻塞 ftwrl
了。
基于这一点考虑,可以参考了 ftwrl
的实现原理,
但是如何让事务中的 select
操作阻塞 ftwrl
呢?
是不是通过 flush table with write lock
, 改变 DML select
的语义呢?
1.让flush table with write lock,获得global EXPILICT MDL X锁。 2.将DML操作的语句级 MDL IX锁,改为事务级的 3.select操作开始时,加一种只于MDL X锁互斥的global锁:SWITCH_IS
SWITCH_IS
的兼容性如下:
object
上已持有锁和请求锁的兼容性矩阵, grant_matrix
:
| Type of active |
Request | scoped lock |
type | IS(*) IX S X SWITCH_IS |
---------+-----------------------------+
IS | + + + + + |
IX | + + - - + |
S | + - + - + |
X | + - - - - |
SWITCH_IS | + + + - + |
object
上等待锁和请求锁的优先级矩阵 , wait_matrix
:
| Pending |
Request | scoped lock |
type | IS(*) IX S X SWITCH_IS |
---------+-------------------------------+
IS | + + + + + |
IX | + + - - + |
S | + + + - + |
X | + + + + - |
SWITCH_IS | + + + - + |
Here: "+" -- means that request can be satisfied "-" -- means that request can't be satisfied and should wait
不足:
这样设计,似乎可以实现需求。但是考虑下面的场景:
session A: begin; select * from test; session B: flush table with write lock;------------hang住
此时 session B
会 hang
住,因为 session A
获得了 SWITCH_IS
,与 X
互斥, X
锁进入等待队列。如果此时session A执行 update test set c2=1 where id=1
, 会发生 死锁 。原因如下:
1.flush table with write lock的X锁等待session A释放SWITCH_IS 2.DML操作需要获得 global IX锁. 3.首先判断grant队列,发现只存在SWITCH_IS锁,gant矩阵中并不互斥 4.于是在判断wait队列,存在X锁。在wait矩阵中IX与X互斥,等待session B释放X,循环等待,导致死锁。
改进
虽然可以在上述场景中加条件判断避免死锁,但改变 DML SELECT
语义风险较大,可能引入其他问题,可以参考方案一:事物开启的时候加锁,事物结束释放锁。
选型3—基于MDL,实现事务锁
综合选型1和选型2的经验,增加两种彼此互斥的 MDL
锁: SWITCH_S
和 SWITCH_X
开始事务,加SWITCH_S,事务结束释放SWITCH_S 执行flush table with write lock加SWITCH_X,unlock tables释放SWITCH_X
3.1 锁粒度
为了将风险降到最低,选择粒度最小目标锁: USER_LOCK
,用法可以参考
https://dev.mysql.com/doc/internals/en/user-level-locks.html
3.2 兼容性
为了不影响其他锁, SWITCH_S
与 SWITCH_X
之间排斥,兼容其他锁
object
上已持有锁和请求锁的兼容性矩阵, grant_matrix
:
Request | Granted requests for lock |
type | S SH SR SW SU SRO SNW SNRW X SWITCH_S SWITCH_X |
----------+-----------------------------------------------------------+
S | + + + + + + + + - + + |
SH | + + + + + + + + - + + |
SR | + + + + + + + - - + + |
SW | + + + + + - - - - + + |
SU | + + + + - + - - - + + |
SRO | + + + - + + + - - + + |
SNW | + + + - - + - - - + + |
SNRW | + + - - - - - - - + + |
X | - - - - - - - - - + + |
SU -> X | - - - - 0 - 0 0 0 + + |
SNW -> X | - - - 0 0 - 0 0 0 + + |
SNRW -> X | - - 0 0 0 0 0 0 0 + + |
SWITCH_S | + + + + + + + + + + - |
SWITCH_X | + + + + + + + + + - - |
object
上等待锁和请求锁的优先级矩阵 , wait_matrix
:
Request | Pending requests for lock |
type | S SH SR SW SU SRO SNW SNRW X SWITCH_S SWITCH_X |
----------+----------------------------------------------------------+
S | + + + + + + + + - + + |
SH | + + + + + + + + + + + |
SR | + + + + + + + - - + + |
SW | + + + + + + - - - + + |
SU | + + + + + + + + - + + |
SRO | + + + - + + + - - + + |
SNW | + + + + + + + + - + + |
SNRW | + + + + + + + + - + + |
X | + + + + + + + + + + + |
SU -> X | + + + + + + + + + + + |
SNW -> X | + + + + + + + + + + + |
SNRW -> X | + + + + + + + + + + + |
SWITCH_S | + + + + + + + + + + - |
SWITCH_X | + + + + + + + + + + + |
Here: "+" -- means that request can be satisfied "-" -- means that request can't be satisfied and should wait
细心的你可能已经发现两个矩阵中 SWITCH_S
与 SWITCH_X
之间的相对关系和S和X的一致。
对于 grant
矩阵很好理解,一般的互斥锁都是这么设计的。对于 wait
矩阵:
a. 为什么 SWITCH_X
兼容 SWITCH_S
?
如果不兼容,考虑以下场景:
session A:begin; session B:flush table with write lock;------hang住 session C:begin;----------------------------hang住
此时 session B
与 grant
矩阵中的 SWITCH_S
互斥,进入等待队列。 session C
与 wait
矩阵中的 SWITCH_X
互斥,进入等待队列。 session A
执行完 commit
,会尝试着唤醒等待队列的 SWITCH_X
,但是 SWITCH_X
与 wait
矩阵中的 SWITCH_S
互斥,无法得得到锁。同理 session C
也无法得到锁,会一直 hang
住。
b. 为什么 SWITCH_X
兼容 SWITCH_X
?为什么 SWITCH_S
兼容 SWITCH_S
?
如果不兼容,考虑如下场景:
session A:begin; session B:flush table with write lock;------hang住 session C:flush table with write lock;------hang住
此时 session B
和 session C
都与 grant
矩阵中的 SWITCH_S
互斥,进入等待队列。 session A
执行完 commit
,会尝试着唤醒等待队列的 SWITCH_X
,但是 SWITCH_X
与 wait
矩阵中的 SWITCH_X
互斥,无法得得到锁。 session B
和 session C
无法得到锁,会一直 hang
住。仔细观察 wait
矩阵可以发现,所有的锁都与自己兼容。
3.3 锁持有时间
3.3.1 对于SWITCH_X:
只需一种类型: EXPLICIT
执行 flush table with write lock 加 EXPLICIT USER_LOCK 执行 unlock tables 释放锁
3.3.2 对于SWITCH_S:
分为 事务级 和 EXPLICIT
a. 需要事务级锁的场景:
-
当
autocommit=1
-
对于不显示指定
begin
的普通sql
在
sql_parse.cc
的switch_commad()
前,增加 事务级锁 ,这里的事务级锁声明周期等同于statement
-
对于会隐式提交的
sql
如DDL
,flush tables
会在隐式提交完,释放之前事务的锁后,加 事务级 锁
-
当
autocommit=0
,在隐式开始事务的函数,增加 事务级 锁void trans_register_ha(THD *thd, bool all, handlerton *ht_arg)
-
显示指定
begin
, 在开启事务的函数,增加 事务级 锁bool trans_begin(THD *thd, uint flags)
-
对于
lock table test read/write
:由于
lock
操作自身加的事务级的锁,但是执行commit
并不会释放锁。只有执行unlock
或者begin
才会释放。所以给lock
操作增加 事务级SWITCH_S锁
b. 需要EXPLICIT锁的场景:
-
对于
sql:select get_lock("func","time") ;select release_lock("func")
由于需要显示释放锁,所以需要 加EXPLICIT锁 ,对应函数:
longlong Item_func_get_lock::val_int()
longlong Item_func_release_lock::val_int()
-
对于
sql:handler handler_table open; handler handler_table close;
由于需要显示释放锁,所以需要 加EXPLICIT锁 ,对应函数:
bool mysql_ha_open(THD *thd, TABLE_LIST *tables, SQL_HANDLER *reopen)
static void mysql_ha_close_table(SQL_HANDLER *handler)
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 分布式事务之事务实现模式与技术(四)
- 嵌套事务、挂起事务,Spring 是怎样给事务又实现传播特性的?
- 实现多数据源事务
- 基于 XA 事务协议,用代码实现一个二阶段分布式事务
- 你可能知道事务的四大特性,但是你不一定知道事务的实现原理
- Spring事务用法示例与实现原理
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
区块链技术驱动金融
阿尔文德·纳拉亚南、约什·贝努、爱德华·费尔顿、安德鲁·米勒、史蒂文·戈德费德 / 林华、王勇 / 中信出版社,中信出版集团 / 2016-8-25 / CNY 79.00
从数字货币及智能合约技术层面,解读了区块链技术在金融领域的运用。“如果你正在寻找一本在技术层面解释比特币是如何运作的,并且你有一定计算机科学和编程的基本知识,这本书应该很适合你。” 《区块链:技术驱动金融》回答了一系列关于比特币如何运用区块链技术运作的问题,并且着重讲述了各种技术功能,以及未来会形成的网络。比特币是如何运作的?它因何而与众不同?你的比特币安全吗?比特币用户如何匿名?区块链如何......一起来看看 《区块链技术驱动金融》 这本书的介绍吧!