考点分析:
- 是否真正理解读写分离与主从复制的关系,而不是只会背概念;
- 是否清楚读写分离的常见实现方式,以及不同方案在业务中的取舍;
- 是否意识到主从延迟带来的数据一致性问题,并具备解决方案;
- 是否具备使用 Java 生态组件落地读写分离的实际编码能力;
- 是否能进一步回答高可用切换、强制主库读、与分库分表结合等追问。
一、标准回答
读写分离,简单说就是把数据库的读操作和写操作分开,分别路由到不同的数据库节点:主库负责写,从库负责读。它的实现前提是 MySQL 主从复制:主库上的数据变更通过 binlog 同步到从库,从库保持一份可读的数据副本。应用层或中间件根据 SQL 类型自动选择主库还是从库。
它的作用主要有三点:
- 降低主库压力:把大量查询流量分散到多个从库,提升系统吞吐和并发能力;
- 提升可用性:主库发生故障时,从库可以承担读请求,甚至切换为主库;
- 支撑规模化扩展:读多写少的典型场景可以通过增加从库水平扩展读能力。
它的特点也很明显:实现成本相对低,但会引入数据延迟和路由复杂度,需要业务上容忍一定时限的主从延迟。
二、核心原理
读写分离底层的核心是 MySQL 主从复制。主从复制的流程可以概括为:
- 主库将所有写操作记录到二进制日志 binlog;
- 从库的 I/O 线程连接主库,把主库的 binlog 复制到本地,写入 relay log 中继日志;
- 从库的 SQL 线程读取 relay log,重放其中的事件,完成数据同步。
binlog 有三种常用格式:STATEMENT记录 SQL 语句、ROW记录行级变更、MIXED混合模式。其中 ROW 模式在大多数读写分离和同步场景下更可靠,因为它记录的是实际数据变化,不会因为函数、触发器等导致主从不一致。
主从复制按同步策略又分为:
- 异步复制:主库提交事务后无需等待从库确认,性能高但可能丢数据;
- 半同步复制:主库等待至少一个从库收到 binlog 后再返回客户端,能减少数据丢失风险;
- 全同步复制:所有从库确认后才返回,性能较差,实际使用较少。
读写分离正是在这套复制机制之上,通过路由层把读请求发送给从库。因此,只要从库没有追平主库,就可能出现“写完立刻读不到”的主从延迟问题,这是面试中核心的追问点。
三、应用场景
- 日常开发场景:电商商品详情、文章内容页、用户中心信息展示等读多写少的业务,查询接口可以走从库,下单、支付等写入接口走主库。
- 企业真实场景:互联网公司通常采用一主多从架构,配合 ShardingSphere、MyCat 等中间件,实现透明读写分离;同时还会加监控、告警和主从延迟检测组件。
- 报表和运营分析:大查询、统计任务独立放在从库或专门的只读实例上,避免拖垮主库。
- 微服务读模型优化:某些服务只读业务数据,整个服务可以只连接从库,从链路层面隔离主库压力。
四、使用方式
实现读写分离的常见方式有三种:
- 应用层路由:手动维护多个数据源,根据方法名或注解选择主从数据源。实现简单但侵入业务代码。
- 中间件层:使用 ShardingSphere-JDBC、MyBatis 插件等,在 Java 应用内完成路由,对业务代码侵入小。
- 数据库代理层:部署 MyCat、ProxySQL、Atlas 等代理,应用连接代理即可,代理负责分发主从请求。
下面以ShardingSphere-JDBC为例,演示 Java 中实现读写分离的落地方式。ShardingSphere-JDBC 以 jar 包形式运行在应用内,通过配置读写分离规则,自动解析 SQL 并路由。
步骤一:引入依赖
<dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core</artifactId> <version>5.4.1</version> </dependency>步骤二:配置数据源和读写分离规则
在application.yml中配置主库和两个从库:
spring: shardingsphere: datasource: names: master, slave1, slave2 master: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://192.168.1.10:3306/shop username: root password: root slave1: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://192.168.1.11:3306/shop username: root password: root slave2: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://192.168.1.12:3306/shop username: root password: root rules: readwrite-splitting: >@Service public class OrderQueryService { @Resource private JdbcTemplate jdbcTemplate; public List<Map<String, Object>> listOrdersByUser(long userId) { String sql = "SELECT order_id, amount, status FROM t_order WHERE user_id = ?"; return jdbcTemplate.queryForList(sql, userId); } @Transactional public void createOrder(long userId, BigDecimal amount) { String sql = "INSERT INTO t_order(user_id, amount, status) VALUES (?, ?, ?)"; jdbcTemplate.update(sql, userId, amount, "CREATED"); } }执行流程解释:
- 应用启动后,ShardingSphere 根据配置文件初始化主库 master 和从库 slave1、slave2 三个数据源;
- 当执行
SELECT查询时,规则引擎识别为读操作,从负载均衡器选择的从库中获取连接; - 当执行
INSERT、UPDATE、DELETE时,路由到 write-data-source 指定的 master; - 如果业务方法标注了
@Transactional且事务内有写操作,ShardingSphere 会强制走主库,避免读旧数据。
注意事项:
- 主从延迟场景:写后立即读的接口应强制走主库,或者使用半同步复制降低延迟;
- 事务一致性:同一事务内不要混合主从数据源,否则可能出现事务提交前读到未同步数据;
- 从库可用性:需要配置从库健康检查和自动摘除,避免从库故障导致查询全部失败;
- SQL 类型识别:ShardingSphere 对复杂 SQL、存储过程等解析可能有限,需提前评估兼容性。
五、扩展延伸
主流方案对比
| 方案 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|
| 应用层手动路由 | 实现简单、可控性强 | 代码侵入大、维护成本高 | 小型项目或临时需求 |
| ShardingSphere-JDBC | 轻量、灵活、支持分库分表 | 依赖应用配置,版本升级需关注 | Java 技术栈主流选择 |
| MyCat/ProxySQL 代理 | 对应用透明、支持多语言 | 引入额外组件、运维复杂度高 | 多语言、大规模集中管控 |
读写分离的优缺点
- 优点:提升读性能、分散压力、便于横向扩展读节点、与主从高可用天然结合。
- 缺点:存在主从延迟,数据一致性需要业务容忍;增加路由和运维复杂度;写扩展仍然受限。
实际开发注意事项
- 生产环境建议开启从库只读模式,防止误写从库;
- 监控主从复制状态,设置延迟告警阈值;
- 关键业务写后必读场景,使用
HintManager或注解强制走主库; - 如果进一步需要分库分表,优先选择同时支持读写分离和数据分片的中间件,避免重复建设。
六、面试追问
追问一:主从延迟导致写后读不到,如何解决?
回答思路:先说明主从延迟的原因,再给出业务层和架构层两类解决手段。
标准答案:主从延迟主要来自网络传输、从库重放速度和从库负载。解决方式包括:
- 写后立即读的接口强制走主库;
- 使用半同步复制减少数据丢失风险,但无法完全消除延迟;
- 在从库读前判断同步位点,未追平则改为读主库;
- 业务上通过缓存或延迟容忍设计规避强一致读。
追问二:主库宕机后如何切换,读写分离还能正常工作吗?
回答思路:从高可用组件、数据补偿和路由切换三个层面回答。
标准答案:主库宕机后需要依靠 MHA、Orchestrator 等工具把某个从库提升为主库,并修改读写分离的路由配置。切换过程中会出现短暂不可写或数据不一致风险,因此需要:
- 提前配置半同步复制,降低切换时的数据丢失;
- 切换后原主库恢复时作为从库重新接入,避免数据冲突;
- 读写分离中间件监听切换事件,自动更新写库地址和读库列表。
追问三:读写分离和分库分表可以一起用吗?
回答思路:先肯定可以,再说明组合方式及注意点。
标准答案:可以一起用。分库分表解决单库单表数据量过大的问题,而读写分离解决读多写少的性能问题。ShardingSphere 等中间件可以同时配置分片规则和读写分离规则,写请求先按分片键路由到对应主库,读请求再分发到该主库下的从库。组合使用时要注意事务一致性、分片键设计以及管理复杂度,建议先做读写分离,再逐步引入分库分表。