09-主从复制与读写分离:MySQL走向分布式的第一步

主从复制与读写分离: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
                                  → 修改从库数据

详细步骤:

  1. 从库执行CHANGE MASTER TO ...配置主库地址,START SLAVE
  2. 从库IO Thread连接主库,请求从某个binlog位点开始拉数据
  3. 主库Binlog Dump Thread读binlog,发给从库
  4. 从库IO Thread把event写入relay log(中继日志)
  5. 从库SQL Thread读relay log,重放里面的SQL,更新数据
  6. 从库和主库数据一致

这是个异步逻辑——主库写完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 延迟怎么来的

异步复制下,从库的数据总是落后主库一段时间——这就是主从延迟。延迟的几个成因:

  1. SQL Thread单线程回放:主库多线程并发写,从库单线程串行回放,写压力大时从库追不上。MySQL 5.7+有并行复制(基于group commit),5.6及之前是硬伤。
  2. 网络延迟:主从跨机房跨地域,binlog传输慢。
  3. 从库压力:从库被大查询占用CPU/IO,回放速度下降。
  4. 大事务:一个事务里有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 自动切换工具

手动切换太慢,生产用自动化工具:

工具说明
MHAMaster 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

设计要点:

  1. 分区域部署主从集群:每个区域一套主从,区域写本地数据,避免跨区域网络延迟。
  2. 总部只读从库:区域主库通过半同步复制到总部只读节点,总部查只读节点。
  3. 门店数据通过门店ID分库db_shop_001~db_shop_500按门店ID取模分库。
  4. 配置下发用消息队列:总部配置变更发到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万行变慢),主从也救不了——这就需要分库分表。下一篇我们聊高可用与分库分表。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值