乐趣区

关于数据库:PolarDBX-致数据库行内人-一-如何有效评测国产数据库的分布式事务

分布式事务评测缘起

近段时间,始终在参加国内金融行业的分布式数据库选型测试工作,围绕银行外部的理论业务场景进行验证。遇到了一个比拟有意思的案例。

后期在联机业务场景测试中,各大数据库厂商在事务测试上都比较顺利,在性能和性能角度都很好的满足了业务要求。但在跑批业务场景的测试中,个别数据库厂商就遇到了分布式事务的一致性问题(会读到分布式事务中间状态的数据),而该厂商通过了行业的各项事务测评认证,因而开展了如何无效评测国产数据库事务一致性的话题,须要大家辩证的思考下。

本文是系列文章的第一篇,介绍第一个重要话题:“数据库的分布式事务”,这也是目前普通用户面对分布式数据库产品介绍问的最多的一个内容,如何无效评测分布式事务也是一个十分重要的能力。致敬同行,咱们将 PolarDB- X 事务架构设计上的一些思考和测试形式,做了整顿和梳理,冀望能对大家更好的了解分布式事务的测试有所帮忙。

这些所谓观点并无谁对谁错之分,仅仅代表咱们的思考。如果你有任何想说的,也欢送在评论区与我探讨。

金融行业通用测评

近年来国内自主可控诉求越来越强烈,国内数据库行业蓬勃发展,诞生了很多守业型公司,国家层面也出台了一系列的数据库评测规范,大体分为集中式和分布式数据库的测评。中国人民银行在 2020 年公布了一个行业标准,《分布式数据库技术金融利用标准 技术架构(JR/T 0203-2020)》对于分布式事务测试的形容:

分布式数据库金融规范,是分布式数据库行业中最残缺的规范之一,给出了对于分布式数据库的事务 ACID 测试的指导性意见。

机构测试设计的测试用例,广泛会采纳模仿数据库故障形式来验证分布式事务,细分一下场景:

  1. 模仿数据库多正本的重启,能够验证数据库事务的持久性 (ACID 中的 D)
  2. 模仿主机的异样故障(断网、IO Hang 等),能够验证数据库事务的原子性 (ACID 中的 A)
  3. 采纳 SQL 交互,设置不同的数据隔离级别,开多个链接,手工验证事务的隔离级别 (ACID 中的 I)
  4. 比方运行 TPC-C 或者 特定的转账测试,在数据导入实现、以及程序运行实现后,运行几条一致性校验的 SQL 来验证数据的一致性 (ACID 中的 C)

整个测评计划更多是围绕事务 ACID,进行了比拟全面的笼罩型验证,但从数据库内核研发的视角视角、以及金融行业理论业务场景的实测来看,还是有肯定的局限性,否则也不会呈现通过分布式数据库金融行业认证,但无奈通过金融行业理论业务场景的测试验收。

PolarDB- X 的设计和思考

事务是所有的根底

先抛开分布式数据库,咱们首先思考一个通用数据库的话题:数据库事务在哪些地方影响了业务应用?咱们拿一个银行的转账场景做一下例子:A 账户余额有 100 元,B 账户余额 0 元,在这个根底上 A 向 B 转账 40 元 复盘常见的业务场景:

  • 在线联机业务,通过事务 ACID 机制,须要保障 A 向 B 转账过程中的数据一致性,满足任意时刻 A 和 B 上的账户总余额为 100
  • 数据库备份和复原,按工夫点的复原 (point-in-time recovery) 目前也是行业的共识需要,咱们须要确保复原进去的备份集数据,满足 A 和 B 上的账户总余额为 100

