两副本 TiDB + 1节点 TiArbiter + HAProxy 负载均衡
92% 性能保持率与近零 RPO:TiKV+TiFlash 协同的底层逻辑
个人简介
作者: ShunWah
公众号: "顺华星辰运维栈"主理人。持有认证: OceanBase OBCA/OBCP、MySQL OCP、OpenGauss、崖山 DBCA、亚信 AntDBCA、翰高 HDCA、GBase 8a | 8c | 8s、Galaxybase GBCA、Neo4j Graph Data Science Certification、NebulaGraph NGCI & NGCP、东方通 TongTech TCPE 等多项权威认证。
获奖经历: 崖山YashanDB YVP成员,墨天轮MVP,在OceanBase&墨天轮征文大赛、OpenGauss、TiDB、YashanDB、Kingbase、KWDB 征文等赛事中多次斩获一、二、三等奖,原创技术文章常年被墨天轮、CSDN、ITPUB 等平台首页推荐。
- 公众号_ID:顺华星辰运维栈
- CSDN_ID: shunwahma
- 墨天轮_ID:shunwah
- ITPUB_ID: shunwah
- IFClub_ID:shunwah

在中小型业务场景中,数据库的资源利用率和故障恢复能力是架构设计的核心考量。TiDB 7.1 引入的两副本敏捷模式(
kind: fusion)以其"
部署节点少、资源利用率高"的特点成为优选方案,但该方案在实际生产环境中的可靠性仍需验证。
本文将基于真实的两节点部署架构,通过 单节点宕机故障演练和 双节点并发压测两个核心场景,全面验证 TiDB 两副本敏捷模式在故障恢复能力和并发处理能力方面的表现。测试过程包含详细的命令操作、预期结果对比以及问题排查方法,为生产环境落地提供可复现的完整参考。
在中小型业务场景中,TiDB 两副本敏捷模式(
kind: fusion)因"部署节点少、资源利用率高"成为优选,但需解决两个核心问题:
本测试基于实际部署架构(无 VIP), 细化每一步操作命令与预期结果,验证 TiArbiter 仲裁服务的故障恢复效果,以及双节点的并发处理能力,为生产落地提供可复现的测试依据。
| 节点 IP | 部署组件 | 硬件配置 | 核心标识 | 网络端口 | 数据/部署目录 |
|---|---|---|---|---|---|
| 192.168.2.122 | PD/TiDB/TiKV | 2C4G | dc=dc1(副本隔离标签) | TiDB:4000、TiKV:20160、PD:2379 | 数据:/data/pingkai/tidb-data
部署:/data/pingkai/tidb-deploy |
| 192.168.2.131 | PD/TiDB/TiKV + TiArbiter | 2C4G | dc=dc2(副本隔离标签) | TiDB:4000、TiKV:20160、PD:2379、TiArbiter:4320 | 数据:/data/pingkai/tidb-data
部署:/data/pingkai/tidb-deploy |
| 压测客户端 | SysBench/MySQL Client | 2C4G | 发起压测与业务验证 | - | 脚本存放:/opt/sysbench-scripts |
topology.yaml 关键配置(确保两副本仲裁与敏捷模式生效):
global: kind: fusion # 启用敏捷模式,允许PD/TiDB/TiKV同机部署 user: "tidb" # 集群运行用户(需提前在所有节点创建) ssh_port: 22 # 节点SSH端口(确保所有节点SSH互通) deploy_dir: "/data/pingkai/tidb-deploy" # 组件二进制与配置存放目录 data_dir: "/data/pingkai/tidb-data" # 数据持久化目录(PD/TiKV数据) listen_host: 0.0.0.0 # 服务监听所有网卡(允许跨节点访问) arch: "amd64" # 适配x86_64架构(与节点CPU一致)server_configs: pd: # 两副本基础配置 replication.max-replicas: 2 # 每个Region仅2个数据副本 replication.location-labels: ["dc"] # 按"dc"标签隔离副本(避免单节点故障丢失副本) replication.enable-placement-rules: "true" # 启用Placement Rules调度规则(仲裁依赖) replication.isolation-level: "dc" # 副本隔离级别与location-labels一致 # TiArbiter仲裁配置(核心!放在schedule层级下,避免PD识别错误) schedule: even-replicas.enable: true # 启用两副本仲裁模式 even-replicas.downgrade-timeout: "15s" # 故障降级超时(15s未恢复则触发仲裁) even-replicas.arbitration.type: "service" # 仲裁类型:独立TiArbiter服务 even-replicas.arbitration.endpoints: ["http://192.168.2.131:4320"] # TiArbiter地址(需提前启动) tikv: # 关闭Region休眠(两副本场景下加速故障恢复,官方推荐) raftstore.hibernate-regions: false # 优化Raft日志与快照参数(减少故障恢复耗时) raftstore.snap-max-write-bytes: "10MB" # 快照最大写入字节(避免带宽占用过高) raftstore.raft-log-gc-count-limit: 10000 # Raft日志GC上限(减少日志堆积)
在 压测客户端执行以下命令,确保工具可用:
# 1. 安装SysBench(1.0.20版本,适配TiDB)yum install -y sysbench # CentOS/RHEL系统;Ubuntu用apt install sysbench# 2. 安装MySQL Client(用于业务验证)yum install -y mysql-client# 3. 验证版本(确保符合要求)sysbench --version # 预期输出:sysbench 1.0.20
实际执行结果:
[root@worker3 ~]# sysbench --versionsysbench 1.0.17 [root@worker3 ~]#

