1. 为什么要折腾 MySQL Cluster 8.0.26先说个实际场景。几个月前一个做电商库存系统的兄弟团队来找我说他们线上 MySQL 主从复制快顶不住了写入量一大从库延迟飙到几十秒应用层刚写进去的库存数读出来还是旧值。搞了读写分离又引入缓存一致性该错还是错。我跟他们说你们这个场景要的是多节点同时可写、数据强一致、故障自动切换与其在主从复制上继续打补丁不如认真看一下 MySQL Cluster也就是 NDB Cluster。趁这段时间我把 MySQL Cluster 8.0.26 从零到一完整搭了一遍写这篇东西就是想把整个搭建过程、配置参数背后的原理、以及踩过的坑一次说透。这篇文章适合三类人一类是被主从复制延迟折磨的 DBA一类是正在做高可用架构选型的后端工程师还有一类是纯粹想搞懂 NDB 引擎和普通 InnoDB 到底差在哪的学习者。看完你能做到的是照着文中的步骤搭出一套可用的集群并且理解每一步为什么要这么做。MySQL Cluster 和普通 MySQL 最大的区别在于存储引擎。普通 MySQL 默认是 InnoDB数据落在每台机器的本地磁盘上主从之间靠 binlog 同步本质上是拷贝而 MySQL Cluster 使用 NDB 引擎数据按主键哈希分布到多个数据节点每个节点同时持有其他节点的副本事务提交采用同步复制协议。意味着任何一个节点挂掉数据都不会丢写入也不会因为单点故障而阻塞应用层看到的就是一台永远不会死的 MySQL。8.0.26 这个版本值得关注它是 NDB 8.0 系列里比较成熟的一个版本。相比 5.7 时代的 Cluster8.0 在数据字典、权限体系、SQL 语法兼容性上都和标准 MySQL 8.0 对齐了很多之前只能用 InnoDB 才能跑的功能现在 NDB 也开始慢慢支持。但要注意它依然不是万能方案后续章节我会重点讲它的边界在哪。2. 角色拆解管理节点、数据节点、SQL 节点各司其职2.1 三种节点的分工以及为什么必须分工很多第一次接触 Cluster 的人会被角色这个概念搞晕。其实它的架构理解起来不难传统 MySQL 是一台机器干所有事Cluster 则是把一件事拆成了三部分管理节点ndb_mgmd是集群的调度中心负责保存集群的配置文件、监控各个数据节点的状态、处理节点加入和退出。它不存业务数据也不处理 SQL平时负载很低但地位非常关键。管理节点挂掉整个集群的元数据就失去协调者数据节点和 SQL 节点之间无法完成新连接建立和故障转移生产环境至少部署两个。数据节点ndbd 或 ndbmtd是集群的存储引擎所有业务数据都分片存在这里。数据节点之间通过内部网络两两同步默认每个分片有两份副本分别落在不同的数据节点上。ndbd 是单线程进程ndbmtd 是多线程进程8.0.26 里我们通常直接用 ndbmtd多核 CPU 能利用得更充分。SQL 节点就是普通 mysqld 进程只不过启用了 ndbcluster 存储引擎插件。应用连接它SQL 解析、优化、执行计划都在这里生成再把底层的数据访问请求下推到数据节点。SQL 节点本身不存数据所以可以横向加很多个应用层随便连哪台都行。2.2 一个最小集群最少要几台机器先算笔账。数据分片默认两个副本也就是 NoOfReplicas2那么想要数据不丢至少两个数据节点。管理节点 8.0.26 里推荐也搞两套SQL 节点最少一个这样最低配置是三台机器一台管理节点SQL节点合并跑、两台数据节点。但注意这种合并方案只适合测试环境。生产上我最建议的拓扑是两台管理节点四台数据节点两台 SQL 节点。四台数据节点意味着每个分片的主副本和备副本都能分散开即使两台机器同时宕机只要不是同一分片的两个副本同时消失集群依然可用。SQL 节点和应用层之间用负载均衡或者 Keepalived 做虚拟 IP任何一个 SQL 节点挂掉连接自动切走。2.3 版本选择与安装包获取MySQL Cluster 的安装包不是标准 MySQL 那种官网专门有 MySQL Cluster 下载通道8.0.26 对应的是基于 MySQL 8.0.26 的 NDB 8.0.26 版本安装包命名里通常带着 mysql-cluster-8.0.26。选版本时有个需要注意的坑NDB 版本号必须和 MySQL 版本号严格匹配。你如果已经装了标准 MySQL 8.0.26也不能直接拿来当 SQL 节点必须用 Cluster 安装包里的 mysqld 二进制因为那个二进制才编译了 ndbcluster 引擎。我见过有人把包下载错了装完发现 ndbcluster 插件不存在浪费了半天。安装方式我是推荐用二进制 tar 包不推荐源码编译。因为 Cluster 涉及三个角色源码编译要分别指定不同配置过程繁琐且容易出错。tar 包解压到统一目录比如 /usr/local/mysql然后各节点创建独立数据目录权限配好比编译省心得多。3. 配置文件的每一行都得知道它是干什么的3.1 管理节点 config.ini 核心参数全解析管理节点的配置文件是 config.ini它不是 my.cnf 的格式而是 NDB 特有的 INI 风格。下面这份配置就是我实际搭建时用的可以直接对照着看[ndbd default] NoOfReplicas2 DataMemory2G MaxNoOfTables2048 MaxNoOfAttributes50000 MaxNoOfOrderedIndexes1024 DataDir/data/mysql-cluster RedoBufferSize64M [ndb_mgmd] NodeId1 HostName192.168.10.10 DataDir/data/mgmd [ndb_mgmd] NodeId2 HostName192.168.10.11 DataDir/data/mgmd [ndbd] NodeId3 HostName192.168.10.12 DataDir/data/ndbdata [ndbd] NodeId4 HostName192.168.10.13 DataDir/data/ndbdata [ndbd] NodeId5 HostName192.168.10.14 DataDir/data/ndbdata [ndbd] NodeId6 HostName192.168.10.15 DataDir/data/ndbdata [mysqld] NodeId101 HostName192.168.10.20 [mysqld] NodeId102 HostName192.168.10.21NoOfReplicas2 表示每个分片保留两份副本这是 Cluster 高可用的根基。DataMemory 则是给数据节点分配的内存上限NDB 默认把数据尽量放内存所以这个值直接决定了集群能存多少数据2G 只是测试值生产环境要看业务量估算后面专门讲。MaxNoOfTables、MaxNoOfAttributes、MaxNoOfOrderedIndexes 这几个参数决定分片内元数据对象的数量上限。很多人不关心它们默认值也能跑但如果业务表特别多、字段特别多把上限撑爆数据节点会直接拒绝建表报错还不好查。我的习惯是给一个业务峰值 3 到 5 倍的余量。RedoBufferSize 是重做日志的内存缓冲64M 对大多数场景够用了。注意NDB 的重做日志机制和 InnoDB 的 redo log 不同它按分片管理如果这个 buffer 配得太小写频繁时分片可能临时进入Redo log 已满的状态整个分片只读。所以宁可给大别给太小。管理节点这里要配两个 IP对应两台管理节点机器。注意 [ndb_mgmd] 段落出现了两次这是 NDB 8.0 支持多管理节点的方式NodeId 必须不同。3.2 数据节点和 SQL 节点的 my.cnf 与启动参数数据节点其实可以不写 my.cnf直接用命令行参数启动但我建议还是在每个数据节点上准备一个独立的 my.cnf便于统一管理核心内容如下[mysqld] ndbcluster ndb-connectstring192.168.10.10,192.168.10.11 [ndbd] ndb-connectstring192.168.10.10,192.168.10.11这里的关键是 ndb-connectstring指向两个管理节点的 IP两个地址用逗号分隔。SQL 节点首次启动时会连上任意一个管理节点拉取集群拓扑信息。如果只填一个管理节点 IP那个节点刚好挂了SQL 节点就找不到集群了这与前面管理节点至少两个的规划是对应的。SQL 节点的配置更简单[mysqld] ndbcluster ndb-connectstring192.168.10.10,192.168.10.11 default-storage-enginendbcluster skip-name-resolve注意 default-storage-enginendbcluster 这行我建议只在确认所有业务表都要走 NDB 时才开。如果你只是部分表用 NDB其余还是 InnoDB就不要写这行而是在建表语句里显式指定 ENGINENDB。因为 MySQL 8.0 的系统表是 InnoDB如果强制默认引擎是 NDB某些内部操作反而会出问题不如显式控制来得稳。skip-name-resolve 强烈建议开不然 SQL 节点每次接受连接都要反解域名DNS 一旦超时连接建立会非常慢。Cluster 内部完全靠 IP 通信域名反解没有意义。3.3 内存参数规划DataMemory 到底该设多大这个问题每个搭建 Cluster 的人都会问。DataMemory 决定的是数据节点能使用多少内存来存放实际数据记录和索引。NDB 8.0 里 IndexMemory 已经被废弃索引不再单独分配统一算进 DataMemory所以配置简单了很多不用再分两笔账。估算方法可以这样看假设一张表有 1000 万行每行平均 300 字节加上 NDB 内部的行头、分片头、哈希索引开销大约 1.2 到 1.5 倍膨胀就是 300 * 1000万 * 1.3约 3.9G。然后因为 NoOfReplicas2每份数据在两个数据节点各存一份所以每个数据节点需要容纳 3.9G 的该表数据。如果有多张表累加再乘以 1.5 到 2 的安全系数基本就是 DataMemory 的合理值。其中有个坑必须提醒DataMemory 是物理内存映射操作系统一旦触发 swapNDB 的数据节点性能会断崖式下跌而且 NDB 自己无法感知 swap它以为内存还够实际上已经慢了几十倍。所以数据节点机器的 Swap 建议直接关掉或者至少设成非常低的 swappiness 值。很多集群在生产环境莫名变慢查到最后都是 swap 在作祟。4. 启动顺序、验证命令、表结构迁移4.1 为什么必须按管理节点、数据节点、SQL 节点顺序启动Cluster 对启动顺序有严格要求第一台必须是管理节点第二台是全部数据节点最后才是 SQL 节点。原因很简单数据节点启动时要到管理节点那里拿集群配置、确认自己的 NodeId、和兄弟节点建立心跳SQL 节点启动时要查询数据分片的分布信息如果数据节点没起来它就拿不到完整拓扑。实际操作中我在四个数据节点上分别执行/usr/local/mysql/bin/ndbmtd --ndb-nodeid3 --ndb-connectstring192.168.10.10,192.168.10.11 --initial这里有一个极其重要的点--initial 参数只在节点数据目录为空或确实需要清空重来时才加。它做的事情是清空该节点的本地数据文件重新从其他副本拉取数据。如果你在节点正常运行期间为了重启图省事加了 --initial等同于告诉集群我本地数据不要了重新同步轻则长时间重建重则因为所有副本都初始化数据直接丢光。所以我在生产操作的准则里写得很清楚日常重启数据节点不加 --initial只有更换节点硬件、恢复损坏的数据目录、或者集群初始化时才用。SQL 节点启动就是正常的 mysqld 启动方式可以用 systemd也可以直接 mysqld_safe关键看它日志里有没有类似 NDB: connected to management server 的记录。没有这个记录说明它根本没连上集群后续任何建 NDB 表的操作都会报错。4.2 ndb_mgm 里面的高频命令建议背下来数据节点启动完成后在任意一台能访问管理节点的机器上执行/usr/local/mysql/bin/ndb_mgm -e SHOW如果一切正常你会看到类似下面的输出Connected to Management Server at: 192.168.10.10:1186 Cluster Configuration --------------------- [ndbd(NDB)] 4 node(s) id3 192.168.10.12 (mysql-8.0.26 ndb-8.0.26, Nodegroup: 0, *) id4 192.168.10.13 (mysql-8.0.26 ndb-8.0.26, Nodegroup: 0) id5 192.168.10.14 (mysql-8.0.26 ndb-8.0.26, Nodegroup: 1, *) id6 192.168.10.15 (mysql-8.0.26 ndb-8.0.26, Nodegroup: 1) [ndb_mgmd(MGM)] 2 node(s) id1 192.168.10.10 (mysql-8.0.26 ndb-8.0.26) id2 192.168.10.11 (mysql-8.0.26 ndb-8.0.26) [mysqld(API)] 2 node(s) id101 192.168.10.20 (mysql-8.0.26 ndb-8.0.26) id102 192.168.10.21 (mysql-8.0.26 ndb-8.0.26)看懂这张表是关键能力。id 是节点编号 后面是对应 IP括号里的 nodegroup 表示数据节点属于哪个节点组。节点组的概念类似于传统分布式系统里的分片同一个 nodegroup 内才是真正的副本关系。上面输出中id3 和 id4 属于 nodegroup 0也就是说它们互为副本id5 和 id6 是 nodegroup 1。这样四个数据节点自动把数据分成两个分片每个分片双副本既分散了负载又保证了冗余。带星号 * 的节点表示当前该分片的主副本所有读写都由它控制另一个是备副本。主备之间遇到网络分区问题时仲裁机构就起作用了这个后面在问题排查章节详细说。日常运维命令备几套就够了# 查看所有节点状态 ndb_mgm -e ALL STATUS # 查看节点详细信息含内存和磁盘占用 ndb_mgm -e ALL REPORT MEMORYUSAGE # 在线备份 ndb_mgm -e START BACKUP # 停止某个数据节点 ndb_mgm -e 3 STOP4.3 把 InnoDB 表迁移到 NDB有哪些硬限制表迁移是很多人用 Cluster 时最纠结的环节。你有一堆现存的 InnoDB 业务表要转成 NDB方法很简单ALTER TABLE orders ENGINENDB;但这条语句能否成功取决于几个硬性条件。NDB 表必须有显式主键或者至少有一个唯一索引因为 NDB 的数据分布依赖主键哈希。没有主键的表执行 ALTER 会直接报错。别想着我们的表没主键也用了很多年在 NDB 里这行不通必须先把主键加上。另外NDB 对 TEXT/BLOB 字段是支持的但这类大字段默认不会完全驻留内存而是存储在磁盘上访问性能比内存字段差一到两个数量级。如果你的一张表里混着一堆 TEXT 字段又没有把常用字段拆开建议提前做垂直拆分把大字段单独拆表否则一部分数据走磁盘整张表的性能预期就要重新评估。还有一个不少新人会忽略的问题ALTER 转换表引擎时NDB 和 InnoDB 的事务行为不完全一致。InnoDB 支持外键NDB 虽然 8.0 也支持外键但不是所有场景都推荐用。我见过有人在 NDB 表上建了一堆外键结果写并发一上来外键检查和分布式操作的开销被放大性能远不如预期。建议在 Cluster 场景里外键约束从应用层做掉数据库只存数据。5. 高频问题排查与生产运维手记5.1 节点连不上、状态显示 Unknown基本从哪里查起搭建过程中的报错70% 都出在连接问题上。最典型的症状是数据节点启动后管理节点那边一直显示 Node 3: Node is not connected。第一排查项是网络。管理节点和数据节点的数据通道默认走 1186 端口如果中间有防火墙、安全组或云防火墙拦截节点之间永远连不上。我在排错时直接在管理节点上 telnet 数据节点的 1186通了再往下查。第二排查项是 NodeId 冲突。config.ini 里写了 3 到 6 四个数据节点如果你在启动时手动指定了错误的 --ndb-nodeid比如两个节点都指定为 3管理节点会日志刷出一堆 ID 冲突错误。记住节点的身份是 NodeId不是 IP。第三排查项是管理节点配置文件改了没重新加载。管理节点不是每次自动重读 config.ini 的你改了配置后需要重启 ndb_mgmd或者执行 ndb_mgmd --reload。如果没重载新节点拿着新配置来连接管理节点还记着旧拓扑自然连不上。SQL 节点连接不上时错误日志通常在 SQL 节点的错误日志里关键词搜 NDB 或者 connectstring。确认 SQL 节点和所有数据节点、管理节点之间都能互相 ping 通这是最基础的。5.2 仲裁机制是什么SQL 节点全挂会导致什么NDB 的数据副本机制里有一个概念叫仲裁arbitration很多人不理解。当两个数据节点之间的网络出现分区比如 nodegroup 0 的 id3 和 id4 彼此联系不上了每个节点都认为对方死了都想接管主副本地位。这时候谁来拍板仲裁节点。默认情况下仲裁节点可以是任何一个 API 节点也就是 SQL 节点也可能是管理节点自身。如果仲裁节点也挂了或者仲裁节点到两个数据节点的网络都断了数据节点会僵持住整个分片不可用因为它们无法确定谁是真正存活的主副本。这就是为什么我说 SQL 节点不要只搞一台。如果唯一一台 SQL 节点宕机而且管理节点没能及时接管仲裁角色那么整个集群的正常读写都会受到影响即使数据节点本身还是好的。生产环境至少两台 SQL 节点不仅是为了应用连接的负载均衡更是为了仲裁可用性。5.3 数据节点报内存不足或者 Redo Log 满应该怎么处理数据节点日志里最让人头皮发麻的两条一条是 Out of DataMemory另一条是 Redo log is full。Out of DataMemory 的意思是现有的 DataMemory 已经用完了新写入无法分配内存空间。这并不代表系统崩溃但所有写入分片的操作会失败。处理办法是先通过 ALL REPORT MEMORYUSAGE 确认当前各个节点的内存使用率然后规划扩容。扩容要改 config.ini 里 DataMemory 参数接着重启管理节点让配置生效再逐个滚动重启数据节点。这里不能直接改完配置就完事数据节点没有重新读取配置之前它还是按旧值运行。Redo log is full 更微妙一些。它通常是因为重做日志的落盘速度跟不上事务生成速度导致 RedoBuffer 被写满分片为了保护数据一致性暂停接受新的写入事务。解决思路是提高 RedoBufferSize或者检查数据节点的磁盘 IO 能力是否成了瓶颈。如果是 SSD 和机械盘的差距那优先升级磁盘比调参管用多了。另外必须强调遇到上述任何一种报错都不要擅自对一个数据节点执行 --initial 重启。--initial 会清掉该节点本地数据如果同时该节点所在 nodegroup 的另一副本也出了问题数据可能真的救不回来。正确做法是先确认至少有一个副本的数据是完整的再决定是否重建节点。5.4 在线扩容给集群加一个数据节点难不难Cluster 理论上支持在线扩容这也是它比主从复制优秀的地方。往集群里加一个数据节点的操作分为四步。第一步修改 config.ini新增一个 [ndbd] 段落NodeId 别和其他节点冲突。第二步在 ndb_mgm 执行 ADD NODE 命令让管理节点重新加载配置。第三步新机器上启动 ndbmtd加入集群。第四步NDB 会自动把已有数据分片的一部分重新分布到新节点上这个过程叫 rebalance会占用内部网络的 IO。这里有一个很现实的问题rebalance 期间集群的写性能会有一定下降。所以没有人会在业务高峰期做扩容一般选凌晨。我在实践中有个原则扩容前先看看当前所有数据节点的内存占用率如果整体已经超过 70%扩容完成后再过一阵子就会又紧张建议一次至少加两个数据节点形成新的 nodegroup让分片布局更均衡。6. 备份、恢复与一致性检查的实用姿势6.1 用 NDB 自带备份比想象中简单Cluster 的备份和普通 MySQL 的 mysqldump 思路完全不同。NDB 备份是物理备份直接在管理节点上执行ndb_mgm -e START BACKUP这个命令会在所有数据节点上并行生成备份文件每个节点只备份自己负责的分片。备份期间集群照常运行不会锁表。备份完成后备份文件存在于每个数据节点的 DataDir 下的 BACKUP 目录里同时管理节点上有一个元数据文件记录备份ID。恢复就稍微复杂一点要用 ndb_restore 工具分节点逐个恢复。基本步骤是先停掉需要恢复的分片节点然后用 --restore_data 和 --restore_meta 两个参数分别恢复元数据和数据。所有节点都恢复完再重启整个集群。我的建议是不要等出事了才去验证备份文件。每做完一次备份就在测试环境里做一次恢复演练。因为 ndb_restore 的权限依赖、备份路径、NodeId 对应关系任何一个地方出错恢复流程就卡住。演练的时候把这些坑提前踩掉生产环境才能心里有底。6.2 怎样确认集群数据一致性真的没问题很多人搭好集群后问的第一个问题是怎么确认多个副本的数据真的是一致的第一道检查是看节点的状态。ndb_mgm -e SHOW 输出里只要所有数据节点都是连着的状态不是 UNKNOWN这说明节点间的心跳和数据同步通道正常副本之间基本是收敛的。第二道检查是验证读写。在 SQL 节点上建一张测试表写入一个随机值然后去另一台 SQL 节点上查询看看值是否一致。这个方法虽然不是强证明但能快速发现底层的分片路由是否异常。第三道检查是看节点日志和系统变量。在 SQL 节点执行 SHOW ENGINE NDB STATUS能查看到大量内部状态比如内存页使用率、节点间通信延迟。如果某些指标出现了异常偏差比如某个数据节点的连接数一直比别人高很多或者传输延迟突然飙升就要警惕是否出现了数据倾斜或者网络故障。7. 搭建过程中最容易忽略的系统级配置7.1 文件描述符与线程限制NDB 数据节点为了高并发会打开大量文件和网络 socket。Linux 默认的 ulimit 是 1024不够用。生产环境需要在 /etc/security/limits.conf 里把 nofile 调到 65535 以上同时把 nproc 也调高。如果不调表象非常诡异数据节点刚启动时一切正常运行一段时间后突然报错 Too many open files然后分片进入只读状态。因为你总不可能在业务高峰时去改 limits 再重启节点所以这个问题必须提前预防。7.2 时钟同步的重要性NDB 集群对节点间的时钟偏差非常敏感。数据同步、事务超时判断、消息确认都依赖时间戳如果两台数据节点系统时间差了几秒轻则出现毫无规律的写入失败重则导致误判节点存活状态触发不必要的仲裁切换。解决办法就是标配 NTP 或 chrony我通常在每一个节点上配好 chrony并且监控时间偏移。用 chrony 的话配置完成后执行 chronyc tracking看 System time 的值尽量保持在个位数毫秒内。这一步很多时候被忽略但等到出问题时往往是最难查的根因。7.3 数据目录放哪决定了故障恢复速度数据节点的 DataDir 不能放在根分区应该单独挂载独立的数据盘最好是 SSD。因为 NDB 的物理备份、redo log、数据页都会写在这个目录如果和系统盘共用磁盘写满时系统直接卡死数据节点日志都来不及写出来。而且 DataDir 的权限也要小心。如果以 mysql 用户运行 ndbmtd那么 DataDir 的所有者必须是 mysql否则启动时直接报权限错误。这个错误在测试环境里我用 root 启动过没暴露问题一到生产用普通用户跑就立刻现形。宁可一开始就统一用专用的服务账户也省得后来来回改权限。8. 写在最后我从这次搭建里沉淀下来的体会这套 MySQL Cluster 8.0.26 从搭建到跑业务前前后后折腾了近两周中间遇到过连接失败、仲裁争议、内存超额、备份恢复各种问题。现在回头看Cluster 真正考验人的不是安装步骤而是对分布式系统的理解副本怎么分布、仲裁怎么运作、故障怎么恢复。如果你只把它当成一个不会挂的 MySQL来用那迟早会在某个深夜被它的分布式特性反噬。我个人最想强调的一点是不要在关键参数上贪图省事。DataMemory 往小了配后患无穷管理节点只部署一台等于给整个集群留了个单点redo log 满了才想起来加业务已经被迫中断。集群搭建最忌讳先跑起来再说因为跑起来容易跑得稳难。如果你正准备在自己团队推广 NDB Cluster我建议先拿一个小业务场景做试点比如库存、账户余额这类对强一致要求高的数据把完整的数据迁移、故障演练、备份恢复流程都跑通再逐步扩大到核心业务。另外8.0.26 之后的新版本也在迭代等社区版本稳定后升级前一定要看官方文档里的行为变更Cluster 的版本升级不像单机 MySQL 那么随意。最后分享一个小技巧在所有节点的本地都保存一份 config.ini 的副本标注好每一版改动的时间和原因。Cluster 的故障排查往往需要回溯配置历史有了这份记录定位问题的速度会快很多。搭建集群本身不难难的是让它在各种意外面前依然稳得住。
阅读完成 · 觉得有帮助?