这也是目前行业测试过程中常见的关注场景,但联合业务场景思考一下,还远不止,比方:

  • 跑批业务,典型的 ETL 机制,通过数据的全量读取和批量写入,为满足业务解决效率,很多数据库厂商会提供旁路导入和导出的机制,同样须要满足事务的总余额为 100。旁路导出例子:select * from user partition(p1) order by id desc limit 10,10,每个分片数据独自做分页排序,缩小分布式的分页排序代价。
  • flashback query,典型的数据疾速复原场景,oracle 基于 MVCC 多版本提供了 AS OF Timestamp 语法,能够疾速读取一个历史的事务数据版本,联合 insert into xxx select xx as of timestamp ’10 分钟前 ’ 能够疾速复原出业务误操作的数据。
  • 读写拆散 or follower read,典型的技术架构场景,比方 paxos/raft 的三正本,很多数据库厂商会提供 follower read 的能力,实质上就是一种读写拆散的架构,同样须要满足事务的总余额为 100。须要留神:事务的一致性读 和 多正本下的数据复制提早带来的数据不统一读,这是两个不同的概念。比方;数据提早只是让咱们读到 1 秒钟之前的事务数据,但 1 秒前的数据读取总余额时也要为 100,不能读到 40 或者 140 的中间状态。
  • 容灾架构(两地三核心、同城 3AZ),典型的容灾架构场景,比方思考两地三核心的极其场景,核心地区挂了,切换到异地机房,异地机房的数据能够有提早(RPO>0),但须要事务粒度的一致性,满足 A 和 B 上的账户总余额为 100。
  • 主备复制 or CDC(mysql binlog 订阅),典型的数据增量复制的场景,常见于数据库的 binlog 增量日志,部署异地多活复制,同样须要须要保障事务的一致性,在外置复制增量数据的状况下,满足 A 和 B 上的账户总余额为 100(查问数据仓库 或者 异构的备库)
  • HTAP 架构,常见的数据库实现为采纳多份数据正本的形式,通过行转列构建异步的列存正本,尽管数据行转列会有提早,但查问到列存正本时同样须要思考事务的一致性,即便读取 1 秒前的数据也要满足总余额时为 100。

大家能够辩证的思考一下,这部分的业务场景在传统的单机数据库事务中是一个默认能力,但在分布式事务中绝不是一个简略的 ACID 机制,还须要有更多的顶层设计,一句话总结:事务是所有的根底,影响重大

举一个反例子来看,常见于传统的分库分表的事务架构,能够通过开源 MySQL 或者 PG 的主备强复制、两阶段的事务提交,能够肯定水平的满足 ACID 的定义,但在遇到其余业务场景会事务统一的局限性:

1. 指定工夫的备份复原,因短少任意工夫点的一致性视图,导致无奈满足一致性复原。变种的办法:定时做高频的一致性快照,比方每各 30 秒备份一次全局沉闷事务链表,能够达到复原到 30 秒的粒度
2. 联机和跑批混合场景,跑批场景读取全量数据过程中,因短少一致性的视图,会有机会读取到事务提交阶段的状态。变种的办法:跑批场景不能做旁路,全量数据拉取都得通过全局沉闷事务链表来判断

分布式事务计划

目前分布式事务常见计划:

简略做一个解读:

  1. XA 协定,全名为 X/Open XA 协定,是一项通用的事务接口标准,最早在 90 年代开始提出,可参考:《Distributed Transaction Processing: The XA Specification》。留神:XA 次要基于 2PC 两阶段提交实现事务的原子性,而分布式下的一致性则须要额定的设计,会呈现读偏斜的问题
  2. GTM,最早起源于 PG-XC 开源数据库,次要是通过 GTM 调配一个事务 ID,通过沉闷事务链表来解决事务的可见性问题。目前常见于 PG 生态,比方 GaussDB,沉闷事务链表在单机数据库中也比拟常见。
  3. TSO/HLC,次要是基于工夫戳技术,参考 Oracle 的 MVCC 多版本设计,每个事务都有 start_ts / end_ts 的工夫戳,通过工夫戳的先后顺序来判断事务的可见性,相比于沉闷事务链表会更轻量。TSO 和 HLC 次要还是对于工夫戳调配算法上的一些差别,目前来看海内数据库重点关注 GEO Partition 带来的多活架构。

PolarDB-X 事务设计上的总结:

一、事务的主链路(在线联机业务的 ACID)

a. 持久性:Paxos 多数派,通过共识协定确保主机故障时 RPO=0,不丢两头数据

