读写分离主库写从库读

wen java案例 1

本文目录导读:

读写分离主库写从库读

  1. 核心架构图
  2. 为什么需要读写分离?
  3. 如何实现?—— 3种主流方式
  4. 必须解决的核心问题:主从延迟
  5. 什么时候不该用读写分离?
  6. 最佳实践建议

这是一个非常经典的数据库架构设计模式,主要用于提升数据库的读性能系统可用性

就是让数据库主库(Master)专门处理写操作(INSERT、UPDATE、DELETE),而让从库(Slave)专门处理读操作(SELECT),通过数据同步机制(通常是MySQL的主从复制)保持数据一致性。

以下是一个从概念到实践的全方位解析:

核心架构图

+--------+    写入(写SQL)    +----------+
| 应用程序 |  ------------>  |  主库Master  | (负责写:INSERT/UPDATE/DELETE)
+--------+                  +----------+
    |                            |
    | 读取(读SQL)               | 异步复制 (Binlog)
    |                            |
    v                            v
+--------+                  +----------+
| 应用程序 |  ------------>  |  从库Slave  | (负责读:SELECT)
+--------+                  +----------+
                            (可以有多个从库)

为什么需要读写分离?

  • 缓解锁竞争:读操作通常不会锁表(InnoDB行锁),但写操作会,读写分离后,主库只负责写,写性能更高。
  • 提升读吞吐量:从库可以水平扩展,增加多个从库,支持更大的并发查询。
  • 容灾备份:主库故障时,可以将某个从库升级为主库,保障系统可用性。
  • 数据分析隔离:复杂的统计查询、报表生成可以放在从库上执行,不会拖垮主库。

如何实现?—— 3种主流方式

程序代码中手动切换(最灵活)

在代码逻辑中,根据SQL类型,选择不同的数据源连接。

// 伪代码示例
public class DataSourceRouter {
    private DataSource masterDataSource;
    private List<DataSource> slaveDataSources;
    private AtomicInteger counter = new AtomicInteger(0);
    public Connection getConnection(boolean isRead) {
        if (isRead) {
            // 轮询策略:选择一个从库
            int index = counter.incrementAndGet() % slaveDataSources.size();
            return slaveDataSources.get(index).getConnection();
        } else {
            return masterDataSource.getConnection();
        }
    }
}
// 使用
// if (sql.startsWith("SELECT")) { router.getConnection(true); }
// else { router.getConnection(false); }
  • 优点:控制粒度最细,可以针对特定查询选择特定从库。
  • 缺点:需要修改代码,对开发人员要求高,容易遗漏或出错。

使用数据库中间件(推荐)

在应用和数据库之间加一层中间件,中间件自动解析SQL,并转发到主库或从库。

  • 代表产品

    • ProxySQL:功能强大,支持规则配置、缓存、故障自动切换。
    • MyCat / DBLE:国内早期开源的数据库中间件,功能丰富但生态较老。
    • ShardingSphere-Proxy:Apache顶级项目,支持读写分离、分库分表。
    • Vitess、MaxScale 等。
  • 配置示例(ProxySQL)

    -- 插入读写分离规则
    INSERT INTO mysql_query_rules (rule_id, active, match_pattern, destination_hostgroup, apply)
    VALUES (1, 1, '^SELECT.*', 2, 1); -- 所有SELECT语句发往hostgroup 2(从库组)
    INSERT INTO mysql_query_rules (rule_id, active, match_pattern, destination_hostgroup, apply)
    VALUES (2, 1, '^.*', 1, 1); -- 其他语句发往hostgroup 1(主库组)
  • 优点:对应用透明,无需修改代码。

  • 缺点:引入中间件会增加网络延迟和运维复杂度。

利用ORM框架特性

很多ORM框架(如ORM框架中的AbstractRoutingDataSource、Sequelize、TypeORM)内置了读写分离支持。

  • 示例(Spring + AbstractRoutingDataSource)
    // 配置两个数据源:master、slave
    // 使用注解或AOP动态切换
    @ReadOnly
    public List<User> getUsers() {
        // 自动路由到从库
    }
  • 优点:结合框架使用,代码侵入性较小。
  • 缺点:通常功能较基础,不支持复杂的高可用切换。

必须解决的核心问题:主从延迟

这是读写分离最头痛的问题,因为主库到从库的复制是异步的,所以在写操作完成后,立刻去读从库,可能读不到数据。

场景:用户注册后立刻登录,写入主库成功,但立刻从从库查询用户信息,由于延迟,从库还没复制完成,导致查询不到,用户注册失败。

解决方案

方案 描述 适用场景
强制读主库 对于关键业务(如刚完成注册、支付后查询订单),强制走主库读取 对数据一致性要求极高的操作
等待复制完成 写入主库时,记录事务ID,读取从库时检查从库是否已经应用了该事务,如果未应用,则等待或回读主库。 技术门槛较高
放弃强一致性 接受短暂时间内显示旧数据(帖子发布后,几秒内别人看不到但自己看得到)。 大部分社交、内容平台
缓存 写入主库后,同时修改/删除缓存,从库读取时如果命中缓存则直接返回,否则回库查。 有缓存架构的系统
主库也承担部分读 让主库也承担一小部分读流量,核心读取从主库走。 系统规模不大时

什么时候不该用读写分离?

  1. 数据一致性是零容忍:例如金融交易系统、强一致性要求的业务。
  2. 系统读压力不大:单库能轻松支持所有读写,引入读写分离是过度设计,增加复杂度。
  3. 写操作远大于读操作:读写分离主要优化读,如果写是瓶颈,应该考虑分库分表或分布式数据库。
  4. 机器资源不足:至少需要2台以上的服务器(1主1从),成本翻倍。

最佳实践建议

  1. 起步阶段:一个主库+一个从库,保持简单。
  2. 路由层:推荐使用ProxySQLShardingSphere-Proxy,对应用透明,方便运维。
  3. 延迟处理:业务代码中,对“读后写”或“写后读”的关键操作,加上一个标记(如写入时间戳),在读取时判断是否需要强制读主库。
  4. 监控:务必监控从库的Seconds_Behind_Master(复制延迟秒数)指标,当延迟超过阈值时要告警。
  5. 高可用:不要只用一主一从,要配合主从切换(如MHA、Orchestrator)或中间件自带的故障检测功能。

如果你正在设计一个具体系统,告诉我你的业务场景(如:电商系统、内容网站、物联网平台)和技术栈(Java/Go/Python + MySQL),我可以给你更具体的实现方案。

抱歉,评论功能暂时关闭!