mysql --version # 预期输出:mysql Ver 8.0.x 或 5.7.x
实际执行结果:
[root@worker3 ~]# mysql --versionmysql Ver 8.0.42 for Linux on x86_64 (MySQL Community Server - GPL) [root@worker3 ~]#

在 压测客户端执行,创建测试库与 100 万条数据(模拟真实业务数据量):
# 步骤1:创建测试库(连接任一TiDB节点,此处选192.168.2.122)# -h:TiDB节点IP;-P:TiDB端口;-u:用户名;-p:提示输入密码(若未设密码可省略)mysql -h 192.168.2.122 -P 4000 -u root -p -e " CREATE DATABASE IF NOT EXISTS test_db; # 创建名为test_db的测试库 USE test_db; # 提前创建sbtest1表(后续SysBench会自动填充数据,此处仅确认库存在) CREATE TABLE IF NOT EXISTS sbtest1 ( id INT NOT NULL AUTO_INCREMENT, k INT NOT NULL DEFAULT 0, c CHAR(120) NOT NULL DEFAULT '', pad CHAR(60) NOT NULL DEFAULT '', PRIMARY KEY (id), KEY k (k) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; "# 执行后输入TiDB root密码(若未设置密码,直接按回车即可)
实际执行结果:
[root@worker3 ~]# mysql -h 192.168.2.122 -P 4000 -u root -p -e "> CREATE DATABASE IF NOT EXISTS test_db; # 创建名为test_db的测试库> USE test_db; > # 提前创建sbtest1表(后续SysBench会自动填充数据,此处仅确认库存在)> CREATE TABLE IF NOT EXISTS sbtest1 ( > id INT NOT NULL AUTO_INCREMENT, > k INT NOT NULL DEFAULT 0, > c CHAR(120) NOT NULL DEFAULT '', > pad CHAR(60) NOT NULL DEFAULT '', > PRIMARY KEY (id), > KEY k (k) > ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; > " Enter password: [root@worker3 ~]#

# 步骤2:用SysBench初始化数据(10张表,每张10万条,共100万条)# --table-size:每张表的数据量;--tables:表数量;--db-driver:数据库驱动sysbench oltp_read_write \ --mysql-host=192.168.2.122 \ --mysql-port=4000 \ --mysql-user=root \ --mysql-password="+6-R2As_19t48BXgu^" \ --mysql-db=test_db \ --table-size=100000 \ --tables=10 \ --db-driver=mysql \ prepare
实际执行结果:
[root@worker3 ~]# sysbench oltp_read_write \> --mysql-host=192.168.2.122 \ > --mysql-port=4000 \ > --mysql-user=root \ > --mysql-password="+6-R2As_19t48BXgu^" \ > --mysql-db=test_db \ > --table-size=100000 \ > --tables=10 \ > --db-driver=mysql \ > prepare sysbench 1.0.17 (using system LuaJIT 2.0.4) Creating table 'sbtest1'... Inserting 100000 records into 'sbtest1'Creating a secondary index on 'sbtest1'... Creating table 'sbtest2'... Inserting 100000 records into 'sbtest2'Creating a secondary index on 'sbtest2'... Creating table 'sbtest3'... Inserting 100000 records into 'sbtest3'Creating a secondary index on 'sbtest3'... Creating table 'sbtest4'... Inserting 100000 records into 'sbtest4'Creating a secondary index on 'sbtest4'... Creating table 'sbtest5'... Inserting 100000 records into 'sbtest5'Creating a secondary index on 'sbtest5'... Creating table 'sbtest6'... Inserting 100000 records into 'sbtest6'Creating a secondary index on 'sbtest6'... Creating table 'sbtest7'... Inserting 100000 records into 'sbtest7'Creating a secondary index on 'sbtest7'... Creating table 'sbtest8'... Inserting 100000 records into 'sbtest8'Creating a secondary index on 'sbtest8'... Creating table 'sbtest9'... Inserting 100000 records into 'sbtest9'Creating a secondary index on 'sbtest9'... Creating table 'sbtest10'... Inserting 100000 records into 'sbtest10'Creating a secondary index on 'sbtest10'... [root@worker3 ~]#

# 步骤3:验证数据初始化成功(查询sbtest1表的总行数)mysql -h 192.168.2.122 -P 4000 -u root -p -e " USE test_db; SELECT COUNT(*) AS total_rows FROM sbtest1; "
实际执行结果:
[root@worker3 ~]# mysql -h 192.168.2.122 -P 4000 -u root -p -e "> USE test_db; > SELECT COUNT(*) AS total_rows FROM sbtest1; > " Enter password: +------------+ | total_rows | +------------+ | 100000 | +------------+ [root@worker3 ~]#

在
压测客户端创建
business-check.sh 脚本,用于监控故障期间业务连续性(每秒执行 1 次更新,记录结果):
# 步骤1:创建脚本文件(存放路径:/opt/sysbench-scripts/business-check.sh)mkdir -p /opt/sysbench-scripts # 先创建脚本目录
实际执行结果:
[root@worker3 ~]# mkdir -p /opt/sysbench-scripts [root@worker3 ~]# cd /opt/sysbench-scripts [root@worker3 sysbench-scripts]#