b. 原子性:2PC + XA,通过两阶段提交,确保事务的原子性

c. 隔离性 & 一致性:TSO + MVCC,通过工夫戳版本号 + MVCC 多版本,实现跨分片的数据一致性读

对于 PolarDB- X 的一些事务设计,能够参考咱们之前的文章:

  • PolarDB-X 强统一分布式事务原理
  • PolarDB-X 分布式事务的实现(一)
  • PolarDB-X 分布式事务的实现(二)InnoDB CTS 扩大
  • PolarDB-X 分布式事务的实现(三):异步提交优化

二、数据库的备份复原 / flashback query

按工夫点的复原(point-in-time recovery),通常诉求是通过指定一个物理工夫,PolarDB- X 在分布式事务设计上,容许将物理工夫转化为事务的 TSO 工夫戳,通过分布式事务的 TSO 来做数据库的准确复原边界。

flashback query,也算一种非凡的数据恢复场景,通过 AS OF TIMESTAMP 指定一个物理工夫,PolarDB- X 会将物理工夫转化为事务的 TSO 工夫戳,基于分布式事务的 MVCC 多版本来实现对历史版本的数据读取。

比方:分布式事务的 TSO,是有 42 位的物理实际 + 16 位的逻辑工夫戳组成,能够通过位移疾速实现物理工夫戳和 TSO 工夫戳的转化。

参考文档:PolarDB-X 备份复原

三、联机 & 跑批 混合场景

联机场景次要是简略的查问和写入为主的高并发业务,非常容易基于分布式多节点来达到线性扩大。而跑批场景更多是围绕数据的批量解决,须要最大化的利用分布式多节点资源的并行性,冀望跑批也能达到线性扩大的性能。PolarDB- X 跑批场景最佳实际设计上,咱们引入了数据旁路 hint 能力,业务能够通过该 hint 进行疾速的旁路数据导入和导出,随着 DN 节点数量的扩容,整体跑批容量和吞吐能力能够达到线性减少。PolarDB- X 在面向跑批场景的分布式事务设计上,引入业界 oracle 通用的 2PC + MVCC 事务机制

  1. 反对 undo/redo,事务两头数据通过 undo 提前落盘,能够反对 GB 级别的超大事务
  2. 计算存储彻底拆散,MVCC 的事务上下文 (undo/buffer、可见性判断等) 全副落在存储节点 DN 上,计算节点 CN 只承当分布式事务协调者的状态,能够确保在旁路导入导出时数据存储 DN 节点的数据一致性。区别于 Spanner/Percolator 等事务模型、以及基于 GTM 的事务机制。

四、同构多正本(读写拆散、容灾架构场景)

分布式下的多正本,个别会依照 multi-raft/paxos 的进行分组,同一个分组会复用一个多数派日志流,而不同的分组会有独立的多个日志流,每个日志流会因为各自的复制进度产生一些差别。

PolarDB- X 在多正本架构下的事务设计上,引入了强统一读写拆散的机制:

  1. 如下图所示,不同正本会因为日志流的复制进度不同而产生差别,须要在读写拆散拜访正本时进行辨认和解决
  2. 分布式事务的上下文,须要在多正本架构下进行数据事务状态的复制,确保在多正本中事务数据的一致性

五、事务增量日志 (CDC 增量复制、HTAP 行转列)

传统数据库会提供 CDC 机制(Change Data Capture),在 mysql 生态里次要就是 binlog,上游能够通过定于 binlog 来实现增量数据同步或者生产。

PolarDB- X 在事务增量日志设计上,参考了 mysql binlog 的协定和格局:

  1. 记录分布式事务的上下文,比方事务中 DML 的提交程序、事务 TSO 工夫戳等,将这些状态数据长久化到单个 DN 的部分增量日志中
  2. 引入 CDC 全局日志组件,基于分布式事务的 TSO 工夫戳进行多个 DN 部分日志的汇总、合并和排序,生成最终的 binlog 增量日志流

增强版转账测试

