主从复制与读写分离:MySQL走向分布式的第一步
作者:黒漂技术佬
适用读者:单机MySQL扛不住了,想搞主从但不知道原理和坑的同学
关联场景:无人售货柜多门店数据架构、智慧农业多基地数据汇总
一、为什么需要主从复制:单机的三道天花板
单机MySQL用着用着会遇到三堵墙:
天花板1:高可用
- 一台MySQL挂了,整个业务停摆
- 售货柜1000台断网2小时,订单全丢
- 需要一台备机顶上
天花板2:读压力大
- 售货柜展示商品列表、查询订单状态——全是读
- 高峰期1000 QPS的读压到一台机器
- CPU打满,写也跟着卡
天花板3:数据隔离
- 报表分析跑大查询,把生产库拖垮
- 想做备份又怕影响线上
主从复制就是解决这三个问题的基础设施:一主多从,主库写,从库读,主挂了从顶上。
二、主从复制原理:binlog→relay log→回放
2.1 三个角色
| 角色 | 说明 |
|---|---|
| 主库(Master) | 接收写请求,把变更记到binlog |
| 从库(Slave) | 连主库拉binlog,回放到自己 |
| binlog/relay log | 主的binlog是变更源头,从的relay log是中转 |
2.2 复制的三个线程
主库:Binlog Dump Thread
→ 负责把binlog发送给从库
从库:
IO Thread → 接收主库binlog,写到本地relay log
SQL Thread → 读relay log,回放到从库数据文件
2.3 完整流程
主库 从库
写请求 → 修改数据
→ 写binlog
Binlog Dump Thread IO Thread
─────── binlog event ──────→ 收到event,写relay log
relay log落盘
SQL Thread
读relay log,回放SQL
→ 修改从库数据
详细步骤:
- 从库执行
CHANGE MASTER TO ...配置主库地址,START SLAVE - 从库IO Thread连接主库,请求从某个binlog位点开始拉数据
- 主库Binlog Dump Thread读binlog,发给从库
- 从库IO Thread把event写入relay log(中继日志)
- 从库SQL Thread读relay log,重放里面的SQL,更新数据
- 从库和主库数据一致
这是个异步逻辑——主库写完binlog就返回客户端成功,不等从库同步完。所以会有"主从延迟"。
三、三种复制方式:异步、半同步、组复制
3.1 异步复制(默认)
主库写完binlog立即返回客户端,不等从库确认。
主库 从库
写完binlog
返回客户端 ✓ (还在同步中)
... 同步完成
优点:性能好,主库几乎不被从库拖累。
缺点:主库挂了,从库可能还没同步完最新数据 → 数据丢失。
# 主库配置
[mysqld]
log_bin = mysql-bin
server_id = 1
3.2 半同步复制
主库写完binlog,至少等一个从库收到binlog并写relay log确认,才返回客户端成功。
主库 从库
写binlog
──── binlog ────→ 收到,写relay log,回复ACK
收到从库ACK ✓
返回客户端 ✓
优点:数据安全性高,至少有一个从库收到了数据。
缺点:比异步慢一点(多一个RTT),从库全挂时主库会降级成异步或阻塞。
# 主库配置
[mysqld]
plugin_load = "rpl_semi_sync_master=semisync_master.so"
rpl_semi_sync_master_enabled = 1
rpl_semi_sync_master_timeout = 3000 # 3秒等不到ACK降级为异步
# 从库配置
[mysqld]
plugin_load = "rpl_semi_sync_slave=semisync_slave.so"
rpl_semi_sync_slave_enabled = 1
3.3 组复制(MGR,MySQL Group Replication)
多个节点组成一个组,用Paxos协议保证数据一致性。任何节点都能写,写完同步到全组。
MGR集群:
节点1 (主) ←→ 节点2 ←→ 节点3
写请求 → Paxos共识 → 全组同步 → 返回
优点:真正的高可用,任何单点挂了集群照常工作;多主可写。
缺点:配置复杂,写性能受共识协议拖累(至少过半数节点确认)。
| 复制方式 | 性能 | 数据安全 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| 异步 | 最高 | 一般(可能丢) | 低 | 读多写少、容忍少量丢失 |
| 半同步 | 中 | 高 | 中 | 大多数生产场景 |
| MGR | 较低 | 极高 | 高 | 强一致性要求高 |
选型建议:普通业务用半同步,金融级用MGR。异步复制只用在纯读从库或测试环境。
四、主从延迟原因和解决方案
4.1 延迟怎么来的
异步复制下,从库的数据总是落后主库一段时间——这就是主从延迟。延迟的几个成因:
- SQL Thread单线程回放:主库多线程并发写,从库单线程串行回放,写压力大时从库追不上。MySQL 5.7+有并行复制(基于group commit),5.6及之前是硬伤。
- 网络延迟:主从跨机房跨地域,binlog传输慢。
- 从库压力:从库被大查询占用CPU/IO,回放速度下降。
- 大事务:一个事务里有10万条UPDATE,主库几秒写完,从库要回放十几秒。
4.2 怎么看延迟
-- 从库执行
SHOW SLAVE STATUS \G
-- 关注:
-- Seconds_Behind_Master: 5 (落后主库5秒)
-- Slave_IO_Running: Yes
-- Slave_SQL_Running: Yes
4.3 解决方案
延迟优化清单:
1. 开启并行复制(MySQL 5.7+)
slave_parallel_type = LOGICAL_CLOCK
slave_parallel_workers = 8
2. 减少大事务
把UPDATE 10万行拆成多次小批量
3. 从库SSD + 大内存
回放是IO密集型,硬件要跟上
4. 业务读走从库时容忍延迟
重要读(扣库存前查余额)走主库
普通读(列表展示)走从库
真实踩坑:用户刚下完单,立刻查订单列表,查不到——因为写走主库,读走从库,从库还在同步。这种叫"读写一致性"问题。解法:关键查询走主库,或用ShardingSphere的读写一致性策略。
五、读写分离架构:主库写、从库读
5.1 架构图
应用层
↓
┌───────────────┐
│ 读写分离中间件 │
└───────────────┘
↓ 写 ↓ 读
主库 从库1 从库2 从库3
(写+强一致读) (读分担)
读写分离的核心思想:写请求必须走主库(只有主库能写),读请求尽量走从库(分担压力)。
5.2 哪些读必须走主库
必须走主库的读:
1. 写完立刻要读的场景(保证读到自己刚写的数据)
2. 涉及金额、库存的关键判断
3. 事务内的查询(事务要读到最新数据)
可以走从库的读:
1. 商品列表、订单历史
2. 报表统计
3. 用户头像、昵称展示
六、读写分离的实现方案
6.1 ShardingSphere-JDBC
Apache ShardingSphere提供JDBC层的读写分离,应用无需改代码,配置一下就能用。
spring:
shardingsphere:
datasource:
names: master,slave0,slave1
master:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://master-db:3306/shop
username: root
password: xxx
slave0:
# 从库0配置
jdbc-url: jdbc:mysql://slave0-db:3306/shop
slave1:
# 从库1配置
jdbc-url: jdbc:mysql://slave1-db:3306/shop
rules:
readwrite-splitting:
data-sources:
readwrite_ds:
write-data-source-name: master
read-data-source-names: slave0,slave1
load-balancer-name: round_robin
load-balancers:
round_robin:
type: ROUND_ROBIN # 轮询负载均衡
ShardingSphere根据SQL类型自动路由:INSERT/UPDATE/DELETE → 主库;SELECT → 从库。事务内的所有SQL都走主库。
// 普通查询走从库(自动路由)
List<Product> list = productMapper.selectList(...);
// 事务内走主库
@Transactional
public void placeOrder() {
productMapper.update(...); // 写主库
productMapper.selectList(); // 也在主库(同事务)
}
6.2 MyCat
MyCat是独立的数据库中间件,应用连接MyCat,MyCat再连后端真实数据库。配置比ShardingSphere重,但功能全。
<!-- schema.xml -->
<schema name="shop">
<table name="product" dataNode="dn1" />
</schema>
<dataHost name="host1" balance="2" writeType="0">
<writeHost host="master" url="master-db:3306" />
<readHost host="slave0" url="slave0-db:3306" />
<readHost host="slave1" url="slave1-db:3306" />
</dataHost>
| 方案 | 部署方式 | 改造成本 | 适合 |
|---|---|---|---|
| ShardingSphere-JDBC | 应用内jar包 | 低,改配置 | Java应用为主 |
| MyCat | 独立中间件 | 中,多一层运维 | 多语言混合环境 |
选型原则:纯Java应用优先ShardingSphere-JDBC,部署简单、性能损耗低。多语言或需要独立DB代理层再考虑MyCat。
七、主从切换和故障恢复
7.1 主库挂了怎么办
主库故障的恢复流程:
1. 检测:监控发现主库不可达
2. 选新主:从多个从库里选一个数据最新的( Seconds_Behind_Master最小)
3. 提升:把选中的从库提升为新主
STOP SLAVE;
RESET SLAVE ALL;
SET GLOBAL read_only = OFF;
4. 应用切流量:把应用的数据源指向新主
5. 旧主恢复:作为从库重新加入集群
7.2 自动切换工具
手动切换太慢,生产用自动化工具:
| 工具 | 说明 |
|---|---|
| MHA | Master High Availability,老牌方案,监控+自动切换 |
| Orchestrator | 拓扑管理+故障切换,Web界面 |
| MGR | 组复制自带故障切换,无需外部工具 |
| 壳厂中间件 | ShardingSphere可配合注册中心做自动切换 |
# MHA Manager监控主库
masterha_manager --conf=/etc/mha/shop.cnf
# 主库挂掉时MHA自动:
# 1. 选出从库中binlog最新的
# 2. 把其他从库的relay log补到选中的从库
# 3. 提升为新主,把其他从库重新指向新主
切换不是万能的,数据丢失是切换时最头疼的问题。半同步复制+延迟监控能大幅降低丢数据概率。
八、结合无人售货柜多门店数据架构
8.1 业务场景
某无人售货柜运营平台覆盖200个城市、5000台柜子。数据架构需求:
- 每个门店(区域)有本地数据库,写本地订单
- 总部要查所有门店数据做报表
- 单台柜子挂了不影响其他门店
- 总部做活动时配置要下发到所有门店
8.2 架构设计
总部数据中心
↑ 汇聚
┌──────────────┼──────────────┐
华北区 华东区 华南区
(主+2从) (主+2从) (主+2从)
↓ ↓ ↓
门店1-50 门店51-200 门店201-500
设计要点:
- 分区域部署主从集群:每个区域一套主从,区域写本地数据,避免跨区域网络延迟。
- 总部只读从库:区域主库通过半同步复制到总部只读节点,总部查只读节点。
- 门店数据通过门店ID分库:
db_shop_001~db_shop_500按门店ID取模分库。 - 配置下发用消息队列:总部配置变更发到Kafka,各区域消费写到本地。
8.3 读写分离在售货柜场景的具体应用
// 用户查询商品列表 → 走从库(读多,容忍秒级延迟)
@GetMapping("/products")
public List<Product> listProducts(Long cabinetId) {
return productService.listByCabinet(cabinetId); // 自动走从库
}
// 用户下单扣库存 → 走主库(必须强一致)
@Transactional
public void placeOrder(OrderRequest req) {
productMapper.deductStock(req.getProductId()); // 写主库
orderMapper.insert(buildOrder(req)); // 写主库
}
// 售货柜状态监控 → 走从库
@GetMapping("/cabinet/status")
public CabinetStatus status(Long cabinetId) {
return cabinetService.getStatus(cabinetId); // 从库
}
经验:售货柜场景的读写比例通常10:1甚至更高(查商品、查订单、查状态远多于下单),读写分离的收益非常明显。从库分担了90%的读压力,主库只专注写和关键读。
九、总结
| 概念 | 一句话 |
|---|---|
| 主从复制原理 | 主写binlog→从拉relay log→SQL Thread回放 |
| 三种复制 | 异步(快但可能丢)、半同步(折中)、MGR(强一致) |
| 主从延迟 | 从库回放慢于主库写入,靠并行复制+小事务缓解 |
| 读写分离 | 写走主库,读走从库,靠ShardingSphere/MyCat路由 |
| 故障切换 | 选最新从库→提升为主→应用切流量,用MHA/Orchestrator自动化 |
| 多门店架构 | 区域分库+总部汇聚从库,读写分离分担读压力 |
主从复制是MySQL从单机走向分布式的第一步。但单库容量到天花板(单表5000万行变慢),主从也救不了——这就需要分库分表。下一篇我们聊高可用与分库分表。

2054

被折叠的 条评论
为什么被折叠?



