背景
在分布式数据库的选型和测试过程中,通常需要关注分布式事务在高可用场景下的一致性和 RPO=0 的容灾技术实现。分布式事务需要能影响多张表的多条记录,实现多表事务和跨节点高可用的验证。
BANK 程序
BANK 程序(链接: https://pan.baidu.com/s/1M14Kf0ULGdbkoMZj0iOvDA?pwd=p93k 提取码: p93k)是一个简化转账模型的并发程序。初始化后,会创建流水 record 表和账务 account 表,流水表对应转账过程中的金额变化和事务时间( tso 列,实际函数为 time.Now().UnixNano() ,即执行节点的纳秒时间),account 表记录最终的账户金额和事务时间(tso 列)。
MySQL [test]> show tables;
+----------------+
| Tables_in_test |
+----------------+
| accounts |
| record |
+----------------+
2 rows in set (0.00 sec)
MySQL [test]> select * from record order by tso desc limit 1;
+---------+-------+--------------+--------------------+------------+------------------+--------+---------------------+
| from_id | to_id | from_balance | from_balance_after | to_balance | to_balance_after | amount | tso |
+---------+-------+--------------+--------------------+------------+------------------+--------+---------------------+
| 384 | 172 | 1226 | 284 | 1915 | 2857 | 942 | 1661398293659372259 |
MySQL [test]> select * from accounts order by tso desc limit 10;
+-----+---------+---------------------------------------------------------------------------------------------------+---------------------+
| id | balance | remark | tso |
+-----+---------+---------------------------------------------------------------------------------------------------+---------------------+
| 384 | 284 | abcdefghijklmnopqrs | 1661398293659372259 |
| 172 | 2857 | abcdefg | 1661398293659372259 |
使用方法
程序的参数如下:
[root@iZuf6d7xln13sovvijl68rZ ~]# ./bank --help
Usage of ./bank:
-accounts int
the number of the bank accounts (default 1000000)
-create
create new tables for test
-driver string
database driver name, support 'mysql', 'postgres' (default "mysql")
-dsn string
data source name
MySQL: root:@tcp(127.0.0.1:4000)/test
PostgreSQL: postgres://root:@127.0.0.1:5432/postgres?sslmode=disable
(default "root:@tcp(127.0.0.1:4000)/test")
-insert
insert data for test
-interval duration
verify interval (default 2s)
-threads int
threads count (default 10)
初次执行
./bank -dsn 'root:password@tcp(172.16.5.31:34000)/test' -create -insert -accounts 100
配好 DSN 后,第一次执行程序添加 -create -insert 参数会自动初始化数据,初始完成后开始转账,就随机挑选两个账户,select for update 锁定账户,然后 update 账户并记录流水,然后提交事务。
中断后可后续执行
./bank -dsn 'root:password@tcp(172.16.5.31:34000)/test' -accounts 100
bank 程序除转账交易的并发线程(默认10个)外,有一个 goroutine,到达校验 interval (默认 2 秒)时间就会算一下账户总金额是不是保持一致,不是预期的就退出,说明出现事务破裂。
分布式事务和 RAFT 强同步的验证场景
在分布式事务的执行过程中,涉及多个节点参与,在此过程中如果出现节点异常就有可能出现分布式事务的异常处理。
以分库分表中间件中常用的基于 MySQL XA 的分布式事务二阶段提交为例:
- 在 xa 事务提交过程中,如果出现 set 节点不可用进行切换,就会有部分节点未提交和事务不一致情况发生。从下图中可以看到,setx 库提交成功时,sety 库如果故障未提交成功,需要有额外的补偿机制 mysqlagent 发现各个 set 的提交情况和全局的 gtid log 不一致,需要补偿处理。
- 在 xa 事务提交过程中,由于在没有事务版本控制,不同节点的提交时间存在先后,有可能会出现事务破裂。由下图中可以看到,如果 XA 事务在 DB-1 和 DB -2 提交, DB-2 晚于 DB-1 提交,如果在提交间隙中查看总金额,就会发现总金额不一致。
TiDB 的 percolator 事务模型中二阶段的提交就是 primary key 改成 commit 的原子操作,通过 Raft 多数派共识容忍节点故障,可以避免以上的情况。
ticdc 表间事务的验证场景
ticdc 的早期版本保证复制过程中表内事务一致,但是受大事务延迟影响。v6.1.1 版本起,可以通过配置 sink uri 参数 transaction-atomicity 来控制 TiCDC 是否拆分单表事务,可选值是 none 和 table。当选择 none 时,TiCDC 会拆分单表事务,也就是说可选择不保证表内事务是一致的,当选择 table 时,仍保证单表事务的原子性。v6.1.3 版本起,将 transaction-atomicity 的默认值从 table 修改为 none。
撇弃表内事务一致通过多并发提高大事务投递性能的基础上, TiCDC 通过 syncpoint 功能保证复制过程每隔多长时间定期一致和 redo 持久化功能保证 redo 数据应用后最终一致。BANK 程序中 record 表和 account 表的同一事务(tso 列) 的交易记录和交易账户能对应,通过最新事务的数据可以验证 ticdc 的表间事务验证能力,是否实现过程定期一致和应用最终一致。
不同场景下的校验语句使用方法
- 节点高可用情况时验证事务一致性。如果总金额不一致,说明发生事务破裂。
集群高可用后执行如下语句,balance 总金额应该不变,record 表和 accounts 表最新记录应该一致。
select sum(balance) from accounts;
select * from record where tso=(select max(tso) from record);
select * from accounts where tso=(select max(tso) from accounts);
- 验证容灾场景的 RPO=0 数据一致性。如果最新的 TSO 不一致,说明不是 RPO=0。
上游集群和下游集群分别执行如下语句,balance 总金额应该不变,最新记录应该一致。
select sum(balance) from accounts;
select * from record where tso=(select max(tso) from record);
select * from accounts where tso=(select max(tso) from accounts);
- 验证逻辑复制场景的历史数据一致性。如果历史 TSO 的数据不一致,说明复制过程数据不一致。
上游集群中执行如下语句。
set tidb_snapshot="redo apply ts";
select sum(balance) from accounts;
select * from record where tso=(select max(tso) from record);
select * from accounts where tso=(select max(tso) from accounts);
下游集群中执行如下语句 ,与上游历史状态下的记录应该一致。
select sum(balance) from accounts;
select * from record where tso=(select max(tso) from record);
select * from accounts where tso=(select max(tso) from accounts);
总结
BANK 程序可以在金融行业等需要验证事务一致的情况下使用,在引入新产品新架构评估的过程,使用 bank 程序非常有利于分析所选产品的一致性和可靠性。TiDB 数据库能通过高可用等测试场景向用户展示本身的健壮性。