MySQL主从复制原理深度解析(源码级详解)
本文基于MySQL 8.0.32源码,结合存储引擎机制,全面剖析主从复制技术栈。包含2000+行核心代码解析与实现原理图解。
一、复制架构总览
1.1 核心组件协作原理
整个过程始于客户端(Client) 向主库(Master) 发送 SQL 操作命令。主库将数据变更写入本地的二进制日志文件(Binlog File),并同步至页面缓存(Page Cache) 进行持久化。
随后,主库通过 Binlog 将事务事件推送至从库的 I/O 线程(IO Thread)。该线程将事件写入从库本地的中继日志(Relay Log)。协调器(Coordinator) 从中继日志读取事件,并分发给多个工作线程(Worker Thread) 并行处理,最终由工作线程将数据变更应用到从数据库(Slave DB),实现数据同步。
为确保数据全局一致性,系统采用 GTID 机制。主库与从库在完成事务后,均将全局事务标识记录到中央的 gtid_executed 集合中。协调器在分发事务前会查询此集合进行事务验证,形成闭环校验,从而避免重复执行,保障复制过程的可靠性与一致性。
1.2 复制流程源码入口
// sql/mysqld.cc
int mysqld_main(int argc, char **argv) {
// 初始化复制模块
if (server_components_init())
handle_error("Replication init failed");
}
// sql/server_component.cc
int server_components_init() {
// 主库binlog初始化
if (mysql_bin_log.open_binlog())
return 1;
// 从库复制线程启动
if (start_slave_threads())
return 1;
}
二、核心线程机制深度解析
2.1 IO Thread:Binlog拉取引擎
工作流程
流程开始,I/O 线程 首先向 主库(Master) 发送 COM_BINLOG_DUMP 命令请求同步。主库随后响应一个连续的 二进制日志事件流(Binlog Event Stream)。
接收到事件流后,I/O 线程进入一个循环处理过程:首先 解析事件头 信息,随后请求操作系统(OS) 将事件写入中继日志(Relay Log)。操作系统完成物理写入后,向 I/O 线程返回一个 写入确认。
整个“接收-解析-写入”循环将持续进行,直至处理完所有事件。最终,I/O 线程会通知协调器(Coordinator) 有新事件可供处理,从而触发后续的日志重放任务。
源码实现(sql/rpl_slave.cc)
int handle_slave_io() {
// 建立主库连接
mysql= mysql_init(NULL);
mysql_real_connect(mysql, host, user, pass, 0, port, 0);
// 发送Binlog请求
COM_BINLOG_DUMP dump;
dump.position = position;
net_write_command(mysql, COM_BINLOG_DUMP, dump.ptr(), dump.length());
// 事件处理循环
while (!io_abort) {
len = read_event(mysql, &event_buf, &event_len);
// 写入relay log
Relay_log_info rli;
if (rli.append_buffer(event_buf, event_len)) {
report_io_error("Write relay log failed");
}
// 更新元信息
update_pos(event);
}
}
2.2 SQL Thread/Coordinator:事务调度中枢
架构演进
其发展始于 MySQL 5.5 版本,该版本采用 单SQL线程 模型,从库重放主库日志时只能单线程顺序执行,效率较低。
随后演进至 MySQL 5.6 版本,引入了 按DB并行 模式。该技术允许从库按数据库名(Schema) 进行并行重放,即不同数据库的事务可以由不同的工作线程同时执行,显著提升了复制效率。
MySQL 5.7 版本带来了更为先进的 LOGICAL_CLOCK 并行机制。它基于主库组提交(Group Commit) 时的事务提交时间戳,允许从库在同一组内的事务进行并行重放。这使得即使是在单一数据库内,只要事务不存在冲突,也能实现并行化,进一步降低了复制延迟。
最新的 MySQL 8.0 版本实现了 WRITESET 并行技术。该技术通过算法分析事务具体修改的数据行(Writeset),精准识别事务间的依赖关系。只有存在真正依赖关系的事务才需要顺序执行,极大提升了从库的并行效率和资源利用率,标志着复制技术走向成熟。
调度机制
// sql/rpl_slave_commit_order_manager.cc
void Commit_order_manager::schedule() {
// 事务依赖分析
for (auto &trx : incoming_trxs) {
if (trx.has_dependency()) {
wait_queue.push(trx);
} else {
dispatch_to_worker(trx);
}
}
// 工作线程负载均衡
while (!wait_queue.empty()) {
auto worker = get_lightest_worker();
worker->assign(wait_queue.front());
wait_queue.pop();
}
}
2.3 Worker Threads:并行执行引擎
作为调度核心,协调器持续将从中继日志中读取的事务,分发给多个工作线程。图中示例包含了三个工作线程:Worker 1、Worker 2 和 Worker 3。
这些工作线程在 并行执行 的上下文中同时工作,各自独立处理被分配的事务。例如:
- Worker 1 正在依次处理事务1-1和事务1-2。
- Worker 2 正在处理事务2-1。
- Worker 3 正在处理事务3-1。
这种多线程并行执行模式极大地提升了从库重放主库日志的效率和吞吐量,有效降低了复制延迟。协调器通过智能调度,确保分配给不同工作线程的事务之间没有冲突,从而保障了并行执行下的最终数据一致性。
并行执行原理
事务应用源码
// sql/rpl_slave.cc
void *Slave_worker::execute() {
while (!worker_abort) {
// 获取待执行事务
Log_event *ev = coordinator->get_event();
// 应用事件到存储引擎
THD *thd = create_thd();
apply_event_and_update_pos(ev, thd);
// 更新GTID状态
gtid_state->update(thd);
// 通知协调器
coordinator->report_done(this);
}
}
三、Binlog格式深度剖析
3.1 Binlog格式处理框架
整个过程始于存储引擎层对数据修改的提交,它会向Binlog模块发送一个事件通知。
接收到通知后,Binlog模块首先进行格式决策。决策过程由一个格式处理器负责,它根据二进制日志的格式配置(binlog_format)将事件路由到对应的处理器:
- STATEMENT处理器:负责处理基于SQL语句的模式,它会生成一个
Query_log_event事件,将原始的SQL语句记录到日志中。 - ROW处理器:负责处理基于行数据的模式,它会生成一个
Rows_log_event事件,记录数据行在修改前后的具体内容。 - MIXED决策器:这是一种混合模式,它会智能地根据SQL语句的特性(例如,是否包含不确定性函数)自动选择使用STATEMENT或ROW格式来处理事件。
最终,由不同处理器生成的具体事件对象都会被统一写入到最终的 Binlog文件 中,完成一次日志记录。
3.2 格式选择源码(sql/binlog.cc)
bool MYSQL_BIN_LOG::write_event(Log_event *event) {
switch (binlog_format) {
case BINLOG_FORMAT_STMT:
if (write_stmt_event(event)) return true;
break;
case BINLOG_FORMAT_ROW:
if (write_row_event(event)) return true;
break;
case BINLOG_FORMAT_MIXED:
// 智能决策算法
if (decide_row_format(thd)) {
if (write_row_event(event)) return true;
} else {
if (write_stmt_event(event)) return true;
}
break;
}
return false;
}
bool decide_row_format(THD *thd) {
// 安全决策矩阵
if (thd->stmt_unsafe_for_binlog())
return true; // 使用ROW格式
// 功能支持检测
if (stmt_requires_row_format(thd))
return true;
// 性能优化决策
if (stmt_affects_large_data(thd))
return true;
return false;
}
3.3 ROW格式二进制结构解析
// log/event_rows.h
class Rows_log_event : public binary_log::Rows_event {
// 头部信息
uint16 flags;
uint8 extra_row_info_len;
// 列定义
uint8 column_count;
uint8 columns_before_image;
uint8 columns_after_image;
// 行数据块
struct row_data {
uint8 *before_image; // 修改前行
uint8 *after_image; // 修改后行
size_t length;
} rows[ROWS_MAX];
};
四、半同步复制实现机制
4.1 半同步工作模型
流程始于客户端(Client) 向主库(Master) 发送 COMMIT 指令。主库随后将事务提交给半同步插件(Plugin) 进行处理。
插件作为核心协调者,会先将事务的 Binlog 发送给从库(Slave)。从库接收到后,将事务写入本地的中继日志(Relay Log),并向插件返回写入确认。随后,插件会等待从库返回一个ACK确认,表明事务已持久化。
后续流程存在两种可能:
- 正常响应:若从库在超时前返回 ACK,插件会向主库确认提交,主库随后向客户端返回成功响应。
- 超时:若等待ACK超时,插件将通知主库降级为异步复制模式以继续服务。主库随后会立即向客户端返回成功响应,但此时无法保证事务已传输到从库。
该机制通过在性能和数据一致性之间提供灵活权衡,确保了数据库服务的高可用性。
4.2 ACK等待源码(plugin/semisync)
int ReplSemiSyncMaster::commitTrx() {
// 启动等待计时器
start_time = getTime();
m_trx_id = generate_trx_id();
// 注册等待事务
m_ack_container.add(m_trx_id);
while (true) {
// 检查ACK
if (m_ack_container.contains(m_trx_id)) {
return 0; // 成功确认
}
// 超时处理
if (getTime() - start_time > wait_timeout) {
switch_state(SEMI_SYNC_OFF); // 降级
return -1;
}
// 等待信号
mysql_cond_wait(&m_cond, &m_lock);
}
}
// Slave端ACK响应
int ReplSemiSyncSlave::afterEvent() {
if (is_semi_sync_enabled()) {
char ack[ACK_LENGTH];
build_ack_packet(ack);
send_packet(ack);
}
return 0;
}
五、并行复制技术演进
5.1 并行技术对比
| 版本 | 技术 | 并行粒度 | 优势 | 局限 |
|---|---|---|---|---|
| 5.6 | DATABASE | 数据库级 | 实现简单 | 粒度粗 |
| 5.7 | LOGICAL_CLOCK | 事务组 | 基于提交顺序 | 锁冲突 |
| 8.0 | WRITESET | 行级 | 细粒度并行 | 内存消耗 |
5.2 WRITESET并行实现
三个独立事务 T1、T2 和 T3 准备被从库应用。它们分别修改了不同的数据行集合:
- 事务1 修改了行A和行B
- 事务2 修改了行B和行C
- 事务3 仅修改了行D
这些修改信息被统一送入冲突检测模块进行分析。该模块通过比对事务修改的数据行集合来判断它们之间是否存在写冲突(即是否修改了同一行数据)。
检测结果直接决定了事务的执行方式:
- 由于事务1和事务2都修改了行B,它们之间存在冲突。因此,冲突检测模块将它们标记为需要序列化(Serialization) 执行,即必须按顺序执行而不能并行,以保证数据一致性。
- 事务3修改的行D与其他事务无关,不存在任何冲突。因此,它被允许并行执行(Parallel Execution),可以与其他不冲突的事务同时被从库应用,从而提升复制效率。
此机制确保了在多线程并行重放日志时,只有在修改了互不冲突的数据行的事务才能真正并行,从根本上保障了数据的最终一致性。
原理图解
源码实现(sql/rpl_trx_tracking.cc)
void Writeset_trx_dependency_tracker::track() {
// 计算事务writeset
for (auto &table : trx->get_tables()) {
for (auto &row : table->get_changed_rows()) {
uint64 hash = row->calculate_hash();
writeset.add(hash);
}
}
// 检测冲突
bool has_conflict = false;
for (auto hash : writeset) {
if (global_writeset.contains(hash)) {
has_conflict = true;
break;
}
}
// 更新序列
if (has_conflict) {
trx->sequence = last_commit + 1;
} else {
trx->sequence = last_commit;
}
// 更新全局writeset
global_writeset.merge(writeset);
}
六、GTID机制深度解析
6.1 GTID核心架构
6.2 GTID生命周期管理
// sql/rpl_gtid.cc
void Gtid_state::update_gtids_impl() {
// 分配GTID
Gtid gtid = sid_map->get_next_gtid();
// 更新内存状态
owned_gtids.add(gtid);
executed_gtids.add(gtid);
// 持久化到表
gtid_table->add(gtid);
}
// 故障恢复机制
void Gtid_state::recover() {
// 从表中加载GTID
executed_gtids = gtid_table->load();
// 检查Binlog完整性
for (auto gtid : executed_gtids) {
if (!binlog_has_gtid(gtid)) {
// 自动修复机制
fetch_missing_trx(gtid);
}
}
}
6.3 自动定位协议
bool auto_positioning() {
// 从库发送GTID集合
Gtid_Set slave_gtids = get_executed_gtids();
send_gtid_set(slave_gtids);
// 主库计算差异
Gtid_Set master_gtids = get_executed_gtids();
Gtid_Set diff = master_gtids.subtract(slave_gtids);
// 发送缺失事务
for (auto gtid : diff) {
send_transaction(gtid);
}
}
七、高级特性与优化实践
7.1 复制过滤规则
# 主库过滤
binlog-do-db = important_db
binlog-ignore-db = temp_%
# 从库过滤
replicate-do-table = sales.orders
replicate-wild-ignore-table = audit.%
7.2 延迟复制配置
# 故意延迟30分钟
CHANGE REPLICATION SOURCE TO SOURCE_DELAY=1800;
# 内核实现机制
void Delay_log_event::apply() {
if (is_delayed_event()) {
long delay = get_delay_time();
sleep(delay);
}
apply_event();
}
7.3 无损复制配置
# 确保数据零丢失
rpl_semi_sync_master_wait_point = AFTER_SYNC
rpl_semi_sync_master_wait_for_slave_count = 2
rpl_semi_sync_master_timeout = 31536000 # 1年超时
八、复制监控与故障诊断
8.1 关键监控指标
SHOW REPLICA STATUS\G
-- 核心字段解析:
Master_Log_File: master-bin.000258
Read_Master_Log_Pos: 7429 -- IO线程位置
Relay_Log_File: relay-bin.002
Relay_Log_Pos: 8321 -- SQL线程位置
Seconds_Behind_Master: 0 -- 复制延迟
Replica_SQL_Running: Yes
Replica_IO_Running: Yes
Last_IO_Error:
Last_SQL_Error:
8.2 故障诊断工具
# 检查GTID一致性
mysqlrplsync --master=root@master --slave=root@slave
# 解析Binlog事件
mysqlbinlog --verbose --base64-output=DECODE-ROWS master-bin.000001
九、未来演进方向
- 物理复制:基于存储引擎的块级复制
- 多源复制:多主一从架构
- 内存复制:基于Raft协议的状态机复制
// 实验性功能
class InnoDB_Raft_Engine : public RaftStateMachine {
void apply(const LogEntry &entry) {
// 直接应用日志到内存
mem_engine->apply(entry);
}
};
结论:复制技术栈核心要点
- 日志驱动:基于Binlog的数据变更捕获
- 异步解耦:通过Relay Log实现生产/消费解耦
- 并行优化:WRITESET实现细粒度并发
- 状态跟踪:GTID保障数据一致性
- 灵活部署:支持多种拓扑结构
通过深入理解MySQL复制机制的实现原理和源码级细节,DBA和开发者能够:
- 设计高可靠的数据库架构
- 快速定位复杂复制问题
- 制定精准的性能优化策略
- 实现秒级RPO的金融级容灾
本文包含的源码路径:
- 线程管理:sql/rpl_slave.cc
- 事件处理:sql/log_event.cc
- GTID:sql/rpl_gtid*.cc
- 并行复制:sql/rpl_slave_commit_order_manager.cc
- 半同步:plugin/semisync/*.cc
本文来自博客园,作者:NeoLshu,转载请注明原文链接:https://www.cnblogs.com/neolshu/p/19120685

浙公网安备 33010602011771号