ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MySQL主从复制实战:从零搭建高可用与读写分离架构

MySQL主从复制实战:从零搭建高可用与读写分离架构 1. 项目缘起为什么我们需要MySQL主从复制在数据库运维和开发工作中数据的高可用性和读写分离是绕不开的话题。想象一下你的应用只有一个MySQL数据库它既是所有用户查询的入口也是所有数据写入的终点。当业务量不大时这没什么问题。但随着用户增长你会发现一个简单的报表查询可能就会拖慢整个应用的响应速度因为它在和核心交易争抢同一份计算和I/O资源。更糟糕的是如果这台唯一的数据库服务器因为硬件故障、机房断电或者一次不小心的误操作而宕机你的整个服务就彻底中断了数据恢复的RTO恢复时间目标和RPO恢复点目标都变得不可控。这就是MySQL主从复制Master-Slave Replication要解决的核心问题。它不是一项高深莫测的技术而是每个后端工程师和DBA都应该掌握的“生存技能”。简单来说主从复制就是让一台MySQL服务器主库上的数据变更自动地、异步地同步到另一台或多台MySQL服务器从库上。这样一来从库就可以承担起读请求的压力实现读写分离提升整体性能同时从库也作为主库的一个实时备份在主库发生故障时可以快速切换保障服务不中断。我经历过不止一次因为单点数据库故障导致的线上事故也见证过在引入主从架构后系统稳定性和性能的显著提升。今天我就从一个实践者的角度带你从零开始手把手搭建一套MySQL主从复制环境并深入探讨在实际使用中会遇到的各种“坑”和最佳实践。无论你是刚接触MySQL的新手还是想系统梳理一下这块知识的老兵相信这篇内容都能给你带来实实在在的收获。2. 环境准备与核心概念澄清在动手搭建之前我们必须把“战场”打扫干净并理解清楚我们要操作的几个核心角色。很多人搭建失败第一步就栽在了环境混乱和概念模糊上。2.1 服务器规划与基础环境理论上主库和从库可以部署在同一台机器的不同端口但这在生产环境中没有意义失去了高可用的价值。我们假设一个最典型的生产级最小化场景主库 (Master): 一台独立的服务器IP:192.168.1.100 我们使用默认的MySQL端口3306。从库 (Slave): 另一台独立的服务器IP:192.168.1.101 同样使用端口3306。注意两台服务器的MySQL版本应尽可能保持一致尤其是大版本如8.0.x。混合大版本如5.7主库8.0从库可能会因语法、默认配置或复制格式的差异导致不可预知的问题。建议都使用MySQL 8.0的最新稳定版。首先在两台服务器上分别安装MySQL。这里以CentOS 7为例使用YUM安装# 1. 下载并安装MySQL官方的YUM仓库 sudo yum install -y https://dev.mysql.com/get/mysql80-community-release-el7-11.noarch.rpm # 2. 安装MySQL服务器 sudo yum install -y mysql-community-server # 3. 启动MySQL服务并设置开机自启 sudo systemctl start mysqld sudo systemctl enable mysqld安装完成后MySQL会为root用户生成一个临时密码位于日志文件中sudo grep temporary password /var/log/mysqld.log使用该密码登录并立即修改为一个强密码mysql -u root -p # 输入临时密码 ALTER USER rootlocalhost IDENTIFIED BY YourNewStrongPassword123!;2.2 理解复制中的三个关键线程这是理解主从复制工作原理的基石。很多教程只教配置不讲原理导致出了问题无从排查。Binlog Dump Thread (主库): 当有从库连接上来时主库会为每个连接的从库创建一个Binlog Dump线程。这个线程的唯一职责就是读取主库的二进制日志Binary Log中的事件并将其发送给从库的I/O线程。你可以通过在主库执行SHOW PROCESSLIST;看到一个Binlog Dump线程其State通常为Master has sent all binlog to slave; waiting for more updates。I/O Thread (从库): 从库上的I/O线程负责连接到主库并向主库的Binlog Dump线程发起请求接收主库发送过来的二进制日志事件。然后它会将这些事件写入从库本地的中继日志Relay Log文件中。在从库执行SHOW SLAVE STATUS\GSlave_IO_Running这个状态就反映了I/O线程是否在正常运行。SQL Thread (从库): 从库上的SQL线程负责读取本地的中继日志Relay Log并解析、重放其中记录的SQL事件或行变更从而让从库的数据与主库保持一致。同样在SHOW SLAVE STATUS\G中Slave_SQL_Running状态反映了SQL线程的运行情况。一个健康的主从复制必须是Slave_IO_Running: Yes且Slave_SQL_Running: Yes。任何一个为No或Connecting都意味着复制中断了。2.3 必须搞清的日志文件Binlog 和 Relay Log二进制日志 (Binary Log, Binlog): 这是主从复制的“源头活水”。主库上所有会修改数据的SQL语句DDL、DML或行数据的变更都会以“事件”的形式按顺序记录在Binlog中。Binlog不仅用于复制也用于数据恢复。它有三种格式STATEMENT基于SQL语句、ROW基于数据行变化、MIXED混合模式。MySQL 8.0 默认使用ROW格式这是最安全、最推荐的方式能最大程度保证主从数据一致性。中继日志 (Relay Log): 你可以把它理解为从库的“收件箱”。从库的I/O线程从主库拉取到的Binlog事件并不会直接执行而是先原封不动地写入本地的Relay Log文件。然后SQL线程再从Relay Log中读取事件来执行。这样的设计实现了“接收”和“执行”的解耦提供了缓冲也使得从库可以在复制中断后从断点继续拉取而不必重新全量同步。理解了这些我们再开始配置你就会清楚每一步是在做什么而不是机械地复制命令。3. 主库Master配置详解主库的配置核心是开启Binlog设置一个唯一的服务器ID并创建一个专门用于复制的用户。3.1 修改MySQL配置文件编辑主库服务器192.168.1.100上的MySQL配置文件通常是/etc/my.cnf或/etc/mysql/my.cnf.d/mysqld.cnf取决于你的系统和安装方式。在[mysqld]部分添加或修改以下配置[mysqld] # 服务器唯一ID这是必须的主从不能相同 server-id 100 # 启用二进制日志并指定日志文件的前缀 log-bin mysql-bin # 设置二进制日志格式ROW模式是8.0的默认值也是最佳实践 binlog-format ROW # 可选指定哪些数据库需要记录Binlog。不配置则记录所有。 # binlog-do-db your_database_name # 可选指定哪些数据库不需要记录Binlog。 # binlog-ignore-db mysql, sys, information_schema, performance_schema关键参数解析server-id100: 我习惯用IP地址的最后一段作为ID方便识别。只要保证主从网络内所有MySQL实例的server-id唯一即可。log-binmysql-bin: 开启了二进制日志日志文件将以mysql-bin.000001,mysql-bin.000002这样的序列命名。binlog-formatROW: 这是最重要的设置之一。在ROW格式下Binlog记录的是每一行数据修改前后的镜像。例如一条UPDATE语句更新了1000行在STATEMENT格式下只记录这条SQL语句本身而在ROW格式下会记录这1000行数据的变化。虽然日志量更大但它不依赖于SQL上下文如触发器、存储过程、自定义函数能最精确地保证主从数据一致。生产环境强烈建议使用ROW格式。配置完成后重启MySQL服务使配置生效sudo systemctl restart mysqld3.2 创建复制专用账户出于安全考虑我们不应该让从库直接用root账户连接主库。需要创建一个权限受限的专用账户。登录主库MySQLmysql -u root -p执行以下SQL-- 创建一个用户名为repl允许从192.168.1.101从库IP登录的用户 CREATE USER repl192.168.1.101 IDENTIFIED BY StrongReplPassword123!; -- 授予该用户复制所需的权限。 -- REPLICATION SLAVE 权限允许该用户连接主库并请求Binlog。 GRANT REPLICATION SLAVE ON *.* TO repl192.168.1.101; -- 刷新权限使授权立即生效 FLUSH PRIVILEGES;实操心得这里repl192.168.1.101限制了该用户只能从指定的从库IP登录更安全。如果你的从库IP可能变化或者有多个从库可以使用repl192.168.1.%允许整个网段或repl%允许任何主机不推荐。密码务必设置得足够复杂。3.3 锁定主库并获取初始状态为了确保从库从一个一致性的数据快照开始同步我们需要暂时阻止主库新的数据写入并记录下当前的Binlog位置。-- 1. 全局读锁阻止所有新的数据写入DML和表结构变更DDL。 FLUSH TABLES WITH READ LOCK; -- 2. 查看当前的Binlog文件名称和位置。请务必记录下 File 和 Position 的值 SHOW MASTER STATUS;执行SHOW MASTER STATUS;你会看到类似下面的输出------------------------------------------------------------------------------- | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set | ------------------------------------------------------------------------------- | mysql-bin.000003 | 785 | | | | -------------------------------------------------------------------------------记下File: mysql-bin.000003和Position: 785。这告诉从库“请从mysql-bin.000003这个文件的第785个字节之后开始复制。”现在保持这个MySQL会话窗口打开不要退出也不要执行UNLOCK TABLES。锁还在生效。我们需要在另一个终端窗口对主库的数据进行备份。4. 数据备份与从库Slave初始化主库锁住后数据是静止的。我们需要将这个静止状态的数据完整地迁移到从库。4.1 使用mysqldump备份主库数据打开一个新的终端连接到主库服务器使用mysqldump工具进行全量备份。这里我们备份所有数据库排除系统库# 在主库服务器上执行 mysqldump -u root -p --all-databases \ --master-data2 \ # 这个选项会在导出的SQL文件中以CHANGE MASTER TO注释的形式包含主库的Binlog位置信息 --single-transaction \ # 对于InnoDB表开启一个事务来确保数据一致性与--master-data一起使用时会自动禁用--lock-tables --routines \ # 备份存储过程和函数 --triggers \ # 备份触发器 --events /tmp/master_full_backup.sql输入root密码后备份文件会保存在/tmp/master_full_backup.sql。参数详解--master-data2: 这是关键。它会在输出的SQL文件中添加一行注释例如-- CHANGE MASTER TO MASTER_LOG_FILEmysql-bin.000003, MASTER_LOG_POS785;。这样我们在从库导入时可以方便地获取起始位置。2表示这行命令被注释掉不会自动执行。--single-transaction: 对InnoDB表进行非阻塞的备份通过启动一个事务来获取一致性视图。这是在线备份InnoDB的推荐方式避免了长时间锁表。--routines --triggers --events: 确保存储过程、触发器、事件等对象也被备份。备份完成后回到之前那个锁表的MySQL会话窗口释放锁UNLOCK TABLES;现在主库可以恢复正常写入了。4.2 传输备份文件并导入从库将备份文件从主库复制到从库192.168.1.101# 在主库执行 scp /tmp/master_full_backup.sql root192.168.1.101:/tmp/然后登录从库服务器将备份数据导入# 在从库服务器上执行 mysql -u root -p /tmp/master_full_backup.sql这个过程可能会比较长取决于数据库的大小。导入完成后从库就拥有了和主库在锁表时刻完全一致的数据。4.3 配置从库并启动复制现在我们来配置从库告诉它谁是主库用什么账号连接以及从哪里开始复制。首先编辑从库的MySQL配置文件/etc/my.cnf[mysqld] # 服务器唯一ID必须与主库不同 server-id 101 # 从库也可以开启binlog这在它作为其他从库的主库级联复制或需要做数据恢复时有用 log-bin mysql-bin # 可选设置中继日志文件的前缀 relay-log mysql-relay-bin # 可选设置从库的更新是否写入自身的binlog。LOG_SLAVE_UPDATES1表示写入这在级联复制中是必须的。 # log-slave-updates 1 # 可选防止从库被意外写入只读模式。强烈建议开启 read-only 1server-id101: 确保与主库不同。read-only1:这是一个非常重要的安全设置。它使得从库普通用户非super权限只能进行读操作不能写入。这可以防止应用误操作或程序BUG导致数据写入从库造成主从数据不一致。但root用户依然可以写。重启从库MySQL服务sudo systemctl restart mysqld登录从库MySQL执行关键的CHANGE MASTER TO命令mysql -u root -p-- 停止从库复制线程如果是首次配置它们本来就是停止的 STOP SLAVE; -- 配置主库连接信息 CHANGE MASTER TO MASTER_HOST 192.168.1.100, -- 主库IP MASTER_USER repl, -- 主库上创建的复制用户 MASTER_PASSWORD StrongReplPassword123!, -- 复制用户密码 MASTER_PORT 3306, -- 主库端口 MASTER_LOG_FILE mysql-bin.000003, -- 之前记录的File MASTER_LOG_POS 785, -- 之前记录的Position MASTER_CONNECT_RETRY 60; -- 连接失败后重试间隔秒 -- 启动从库复制线程 START SLAVE;这里有一个非常重要的技巧如果你在备份时使用了--master-data2其实可以不用手动去记File和Position。你可以用以下命令从备份文件中提取head -n 100 /tmp/master_full_backup.sql | grep CHANGE MASTER TO输出结果中的MASTER_LOG_FILE和MASTER_LOG_POS就是你需要填写的值。5. 验证与监控你的复制真的成功了吗执行完START SLAVE;后复制并不会立刻完成。你需要检查从库的复制状态。5.1 使用SHOW SLAVE STATUS进行深度诊断这是监控主从复制最核心的命令。执行SHOW SLAVE STATUS\G\G使结果垂直显示更易读。你会看到非常多的字段我们聚焦几个最关键的*************************** 1. row *************************** Slave_IO_State: Waiting for master to send event Master_Host: 192.168.1.100 Master_User: repl Master_Port: 3306 Connect_Retry: 60 Master_Log_File: mysql-bin.000003 Read_Master_Log_Pos: 785 Relay_Log_File: mysql-relay-bin.000002 Relay_Log_Pos: 320 Relay_Master_Log_File: mysql-bin.000003 Slave_IO_Running: Yes Slave_SQL_Running: Yes Replicate_Do_DB: Replicate_Ignore_DB: Replicate_Do_Table: Replicate_Ignore_Table: Replicate_Wild_Do_Table: Replicate_Wild_Ignore_Table: Last_Errno: 0 Last_Error: Skip_Counter: 0 Exec_Master_Log_Pos: 785 Relay_Log_Space: 526 Until_Condition: None Until_Log_File: Until_Log_Pos: 0 Master_SSL_Allowed: No Master_SSL_CA_File: Master_SSL_CA_Path: Master_SSL_Cert: Master_SSL_Cipher: Master_SSL_Key: Seconds_Behind_Master: 0 Master_SSL_Verify_Server_Cert: No Last_IO_Errno: 0 Last_IO_Error: Last_SQL_Errno: 0 Last_SQL_Error: Replicate_Ignore_Server_Ids: Master_Server_Id: 100 Master_UUID: f6a16b8e-d3a7-11ed-9c7b-000c29a4b4e2 Master_Info_File: mysql.slave_master_info SQL_Delay: 0 SQL_Remaining_Delay: NULL Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates Master_Retry_Count: 86400 Master_Bind: Last_IO_Error_Timestamp: Last_SQL_Error_Timestamp: Master_SSL_Crl: Master_SSL_Crlpath: Retrieved_Gtid_Set: Executed_Gtid_Set: Auto_Position: 0 Replicate_Rewrite_DB: Channel_Name: Master_TLS_Version: Master_public_key_path: Get_master_public_key: 0 Network_Namespace:成功运行的黄金标准Slave_IO_Running: Yes— I/O线程正在运行正在从主库拉取日志。Slave_SQL_Running: Yes— SQL线程正在运行正在执行中继日志。Seconds_Behind_Master: 0— 从库落后于主库的秒数。0表示已完全同步。这个值在持续写入时可能会是一个很小的正数这是正常的网络和重放延迟。如果这个值持续增长或非常大说明复制可能跟不上主库的写入速度。Last_IO_Errno: 0和Last_SQL_Errno: 0— 没有错误。如果Slave_IO_Running或Slave_SQL_Running是No或者Last_IO_Error/Last_SQL_Error字段有错误信息说明复制出了问题。常见的错误包括网络不通、主库用户权限不足、主库的Binlog文件已被清理Position太旧等。5.2 简单功能测试在从库状态一切正常后我们可以做一个简单的测试来验证复制是否真的在工作。在主库上创建一个测试数据库和表并插入数据-- 在主库执行 CREATE DATABASE IF NOT EXISTS test_replication; USE test_replication; CREATE TABLE test_table (id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50)); INSERT INTO test_table (name) VALUES (data_from_master);稍等片刻通常1秒内然后在从库上查询-- 在从库执行 USE test_replication; SELECT * FROM test_table;如果你能在从库看到data_from_master这条记录恭喜你主从复制已经成功运行6. 生产环境进阶配置与调优基础的搭建完成了但要用于生产环境我们还需要考虑更多。6.1 半同步复制Semisynchronous Replication默认的复制是异步的。主库提交事务后只要将事件写入自己的Binlog就会立即返回给客户端成功而不管从库是否收到。如果主库在返回成功给客户端后立刻宕机且未来得及将Binlog事件发送给任何从库那么即使有从库被提升为新主也会丢失这个事务导致数据不一致。半同步复制就是为了解决这个问题。它要求主库在提交事务时必须至少等待一个从库接收并确认写入Relay Log了该事务的Binlog事件后才能返回成功给客户端。这在一定程度上提高了数据的安全性。配置步骤在主库和从库上安装半同步插件MySQL 5.7 通常已内置-- 在主库执行 INSTALL PLUGIN rpl_semi_sync_master SONAME semisync_master.so; -- 在从库执行 INSTALL PLUGIN rpl_semi_sync_slave SONAME semisync_slave.so;启用插件并设置参数-- 在主库执行 SET GLOBAL rpl_semi_sync_master_enabled ON; SET GLOBAL rpl_semi_sync_master_timeout 1000; -- 等待从库确认的超时时间毫秒超时后会退化为异步 -- 在从库执行 SET GLOBAL rpl_semi_sync_slave_enabled ON;重启从库的I/O线程以应用半同步设置-- 在从库执行 STOP SLAVE IO_THREAD; START SLAVE IO_THREAD;验证在主库执行SHOW STATUS LIKE Rpl_semi_sync%;查看Rpl_semi_sync_master_status是否为ON。注意半同步复制会增加主库事务的响应延迟因为多了一次网络往返。超时时间rpl_semi_sync_master_timeout需要根据网络状况合理设置。在MySQL 5.7中还有了“无损半同步”Lossless Semisynchronous的改进通过调整rpl_semi_sync_master_wait_point参数可以进一步保证数据不丢失。6.2 复制过滤规则你可能不希望所有数据都复制到从库。例如一些用于缓存的临时表或者某些日志库。这时可以使用复制过滤。过滤可以在主库或从库进行但强烈建议只在从库进行过滤。在主库过滤binlog-do-db,binlog-ignore-db容易因为USE database;语句的上下文问题导致过滤规则混乱。在从库配置过滤推荐 在从库的my.cnf中配置或在运行时用CHANGE REPLICATION FILTER命令设置。# 在my.cnf中 [mysqld] # 只复制db1和db2库 replicate-do-db db1 replicate-do-db db2 # 忽略test库和tmp_开头的所有库 replicate-ignore-db test replicate-wild-ignore-table tmp_%.%或者在从库MySQL中动态设置STOP SLAVE; CHANGE REPLICATION FILTER REPLICATE_DO_DB (db1, db2), REPLICATE_IGNORE_DB (test), REPLICATE_WILD_IGNORE_TABLE (tmp_%.%); START SLAVE;6.3 延迟复制Delayed Replication有时候我们希望从库故意延迟一段时间再应用主库的变更。这主要用于防止在主库发生误操作如DROP DATABASE时可以从延迟的从库上找回数据。-- 在从库上执行 STOP SLAVE; CHANGE MASTER TO MASTER_DELAY 3600; -- 延迟3600秒1小时 START SLAVE;设置后从库的SQL线程会等待指定的秒数后再执行中继日志中的事件。注意延迟是针对每个事件计算的所以从库的Seconds_Behind_Master会稳定在设定值附近。7. 日常运维、故障排查与切换演练搭建好只是开始日常的监控和问题处理才是重头戏。7.1 监控关键指标除了手动执行SHOW SLAVE STATUS在生产环境中你应该将这些指标集成到监控系统如Prometheus Grafana中。关键指标包括Slave_IO_Running/Slave_SQL_Running(0/1)Seconds_Behind_MasterLast_IO_Errno/Last_SQL_ErrnoSlave_SQL_Running_StateRelay_Log_Space(中继日志占用空间防止磁盘写满)7.2 常见故障与处理1. 主从数据不一致这是最棘手的问题。原因可能是从库被意外写入、复制过滤规则配置错误、SQL线程错误被跳过等。检查使用pt-table-checksumPercona Toolkit工具来校验主从数据一致性。修复如果差异不大可以手动修复差异行。如果差异巨大或原因不明最稳妥的方法是重建从库在主库做一次新的全量备份然后在从库停止复制、清空数据、重新导入并配置新的复制起点。2. 复制中断SQL线程错误 (Last_SQL_Error)常见错误如1062 (Duplicate entry)主键冲突或1032 (Can‘t find record)行不存在。原因通常是因为从库被直接写入了数据或者主库上某些操作具有不确定性在STATEMENT格式下或者在复制中断期间手动修改了从库数据。处理临时跳过如果确定可以跳过这个错误比如重复主键但数据一致可以执行STOP SLAVE; SET GLOBAL sql_slave_skip_counter 1; START SLAVE;跳过1个事件。慎用这可能导致后续更多不一致。GTID模式下的跳过如果启用了GTID跳过操作更复杂需要使用SET GTID_NEXT等命令。最佳实践分析错误原因。如果是偶发的重复键且数据确实一致可以跳过。否则应考虑重建从库。3. 复制延迟 (Seconds_Behind_Master 持续很高)原因硬件/网络从库硬件尤其是磁盘I/O比主库差或网络带宽不足。单线程瓶颈传统复制中SQL线程是单线程的主库并发写入高时从库重放跟不上。大事务主库执行了一个耗时很长的大事务如批量更新百万行。优化升级硬件给从库配置更好的CPU、内存尤其是使用SSD磁盘。启用多线程复制 (MTS)MySQL 5.6 支持基于库DATABASE的并行复制5.7 支持基于逻辑时钟LOGICAL_CLOCK的更细粒度并行复制能极大提升重放速度。STOP SLAVE; SET GLOBAL slave_parallel_workers 4; -- 设置4个并行工作线程 SET GLOBAL slave_parallel_type LOGICAL_CLOCK; -- MySQL 5.7 START SLAVE;避免大事务优化业务SQL将大事务拆分为小批次。7.3 主从切换演练Failover高可用架构不是摆设必须定期演练。假设主库192.168.1.100宕机我们需要将应用切换到从库192.168.1.101。确认主库故障通过监控和人工确认主库无法恢复。提升从库为新主库-- 在从库192.168.1.101上执行 STOP SLAVE; RESET SLAVE ALL; -- 清除所有复制信息使其成为一个独立的主库修改其配置文件移除read-only1设置并重启或动态设置SET GLOBAL read_only OFF;。应用层切换将应用的数据库连接地址从旧主库IP改为新主库IP。这通常需要借助中间件如MyCat, ProxySQL或服务发现组件来实现快速、自动的切换。其他从库指向新主库如果还有其他从库需要将它们重新指向新的主库192.168.1.101。-- 在其他从库上执行 STOP SLAVE; CHANGE MASTER TO MASTER_HOST192.168.1.101, MASTER_USERrepl, ...; -- 使用新主库的信息 START SLAVE;修复旧主库旧主库修复后可以将其作为新主库的从库重新加入集群形成新的主从关系。这个过程手动操作非常容易出错且耗时。因此生产环境强烈建议使用成熟的高可用解决方案如MHA (Master High Availability)、Orchestrator或云数据库服务商提供的自动故障转移功能。它们能自动化完成故障检测、主从切换、虚拟IP漂移等复杂操作。搭建和使用MySQL主从复制就像给数据库系统上了第一道保险。它成本相对较低却能带来读写分离、负载均衡、数据备份等多重收益。然而它也不是银弹异步复制带来的数据延迟、故障切换的复杂性都是需要持续关注和优化的问题。从我个人的经验来看在项目初期就规划好主从架构并配套完善的监控和告警远比出了问题再临时搭建要从容得多。理解其原理掌握其配置熟悉其排错是确保这套机制稳定运行的关键。希望这篇从搭建到使用的详细指南能帮助你建立起属于自己的、可靠的数据层基础架构。
返回列表