PolarDB-X 联合泛滥业务的理论应用场景,咱们在典型银行转账测试的场景根底上,加强了场景正交组合测试,能够帮忙大家更快、更无效的评测分布式事务。阐明:本测试重点针对 ACID 中的一致性 C 场景的测试,事务长久化、原子性的测试,能够复用行业通用测试中的模仿故障来进行验证。

银行转账测试的技术模型形象

一直随机选取两个账户 A/B,而后模仿 A 账户给 B 账户转账。每一笔转账在数据库中如下图所示:

其中,balance 示意余额,version 示意该账户实现了多少笔转账。在备库一致性读的验证中,咱们除了会验证总余额必须统一外,还会先开启事务查问主库所有账户,再开启另一个事务查问备库的所有账户,并确保备库上看到的每个账户 version 值,必须不小于主库中该账户的 version 值,即备库不能产生提早。

内置数据库测试场景

整体测试设计围绕金融行业的典型场景,进行组合验证分布式事务的一致性。比方:模仿联机和跑批场景的组合、跑批场景中直连备库(满足联机和跑批的资源强隔离)、以及跑批场景指定拜访某时刻的数据(比方,拜访历史某时刻的数据,进行数据 ETL 解决后复原误操作的数据)

外置组合测试场景

整体测试设计围绕金融行业的测试规范,通过组合一些额定的人肉操作来验证 PolarDB- X 数据库实例以外的事务一致性。

如何运行测试

PolarDB- X 提供了转账测试工具,通过 docker 一键启动即可疾速实现验证,疾速配置能够适配特定数据库的语法,从而满足对数据库厂商的分布式事务验证。比拟典型的组合场景:联机场景 + 跑批场景 + 备库一致性(将跑批场景的 ETL 调度到备库上,在满足事务一致性下,实现联机和跑批业务的相互隔离)

测试场景设计:

装置数据库

1.PolarDB- X 装置

参考:PolarDB- X 疾速开始

# 应用 virtual environment
python3 -m venv venv
source venv/bin/activate
# 装置 pxd
pip install -i https://mirrors.aliyun.com/pypi/simple/ pxd

创立 polardbx.yaml 文件

version: v1
type: polardbx
cluster:
  name: pxc-tryout-new
  gms:
    image: polardbx/polardbx-engine:latest
    engine: galaxy
    engine_version: "8.0"
    host_group: [127.0.0.1]
    resources:
      mem_limit: 4G
  cn:
    image: polardbx/polardbx-sql:latest
    replica: 1
    nodes:
      - host: 127.0.0.1
    resources:
      mem_limit: 8G
      cpu_limit: 4
  dn:
    image: polardbx/polardbx-engine:latest
    engine: galaxy
    engine_version: "8.0"
    replica: 1
    nodes:
      - host_group: [127.0.0.1, 127.0.0.1, 127.0.0.1]
    resources:
      mem_limit: 8G
  cdc:
    image: polardbx/polardbx-cdc:latest
    replica: 1
    nodes:
      - host: 127.0.0.1
    resources:
      mem_limit: 4G

通过 pxd create 创立实例,倡议主机规格 >=8C32G (压测工具会产生肯定的并发压力)

pxd create -f polardbx.yaml

2. 部署其余数据库(比方:开源的单机 MySQL),作为 PolarDB-X CDC 同步到的上游数据库

# 部署 mysql 8.0.32
docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 -v /etc/localtime:/etc/localtime -d mysql:8.0.32 --default-authentication-plugin=mysql_native_password --gtid_mode=ON_PERMISSIVE --enforce_gtid_consistency=OFF
# 配置 PolarDB-X CDC 和 MySQL 的复制关系,指定 PolarDB- X 的账号和明码
docker exec -it some-mysql  bash
mysql -h127.0.0.1 -uroot -p123456
> CHANGE MASTER TO
    MASTER_HOST='10.0.0.102', 
    MASTER_USER='polardbx_root',
    MASTER_PASSWORD='',
    MASTER_PORT=xxx,
    MASTER_LOG_FILE='binlog.000001',
    MASTER_LOG_POS=4;
# 启动主从复制
> start slave ;
> show slave status;

