读写分离的实现方式与架构设计

读写分离的实现方式与架构设计

概述

读写分离(Read-Write Splitting)是 MySQL 最常见的扩展架构:将写操作(INSERT/UPDATE/DELETE)发送到主库,读操作(SELECT)分发到从库,从而分散数据库负载,提升系统吞吐量。

为什么需要读写分离

单库架构:
客户端 → 一个 MySQL 实例
         ├── 写入
         └── 读取 ← 写入和读取争抢资源

读写分离架构:
客户端
 ├── 写入 → 主库(写能力强)
 └── 读取 → 从库 1
           → 从库 2  ← 读取分散到多个节点
           → 从库 3

核心收益:
– 读能力水平扩展(加从库即可)
– 写操作不受读查询干扰
– 主库专注于写入,性能更好
– 从库故障不影响写入

实现方式

方式一:应用层实现(代码中硬编码)

在应用程序代码中区分读写:

// Java 示例:简单的读写分离
public class DataSourceRouter {

    private DataSource master;   // 主库
    private List<DataSource> slaves;  // 从库列表
    private AtomicInteger counter = new AtomicInteger(0);

    public Connection getConnection(boolean isRead) {
        if (isRead) {
            // 路由到从库(简单轮询)
            int index = counter.incrementAndGet() % slaves.size();
            return slaves.get(Math.abs(index)).getConnection();
        } else {
            // 路由到主库
            return master.getConnection();
        }
    }
}

// 使用
// 事务或者写操作
dataSourceRouter.getConnection(false).execute("UPDATE users SET ...");

// 读操作
dataSourceRouter.getConnection(true).execute("SELECT * FROM users ...");

优点: 简单直接,无额外中间件
缺点: 代码侵入性强,配置变更需要重新部署

方式二:框架级实现(ShardingSphere / MyCat)

通过中间件或数据库驱动实现透明的读写分离:

# ShardingSphere-JDBC 配置
spring:
  shardingsphere:
    datasource:
      names: master,slave1,slave2
      master:
        url: jdbc:mysql://192.168.1.10:3306/db
      slave1:
        url: jdbc:mysql://192.168.1.11:3306/db
      slave2:
        url: jdbc:mysql://192.168.1.12:3306/db
    rules:
      readwrite-splitting:
        data-sources:
          rw:
            write-data-source-name: master
            read-data-source-names:
              - slave1
              - slave2
            load-balancer: round_robin

优点: 无代码侵入,配置灵活
缺点: 增加中间件运维成本

方式三:代理层实现(Proxy)

独立的数据库代理服务(如 ProxySQL、MySQL Router):

# ProxySQL 配置
mysql_servers:
  - hostgroup: 0    # 写组
    address: 192.168.1.10
    port: 3306
  - hostgroup: 1    # 读组
    address: 192.168.1.11
    port: 3306
  - hostgroup: 1
    address: 192.168.1.12
    port: 3306

mysql_query_rules:
  - rule_id: 1
    active: 1
    match_pattern: "^SELECT"
    destination_hostgroup: 1    # SELECT 到从库
    cache_ttl: 1000
  - rule_id: 2
    active: 1
    match_pattern: "^.*"
    destination_hostgroup: 0    # 其他到主库
客户端 → ProxySQL(3306)
           │
           ├── SELECT → 从库池(轮询/权重)
           │
           └── INSERT/UPDATE/DELETE → 主库

优点: 对应用完全透明
缺点: 增加网络跳数,代理自身也有性能开销

三种方式对比

方式 复杂度 代码侵入 性能损耗 运维成本 灵活性
应用层
框架层
代理层

读负载均衡策略

策略 描述 适用场景
Round Robin 轮询,每个从库轮流接收读请求 从库配置相同
Least Connections 请求发到连接数最少的从库 从库负载不均
权重分配 按权重分配(配置高的从库权重高) 异构从库
随机 随机选择 简单场景

事务内的读写

重要原则:事务内所有操作应该走同一个数据源

// 错误做法
@Transactional
public void updateAndQuery() {
    // 写操作走主库
    userMapper.updateBalance(1, 100);

    // 读操作可能走到从库
    User user = userMapper.selectById(1);  
    // ❌ 从库可能还没同步到新数据!
}

// 正确做法:事务强制走主库
@Transactional
public void updateAndQuery() {
    userMapper.updateBalance(1, 100);

    // 强制使用主库读取
    HintManager.getInstance().setWriteRouteOnly();
    User user = userMapper.selectById(1);
    // ✅ 读写都在主库,数据一致
}

面试要点

  1. 读写分离的核心:写主库(一个)、读从库(多个),分散读压力
  2. 三种实现方式:应用层硬编码(最简单)、框架层 ShardingSphere(最常用)、代理层 ProxySQL(最透明)
  3. 事务内强制走主库:避免”写后读”读到旧数据
  4. 读负载均衡:轮询、最少连接、权重三种主要策略
  5. 读写分离不能解决写瓶颈:写操作仍然集中在主库
  6. MySQL Router 是官方方案:简单场景推荐 MySQL Router + 从库启用插件
© 版权声明
THE END
喜欢就支持一下吧
点赞13 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容