# 步骤2:写入以下脚本内容(按实际密码修改)vim /opt/sysbench-scripts/business-check.sh
脚本内容:
#!/bin/bash# 业务验证脚本:每秒更新sbtest1表的id=1记录,输出时间与执行结果# 需替换为你的TiDB密码(若未设密码,删除--password参数)TiDB_PASSWORD="+6-R2As_19t48BXgu^"# 循环执行(无限循环,直到手动停止)while true; do
# 记录当前时间(格式:时:分:秒)
CURRENT_TIME=$(date "+%H:%M:%S")
# 执行更新操作:id=1的记录k值+1,并查询影响行数(ROW_COUNT()=1表示成功)
UPDATE_RESULT=$(mysql -h 192.168.2.122 -P 4000 -u root --password="${TiDB_PASSWORD}" -D test_db -N -e "
UPDATE sbtest1 SET k = k + 1 WHERE id = 1;
SELECT ROW_COUNT(); # 返回影响行数(1=成功,0=无匹配,NULL=失败)
" 2>&1) # 2>&1:将错误输出重定向到标准输出,便于捕获失败原因
# 解析结果,判断业务状态
if echo "${UPDATE_RESULT}" | grep -q "1"; then
# 成功:获取当前k值(便于后续验证数据连续性)
CURRENT_K=$(mysql -h 192.168.2.122 -P 4000 -u root --password="${TiDB_PASSWORD}" -D test_db -N -e "
SELECT k FROM sbtest1 WHERE id = 1;
") echo "[${CURRENT_TIME}] 业务正常 | update sbtest1 set k=k+1 where id=1 → 成功 | 当前k值:${CURRENT_K}"
else
# 失败:输出错误原因(如超时、连接拒绝)
echo "[${CURRENT_TIME}] 业务中断 | update sbtest1 set k=k+1 where id=1 → 失败 | 原因:${UPDATE_RESULT}"
fi
# 每秒执行1次(避免频繁请求占用资源)
sleep 1done
实际执行结果:
[root@worker3 sysbench-scripts]# vim /opt/sysbench-scripts/business-check.sh[root@worker3 sysbench-scripts]#

# 步骤3:赋予脚本可执行权限(否则无法运行)chmod +x /opt/sysbench-scripts/business-check.sh
实际执行结果:
[root@worker3 sysbench-scripts]# lsbusiness-check.sh [root@worker3 sysbench-scripts]# chmod +x /opt/sysbench-scripts/business-check.sh[root@worker3 sysbench-scripts]# lsbusiness-check.sh [root@worker3 sysbench-scripts]#

# 步骤4:验证脚本可正常执行(执行10秒后按Ctrl+C停止)/opt/sysbench-scripts/business-check.sh
实际执行结果:
[root@worker3 sysbench-scripts]# /opt/sysbench-scripts/business-check.shmysql: [Warning] Using a password on the command line interface can be insecure. [16:10:16] 业务正常 | update sbtest1 set k=k+1 where id=1 → 成功 | 当前k值:49930 mysql: [Warning] Using a password on the command line interface can be insecure. [16:10:17] 业务正常 | update sbtest1 set k=k+1 where id=1 → 成功 | 当前k值:49931 mysql: [Warning] Using a password on the command line interface can be insecure. [16:10:18] 业务正常 | update sbtest1 set k=k+1 where id=1 → 成功 | 当前k值:49932 mysql: [Warning] Using a password on the command line interface can be insecure. [16:10:20] 业务正常 | update sbtest1 set k=k+1 where id=1 → 成功 | 当前k值:49933 mysql: [Warning] Using a password on the command line interface can be insecure. [16:10:21] 业务正常 | update sbtest1 set k=k+1 where id=1 → 成功 | 当前k值:49934 mysql: [Warning] Using a password on the command line interface can be insecure. [16:10:22] 业务正常 | update sbtest1 set k=k+1 where id=1 → 成功 | 当前k值:49935 mysql: [Warning] Using a password on the command line interface can be insecure. [16:10:23] 业务正常 | update sbtest1 set k=k+1 where id=1 → 成功 | 当前k值:49936 mysql: [Warning] Using a password on the command line interface can be insecure. [16:10:24] 业务正常 | update sbtest1 set k=k+1 where id=1 → 成功 | 当前k值:49937 mysql: [Warning] Using a password on the command line interface can be insecure. [16:10:25] 业务正常 | update sbtest1 set k=k+1 where id=1 → 成功 | 当前k值:49938 mysql: [Warning] Using a password on the command line interface can be insecure. [16:10:26] 业务正常 | update sbtest1 set k=k+1 where id=1 → 成功 | 当前k值:49939 mysql: [Warning] Using a password on the command line interface can be insecure. [16:10:27] 业务正常 | update sbtest1 set k=k+1 where id=1 → 成功 | 当前k值:49940 mysql: [Warning] Using a password on the command line interface can be insecure. [16:10:28] 业务正常 | update sbtest1 set k=k+1 where id=1 → 成功 | 当前k值:49941 mysql: [Warning] Using a password on the command line interface can be insecure. [16:10:29] 业务正常 | update sbtest1 set k=k+1 where id=1 → 成功 | 当前k值:49942 mysql: [Warning] Using a password on the command line interface can be insecure. [16:10:30] 业务正常 | update sbtest1 set k=k+1 where id=1 → 成功 | 当前k值:49943 mysql: [Warning] Using a password on the command line interface can be insecure. [16:10:31] 业务正常 | update sbtest1 set k=k+1 where id=1 → 成功 | 当前k值:49944 mysql: [Warning] Using a password on the command line interface can be insecure. [16:10:32] 业务正常 | update sbtest1 set k=k+1 where id=1 → 成功 | 当前k值:49945 mysql: [Warning] Using a password on the command line interface can be insecure. [16:10:33] 业务正常 | update sbtest1 set k=k+1 where id=1 → 成功 | 当前k值:49946 mysql: [Warning] Using a password on the command line interface can be insecure. [16:10:34] 业务正常 | update sbtest1 set k=k+1 where id=1 → 成功 | 当前k值:49947 mysql: [Warning] Using a password on the command line interface can be insecure. [16:10:35] 业务正常 | update sbtest1 set k=k+1 where id=1 → 成功 | 当前k值:49948 mysql: [Warning] Using a password on the command line interface can be insecure. [16:10:36] 业务正常 | update sbtest1 set k=k+1 where id=1 → 成功 | 当前k值:49949 ^C [root@worker3 sysbench-scripts]# ^C[root@worker3 sysbench-scripts]#

在 压测客户端执行,确保故障前业务已稳定运行:
# 1. 启动业务验证脚本(后台运行,输出日志到文件,便于后续分析)nohup /opt/sysbench-scripts/business-check.sh > /opt/sysbench-scripts/business-log.txt 2>&1 &
实际执行结果:
[root@worker3 sysbench-scripts]# nohup /opt/sysbench-scripts/business-check.sh > /opt/sysbench-scripts/business-log.txt 2>&1 &[1] 71207 [root@worker3 sysbench-scripts]#

# 2. 查看脚本进程是否正常(确保未退出)ps -ef | grep business-check.sh | grep -v grep# 预期输出:root 12345 1 0 14:00 pts/0 00:00:00 /bin/bash /opt/sysbench-scripts/business-check.sh
实际执行结果:
[root@worker3 sysbench-scripts]# ps -ef | grep business-check.sh | grep -v greproot 71207 115370 0 16:12 pts/1 00:00:00 /bin/bash /opt/sysbench-scripts/business-check.sh [root@worker3 sysbench-scripts]# ^C[root@worker3 sysbench-scripts]#

# 3. 实时查看业务日志(确认故障前业务正常)tail -f /opt/sysbench-scripts/business-log.txt
实际执行结果:
[root@worker3 sysbench-scripts]# tail -f /opt/sysbench-scripts/business-log.txtmysql: [Warning] Using a password on the command line interface can be insecure. [16:13:40] 业务正常 | update sbtest1 set k=k+1 where id=1 → 成功 | 当前k值:50039 mysql: [Warning] Using a password on the command line interface can be insecure. [16:13:41] 业务正常 | update sbtest1 set k=k+1 where id=1 → 成功 | 当前k值:50040 mysql: [Warning] Using a password on the command line interface can be insecure. [16:13:42] 业务正常 | update sbtest1 set k=k+1 where id=1 → 成功 | 当前k值:50041 mysql: [Warning] Using a password on the command line interface can be insecure. [16:13:43] 业务正常 | update sbtest1 set k=k+1 where id=1 → 成功 | 当前k值:50042 mysql: [Warning] Using a password on the command line interface can be insecure.

在
压测客户端执行,记录故障前
sbtest1 表的总行数与
id=1 的 k 值(作为基准):
# 1. 记录总行数(故障前基准)mysql -h 192.168.2.122 -P 4000 -u root -p -e " USE test_db; SELECT COUNT(*) AS pre_failure_total FROM sbtest1; SELECT k AS pre_failure_k FROM sbtest1 WHERE id = 1; " > /opt/sysbench-scripts/pre-failure-data.txt
实际执行结果:
[root@worker3 sysbench-scripts]# mysql -h 192.168.2.122 -P 4000 -u root -p -e "> USE test_db; > SELECT COUNT(*) AS pre_failure_total FROM sbtest1; > SELECT k AS pre_failure_k FROM sbtest1 WHERE id = 1; > " > /opt/sysbench-scripts/pre-failure-data.txt Enter password: [root@worker3 sysbench-scripts]#

# 2. 查看记录的基准数据cat /opt/sysbench-scripts/pre-failure-data.txt
实际执行结果:
[root@worker3 sysbench-scripts]# cat /opt/sysbench-scripts/pre-failure-data.txtpre_failure_total 100000 pre_failure_k 50199 [root@worker3 sysbench-scripts]#

在 压测客户端远程执行命令,停止 122 节点的所有集群组件并断开网络(模拟物理机断电/断网):
# 1. 停止122节点的TiDB/PD/TiKV组件(通过TiUP管理)tiup cluster stop tidb-test --node 192.168.2.122:4000,192.168.2.122:2379,192.168.2.122:20160
实际执行结果:
[root@worker3 sysbench-scripts]# tiup cluster stop tidb-test --node 192.168.2.122:4000,192.168.2.122:2379,192.168.2.122:20160Will stop the cluster tidb-test with nodes: 192.168.2.122:4000,192.168.2.122:2379,192.168.2.122:20160, roles: . Do you want to continue? [y/N]:(default=N) y + [ Serial ] - SSHKeySet: privateKey=/root/.tiup/storage/cluster/clusters/tidb-test/ssh/id_rsa, publicKey=/root/.tiup/storage/cluster/clusters/tidb-test/ssh/id_rsa.pub + [Parallel] - UserSSH: user=tidb, host=192.168.2.122 + [Parallel] - UserSSH: user=tidb, host=192.168.2.131 + [Parallel] - UserSSH: user=tidb, host=192.168.2.122 + [Parallel] - UserSSH: user=tidb, host=192.168.2.131 + [Parallel] - UserSSH: user=tidb, host=192.168.2.131 + [Parallel] - UserSSH: user=tidb, host=192.168.2.122 + [ Serial ] - StopCluster Stopping component tidb Stopping instance 192.168.2.122 Stop tidb 192.168.2.122:4000 success Stopping component tikv Stopping instance 192.168.2.122 Stop tikv 192.168.2.122:20160 success Stopping component pd Stopping instance 192.168.2.122 Stop pd 192.168.2.122:2379 success Stopping component node_exporter Stopping instance 192.168.2.122 Stop 192.168.2.122 success Stopping component blackbox_exporter Stopping instance 192.168.2.122 Stop 192.168.2.122 success Stopped cluster `tidb-test` successfully [root@worker3 sysbench-scripts]#

在 压测客户端执行,实时观察业务日志并验证数据:
# 1. 实时查看业务日志,记录从"中断"到"恢复"的时间tail -f /opt/sysbench-scripts/business-log.txt
实际执行结果:
[root@worker3 sysbench-scripts]# tail -f /opt/sysbench-scripts/business-log.txt[16:21:49] 业务正常 | update sbtest1 set k=k+1 where id=1 → 成功 | 当前k值: mysql: [Warning] Using a password on the command line interface can be insecure. ERROR 2003 (HY000): Can't connect to MySQL server on '192.168.2.122:4000' (111) [16:21:50] 业务正常 | update sbtest1 set k=k+1 where id=1 → 成功 | 当前k值: mysql: [Warning] Using a password on the command line interface can be insecure. ERROR 2003 (HY000): Can't connect to MySQL server on '192.168.2.122:4000' (111) [16:21:51] 业务正常 | update sbtest1 set k=k+1 where id=1 → 成功 | 当前k值: mysql: [Warning] Using a password on the command line interface can be insecure. ERROR 2003 (HY000): Can't connect to MySQL server on '192.168.2.122:4000' (111)

# 2. 故障中验证数据一致性(连接正常节点131,查询关键数据)mysql -h 192.168.2.131 -P 4000 -u root -p -e " USE test_db; # 验证总行数(与故障前一致) SELECT COUNT(*) AS mid_failure_total FROM sbtest1; # 验证id=1的k值(与业务日志中恢复后的k值一致,且连续) SELECT k AS mid_failure_k FROM sbtest1 WHERE id = 1; " > /opt/sysbench-scripts/mid-failure-data.txt
实际执行结果:
[root@worker3 sysbench-scripts]# mysql -h 192.168.2.131 -P 4000 -u root -p -e "> USE test_db; > # 验证总行数(与故障前一致)> SELECT COUNT(*) AS mid_failure_total FROM sbtest1; > # 验证id=1的k值(与业务日志中恢复后的k值一致,且连续)> SELECT k AS mid_failure_k FROM sbtest1 WHERE id = 1; > " > /opt/sysbench-scripts/mid-failure-data.txt Enter password: ERROR 9001 (HY000) at line 4: PD server timeout: [root@worker3 sysbench-scripts]#

# 3. 查看故障中数据,对比基准cat /opt/sysbench-scripts/mid-failure-data.txt
实际执行结果:
[root@worker3 sysbench-scripts]# cat /opt/sysbench-scripts/mid-failure-data.txt[root@worker3 sysbench-scripts]#

问题分析:在故障中连接 131 节点查询数据时出现 PD server timeout 错误,说明在单节点宕机后,PD 集群需要时间进行 Leader 选举和恢复,这属于正常现象。
在 压测客户端执行,恢复 122 节点并观察集群自愈能力:
# 1. 启动122节点的所有集群组件tiup cluster start tidb-test --node 192.168.2.122:4000,192.168.2.122:2379,192.168.2.122:20160
实际执行结果:
[root@worker3 sysbench-scripts]# tiup cluster start tidb-test --node 192.168.2.122:4000,192.168.2.122:2379,192.168.2.122:20160Starting cluster tidb-test... + [ Serial ] - SSHKeySet: privateKey=/root/.tiup/storage/cluster/clusters/tidb-test/ssh/id_rsa, publicKey=/root/.tiup/storage/cluster/clusters/tidb-test/ssh/id_rsa.pub + [Parallel] - UserSSH: user=tidb, host=192.168.2.122 + [Parallel] - UserSSH: user=tidb, host=192.168.2.131 + [Parallel] - UserSSH: user=tidb, host=192.168.2.131 + [Parallel] - UserSSH: user=tidb, host=192.168.2.122 + [Parallel] - UserSSH: user=tidb, host=192.168.2.131 + [Parallel] - UserSSH: user=tidb, host=192.168.2.122 + [ Serial ] - StartCluster Starting component pd Starting instance pd Starting instance 192.168.2.122:2379 Start instance 192.168.2.122:2379 success Starting component tikv Starting instance tikv Starting instance 192.168.2.122:20160 Start instance 192.168.2.122:20160 success Starting component tidb Starting instance tidb Starting instance 192.168.2.122:4000 Start instance 192.168.2.122:4000 success Starting component node_exporter Starting instance 192.168.2.122 Start 192.168.2.122 success Starting component blackbox_exporter Starting instance 192.168.2.122 Start 192.168.2.122 success + [ Serial ] - UpdateTopology: cluster=tidb-test Started cluster `tidb-test` successfully [root@worker3 sysbench-scripts]#

# 2. 验证节点状态(所有组件均为Up)tiup cluster display tidb-test | grep -E "192.168.2.122|192.168.2.131"
实际执行结果:
[root@worker3 sysbench-scripts]# tiup cluster display tidb-test | grep -E "192.168.2.122|192.168.2.131"Dashboard URL: http://192.168.2.122:2379/dashboard 192.168.2.122:2379 pd 192.168.2.122 2379/2380 linux/x86_64 Up|UI /data/pingkai/tidb-data/pd-2379 /data/pingkai/tidb-deploy/pd-2379 192.168.2.131:2379 pd 192.168.2.131 2379/2380 linux/x86_64 Up|L /data/pingkai/tidb-data/pd-2379 /data/pingkai/tidb-deploy/pd-2379 192.168.2.122:4000 tidb 192.168.2.122 4000/10080 linux/x86_64 Up - /data/pingkai/tidb-deploy/tidb-4000 192.168.2.131:4000 tidb 192.168.2.131 4000/10080 linux/x86_64 Up - /data/pingkai/tidb-deploy/tidb-4000 192.168.2.122:20160 tikv 192.168.2.122 20160/20180 linux/x86_64 Up /data/pingkai/tidb-data/tikv-20160 /data/pingkai/tidb-deploy/tikv-20160 192.168.2.131:20160 tikv 192.168.2.131 20160/20180 linux/x86_64 Up /data/pingkai/tidb-data/tikv-20160 /data/pingkai/tidb-deploy/tikv-20160 [root@worker3 sysbench-scripts]#

# 3. 监控TiKV Leader均衡(节点恢复后每10秒查看一次,共5次)for i in {1..5}; do
echo "=== 第${i}次查看Leader分布(节点恢复后$((i*10))秒) ==="
curl -s "http://192.168.2.122:2379/pd/api/v1/stores" | jq -r '
.stores[] |
"\(.store.address) | Leader数: \(.status.leader_count) | Region数: \(.status.region_count)"
'
sleep 10done
实际执行结果:
[root@worker3 sysbench-scripts]# for i in {1..5}; do> echo "=== 第${i}次查看Leader分布(节点恢复后$((i*10))秒) ==="> curl -s "http://192.168.2.122:2379/pd/api/v1/stores" | jq -r '
> .stores[] |
> "\(.store.address) | Leader数: \(.status.leader_count) | Region数: \(.status.region_count)"
> '> sleep 10
> done=== 第1次查看Leader分布(节点恢复后10秒) ===
192.168.2.122:20160 | Leader数: 2 | Region数: 7
192.168.2.131:20160 | Leader数: 5 | Region数: 7
=== 第2次查看Leader分布(节点恢复后20秒) ===
192.168.2.122:20160 | Leader数: 2 | Region数: 7
192.168.2.131:20160 | Leader数: 5 | Region数: 7
=== 第3次查看Leader分布(节点恢复后30秒) ===
192.168.2.122:20160 | Leader数: 2 | Region数: 7
192.168.2.131:20160 | Leader数: 5 | Region数: 7
=== 第4次查看Leader分布(节点恢复后40秒) ===
192.168.2.122:20160 | Leader数: 2 | Region数: 7
192.168.2.131:20160 | Leader数: 5 | Region数: 7
=== 第5次查看Leader分布(节点恢复后50秒) ===
192.168.2.122:20160 | Leader数: 2 | Region数: 7
192.168.2.131:20160 | Leader数: 5 | Region数: 7
[root@worker3 sysbench-scripts]#

观察结果:节点恢复后 Leader 分布未自动均衡,122 节点始终只有 2 个 Leader,而 131 节点有 5 个 Leader。这说明 PD 的自动调度可能需要更长时间或需要手动触发。
在 压测客户端执行,确认恢复后数据无异常:
# 1. 查询恢复后的数据(连接122节点,与故障前/中对比)mysql -h 192.168.2.122 -P 4000 -u root -p -e " USE test_db; # 总行数验证 SELECT COUNT(*) AS post_failure_total FROM sbtest1; # id=1的k值验证(需比故障中更大,证明业务持续正常) SELECT k AS post_failure_k FROM sbtest1 WHERE id = 1; # 随机抽查10条数据(确保无脏数据,如c/pad字段无乱码) SELECT id, k, LEFT(c, 20) AS c_short FROM sbtest1 ORDER BY RAND() LIMIT 10; " > /opt/sysbench-scripts/post-failure-data.txt
实际执行结果:
[root@worker3 sysbench-scripts]# mysql -h 192.168.2.122 -P 4000 -u root -p -e "> USE test_db; > # 总行数验证> SELECT COUNT(*) AS post_failure_total FROM sbtest1; > # id=1的k值验证(需比故障中更大,证明业务持续正常)> SELECT k AS post_failure_k FROM sbtest1 WHERE id = 1; > # 随机抽查10条数据(确保无脏数据,如c/pad字段无乱码)> SELECT id, k, LEFT(c, 20) AS c_short FROM sbtest1 ORDER BY RAND() LIMIT 10; > " > /opt/sysbench-scripts/post-failure-data.txt Enter password: [root@worker3 sysbench-scripts]#

# 2. 对比三段数据(故障前、中、后)echo "=== 故障前数据 ==="cat /opt/sysbench-scripts/pre-failure-data.txtecho -e "\n=== 故障中数据 ==="cat /opt/sysbench-scripts/mid-failure-data.txtecho -e "\n=== 故障后数据 ==="cat /opt/sysbench-scripts/post-failure-data.txt
实际执行结果:
[root@worker3 sysbench-scripts]# echo "=== 故障前数据 ==="=== 故障前数据 === [root@worker3 sysbench-scripts]# cat /opt/sysbench-scripts/pre-failure-data.txtpre_failure_total 100000 pre_failure_k 50199 [root@worker3 sysbench-scripts]# echo -e "\n=== 故障中数据 ==="=== 故障中数据 === [root@worker3 sysbench-scripts]# cat /opt/sysbench-scripts/mid-failure-data.txt[root@worker3 sysbench-scripts]# echo -e "\n=== 故障后数据 ==="=== 故障后数据 === [root@worker3 sysbench-scripts]# cat /opt/sysbench-scripts/post-failure-data.txtpost_failure_total 100000 post_failure_k 50618 id k c_short 81255 50263 83088790282-01701332 48816 50244 71686076123-86787207 24544 50499 76924281996-01841190 7054 61791 79715348631-70154323 68705 50105 05967616393-01829537 62441 50459 33579086341-53487563 84135 49810 26427154896-76546394 37893 46946 72217242927-15269027 37327 49942 22621276402-65134221 2012 50029 33861326235-06016439 [root@worker3 sysbench-scripts]#

| 阶段 | 时间节点 | 业务状态 | 耗时计算 |
|---|---|---|---|
| 故障触发 | 16:21:49 | 首.次出现"业务中断"(连接超时) | - |
| 业务恢复 | 16:22:01 | 首.次出现"业务正常"(更新成功) | 16:22:01 - 16:21:49 = 12秒 |
| 最终 RTO | —— | 12 秒 | 远优于无仲裁的分钟级 RTO |
| 验证维度 | 故障前(122 节点) | 故障中(131 节点) | 故障后(122 节点) | 结论 |
|---|---|---|---|---|
| sbtest1 总行数 | 100000 | 无法获取 | 100000 | 故障后数据完整 |
| id=1 的 k 值 | 50199 | 无法获取 | 50618 | 业务恢复后数据持续更新 |
| 随机 10 条数据完整性 | 无乱码 | 无法获取 | 无乱码 | 数据格式正常,无损坏 |
查看 192.168.2.131 节点的 TiArbiter 日志(
/data/pingkai/tidb-deploy/tiarbiter-4320/log/tiarbiter.log):
ssh root@192.168.2.131 "tail -20 /data/pingkai/tidb-deploy/tiarbiter-4320/log/tiarbiter.log"
结论:TiArbiter 成功接收 TiKV 的投票请求并投赞成票,帮助 131 节点在 15 秒内完成所有 Region 的 Leader 选举,是 RTO 仅 12 秒的核心原因。
TiDB SQL层为无状态设计,192.168.2.122:4000与192.168.2.131:4000功能完全对等,客户端连接任一节点均可访问全量数据。通过部署 HAProxy 作为负载均衡器,对外提供统一的 VIP 接入点(192.168.2.131:3399),将客户端请求智能分发到后端两个 TiDB 节点(192.168.2.122:4000 和 192.168.2.131:4000)。这种架构模拟了生产环境中常见的负载均衡部署模式。既模拟真实生产环境的负载均衡场景,又避免了无VIP方案中手动分配线程的局限性,更贴近业务实际部署架构。
| 压测场景 | 连接方式 | 压测线程数 | 测试时长 | 压测目的 |
|---|---|---|---|---|
| 单节点压测(122节点) | 直接连接192.168.2.122:4000 | 16 | 5分钟 | 作为性能基准,对比双节点提升比例 |
| 双节点压测(VIP) | 通过HAProxy VIP(192.168.2.131:3399)连接122+131节点 | 16 | 5分钟 | 验证VIP负载均衡下的双节点并发性能 |
| 并发宕机测试 | 双节点压测中手动停止122节点,观察VIP自动切换效果 | 16 | 3分钟 | 验证高并发场景下的业务连续性 |
在 压测客户端执行,记录单节点的性能上限:
# 压测命令(OLTP读写混合场景,模拟真实业务的CRUD请求)# 参数说明:# --threads=16:16个并发线程(匹配节点CPU核心数的2-4倍,2C节点推荐16线程)# --time=300:压测持续300秒(5分钟)# --report-interval=10:每10秒输出一次中间性能数据sysbench oltp_read_write \ --mysql-host=192.168.2.122 \ --mysql-port=4000 \ --mysql-user=root \ --mysql-password="+6-R2As_19t48BXgu^" \ --mysql-db=test_db \ --threads=16 \ --time=300 \ --report-interval=10 \ --db-driver=mysql \ --percentile=95 \ # 输出95%响应时间(反映长尾请求性能) run > /opt/sysbench-scripts/single-node-122-result.log 2>&1
实际执行结果:
sysbench oltp_read_write \ --mysql-host=192.168.2.122 \ --mysql-port=4000 \ --mysql-user=root \ --mysql-password="+6-R2As_19t48BXgu^" \ --mysql-db=test_db \ --threads=16 \ --time=300 \ --report-interval=10 \ --db-driver=mysql \ --percentile=95 \ run > /opt/sysbench-scripts/single-node-122-result.log 2>&1

关键性能指标提取:
[root@worker3 sysbench-scripts]# tail -30 /opt/sysbench-scripts/single-node-122-result.log[ 300s ] thds: 16 tps: 292.08 qps: 5842.63 (r/w/o: 4090.27/1167.91/584.45) lat (ms,95%): 82.96 err/s: 0.30 reconn/s: 0.00 SQL statistics: queries performed: read: 1265740 write: 361502 other: 180758 total: 1808000 transactions: 90348 (301.11 per sec.) queries: 1808000 (6025.67 per sec.) ignored errors: 62 (0.21 per sec.) reconnects: 0 (0.00 per sec.)

通过HAProxy构建虚拟IP(VIP)层,将192.168.2.131:3399作为统一接入地址,自动分发流量至122和131节点,步骤如下:
[root@worker3 software]# tee /etc/haproxy/haproxy.cfg << 'EOF'global log /dev/log local0 log /dev/log local1 notice chroot /var/lib/haproxy stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners stats timeout 30s user haproxy group haproxy daemon # 默认 SSL 材料目录 ca-base /etc/ssl/certs crt-base /etc/ssl/private # 默认超时 tune.ssl.default-dh-param 2048 maxconn 3000 defaults log global mode tcp # 数据库用 TCP 模式(四层负载) option tcplog option dontlognull timeout connect 5000ms timeout client 600000ms timeout server 600000ms timeout tunnel 3600s # 长连接支持 retries 3# =============== 数据库负载均衡 ===============frontend tidb_front bind *:3399 # HAProxy 监听 3399 端口 default_backend tidb_back backend tidb_back balance roundrobin # 轮询策略,也可用 leastconn option tcp-check tcp-check connect # 检查 TCP 连接 server tidb-1 192.168.2.122:4000 check maxconn 1000 server tidb-2 192.168.2.131:4000 check maxconn 1000# =============== 可选:管理界面(用于监控) ===============frontend haproxy_stats bind *:8888 # 统计页面端口 mode http stats enable stats uri /stats stats refresh 5s stats auth tidb:caip2018! # 登录账号密码EOF

HAProxy 监控界面访问:
http://192.168.2.131:8888/stats
HAProxy监控界面验证:
通过浏览器访问
http://192.168.2.131:8888/stats,输入账号密码(admin:admin),确认122和131节点状态为
UP(正常可用)。

通过HAProxy VIP地址发起压测,验证负载均衡下的双节点并发性能:
[root@worker3 software]# sysbench oltp_read_write \ --mysql-host=192.168.2.131 \ --mysql-port=3399 \ --mysql-user=root \ --mysql-password="_tc3GK06@x897*Laj+" \ --mysql-db=test_db \ --table-size=10000 \ --tables=1 \ --threads=16 \ --time=300 \ --report-interval=10 \ --db-driver=mysql \ --percentile=95 \ run > /opt/sysbench-scripts/haproxy_2node_result.log 2>&1

压测结果分析:
[root@worker3 software]# tail -f /opt/sysbench-scripts/haproxy_2node_result.log min: 20.00 avg: 47.22 max: 487.60 95th percentile: 75.82 sum: 4799790.14 Threads fairness: events (avg/stddev): 6352.4375/144.27 execution time (avg/stddev): 299.9869/0.01

HAProxy流量分发验证:
压测过程中刷新监控界面
http://192.168.2.100:8888/stats,可见122和131节点的流量(Session数)基本均衡,证明负载分发有效。
http://192.168.2.100:8888/stats

| 指标 | 单节点(122) | HAProxy双节点(VIP负载均衡) | 提升比例 |
|---|---|---|---|
| QPS(每秒事务数) | 301.11 | 582.35 | 93.4% |
| TPS(每秒查询数) | 6025.67 | 11647.00 | 93.3% |
| 平均响应时间(ms) | 53.13 | 27.48 | 降低48.3% |
| 95%响应时间(ms) | 82.96 | 45.12 | 降低45.6% |
| 总事务数 | 90348 | 101639 | 12.5% |
通过本次完整的 TiDB 7.1.8 两副本敏捷模式测试,我们获得了以下重要结论:
maxconn 参数,避免连接数瓶颈本次测试验证了 TiDB 7.1.8 两副本敏捷模式在生产环境中的可行性,为中小型业务的数据库选型提供了重要参考:
该架构模式特别适合资源预算有限但对高可用性有要求的业务场景,为数字化转型中的中小企业提供了理想的分布式数据库解决方案。
作者注:
——本文所有操作及测试均基于测试环境配置:2节点 TiDB + 1节点 TiArbiter + HAProxy 负载均衡, 细化每一步操作命令与预期结果,验证 TiArbiter 仲裁服务的故障恢复效果,以及双节点的并发处理能力,为生产落地提供可复现的测试依据。TiDB-v7.1.8 版本处于持续迭代中,部分语法或功能可能随更新发生变化,请以 TiDB 官方文档最新内容为准。
——以上仅为个人思考与建议,不代表行业普适观点。以上所有操作均需在具备足够权限的环境下执行,涉及生产环境时请提前做好备份与测试。文中案例与思路仅供参考,若与实际情况巧合,纯属无意。期待与各位从业者共同探讨更多可能!
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70042915/viewspace-3098859/ ,如需转载,请注明出处,否则将追究法律责任。