1. 读写分离与分库分表策略解析
数据库性能优化是后端开发永恒的话题。当单机数据库遇到性能瓶颈时,读写分离和分库分表是最常用的两种水平扩展方案。我在电商和社交类项目中多次实施过这两种策略,实测QPS从最初的2000提升到20000+。下面结合具体案例,拆解这两种策略的原理、实现方式和避坑指南。
2. 读写分离实战
2.1 核心原理与适用场景
读写分离的本质是将数据库的读操作和写操作分离到不同的节点。主库(Master)负责写操作,从库(Slave)负责读操作,通过主从复制保持数据同步。这种架构特别适合读多写少的场景,比如新闻网站、电商商品页等。
我去年优化过一个日均PV 500万的社区项目,原始架构中所有请求都打到同一个MySQL实例。通过读写分离改造后:
- 写操作TPS从1500降到800(主库压力降低47%)
- 读操作QPS从3000提升到12000(3个从库分担负载)
- 99%的查询响应时间从120ms降到35ms
2.2 具体实现方案
2.2.1 基于中间件实现
推荐使用ShardingSphere-JDBC或MyCat。以ShardingSphere为例,配置示例:
spring: shardingsphere: datasource: names: master,slave0,slave1 master: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.jdbc.Driver jdbc-url: jdbc:mysql://master:3306/db username: root password: 123456 slave0: ... # 从库配置 slave1: ... # 从库配置 masterslave: name: ms master-data-source-name: master slave-data-source-names: slave0,slave1 load-balance-algorithm-type: round_robin2.2.2 基于代码实现
对于简单场景,可以手动维护数据源路由:
public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return TransactionSynchronizationManager.isCurrentTransactionReadOnly() ? "slave" : "master"; } }2.3 关键问题与解决方案
2.3.1 主从延迟问题
这是读写分离最大的痛点。我遇到过用户刚下单后立即查询订单显示不存在的case。解决方案:
- 强制读主库:对实时性要求高的查询添加
/* FORCE_MASTER */Hint - 延迟监控:部署Prometheus监控主从延迟,超过阈值告警
- 半同步复制:配置
semi-sync确保至少一个从库收到日志
2.3.2 连接池配置
从库连接池需要特别配置:
# HikariCP配置示例 slave.hikari.maximum-pool-size=20 # 比主库小 slave.hikari.idle-timeout=300000 # 空闲连接超时5分钟 slave.hikari.connection-timeout=5000 # 连接超时5秒重要提示:从库连接数建议设置为主库的1/N(N为从库数量),避免雪崩效应
3. 分库分表深度实践
3.1 何时需要分库分表
根据我的经验,当单表数据量达到以下阈值时需要考虑分片:
- MySQL:500万行以上
- PostgreSQL:1000万行以上
- 特殊场景:宽表(字段>50)或大字段(如TEXT)需提前规划
3.2 分片策略选型
3.2.1 水平分片方案对比
| 策略类型 | 示例 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 哈希取模 | user_id % 8 | 分布均匀 | 扩容困难 | 用户数据 |
| 范围分片 | 按时间范围 | 易于扩容 | 可能热点 | 日志数据 |
| 一致性哈希 | Ketama算法 | 扩容影响小 | 实现复杂 | 缓存系统 |
3.2.2 分片键选择原则
- 选择高基数字段(如user_id而不是gender)
- 避免选择单调递增字段(如自增ID)
- 优先选择查询条件中的字段
我在订单系统中使用(user_id % 16)作为分片键,配合order_time的二级索引,查询效率提升8倍。
3.3 ShardingSphere实战配置
spring: shardingsphere: datasource: names: ds0,ds1,ds2,ds3 ds0: ... # 数据源配置 ds1: ... sharding: tables: t_order: actual-data-nodes: ds$->{0..3}.t_order_$->{0..7} table-strategy: inline: sharding-column: order_id algorithm-expression: t_order_$->{order_id % 8} database-strategy: inline: sharding-column: user_id algorithm-expression: ds$->{user_id % 4}3.4 跨分片查询优化
- 广播表:配置小表为广播表,全库冗余存储
sharding: broadcast-tables: t_region,t_config - 绑定表:确保关联表使用相同分片规则
binding-tables: t_order,t_order_item - 分布式事务:对于跨库事务,建议使用Seata的AT模式
4. 混合架构实践心得
在千万级用户的社交APP中,我采用读写分离+分库分表混合架构:
- 用户基础信息:16个分库,每个库1主2从
- 好友关系:8个分库,采用图数据库+分片
- 动态内容:按时间范围分片+ES全文检索
血泪教训:曾经在凌晨3点因为漏配置从库的
read_only参数,导致从库被误写数据,主从数据不一致。现在我的检查清单必含:
- 所有从库
read_only=1- 主从复制账号权限最小化
- 定期校验主从数据checksum
5. 监控与调优要点
5.1 关键监控指标
| 指标类别 | 具体指标 | 报警阈值 |
|---|---|---|
| 主从延迟 | Seconds_Behind_Master | >30s |
| 连接数 | Threads_connected | >max_connections*0.8 |
| 查询性能 | Slow_queries | >100次/分钟 |
5.2 性能调优参数
# InnoDB缓冲池(建议物理内存的70-80%) SET GLOBAL innodb_buffer_pool_size=12G; # 主库写性能优化 SET GLOBAL sync_binlog=1000; SET GLOBAL innodb_flush_log_at_trx_commit=2; # 从库读性能优化 SET GLOBAL read_buffer_size=4M; SET GLOBAL read_rnd_buffer_size=8M;6. 迁移方案设计
对于已有系统的分库分表改造,我总结出三阶段方案:
- 双写阶段:新老库同时写入,用canal监听老库变更
- 校验阶段:部署数据比对任务,确保两边数据一致
- 切流阶段:通过配置中心动态切换读流量
整个过程建议在低峰期进行,每次切流不超过10%的流量,持续观察至少30分钟。