3. 登录 PolarDB- X 数据库,创立测试库 transfer_test:

基于步骤 1 返回的连贯串,疾速登录 PolarDB-X:

mysql -h127.0.0.1 -uroot -pxxxx
# 创立 PolarDB- X 数据库
> create database transfer_test mode = auto
# 开启 PolarDB- X 的 follower read(正本读)
> set global ENABLE_FOLLOWER_READ=true;

创立测试库后,能够登录单机 MySQL 实例看下是否失常同步了 transfer_test 库,如果看到了库阐明整体复制链路就失常了

测试工具应用

1. 拉取镜像

docker pull polardbx/transfer-test:latest

2. 筹备配置文件 config.toml,假如寄存于 /tmp/transfer-config/config.toml,以下是一个示例:

# 主库的数据源, 格局: '${user_name}:${password}@tcp(${ip}:${port})/${db_name}'
dsn = 'polardbx_root:123456@tcp(127.0.0.1:8527)/transfer_test'
# 账户数量
row_count = 100
# 每个账户的初始余额
initial_balance = 1000
# 建表语句后缀,适配不同的分布式数据库
create_table_suffix = 'PARTITION BY HASH(id) PARTITIONS 4'
# 数据库增强个性:备库强统一读 (查问 follower 正本或者备库场景,是否强制校验写后读的数据一致性)
# 比方:主库写了一条测试记录并更新 version=100,查问路由到备库后,对应的读操作须要确保肯定读到业务测试的写,保障读到的 version>=100
replica_strong_consistency = true
# 模仿转账,结构分布式事务
[transfer_simple]
enabled = true
threads = 10
# 场景 1. 失常在线查问,校验主库的数据一致性
[check_balance]
enabled = true
threads = 2
# 场景 2. 应用 hint 模仿跑批工作,校验主库的数据一致性
[session_hint]
enabled = true
threads = 2
# 场景 3:应用 flashback query 模仿数据恢复,校验主库一致性
[flashback_query]
enabled = true
threads = 2
# flashback 到 (min_seconds, max_seconds) 秒之前
min_seconds = 10
max_seconds = 20
# 场景 4. 查问 follower 正本或者备库,校验正本的数据一致性
[replica_read]
enabled = true
threads = 2
# 加在 SQL 语句前的 hint,对于 PolarDB-X,该 hint 能够强制查问路由到备库
replica_read_hint = '/*+TDDL:SLAVE()*/'
# 备库数据源,对于 PolarDB- X 和主库 dsn 保持一致即可,如果测试其余数据库的备库,能够通过指定备库的连贯串
replica_dsn = 'polardbx_root:123456@tcp(127.0.0.1:8527)/transfer_test'
# 场景 2 和 4 的组合,应用 hint 查问 follower 正本或者备库,校验组合场景的数据一致性
[replica_session_hint]
enabled = true
threads = 2
# 加在 SQL 语句前的 hint,对于 PolarDB-X,该 hint 能够强制查问路由到备库
replica_read_hint = '/*TDDL:SLAVE()*/'
# 备库数据源,对于 PolarDB- X 和主库 dsn 保持一致即可,如果测试其余数据库的备库,能够通过指定备库的连贯串
replica_dsn = 'polardbx_root:123456@tcp(127.0.0.1:8527)/transfer_test'
# 场景 3 和 4 的组合,应用 flashback query 查问 follower 正本或者备库,校验组合场景的数据一致性
[replica_flashback_query]
enabled = true
threads = 2
# 加在 SQL 语句前的 hint,对于 PolarDB-X,该 hint 能够强制查问路由到备库
replica_read_hint = '/*+TDDL:SLAVE()*/'
# 备库数据源,对于 PolarDB- X 和主库 dsn 保持一致即可,如果测试其余数据库的备库,能够通过指定备库的连贯串
replica_dsn = 'polardbx_root:123456@tcp(127.0.0.1:8527)/transfer_test'
# flashback 到 (min_seconds, max_seconds) 秒之前
min_seconds = 10
max_seconds = 20
# 场景 5,校验数据库通过 CDC 同步到上游数据库,通过链接上游数据库,检查数据一致性
[check_cdc]
enabled = true
threads = 2
# 上游 MySQL(或其余 DB)数据源
downstream_dsn = 'root:123456@tcp(127.0.0.1:3306)/transfer_test'

