
想在一堆鲲鹏ARM机器上搭MySQL主从集群我接到的第一反应就是必须用镜像方式别去碰源码编译。ARM架构下的MySQL手工部署坑太多编译耗时长、依赖版本乱、后续升级又得重新来一遍而官方镜像把运行环境提前固化好了拉下来就是一个能跑的MySQL主从复制要做的只是配置、连库、起复制通道思路非常干净。这篇内容我会把镜像选型、网络规划、GTID复制参数、容器启动、复制校验和踩坑排查完整拆开讲。适合两类人一类是把业务往ARM平台迁移、正在焦虑MySQL怎么装的运维另一类是已经用上鲲鹏服务器、想通过容器方式快速交付一个高可用读写架构的后端研发。全程按我实际跑通的方案来命令直接可用。1. 为什么在鲲鹏ARM上优先选镜像方式搭MySQL主从1.1 源码编译不是不行但没必要ARM架构下最常见的MySQL安装方式有三个源码编译、发行版二进制包、容器镜像。源码编译在x86上已经很折腾了到了ARM上问题会翻倍——你需要确认编译器的ARM版本支持、处理MySQL源码里对ARM的特定优化选项、等待动辄一两个小时的编译过程这还只是单机。如果一套集群有三台机器意味着同样的坑要踩三次。发行版二进制包要稍微好一点比如用包管理器直接装但这个方案依赖操作系统发行版的维护节奏。MySQL 8.0的某些补丁版本、参数特性系统源里的包可能滞后而且不同操作系统的源维护质量差异很大你在银河麒麟上装出来的MySQL和在openEuler上装出来的可能行为都不一样。镜像方式的最大优势在于可复现性。官方MySQL镜像已经针对glibc、openssl等公共依赖做了适配底层是经过测试的Linux环境拉到鲲鹏上只要CPU架构是aarch64就能跑。同一套配置、同一个镜像版本在测试环境和生产环境得到的是完全一致的行为这个确定性对集群运维来说是第一位的。1.2 镜像方式的主从集群建立链路更简单主从复制的本质并不复杂主库把变更写进binlog从库拉取binlog并重放到自己的数据文件。真正麻烦的是让两台MySQL实例能互相连通、账号权限正确、日志格式一致、位点或GTID信息对齐。镜像方式把这些约束收敛成了三类物料配置文件通过挂载卷注入容器确定server_id、binlog格式、GTID开关。环境变量控制初始化密码、字符集、时区。网络配置通过自定义bridge网络分配固定IP避免容器重启后IP漂移。把这三类物料准备好集群的建立就只剩下启动容器和执行CHANGE REPLICA两条命令。所以我个人强烈建议在鲲鹏ARM服务器上用镜像方式部署尤其是当你有多个环境要交付、还希望以后能快速扩容从库的时候。2. 部署前要确认的硬件与系统环境2.1 架构检测别一上来就把镜像拉错了鲲鹏920处理器是ARMv8架构Linux下通常显示为aarch64。在动手拉镜像之前先执行下面命令确认架构uname -m输出结果必须是aarch64只要是这个结果官方MySQL镜像的多架构manifest会自动选择ARM版本。但我见过不少同事在鲲鹏机器上拉镜像时因为配置了旧版Docker或者镜像源不支持manifest列表最终拉到了x86_64的镜像启动的时候直接报exec format error。另外建议看下操作系统版本和内核cat /etc/os-release uname -r比如openEuler 22.03 LTS或者麒麟V10内核对容器运行时的支持都比较成熟Docker或Podman都能正常使用。内核版本太老的话建议先升级到支持overlay2存储驱动的版本。2.2 目录规划配置、数据、日志三分离虽然容器给了我们隔离的运行环境但数据不能放在容器可写层里否则容器一删数据就没了。我习惯在宿主机上规划三个基础目录mkdir -p /data/mysql-master/conf mkdir -p /data/mysql-master/data mkdir -p /data/mysql-slave/conf mkdir -p /data/mysql-slave/dataconf目录存放自定义配置文件挂载到容器内/etc/mysql/conf.d/MySQL会在这个目录下自动加载所有.cnf文件。data目录对应MySQL数据目录挂载到容器内/var/lib/mysql实际数据文件保存在宿主机磁盘上。这样做的理由很简单容器可以被销毁重建配置和数据都留在宿主机上。升级镜像版本、迁移到另一台机器只要把这两个目录带走数据就还在。2.3 网络规划给容器固定IP默认bridge网络下容器每次重启IP都可能变化这对MySQL主从复制来说是灾难。从库连接主库的IP如果变了复制线程会一直重试直到超时。所以我会先创建一个自定义网络并指定子网docker network create --driver bridge --subnet 172.28.0.0/24 mysql-net规划如下表示例节点容器名固定IP对外映射端口主库mysql-master172.28.0.103306从库mysql-slave172.28.0.113307从库映射3307是为了在宿主机上区分两个实例。容器内部都是3306互相访问直接用容器名mysql-master和mysql-slaveDocker内置DNS会自动解析我后面建立复制通道时会用到这个特性。3. MySQL主从复制核心配置参数详解3.1 为什么用GTID而不是传统binlog位点主从复制有两种衔接方式传统方式基于binlog文件名偏移量从库要记住自己消费到了哪个文件的哪个位置GTID方式则完全不一样每次事务提交都会生成一个全局唯一的事务ID从库只要知道自己执行过哪些GTID遇到没执行过的事务就接着执行自动跳过已经执行过的。差距在切换场景下非常明显。传统位点方式在主库宕机后需要人工比对主从日志找位点这个过程只要业务在持续写入位点就一直在变手工追平很痛苦。GTID方式下只需确认主从两端gtid_executed集合的差异新主库上的事务集合完整包含从库的集合就能直接切换。所以在昆仑ARM这类新平台上搭建集群我直接用GTID配好gtid_modeON和enforce_gtid_consistencyON之后建立和维护复制都简单很多。3.2 主库配置的关键参数主库配置文件/data/mysql-master/conf/master.cnf内容如下[mysqld] server_id1 port3306 binlog_formatROW binlog_row_imageFULL log_binmysql-bin binlog_expire_logs_seconds604800 gtid_modeON enforce_gtid_consistencyON log_replica_updatesON max_connections500 innodb_buffer_pool_size8G skip_name_resolveON default_time_zone08:00 character_set_serverutf8mb4 collation_serverutf8mb4_0900_ai_ci逐个解释关键项server_id1整个集群内每个实例的标识必须唯一。从库的server_id如果和主库一样复制线程会报错。binlog_formatROW行级日志比statement更可靠不会因为函数、存储过程导致主从数据不一致。ARM平台上这点没有特别差异但行级格式对数据一致性保障最好。log_binmysql-bin开启binlog这是主从复制的数据源。gtid_modeON和enforce_gtid_consistencyON启用GTID模式并强制所有语句都使用GTID安全的方式执行。log_replica_updatesON允许从库把从主库同步过来的事务也记录进自己的binlog。这在级联复制场景下必须开启即使现在只有一主一从也建议提前打开避免以后扩容时再调整。innodb_buffer_pool_size8G这个值根据物理内存调整一般设物理内存的50%~60%。比如机器有32G内存设16G是合理的但如果你还要在同一台机器跑从库两个容器加起来别超过物理内存的70%。3.3 从库配置的关键参数从库配置文件/data/mysql-slave/conf/slave.cnf内容如下[mysqld] server_id2 port3306 relay_logmysql-relay-bin log_binmysql-bin log_replica_updatesON gtid_modeON enforce_gtid_consistencyON read_onlyON super_read_onlyON innodb_buffer_pool_size8G skip_name_resolveON default_time_zone08:00 character_set_serverutf8mb4 collation_serverutf8mb4_0900_ai_cirelay_logmysql-relay-bin中继日志保存在从库本地主库binlog拉取下来先写入中继日志再由SQL线程回放。read_onlyON和super_read_onlyON强制从库只读防止应用程序绕过主从架构直接往从库写数据。super_read_only连超级用户都拦住了除非以后提升为主库时手动关闭。从库同样打开了log_bin这看起来有点矛盾从库只读为什么还要开binlog因为如果未来要把这台从库提升为新主库或者再加一个二级从库没有binlog的从库是没法继续向后传递数据的。3.4 复制账号与授权SQL在主库初始化完成后执行以下SQL创建复制专用账号。注意主机限定为从库所在网段CREATE USER repl172.28.0.% IDENTIFIED WITH mysql_native_password BY Repl_2024_StrongPass; GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO repl172.28.0.%; FLUSH PRIVILEGES;这里故意用了mysql_native_password而不是MySQL 8.0默认的caching_sha2_password原因后面问题排查部分会详细讲。简单说在容器网络内如果不配置SSL或RSA公钥交换默认认证插件可能让从库连接主库时卡住直接用mysql_native_password能省掉这个麻烦。生产环境建议把账号密码放到密钥管理工具或Docker Secret里这里为了演示就直接写SQL了。密码策略上长度不低于16位包含大小写字母和特殊字符。4. 用镜像和容器把整套集群跑起来4.1 启动主库容器确保配置文件已经写入宿主机后执行docker run -d \ --name mysql-master \ --network mysql-net --ip 172.28.0.10 \ -p 3306:3306 \ -v /data/mysql-master/conf/master.cnf:/etc/mysql/conf.d/master.cnf \ -v /data/mysql-master/data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORDRoot_Admin2024 \ -e TZAsia/Shanghai \ --restartalways \ mysql:8.0.36注意mysql:8.0.36这个tag官方镜像的manifest会根据当前平台的arch自动拉取对应的ARM64版本。如果你用的是私有镜像仓库或者镜像源确保该源同步了多架构manifest。启动后观察容器日志docker logs -f mysql-master看到类似[System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections.的输出就说明初始化成功。这里有个非常关键的细节MYSQL_ROOT_PASSWORD环境变量只在数据目录为空首次初始化时生效。如果数据目录已经存在这个变量会被忽略密码保持为之前初始化的值。所以如果你发现容器启动后密码不生效先检查数据目录是否被清空过。4.2 创建复制账号主库起来后进入容器执行授权SQLdocker exec -it mysql-master mysql -uroot -p密码输入前面设置的环境变量值。执行第3.4小节的SQL完成账号创建。如果主库此时已经有业务数据需要在建立从库前先给从库做一次数据快照。最简单的方式是用mysqldump逻辑备份docker exec mysql-master sh -c exec mysqldump --all-databases --single-transaction --set-gtid-purgedON --source-data2 -uroot -p$MYSQL_ROOT_PASSWORD /data/master_dump.sql--single-transactionInnoDB引擎下利用MVCC一致性快照备份期间不锁表。--set-gtid-purgedON导出GTID信息从库导入后能正确衔接主库的GTID集合。--source-data2在备份文件中记录CHANGE MASTER所需的位点信息虽然GTID模式下不太依赖但作为双保险记录在案。4.3 启动从库容器并导入数据用同样的方式启动从库容器docker run -d \ --name mysql-slave \ --network mysql-net --ip 172.28.0.11 \ -p 3307:3306 \ -v /data/mysql-slave/conf/slave.cnf:/etc/mysql/conf.d/slave.cnf \ -v /data/mysql-slave/data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORDRoot_Admin2024 \ -e TZAsia/Shanghai \ --restartalways \ mysql:8.0.36等从库初始化完成后导入主库的备份文件docker exec -i mysql-slave sh -c exec mysql -uroot -p$MYSQL_ROOT_PASSWORD /data/master_dump.sql这里需要注意导入的是全量数据加GTID集合。导入完成后从库的gtid_purged必须和主库的gtid_executed一致否则后面启停复制时会报错。4.4 建立复制通道在从库容器内执行MySQL命令docker exec -it mysql-slave mysql -uroot -p执行以下SQLCHANGE REPLICATION SOURCE TO SOURCE_HOSTmysql-master, SOURCE_PORT3306, SOURCE_USERrepl, SOURCE_PASSWORDRepl_2024_StrongPass, SOURCE_AUTO_POSITION1, SOURCE_SSL1; START REPLICA;如果你的MySQL版本是8.0.23之前命令是CHANGE MASTER TO和START SLAVE后面检查状态的命令也是SHOW SLAVE STATUS。我这是在8.0.36上运行的所以用新写法。SOURCE_HOSTmysql-master用的是Docker网络内的容器名这样即使容器重建导致IP变化虽然我们已经固定IP了只要容器名不变复制链路就不会断。SOURCE_AUTO_POSITION1表示通过GTID自动找位点这是GTID模式相比传统位点最大的省心之处。4.5 校验复制状态和数据一致性在从库执行SHOW REPLICA STATUS\G重点看这几个指标指标期望值说明Replica_IO_RunningYesIO线程连接主库并拉取binlog正常Replica_SQL_RunningYesSQL线程重放中继日志正常Seconds_Behind_Source0从库和主库的延迟秒数Retrieved_Gtid_Set有值已经从主库拉取到的GTID集合Executed_Gtid_Set有值已经执行完成的GTID集合Last_IO_Error空最近一次的IO线程报错信息Last_SQL_Error空最近一次的SQL线程报错信息再验证一下实际数据同步。主库建一张测试表并插入数据CREATE TABLE test.sync_check(id INT PRIMARY KEY, ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP); INSERT INTO test.sync_check(id) VALUES (1);从库查询SELECT * FROM test.sync_check;能查到记录说明主从链路已经通了。5. 实际部署中容易踩的坑5.1 exec format error镜像架构不匹配这个报错在ARM平台上极其常见。启动容器后日志里只有一行exec format error看起来像权限问题实际是CPU架构不认识可执行文件。出现这个报错说明拉取的不是ARM64镜像。常见原因有两个一是私有镜像仓库里的镜像本来就是x86_64的二是Docker没有通过manifest自动选择架构。先检查镜像架构docker inspect mysql:8.0.36 | jq .[0].Architecture必须是arm64。如果显示amd64删除镜像后重新拉取。如果还是拉不到ARM版本去镜像源确认有没有同步多架构tag。另一个做法是显式指定平台docker pull --platform linux/arm64 mysql:8.0.36但注意如果仓库里没有arm64版本这个命令也是白搭。5.2 MySQL8.0默认认证插件导致从库连接失败复制账号刚开始如果建的默认账号是caching_sha2_password格式从库启动复制后很容易出现Authentication plugin caching_sha2_password cannot be loaded或者Public Key Retrieval is not allowed的报错。原因是caching_sha2_password在非SSL连接下需要额外的密钥交换步骤而默认的复制通道不会主动处理。这个问题的解法有三个复制账号用mysql_native_password创建最省事我从一开始就推荐这个方式。在CHANGE REPLICA命令里加上SOURCE_SSL1用SSL加密通道绕过公钥交换问题。如果是老版本MySQL客户端连接把客户端的--get-server-public-key选项加上。个人建议直接选方案一。虽然mysql_native_password在安全强度上不如默认插件但在容器内部的专用网段里配合网络隔离和账号权限限制风险可控。如果你所在企业的安全基线强制要求默认插件那就必须用SSL方案。5.3 从库重启后复制异常server_uuid重复假设你图省事直接拷贝了主库的整个数据目录来搭建从库启动从库后发现IO线程在反复重试日志报Fatal error: The slave I/O thread stops because master and slave have equal MySQL server UUIDs这是主从两端的auto.cnf文件完全一致导致的。从库复制了主库的数据连server_uuid也一起复制过来了。MySQL用server_uuid唯一标识每个实例主从重复UUID会被认定为同一实例。修复很简单进入从库数据目录删掉auto.cnf后重启从库容器rm -f /data/mysql-slave/data/auto.cnf docker restart mysql-slaveMySQL会在重启时自动生成新的UUID。这个坑在容器数据卷方案下尤其容易踩因为数据卷拷贝比逻辑备份更常见所以提个醒。5.4 Docker重启后从库复制状态异常宿主机重启后容器如果按依赖顺序启动可能出现从库先启动但主库还没起来的情况。这不会导致配置丢失MySQL复制线程会按重试间隔自动重连但有一个隐藏问题如果容器IP漂移了从库连接主库的IP地址就失效了。所以前面强调一定要固定IP。--restartalways保证了容器随Docker守护进程自动拉起固定IP保证了拉起后网络关系不变这两个配置缺一不可。如果你是在docker-compose里编排的注意把自定义网络的subnet配置加上并在服务里用ipv4_address固定地址。networks: mysql-net: driver: bridge ipam: config: - subnet: 172.28.0.0/24 services: mysql-master: networks: mysql-net: ipv4_address: 172.28.0.10 mysql-slave: networks: mysql-net: ipv4_address: 172.28.0.115.5 半同步复制插件在ARM镜像中的加载问题有强一致性诉求的场景会想开半同步复制避免主库提交成功但从库还没来得及收到binlog就发生切换的数据丢失。MySQL官方镜像自带semisync_source.so和semisync_replica.so插件文件安装方式主库执行INSTALL PLUGIN rpl_semi_sync_source SONAME semisync_source.so; SET GLOBAL rpl_semi_sync_source_enabled 1;从库执行INSTALL PLUGIN rpl_semi_sync_replica SONAME semisync_replica.so; SET GLOBAL rpl_semi_sync_replica_enabled 1;MySQL 5.7版本插件名是semisync_master.so和semisync_slave.so函数名也不同。ARM镜像下插件本身是aarch64架构的和官方镜像匹配没问题但如果你是自己编译的MySQL插件路径可能会对不上启动时报Cant open shared library。这时检查一下plugin_dir变量指向的目录是否有对应so文件别把插件名和文件路径搞混。半同步不是必选项如果你的业务能容忍极短时间的丢失异步复制已经够用。开了半同步且从库不可用时主库会阻塞等待超时然后退化为异步这个行为要在压测时摸清楚别等故障了才去查。6. 维护与扩展的个人经验6.1 已有大量存量数据时怎么快速加从库前面讲的mysqldump逻辑备份适合数据量在几十GB以内的场景。数据到几百GB以上逻辑备份的导入速度会让人绝望。这时候建议用物理备份工具Xtrabackup。步骤思路在主库上用Xtrabackup做一个全量物理备份备份过程中记录binlog位点。将备份恢复到从库的数据目录。清空从库数据目录下的auto.cnf。启动从库容器执行CHANGE REPLICA ... SOURCE_AUTO_POSITION1。启动复制后观察Seconds_Behind_Source持续归零。Xtrabackup在ARM上有对应的aarch64 RPM包别装成x86版本。备份和恢复的速度比逻辑备份快一个数量级尤其是InnoDB表很多时。还有一个更取巧的方案直接对主库的数据目录做文件系统快照。比如用LVM快照或云盘快照把快照挂载到新机器上恢复数据目录。这种方式要求在快照前执行FLUSH TABLES WITH READ LOCK短暂只读对于允许秒级只读的场景非常合适。6.2 镜像升级时保持集群不中断的流程镜像版本升级最怕的是数据目录和配置文件被容器重建影响。我的标准操作顺序是在每台宿主机上把新版镜像拉到本地但不重建容器。先在从库容器上操作停掉复制确认没有延迟。停从库容器用新版镜像启动相同数据卷的容器。从库启动后重新执行START REPLICA检查复制是否恢复。确认从库数据一致后再处理主库升级。主库升级会有一段时间的写中断所以需要先做一次主从切换或业务侧维护。如果我对操作没把握宁可在凌晨低峰期做也不在白天硬刚。镜像版本尽量小步迭代别一次性跨越两个大版本。6.3 ARM平台容器化MySQL的一点实际感受最后说点实在的。鲲鹏ARM平台的性能在数据库并发读写下并不弱但有几个点需要注意。一是内存分配容器看不到宿主机实际压力MySQL的innodb_buffer_pool_size如果设得过大多个容器叠加可能触发OOM Killer。二是磁盘IOMySQL对磁盘延迟非常敏感ARM服务器如果配的是SATA盘撑不住高并发事务建议直接用NVMe SSD。三是内核页大小鲲鹏920支持4K和64K页MySQL镜像的编译方式对页大小有一定要求遇到性能异常先确认内核页大小和数据库二进制是否匹配官方镜像一般没有这个问题但自己编译的就有风险了。主从集群搭好只是第一步后续的备份、监控、切换演练才是真正考验日常运维的环节。GTID模式已经帮你排掉了位点同步这颗最大的雷剩下的就是保持配置一致、数据目录独立、网络关系固定这套思路在x86和ARM上完全一致。我在ARM平台踩过几轮坑之后总结出的原则就一句话能让镜像固化的事情不要手动做能固定下来的网络关系不要让它漂移能给复制账号的最小权限就不要多给。照着这个原则去做鲲鹏ARM上的MySQL主从集群会很稳。