高可用读写分离实战(二):我把数据库主库停了,结果整个集群的反应和我想象的不一样
高可用读写分离实战(二):我把数据库主库停了,结果整个集群的反应和我想象的不一样
📌 上一篇把读写分离集群搭好了,本来只是想验证一下故障切换,没想到最后连续做了两个多小时实验。从主库宕机、VIP漂移,到读写分离和SQL路由,我把整个过程都记录了下来。这篇不是官方文档,而是一次真实的实验记录。
“要不直接把主库停了试试?”
上一篇部署完成以后,我一直觉得心里没底。
虽然repmgr cluster show显示一切正常,但总感觉少了点什么。
毕竟生产环境不会因为你部署成功,就真的万事大吉。
晚上九点多,我重新登录两台服务器,打开了三个终端。
一个窗口盯数据库日志。
一个窗口不停执行SQL。
还有一个窗口一直刷新集群状态。
整个环境大概长这样。
应用系统 │ JDBC读写分离驱动 │ ┌────────────────┐ │ VIP │ └────────────────┘ │ │ │ │ ┌───────┘ │ ▼ ▼ ┌────────────┐ Streaming WAL ┌────────────┐ │ Primary │ ───────────────▶ │ Standby │ │ node1 │ │ node2 │ └────────────┘ └────────────┘当时脑子里只有一个问题。
如果现在把 Primary 干掉,业务到底会不会中断?
我按下回车以后,数据库居然一点反应都没有
直接停止数据库。
sys_monitor.sh stop执行完成以后,我第一时间不是看日志,而是刷新业务页面。
结果页面还能打开。
又执行了一条SQL。
SELECTNOW();返回正常。
我当时第一反应就是:
“不会吧?是不是我停错数据库了?”
赶紧去服务器确认。
ps-ef|grepkingbase数据库进程已经没有了。
说明主库确实停了。
但是业务为什么还能访问?
我盯着日志看了几秒。
没有切换。
没有漂移。
什么事情都没有发生。
那几秒钟,我甚至怀疑自己的高可用配置是不是有问题。
后来看日志,我才知道数据库比我冷静得多
过了几秒以后。
日志终于开始刷新。
内容类似下面这样。
Reconnect to primary... retry 1... retry 2... retry 3...看到这里,我一下子明白了。
原来数据库根本不会因为一次连接失败就立刻升主。
它第一件事情不是切换。
而是确认。
Primary到底是真的挂了,还是只是网络抖了一下?
这一点我觉得设计得挺合理。
如果网络偶尔丢两个包就开始Failover,那整个集群一天不知道要切多少次。
所以生产环境里,"慢一点"反而是一种保护。
💡 我后来还专门去调整了reconnect_attempts参数,想让切换快一点。
测试环境确实舒服。
但想想生产环境,最后还是改回来了。
切得太快,有时候反而不是好事。
真正开始切换的时候,比想象中复杂
确认主库无法恢复以后,集群才真正进入切换流程。
我后来按照日志,大概整理了一下整个过程。
Primary异常 │ ▼ 持续重连确认 │ ▼ 停止接收新的WAL │ ▼ Standby结束Recovery │ ▼ 提升为Primary │ ▼ VIP漂移 │ ▼ 业务重新连接以前一直觉得,高可用就是一句话:
主挂了,备升主。
真正看完日志以后才发现,中间还有很多细节。
尤其是WAL同步。
如果最后几条日志还没有同步完成,就直接升主,很容易出现数据不一致。
所以真正的数据库,比我们想象得谨慎得多。
我最关心的,其实是VIP
数据库什么时候升主,我其实没那么关心。
我真正关心的是:
Java程序还能不能连?
因为线上配置的数据库地址一直都是VIP。
jdbc:kingbase://192.168.10.100:54321/test整个业务根本不知道后面是哪台服务器。
于是我登录两台机器。
执行:
ipaddr发现VIP已经从node1消失。
过了几秒。
出现在node2。
整个过程基本不用人工参与。
这也是为什么业务始终连接同一个地址,却能够自动恢复。
第一次看到VIP漂移的时候,我终于理解以前为什么很多项目数据库切换以后,Java服务根本不用改配置。
实验做到这里,我突然想到另一个问题
业务恢复以后,我本来准备结束。
结果脑子里突然冒出来一个问题。
现在新的Primary已经起来了。
那我执行一条SELECT,到底是谁在处理?
于是我没有停。
继续做实验。
打开两个数据库终端。
不停执行下面这条SQL。
SELECTinet_server_addr();结果还真发现了点东西。
查询请求已经开始分散到不同节点。
也就是说。
读写分离已经开始工作了。
顺手做了一次小压测
没有用特别复杂的工具。
直接写了一个简单测试程序。
循环执行查询。
for(inti=0;i<10000;i++){jdbcTemplate.queryForObject("select now()",Timestamp.class);}同时观察两个节点。
发现一个很有意思的现象。
开始的时候。
所有连接都集中在Primary。
调整JDBC读写分离配置以后。
Standby开始承担越来越多查询。
CPU也慢慢降下来了。
虽然只是一个简单测试。
但至少证明了一件事。
备库终于不是"摆设"了。
以前它只是负责同步。
现在真正开始承担查询压力。
我还踩了一个特别低级的坑
这里提醒一下。
我第一次压测一直没成功。
所有查询还是跑Primary。
我还怀疑是不是数据库配置有问题。
后来排查半天。
发现居然是JDBC URL写错了。
驱动根本没有启用读写分离模式。
所以看似数据库没生效,其实问题出在客户端。
这种问题在线上其实挺常见。
很多时候不是数据库不会工作,而是客户端根本没按预期连接。
我的一点体会
做完这次实验,我最大的感受就是:
很多人理解高可用,都是站在数据库角度。
但真正上线以后,用户根本不关心数据库。
他们只关心:
- 页面有没有打不开?
- SQL有没有报错?
- 订单有没有丢?
- 查询是不是变快了?
所以我现在做高可用测试,都会多验证几件事。
✅ JDBC连接池有没有恢复?
✅ VIP是不是正常漂移?
✅ 查询是不是已经进入Standby?
✅ 主备数据有没有延迟?
这些看起来不起眼,却比"数据库Running"更重要。
写在最后
这次实验,本来只是想验证一下Failover。
结果最后把读写分离、VIP漂移、SQL路由也一起测试了。
整个过程下来,我反而觉得,高可用真正难的地方不是部署,而是验证。
因为只有亲手做一次故障演练,你才知道自己的集群到底有没有准备好面对真正的生产环境。
下一篇,我准备把这次压测的数据整理出来,重点聊聊读写分离到底能提升多少性能、哪些SQL不会走备库,以及生产环境如何选择同步或异步复制。这部分也是我觉得最值得深入研究的内容。
