做了几年数据库平台相关的运维后我越来越确定一件事很多所谓的高可用方案只有在真出故障那一刻才暴露真实水平。这次要分享的这套组合NDB Cluster 做数据层多副本同步HAProxy 做 SQL 访问入口的负载均衡Keepalived 给负载均衡器本身提供 VIP 漂移能力三层各司其职是我目前用下来最接近可以安心睡觉的一套数据库高可用架构。整套部署我前前后后搭过好几回也专门做过故障注入演练下面把架构设计、节点规划、每一层的手动部署步骤、验证方法以及那些文档里不会写的坑一次性梳理清楚。1. 为什么偏偏是 NDB Cluster HAProxy Keepalived 这个组合1.1 三个组件各管哪一段先说清楚这个架构里每一层的位置和作用不然很多人做着做着就糊涂了。NDB Cluster也就是 MySQL Cluster 里的 NDB 存储引擎是这套架构的数据底座。它和普通的 InnoDB 主从复制有本质区别。InnoDB 主从是一份数据多份拷贝主库写入后异步或半同步复制到从库存在丢失窗口NDB 则是 shared-nothing 架构数据按照主键哈希被分片partition存储到多个数据节点上并且每个分片默认在两个不同的数据节点上各存一份副本。写入任何一个 SQL 节点事务会在数据节点之间做同步提交所有副本同时更新。所以 NDB 天然就是多活的不存在主库挂了从库数据落后这种问题。HAProxy 在这个架构里负责四层负载均衡。因为 NDB Cluster 的 SQL 节点可以横向增加应用不需要关心到底有几个 SQL 节点、哪一个活着只需要连固定入口。HAProxy 把来自应用的 MySQL 连接按策略分发到后端的多个 SQL 节点同时通过健康检查自动摘除故障节点。Keepalived 是给 HAProxy 兜底的。HAProxy 本身是无状态的转发层如果只有一台 HAProxy它就是整个链路里的单点。Keepalived 通过 VRRP 协议在两台 HAProxy 之间虚拟出一个漂移 IPVIP正常情况下 VIP 绑定在主节点上主节点故障后 VIP 自动切到备用节点客户端连接地址完全不用变。这三个东西的关系可以类比成NDB Cluster 是数据保险箱HAProxy 是前台接待Keepalived 是前台缺人时自动顶上的备用接待。1.2 和MySQL主从VIP相比它强在哪很多人一提到数据库高可用第一反应就是 MySQL 主从复制 MHA 或 Orchestrator再在前面挂个 VIP。这套方案成熟但不等于没有短板。主从架构最大的痛点是复制延迟。主库写入压力一大从库延迟从几百毫秒到几秒都很常见。更别说半同步复制在极端情况下也可能丢事务。NDB 的同步复制机制从源头规避了这个问题——所有副本在事务提交前已经确认写入。这意味着读任何 SQL 节点看到的数据都是一致的不用自己去做读写分离的数据一致性校验。另一个痛点是故障切换的空窗期。主从方案在主库宕机后通常需要脚本或管理端探测、提升新主、改指向几十秒的不可用是常态。NDB 的数据节点故障对 SQL 层完全透明SQL 节点不感知底层数据分片的位置变化而 HAProxy 那层即使某台机器故障Keepalived 的切换时间一般也就 2~3 秒配合应用连接池重试用户基本无感。当然NDB 也不是银弹。它是内存型存储引擎为主对内存的依赖非常大SQL 功能集也比 InnoDB 少一些更适合中等数据量、并发高、可用性要求极高的核心业务。选不选它得先看数据量和业务形态这点后面还会专门说。2. 动手前必须定的三个事节点规划、版本、网络2.1 节点角色与资源分配先给出一份我实际测试环境用的节点规划你可以直接照抄也可以按需调整。角色主机名IP配置建议管理节点 mgmdmgm1192.168.100.102C4G磁盘不用大数据节点 ndbdndb1192.168.100.208C16GSSD数据节点 ndbdndb2192.168.100.218C16GSSDSQL 节点 mysqldsql1192.168.100.304C8GSQL 节点 mysqldsql2192.168.100.314C8GHAProxy Keepalivedlb1192.168.100.404C8GHAProxy Keepalivedlb2192.168.100.414C8GVIP-192.168.100.100-几个角色要注意的点管理节点可以和后端服务共用一台低配机器但生产环境建议独立。它保存的是整个集群的配置和状态故障时所有节点都会失去协调人。虽然 NDB 允许配置多个管理节点但为了保持配置一致性我习惯先部署一个后续有需要再扩。数据节点是 NDB 的性能核心。NDB 默认把数据放在内存里所以内存大小直接决定你能存多少数据。我这里的 16G 内存只是测试基准生产环境按业务数据量重新计算 DataMemory 和 IndexMemory。数据节点数量建议从 2 个起步并且要满足 NoOfReplicas 的设置副本数必须小于等于数据节点数。SQL 节点本质上是编译进 NDB 引擎的 mysqld它的内存需求不大但连接数会吃内存需要按应用连接池规模估算。HAProxy 和 Keepalived通常部署在同一台机器上。两台机器都运行 HAProxyKeepalived 负责在它们之间漂移 VIP。这里的资源规格哪怕减半也能跑但连接数高时 HAProxy 的 CPU 和文件描述符会成为瓶颈。2.2 版本选型和安装包准备我这次用的是 MySQL Cluster 8.0.xNDB 8.0具体到写这篇文章时的最新补丁版本。8.0 的 NDB 相比 7.6 时代在 SQL 兼容性、性能、管理工具上都有明显提升而且和 MySQL 8.0 的 SQL 节点语法更统一。如果你还在维护老业务且必须兼容 MySQL 5.7 的语义那 NDB 7.6 是更稳的选择。但新项目直接上 8.0 没问题尤其 NDB 8.0 对 JSON、窗口函数、CTE 的支持已经和 InnoDB 版本非常接近。所有节点下载同一个安装包比如mysql-cluster-8.0.x-linux-glibc2.17-x86_64.tar.gz。管理节点、数据节点、SQL 节点用的是同一套二进制只是角色由启动参数和配置文件决定。这个设计很方便意味着你不用在每台机器上单独找对应版本的包。2.3 端口、目录与其他前置条件规划阶段就把端口列清楚避免部署一半才发现冲突。用途默认端口说明管理节点 mgmd1186所有 NDB 节点都连这个端口数据节点间通信2202ndbd 之间传递数据和同步事务SQL 节点 mysqld3306应用/HAProxy 连接入口HAProxy 监听3306和 SQL 节点的 MySQL 端口保持同一端口应用无感知VRRP 组播/单播需要放行Keepalived 节点间的心跳CentOS/Rocky 注意放行操作系统层面我习惯统一关闭防火墙或者只放行必要端口测试环境直接systemctl stop firewalld省事生产环境建议精确放行。SELinux 在 CentOS/Rocky 下记得设为 permissive 或正确配置策略否则很容易出现端口通但服务连不上的诡异问题。每个节点要准备独立的数据目录。管理节点用/var/lib/mysql-cluster数据节点和 SQL 节点也各自有专属目录。所有目录属主改为mysql:mysql并统一创建mysql系统用户。这个是 MySQL 二进制包的默认要求不做后面初始化必踩权限坑。3. NDB Cluster 部署6 步把集群拉起来3.1 管理节点config.ini 是最重要的图纸先把安装包解压到统一位置我习惯放在/opt/mysql-clustertar -xzf mysql-cluster-8.0.x-linux-glibc2.17-x86_64.tar.gz mv mysql-cluster-8.0.x-linux-glibc2.17-x86_64 /opt/mysql-cluster groupadd mysql useradd -r -g mysql mysql mkdir -p /var/lib/mysql-cluster chown -R mysql:mysql /var/lib/mysql-cluster然后编写管理节点的核心配置文件/var/lib/mysql-cluster/config.ini[ndb_mgmd] NodeId1 HostName192.168.100.10 DataDir/var/lib/mysql-cluster PortNumber1186 [ndbd default] NoOfReplicas2 DataMemory1024M IndexMemory512M ServerPort2202 MaxNoOfConcurrentOperations10000 MaxNoOfConcurrentTransactions4096 [ndbd] NodeId2 HostName192.168.100.20 DataDir/var/lib/mysql-cluster [ndbd] NodeId3 HostName192.168.100.21 DataDir/var/lib/mysql-cluster [mysqld] NodeId4 HostName192.168.100.30 [mysqld] NodeId5 HostName192.168.100.31 [api] NodeId6 HostName192.168.100.40 [api] NodeId7 HostName192.168.100.41解释几个关键参数NoOfReplicas2每个分片存 2 份副本这是高可用的基础。设置为 2 意味着至少需要 2 个数据节点。DataMemory1024M用来存实际数据的内存上限数据总量要控制在这个值以内否则写不进去。IndexMemory512M存哈希索引和有序索引的内存和 DataMemory 一起决定集群容量。MaxNoOfConcurrentOperations全局并发操作数写并发高的场景必须调大默认值非常保守。为[mysqld]和[api]分配 NodeId 是为了约束哪些 IP 允许作为 SQL 节点或 API 应用接入也方便在ndb_mgm里一眼看出谁是谁。启动管理节点/opt/mysql-cluster/bin/ndb_mgmd -f /var/lib/mysql-cluster/config.ini --configdir/var/lib/mysql-cluster看到类似NDB Management Server started的日志就说明起来成功了。管理节点启动后可以用ndb_mgm -e show查看当前集群状态这时候数据节点还没起来会显示未连接。3.2 数据节点首次启动与后续启动的差别数据节点的启动有一个非常容易踩坑的地方首次启动要用--initial之后正常启动绝不能带--initial。--initial的作用是清空数据节点本地文件系统重新初始化。首次用是因为要生成 NDB 需要的头文件和日志文件但如果你在之后的重启中还带着它数据节点会认为自己是一张白纸直接失去本地数据副本然后从其他节点重新同步等于人为制造一次全量重建。首次启动命令/opt/mysql-cluster/bin/ndbd --initial --connect-stringnodeid2;host192.168.100.10:1186后续正常启动/opt/mysql-cluster/bin/ndbd --connect-stringnodeid2;host192.168.100.10:1186建议把启动命令写成 systemd service 或守护脚本别裸着跑前台进程。两台数据节点都起来后在管理节点执行/opt/mysql-cluster/bin/ndb_mgm -e show输出里每个节点后面会带上角色和状态看到started或者connected就代表数据节点已经加入集群。如果某个数据节点反复重启多半是 DataDir 权限、内存参数、或 NodeId 对应 IP 写错先去这几个地方排查。3.3 SQL 节点让 mysqld 以 NDB 引擎工作SQL 节点本质就是编译了 NDB 存储引擎的 mysqld配置上比普通 MySQL 多两行。以 sql1 为例编辑/etc/my.cnf[mysqld] datadir/var/lib/mysql socket/var/lib/mysql/mysql.sock usermysql ndbcluster ndb-connectstring192.168.100.10:1186初始化数据目录这里我习惯用--initialize-insecure因为初始化后需要快速进入创建业务账号密码策略后面再统一设mkdir -p /var/lib/mysql chown -R mysql:mysql /var/lib/mysql /opt/mysql-cluster/bin/mysqld --defaults-file/etc/my.cnf --initialize-insecure --usermysql然后启动 mysqld。第一次启动时mysqld 会通过ndb-connectstring连接管理节点自动在 NDB 里创建 SQL 节点所需的系统表。注意如果这时候集群管理节点或数据节点没起来SQL 节点虽然可能以 InnoDB 模式启动但它并不能正常操作 NDB 表所以启动顺序一定是先管理节点、再数据节点、最后 SQL 节点。启动成功后进入 MySQL/opt/mysql-cluster/bin/mysql -uroot --socket/var/lib/mysql/mysql.sock创建应用账号和 HAProxy 健康检查账号CREATE USER appuser192.168.100.% IDENTIFIED BY StrongPass123; GRANT ALL PRIVILEGES ON appdb.* TO appuser192.168.100.%; CREATE USER haproxy_check192.168.100.% IDENTIFIED WITH mysql_native_password BY ; GRANT USAGE ON *.* TO haproxy_check192.168.100.%; FLUSH PRIVILEGES;haproxy_check这个账号专门给 HAProxy 做 MySQL 协议握手探测用不需要任何权限但不能不存在。用mysql_native_password是因为 HAProxy 的mysql-check对 MySQL 8 默认的caching_sha2_password支持不理想这个细节后面踩坑部分还会详细说。3.4 上线前必做的集群状态检查所有节点启动完毕后别急着部署上层先做一轮完整检查/opt/mysql-cluster/bin/ndb_mgm -e show正常输出里应该看到管理节点、两个数据节点、两个 SQL 节点全部connected/started。然后在任意 SQL 节点验证 NDB 是否真正可用SHOW ENGINE NDB STATUS\G CREATE TABLE test.t ( id INT PRIMARY KEY, v VARCHAR(50) ) ENGINENDB; INSERT INTO test.t VALUES (1, cluster up); SELECT * FROM test.t;SHOW ENGINE NDB STATUS能显示 SQL 节点与数据节点的连接状态。建表时明确指定ENGINENDB是关键如果只写CREATE TABLE不指定引擎默认可能落到 InnoDB那样的表只是存在 SQL 节点本地并没有参与 NDB 集群同步这是个非常隐蔽的坑。4. HAProxy 层把两个 SQL 节点包装成一个入口4.1 基础配置与健康检查在 lb1 和 lb2 上安装 HAProxyRocky/CentOS 用yum install haproxyUbuntu 用apt install haproxy即可。配置主文件/etc/haproxy/haproxy.cfgglobal log 127.0.0.1 local2 maxconn 8192 chroot /var/lib/haproxy user haproxy group haproxy daemon defaults log global mode tcp option tcplog timeout connect 5s timeout client 30s timeout server 30s listen mysql_cluster bind *:3306 mode tcp balance roundrobin option mysql-check user haproxy_check server sql-node1 192.168.100.30:3306 weight 1 check inter 3s fall 2 rise 2 server sql-node2 192.168.100.31:3306 weight 1 check inter 3s fall 2 rise 2这里的几个关键点bind *:3306让它监听所有本地地址这样当 Keepalived 把 VIP 绑到这台机器时到达 VIP 的 MySQL 连接会被内核直接交给 HAProxy。option mysql-check user haproxy_check是 MySQL 专用的健康检查方式比单纯 TCP 端口探测更准。它会让 HAProxy 用 3306 端口向后端 SQL 节点发送 MySQL 握手包验证能建立 MySQL 协议连接。如果只是看端口通mysqld 卡死但端口还在探测成功的情况会误判节点健康。inter 3s fall 2 rise 2表示每 3 秒探测一次连续失败 2 次摘除、连续成功 2 次恢复。如果 SQL 节点偶尔重启这个参数决定 HAProxy 多久发现它恢复。参数设得太长故障窗口变大太短节点一抖动就被摘掉容易引发连接风暴。检查配置并启动haproxy -c -f /etc/haproxy/haproxy.cfg systemctl start haproxy systemctl enable haproxy-c参数用来做配置语法校验改配置后我会先把 HAProxy 的 socket 打开方便热加载和状态查看。在global段加一行stats socket /run/haproxy/admin.sock mode 600 level admin然后用socat查状态echo show stat | socat stdio /run/haproxy/admin.sock能看到每个后端节点的Status是UP还是DOWN这是排查负载均衡最直接的手段。4.2 负载均衡策略怎么选HAProxy 在 TCP 模式下常见的算法有三种roundrobin、leastconn、source。roundrobin是把新连接轮流分发到每个 SQL 节点适合两个节点配置一致、连接时长基本稳定的场景。我对这套架构默认就推荐它因为 NDB 的 SQL 节点是无状态对等的不像 Redis 需要按source哈希保持会话。leastconn更适合连接时长差异很大的场景比如某些查询是小时级的长事务。它会把新连接分给当前连接数最少的节点避免所有长事务压在同一台上。source是按客户端 IP 做哈希适用于需要在某段时间内固定同一后端做有限状态缓存比如临时表的场景。但 NDB 本身支持跨 SQL 节点的临时表同步吗实际上CREATE TEMPORARY TABLE在 NDB 里是节点本地的如果应用用了临时表且连接会漂移就可能出问题。所以如果应用强依赖临时表可以考虑source算法或调整应用让它使用普通 NDB 表。我对这套架构的建议大部分业务直接用roundrobin。NDB 的设计目标就是让 SQL 节点完全对等没必要为了负载均衡算法把架构搞复杂。4.3 只读与写入分离的扩展思路如果你的业务读多写少且愿意在应用层做读写标识可以扩展出两个 HAProxy 监听端口一个用于读写、一个用于只读。SQL 节点还是同一批但只读池可以挂更多 SQL 节点或者给某些 SQL 节点调低权重。listen mysql_rw bind *:3306 balance roundrobin option mysql-check user haproxy_check server sql-node1 192.168.100.30:3306 weight 2 check server sql-node2 192.168.100.31:3306 weight 2 check listen mysql_ro bind *:3307 balance roundrobin option mysql-check user haproxy_check server sql-node1 192.168.100.30:3306 weight 1 check server sql-node2 192.168.100.31:3306 weight 3 check不过对 NDB 来说每个 SQL 节点都是可写的不存在主从概念所以读写分离的意义不在于缓解复制延迟而在于把大的分析查询划到独立端口避免它占满某个 SQL 节点的线程资源影响线上事务。这个扩展很有用但不要为了像主从方案一样而机械地做分离。5. Keepalived给 HAProxy 再上一道保险5.1 VRRP 与 VIP 漂移的基本原理Keepalived 的核心是 VRRP虚拟路由冗余协议。简单理解就是两台机器组成一个虚拟路由器组对外暴露同一个 VIP。同一时刻只有 Master 持有 VIP 并响应业务请求Backup 一直在监听 Master 的心跳。当心跳在某段时间内丢失Backup 认为 Master 挂了就抢占 VIP 继续服务整个过程对客户端透明。这里有个常见误区Keepalived 本身不做应用健康检查它只负责看对端机器还有没有心跳。如果 HAProxy 进程死了但机器还活着VRRP 心跳照常VIP 不会切换。所以必须用track_script把对 HAProxy 进程的检查绑定进去。5.2 配置主备节点在 lb1主上编辑/etc/keepalived/keepalived.confvrrp_script chk_haproxy { script /etc/keepalived/check_haproxy.sh interval 2 weight -20 fall 2 rise 1 } vrrp_instance VI_DB { state MASTER interface eth0 virtual_router_id 51 priority 150 advert_int 1 authentication { auth_type PASS auth_pass dbvip51 } virtual_ipaddress { 192.168.100.100/24 dev eth0 label eth0:1 } track_script { chk_haproxy } notify_master /etc/keepalived/notify_master.sh notify_backup /etc/keepalived/notify_backup.sh notify_fault /etc/keepalived/notify_fault.sh }lb2备的配置几乎一样只改两处state BACKUPpriority 100。其他项目包括virtual_router_id、auth_pass必须完全一致否则两组节点互相不认识会导致脑裂。virtual_router_id是同一广播域里区分不同 VRRP 实例的 ID范围 0~255。如果同一二层网络里有其他 Keepalived 组也用了51两边会互相干扰表现为 VIP 乱跳。规划时提前给自己网络里的 VRRP ID 做个登记能省很多排查时间。auth_pass只有 8 位长度限制别设太长的字符串这一点经常被忽略。5.3 健康检查脚本别让 Keepalived 误判最核心的是/etc/keepalived/check_haproxy.sh#!/bin/bash if ! pgrep -x haproxy /dev/null 21; then systemctl start haproxy sleep 2 if ! pgrep -x haproxy /dev/null 21; then exit 1 fi fi exit 0这个脚本的逻辑是先看 HAProxy 进程在不在不在就尝试拉起拉不起来才让 Keepalived 判定为主机不健康。这样设计的目的是如果 HAProxy 只是意外退出本机能直接恢复就不必触发 VIP 切换毕竟切换一次对应用多少还是有感知的。更严格一点的检查方式是通过 HAProxy 的 socket 看运行状态#!/bin/bash if [ ! -S /run/haproxy/admin.sock ]; then exit 1 fi if echo show info | socat stdio /run/haproxy/admin.sock 2/dev/null | grep -q Process running; then exit 0 else exit 1 fi这样连进程僵死但端口还在的情况也能挡掉。track_script里weight -20意味着脚本失败时优先级减 20主节点 priority 150 减到 130备节点 100所以主挂了备才会接管。notify_master这类通知脚本可以用来自动发告警或清 ARP 缓存实际环境中我会在通知脚本里调 Webhook 推一条变更消息到运维群故障切换的每一个动作都有记录。6. 故障演练我实际杀掉节点之后发生的事6.1 停掉一个 SQL 节点在 sql1 上执行systemctl stop mysqld然后立刻从一台应用机器用 VIP 连库mysql -h 192.168.100.100 -u appuser -p -e SELECT 1连接正常因为 HAProxy 在 3 秒内已经探测到 sql1 失败把连接全部转给 sql2。在 HAProxy 上用socat看状态确认 sql1 是DOWN。这个验证的重点有两个一是连接是否还能建立二是连接建立速度有没有明显变慢。如果inter 3s太短SQL 节点重启瞬间会被摘掉如果太长故障窗口内请求还是会涌向已经挂掉的节点表现为部分连接失败。实测下来inter 3s fall 2 rise 2是平衡性较好的参数。6.2 停掉主 HAProxy 的 Keepalived在 lb1 上执行systemctl stop keepalived。注意我没有直接停 HAProxy而是停 Keepalived这是更接近真实故障的演练方式。几秒后在 lb2 执行ip addr show eth0能看到192.168.100.100已经绑到 lb2 上。再回到应用机器执行一次 MySQL 连接成功。这里有两个细节第一已经建立的 TCP 连接不会自动切走。应用如果之前连着 VIP连接实际是在 lb1 上而 lb1 的 Keepalived 停了之后 HAProxy 还活着因此这条连接可能还能继续用。只有当连接因 lb1 彻底挂掉而断开时新连接才会走 VIP 到 lb2。所以应用侧的连接池重试机制还是必要的。第二VIP 漂移后有些网络交换机会保留老的 ARP 缓存导致短暂时间内包还是发到旧机器。Keepalived 默认会发送免费 ARP 通告来刷新但如果遇到交换机端口安全策略还是可能出现 1 秒左右的连接延迟。notify_master脚本里再手动发一次ip neigh flush可以加速收敛。6.3 停掉一个数据节点这个测试最有意思。在 ndb1 上执行/opt/mysql-cluster/bin/ndbd --stop --connect-stringhost192.168.100.10:1186或直接killndbd 进程。然后去 SQL 节点执行SELECT COUNT(*) FROM appdb.orders; INSERT INTO appdb.orders(id, amount) VALUES (999999, 1.00);查询和插入都成功。原因是我们在config.ini里配置了NoOfReplicas2数据在两个数据节点上各有一份副本ndb1 宕掉后ndb2 上仍保留完整数据NDB 会自动把相关分片标记为需要重建等 ndb1 重新加入集群后后台自动从 ndb2 拉取数据补齐副本。在管理节点上执行ndb_mgm -e show能看到 ndb1 状态是Down或被标记为unavailable稍后重启它状态又会变回started。这里要说清楚ndb1 数据节点重启不需要--initial它本地文件系统还留着之前的重做日志和检查点启动后可以和 ndb2 对账完成同步。如果你手滑加了--initial它会自认为是空节点把副本整个删掉重新同步数据量大会很痛苦。7. 踩坑清单与部署完之后的调优建议7.1 我记下来的 8 个坑第一个坑是端口和防火墙。我遇到过一台 SQL 节点反复报节点拒绝连接排查半天发现是防火墙没放行 1186 和 2202。NDB 的管理端口是 1186数据节点之间的数据通道是 2202这两个端口在管理节点、数据节点上都要双向放行。SQL 节点到管理节点只需要 1186但 SQL 节点到数据节点还需要 2202。第二个坑是--initial的误用。刚搭集群时我用脚本统一启动两个数据节点参数里带了一次--initial结果第二次重启还是同一个脚本两个节点全部重新初始化等于把集群状态清空重来了。后来我把首次初始化和日常启动拆成了两个 systemd unit日常启动的 unit 里绝不带--initial。第三个坑是表引擎不是 NDB。团队里有人按 InnoDB 习惯建表没写ENGINENDB导致这张表只存在于某个 SQL 节点本地。应用通过 VIP 连接时如果 HAProxy 把请求分到另一个 SQL 节点直接报table not exist。我的解决办法是在 SQL 节点上设置default_storage_engineNDB并且在建表规范里强制校验。第四个坑是 HAProxy 的mysql-check和 MySQL 8 认证插件不兼容。MySQL 8 默认的caching_sha2_password会让 HAProxy 的健康检查表现为连接后无法正常握手后端节点会被误判为 DOWN。解决方案就是给 HAProxy 单独建一个mysql_native_password的空密码账号像我在 3.3 节做的那样。第五个坑是DataMemory和IndexMemory设置过小。NDB 是内存优先的存储引擎数据写满后会出现Temp out of memory之类的报错而且是全集群级别的写入失败。上线前要评估 DataMemory 增长留出 30% 余量并配置磁盘日志做重放。第六个坑是 Keepalived 的auth_pass两边不一致。配置里只要有一个字符不同主备就互相认为对方不合法VIP 会频繁漂移甚至出现双 Master也就是脑裂。排查方法是在两台 lb 上分别执行journalctl -u keepalived看有没有invalid authentication之类日志。第七个坑是应用侧没有连接池重试。即使 Keepalived 切换只要几秒但切换瞬间新建连接还是可能失败一次。Java 应用用 HikariCP 的话把connectionTimeout设为 3000ms、开启connectionTestQuery并做两层重试就能平滑扛过切换。第八个坑是管理节点本身单点。NDB 8.0 支持配置多个管理节点但大部分人部署时为了省事只起了一个。如果管理节点所在机器宕机集群虽然还能继续服务但无法执行节点管理操作。生产环境我建议至少两个管理节点config.ini 里追加[ndb_mgmd]段但要注意管理节点配置必须保持完全一致否则会出现两个管理节点各自认为自己是权威的混乱状态。7.2 应用侧配合才能算是真正的高可用架构部署到位只是高可用的一半另一半在应用代码和连接池配置里。应用连接数据库的地址要固定用 VIP不能用 SQL 节点 IP 去连。一旦直接硬编码了某个 SQL 节点 IPHAProxy 那层就被绕过了负载均衡和高可用全都失效。连接池大小要克制。HAProxy 的maxconn默认 8192但每个后端 SQL 节点能承载的连接数有限。应用连接池设太大瞬间并发就把 SQL 节点打满反而拖垮集群。我建议数据库连接池的核心线程数从 10~20 起步压测后再逐步调大。SQL 侧要避免长事务和SELECT ... FOR UPDATE跨节点锁。NDB 支持事务但分布式事务的开销比 InnoDB 本地事务大锁冲突时等待时间会更明显。写后端的同事要养成快提交的习惯几百毫秒内的短事务对 NDB 是最友好的。最后给一个小建议这套架构搭完后保留一次完整的故障演练记录包括每次停节点的时间点、VIP 切换耗时、应用重连耗时。下次再有人问你数据库高可用做了吗你把这份记录甩出去比任何架构图都有说服力。我自己的习惯是每季度做一次注入演练顺便验证监控告警是不是真的会响。