[session_hint] 和 [flashback_query] 都开启,还会额定测试场景 2 和 3 的组合,应用 flashback query + session hint 进行测试,校验组合场景下主库的数据一致性。[replica_session_hint] 和 [replica_flashback_query] 都开启,还会额定测试场景 2、3、4 的组合,应用 flashback query + session hint + 备库读进行测试,校验组合场景下备库的数据一致性。

3. 导入数据,留神替换配置文件寄存目录

docker run --rm -v /etc/localtime:/etc/localtime \
-v #配置文件寄存目录 #:/tmp \
polardbx/transfer-test:latest prepare -config=/tmp/config.toml
示例:docker run --rm -v /etc/localtime:/etc/localtime \
-v /tmp/transfer-config:/tmp \
polardbx/transfer-test:latest prepare -config=/tmp/config.toml

如果上一步配置了 127.0.0.1 作为数据库 ip,容器运行须要加上 –network host 参数。

4. 察看测试后果 (如果满足事务一致性会继续运行,不满足一致性会立刻终止)

2023/04/13 08:45:27 all partitions: p1, p2, p3, p4
2023-04-13T08:45:27.364Z    INFO    transfer/app.go:30  App start
2023-04-13T08:45:37.364Z    INFO    transfer/main.go:349    TPS check_balance: 30.90
2023-04-13T08:45:37.365Z    INFO    transfer/main.go:349    TPS flashback_query: 0.00
2023-04-13T08:45:37.365Z    INFO    transfer/main.go:349    TPS flashback_session_hint: 0.00
2023-04-13T08:45:37.365Z    INFO    transfer/main.go:349    TPS session_hint: 16.50
2023-04-13T08:45:37.365Z    INFO    transfer/main.go:349    TPS transfer_simple: 180.40
2023-04-13T08:45:47.364Z    INFO    transfer/main.go:349    TPS check_balance: 0.00
2023-04-13T08:45:47.364Z    INFO    transfer/main.go:349    TPS flashback_query: 0.00
2023-04-13T08:45:47.364Z    INFO    transfer/main.go:349    TPS flashback_session_hint: 0.00
2023-04-13T08:45:47.364Z    INFO    transfer/main.go:349    TPS session_hint: 0.00
2023-04-13T08:45:47.364Z    INFO    transfer/main.go:349    TPS transfer_simple: 0.00
2023-04-13T08:45:57.364Z    INFO    transfer/main.go:349    TPS check_balance: 58.20
2023-04-13T08:45:57.364Z    INFO    transfer/main.go:349    TPS flashback_query: 157.90

运行测试过程中,数据库 show full processlist 的截图:

通用数据库场景的验证

最初

分布式事务的评测模型,目前行业的通用评测重点聚焦于模仿数据库故障(验证了事务的原子性和持久性),数据库事务的隔离性和一致性测试,次要也以 SQL 性能隔离级别的功能测试为主,短少比拟强壮且无效的测试方法。同时在面向理论业务场景测试中,后期大家关注焦点也是常见的联机事务 ACID(很多数据库厂商的事务计划也是针对性的设计),最初在关联了上下游业务后才发现国产数据库在事务机制上的缺点性。

最初,从数据库内核研发的角度来看,分布式事务的一致性,会作为十分重要的根底能力,除了数据库内核的 ACID 机制外,同时也会波及:读写拆散多正本、备份复原一致性、两地三核心容灾切换、CDC 事务增量日志等多个场景的一致性问题,不要迷恋任何国产数据库的介绍,能够通过更有技巧性的事务一致性测试来验证(能够辅助常见的主机故障模拟),基于联机和跑批的组合场景,实现国产数据库事务的无效评测。

本文作者:七锋、勿遮

原文链接

本文为阿里云原创内容,未经容许不得转载。

退出移动版