前言
上一篇做 TiDB Server、PD、TiKV 节点故障测试时,发现应用入口还有一个问题,就是当测试程序直接连接:
192.168.56.101:4000
两台 TiDB Server 都正常时,这个地址可以使用。第一台 TiDB Server 停掉以后,第二台 192.168.56.102:4000 虽然还能提供数据库服务,原来建立在第一台 TiDB Server 上的连接不会自动切到第二台。
TiDB Server 是无状态计算层,可以部署多台,但 HIS、EMR、LIS、收费和药房系统不能在每次维护或停掉一台 TiDB Server 时都修改 JDBC 地址。所以这次在测试环境中增加一台 HAProxy,把两台 TiDB Server 放到同一个入口后面,应用只连接:
192.168.56.110:3390
后端连接到 192.168.56.101:4000 还是 192.168.56.102:4000,由 HAProxy 处理。TiDB v7.5 的文档也列出了支持 LVS、HAProxy、F5 等负载均衡方式,用于给多台 TiDB Server 提供统一访问入口。
一、为什么这次不用 TiProxy
TiProxy 是 TiDB 官方代理组件,支持连接迁移和服务发现。但是我这套测试环境使用的是 TiDB v7.5.7,官方 Release Notes:TiProxy 在 v7.6.0 作为实验特性引入,到 v8.0.0 才成为正式功能。为了保证测试与后期生产一致,所以不使用 TiProxy,采用 v7.5 官方架构文档中列出的 HAProxy。以后测试环境升级到 v8.x,再单独比较 TiProxy 和 HAProxy。
二、这次的连接关系
上一篇测试时,应用直接连接 TiDB01:
HIS测试程序
|
|
192.168.56.101:4000
|
TiDB
现在改成:
+--> TiDB01 192.168.56.101:4000
|
应用 --> HAProxy --+
192.168.56.110:3390
|
+--> TiDB02 192.168.56.102:4000
测试环境保持和原有一样,也没有重新部署数据库集群,只增加了 HAProxy 节点。
| 组件 | 地址 |
|---|---|
| HAProxy | 192.168.56.110:3390 |
| TiDB01 | 192.168.56.101:4000 |
| TiDB02 | 192.168.56.102:4000 |
| PD | 192.168.56.101/102/103:2379 |
| TiKV | 192.168.56.101/102/103:20160 |
| TiDB | v7.5.7 |
| 测试库 | his_source |
HAProxy 单独放在 192.168.56.110,没有和 TiDB Server 安装在一起,便于单独观察代理层。配置前,先从 HAProxy 机器检查到两台 TiDB Server 的 TCP 4000 端口:
nc -vz 192.168.56.101 4000
nc -vz 192.168.56.102 4000
两个端口都能连通后再继续配置代理。
三、HAProxy 配置没有做得很复杂
这次只验证 TiDB Server 后端切换,所以使用了比较简单的 HAProxy 配置。
/etc/haproxy/haproxy.cfg:
global
log 127.0.0.1 local2
daemon
maxconn 4096
defaults
log global
mode tcp
option tcplog
timeout connect 5s
timeout client 1h
timeout server 1h
listen tidb-cluster
bind 0.0.0.0:3390
mode tcp
balance leastconn
server tidb01 192.168.56.101:4000 check inter 2000 rise 2 fall 3
server tidb02 192.168.56.102:4000 check inter 2000 rise 2 fall 3
TiDB 对外提供 MySQL TCP 协议,这里使用 mode tcp,没有做应用层转发。balance leastconn 会优先把新连接分配给当前连接数较少的 TiDB Server。TiDB 官方 HAProxy 最佳实践给出的示例也使用了 mode tcp 和 balance leastconn。
后面的参数:
check inter 2000 rise 2 fall 3
表示 HAProxy 每 2 秒对 TiDB Server 做一次 TCP 健康检查。连续 3 次失败后把后端标记为不可用,恢复时连续成功 2 次再重新加入。检测时间还会受探测起点和网络连接状态影响,不会把它直接换算成“TiDB 停止后第 6 秒整完成切换”。
配置写好后,先检查语法:
haproxy -c -f /etc/haproxy/haproxy.cfg
确认没有错误后再启动:
systemctl enable haproxy
systemctl restart haproxy
systemctl status haproxy
然后检查监听端口:
ss -lntp | grep 3390
正常情况下,HAProxy 会监听:
0.0.0.0:3390
四、用 MySQL Client 验证入口
不改测试程序,只用 MySQL Client 检查 HAProxy 入口。以前的连接命令是:
mysql -h 192.168.56.101 \
-P 4000 \
-u his_app \
-p his_source
现在改为:
mysql -h 192.168.56.110 \
-P 3390 \
-u his_app \
-p his_source
连接后,先看会话实际落在哪台 TiDB Server:
SELECT
@@hostname AS tidb_host,
CONNECTION_ID() AS conn_id,
DATABASE() AS current_db;
TiDB 7.5 中,hostname 是只读系统变量,返回当前 TiDB Server 的主机名;CONNECTION_ID() 返回当前连接 ID。下面是示例输出格式:
tidb_host : tidb01
conn_id : 138247
current_db: his_source
再开几个客户端,仍然连接:
192.168.56.110:3390
再分别执行:
SELECT @@hostname, CONNECTION_ID();
正常情况下,新连接会被 HAProxy 分配到当前可用的 TiDB Server。leastconn 根据当时的连接数选择后端,不要求第一个连接一定到 tidb01、第二个一定到 tidb02。这一步只检查同一个 HAProxy 地址能否把新连接交给两台 TiDB Server。
五、再从集群侧确认连接到底在哪
客户端查询 @@hostname 已经比较直观。从数据库侧统一查看连接分布:
SELECT
INSTANCE,
ID,
USER,
HOST,
DB,
COMMAND,
TIME,
STATE
FROM information_schema.CLUSTER_PROCESSLIST
WHERE USER = 'his_app'
ORDER BY INSTANCE, ID;
CLUSTER_PROCESSLIST 可以看到整个 TiDB 集群的连接,比普通 PROCESSLIST 多出的 INSTANCE 字段表示会话所属的 TiDB Server。如果只执行:
SHOW PROCESSLIST;
看到的只是当前 TiDB Server 上的会话。加上 HAProxy 后,同一个应用的连接可能分散在多台 TiDB Server 上,排查会话时会使用集群视图。
六、换成一笔更接近医院业务的测试数据
这里没有再建 test_table、ha_probe 之类的探针表,继续使用之前的门诊、收费和医嘱表。先通过 HAProxy 登录:
mysql -h 192.168.56.110 \
-P 3390 \
-u his_app \
-p his_source
然后模拟一次门诊挂号:
INSERT INTO outpatient_visit
(
visit_no,
patient_no,
dept_code,
visit_type,
visit_time,
visit_status,
update_time
)
VALUES
(
'TESTMZ2609021476',
'TESTP04261',
'CARD',
'OUTPATIENT',
NOW(),
'WAITING',
NOW()
);
TESTMZ2609021476 是模拟门诊号,TESTP04261 是模拟患者编号,CARD 表示心内科测试科室编码。表里不放患者姓名、身份证号、电话和医保卡号等与本次技术验证无关的字段。插入后,用下面的 SQL 查询:
SELECT
visit_no,
patient_no,
dept_code,
visit_status,
visit_time
FROM outpatient_visit
WHERE visit_no = 'TESTMZ2609021476';
后面的节点切换和收费操作继续使用这条模拟门诊记录。
七、停掉当前连接所在的 TiDB Server
先看客户端当前落在哪台 TiDB Server:
SELECT @@hostname, CONNECTION_ID();
以下假设当前连接位于 tidb01,对应 192.168.56.101:4000。从 TiUP 中控机执行:
tiup cluster stop tidb75-lab \
-N 192.168.56.101:4000
这是人为停止 TiDB Server 服务,用来模拟一个后端不可用,不等同于服务器突然断电或交换机故障。TiUP 支持通过 -N 只停止指定节点。
原来建立在 tidb01 上的 MySQL 会话会断开。HAProxy 可以把新连接送到健康的 TiDB Server,但不会把已有的 MySQL TCP 会话从 tidb01 原样迁到 tidb02。较新版本的 TiProxy 支持计划内下线或重启时的连接迁移,但官方文档也明确说明,TiDB Server 意外下线时连接仍会断开,不能把两种情况混在一起。
八、应用地址没有变,再连一次就够了
等 HAProxy 把 tidb01 判断为不可用后,仍然使用原地址连接:
mysql -h 192.168.56.110 \
-P 3390 \
-u his_app \
-p his_source
连接串没有变化。连接后再执行:
SELECT @@hostname, CONNECTION_ID();
预期的新连接会落到仍然健康的 tidb02,也就是 192.168.56.102:4000。应用始终使用 192.168.56.110:3390,后端 TiDB Server 的变化不需要同步修改应用配置。这里还要看应用连接池能否正常断线重连。如果连接池一直使用已经失效的 TCP 连接,HAProxy 无法替它重建连接。
九、TiDB01 停着的时候,再完成后续收费
重新连接后,继续处理刚才的模拟就诊,先查询门诊记录:
SELECT
visit_no,
patient_no,
dept_code,
visit_status
FROM outpatient_visit
WHERE visit_no = 'TESTMZ2609021476';
然后写入一笔门诊诊查费:
INSERT INTO charge_detail
(
charge_no,
visit_no,
item_type,
item_code,
item_name,
qty,
unit_price,
amount,
charge_time,
charge_status
)
VALUES
(
'TESTSF260902318',
'TESTMZ2609021476',
'REG',
'REG-FEE',
'普通门诊诊查费',
1,
15.00,
15.00,
NOW(),
'PAID'
);
写入后查询:
SELECT
charge_no,
visit_no,
item_name,
amount,
charge_status,
charge_time
FROM charge_detail
WHERE charge_no = 'TESTSF260902318';
一次模拟门诊和一次收费用于检查新连接是否能继续访问业务表。TiDB Server 不存储业务数据,业务数据保存在 TiKV 中。
十、已有事务中途断掉,要单独看
单独检查事务执行到一半时 TiDB Server 停止的情况。开始前,先恢复上一节停止的 TiDB01:
tiup cluster start tidb75-lab \
-N 192.168.56.101:4000
等两台 TiDB Server 都重新进入 HAProxy 可用后端后再继续。如果 TiDB01 还停着,当前连接落在 TiDB02,再停 TiDB02 就没有可用的 SQL 节点,后面的重连测试无法进行。
事务测试不需要在收费表里制造半笔账,用前面的模拟就诊修改一次状态即可。先确认客户端当前连接的 TiDB Server:
SELECT @@hostname;
然后:
BEGIN;
UPDATE outpatient_visit
SET visit_status = 'IN_SERVICE',
update_time = NOW()
WHERE visit_no = 'TESTMZ2609021476';
这里先不执行:
COMMIT;
然后从另一个终端停止当前会话所在的 TiDB Server,-N 后面的地址按 @@hostname 的实际结果填写。客户端连接中断后,再通过 HAProxy 登录:
mysql -h 192.168.56.110 \
-P 3390 \
-u his_app \
-p his_source
查询:
SELECT
visit_no,
visit_status,
update_time
FROM outpatient_visit
WHERE visit_no = 'TESTMZ2609021476';
TiDB 官方事务文档说明,客户端连接中止或关闭时,未提交事务会自动回滚。前面没有执行 COMMIT 的 visit_status = 'IN_SERVICE' 不应作为已提交状态保留下来,用查询结果核对这一点。
十一、COMMIT 附近断连接,处理方法又不一样
检查提交边界附近的连接异常。假设程序已经发送:
COMMIT;
此时网络或 TiDB Server 出现异常,客户端没有收到明确的成功响应。应用不能直接按下面的判断处理:
连接断了 = 事务肯定没提交
数据库与客户端之间发生通信故障时,业务端不能只凭连接错误判断事务最终状态。这里使用收费业务号 TESTSF260902318,数据库中有相应的唯一约束:
UNIQUE KEY uk_charge_no (charge_no)
发生异常后,先按业务号查询:
SELECT
charge_no,
visit_no,
amount,
charge_status
FROM charge_detail
WHERE charge_no = 'TESTSF260902318';
查清原业务是否已经存在后,再决定补做还是继续后面的流程。这个问题和 HAProxy 没有直接关系,但断线重连时不能用“报错后再发一次 SQL”代替事务状态确认。收费、退费、发药和医嘱执行都要按业务唯一号核对。
十二、恢复刚才停止的 TiDB Server
检查完成后,恢复刚才停止的 TiDB Server。以下仍以当前会话位于 TiDB01 为例;如果实际停止的是 TiDB02,-N 后面的地址应改为 192.168.56.102:4000。
tiup cluster start tidb75-lab \
-N 192.168.56.101:4000
TiDB 默认的 MySQL 协议端口是 4000,状态服务端口是 10080。先检查状态接口:
curl http://192.168.56.101:10080/status
TiDB v7.5 的 /status 接口会返回版本和当前连接数等状态。服务恢复后,等待 HAProxy 重新把 tidb01 判定为可用,再新建几个连接:
mysql -h 192.168.56.110 \
-P 3390 \
-u his_app \
-p his_source
分别执行:
SELECT @@hostname, CONNECTION_ID();
用查询结果检查后续新连接能否再次分布到两台 TiDB Server。原来建立在 tidb02 上的连接不会因为 tidb01 恢复而强制迁移,HAProxy 也不保证两台 TiDB Server 的连接数始终完全相同。
十三、医院应用切到 HAProxy以后,连接池也要测
MySQL Client 的检查完成后,还要用实际连接池验证 HIS 接入。医院应用常见的连接池有 HikariCP、Druid、DBCP、WebLogic DataSource 和 Tomcat JDBC Pool,也可能由应用服务器管理数据源。连接有效性检测、连接超时、失效连接剔除和事务异常后的连接回收都与具体连接池有关。测试时,让应用持续查询和写入,再停止一台 TiDB Server,记录已有连接的错误、失效连接多久被清理,以及新连接能否通过 HAProxy 重新建立。如果 HAProxy 已经可以建立新连接,应用仍长时间重复使用失效连接,再继续检查连接池配置。
十四、是否需要透传真实客户端 IP
加上 HAProxy 后,还要确认是否需要保留真实客户端 IP。应用直接连接 TiDB 时,SHOW PROCESSLIST 可以看到客户端地址;经过 HAProxy 后,TiDB 默认看到的连接来源可能变成 HAProxy 服务器 192.168.56.110。如果需要按客户端 IP 审计或排查问题,可以使用 TiDB v7.5 支持的 PROXY Protocol。
HAProxy 后端增加 send-proxy:
send-proxy
例如:
server tidb01 192.168.56.101:4000 send-proxy check inter 2000 rise 2 fall 3
server tidb02 192.168.56.102:4000 send-proxy check inter 2000 rise 2 fall 3
TiDB 端同时配置 proxy-protocol.networks,只允许指定的代理服务器使用 PROXY Protocol。例如:
server_configs:
tidb:
proxy-protocol.networks: "192.168.56.110"
不要为了省事写成:
*
官方文档提示,* 允许任意地址自行报告客户端源 IP,存在安全风险。这次个人测试环境没有开启 PROXY Protocol,主要是先验证统一入口,客户端 IP 透传另行配置和检查。
十五、HAProxy 自己也是一个节点
应用入口从 192.168.56.101:4000 改为 192.168.56.110:3390 后,还要看 HAProxy 自身的可用性。目前只有一台 192.168.56.110,它仍然是单点。这次验证范围只有:
HAProxy 可以给两台 TiDB Server 提供统一地址,并在某个后端 TiDB Server 不可用后,把后续新连接分配到健康节点。
HAProxy 主机关机后,192.168.56.110:3390 仍然无法访问。医院正式环境如果已经有 F5、A10 或其他高可用负载均衡设备,可以复用现有入口;如果使用软件 HAProxy,还要继续处理 HAProxy 双机和统一 VIP。我没这个环境,所以就没测试这一部分。这次改造后,应用只使用 192.168.56.110:3390,两台 TiDB Server 的地址由 HAProxy 管理。后续增加 TiDB Server 或停机维护时,不需要逐个修改 HIS、EMR、LIS、PACS 和接口平台的数据库 IP。应用连接地址与 TiDB Server 节点地址分开后,入口配置由数据库侧统一维护。
结语
这次把测试应用的数据库地址统一改为 192.168.56.110:3390。一台 TiDB Server 不可用时,HAProxy 会停止向它分配新连接,应用地址不需要修改。已有连接仍会断开,应用连接池需要重新建连;提交边界附近的异常仍要按业务唯一号核对。当前只有一台 HAProxy,它本身也是单点,如果生产环境可以复用现有的高可用负载均衡设备,使用软件 HAProxy 时还要继续验证双机和 VIP。