1. 读写分离架构 1.1 为什么需要读写分离 大多数业务场景读多写少 (读:写 ≈ 8:1 ~ 50:1)。将读请求分发到多个从库,可线性扩展读性能。
单库架构的问题:
App ──→ MySQL
│
├── 读请求(80% CPU)
└── 写请求(20% CPU)
读请求增加 → CPU 100% → 所有请求变慢
读写分离架构:
写请求 ──→ [Master] ──→ 异步复制 ──→ [Slave 1]
──→ [Slave 2]
──→ [Slave 3]
读请求 ──→ [负载均衡器] ──→ Slave 1 / Slave 2 / Slave 3
1.2 实现方式 方式 原理 代表产品 侵入性 代理层 独立进程解析 SQL 路由 MySQL Router、ProxySQL、MaxScale 无 中间件 分库分表中间件内置读写分离 ShardingSphere、MyCat 低 应用层 代码中配置多数据源 Spring RoutingDataSource 高
// Spring Boot 应用层读写分离
@Configuration
public class DataSourceConfig {
@Bean
public DataSource routingDataSource (
@Qualifier ( "masterDataSource" ) DataSource master ,
@Qualifier ( "slaveDataSource" ) DataSource slave ) {
DynamicRoutingDataSource routing = new DynamicRoutingDataSource ();
Map < Object , Object > targets = new HashMap <> ();
targets . put ( "master" , master );
targets . put ( "slave" , slave );
routing . setTargetDataSources ( targets );
routing . setDefaultTargetDataSource ( master );
return routing ;
}
}
// Service 方法指定数据源
@Transactional ( readOnly = true ) // 走 Slave
public List < Order > getOrders () { ... }
@Transactional // 走 Master
public void createOrder ( Order order ) { ... }
1.3 复制延迟问题 写 Master ──→ 复制到 Slave(延迟 1ms ~ 数秒)
│
└──→ 用户立即读 Slave → 读不到刚写入的数据!
解决策略:
1. 关键读走 Master(写入后立即读取的场景)
2. 缓存补偿(写入后先更新缓存,读时先查缓存)
3. 半同步复制(Semi-Sync:至少一个 Slave 确认后再返回)
4. 会话绑定(同一连接内写后读走 Master)
2. 分库分表 2.1 何时需要分库分表 指标 建议分片 单表数据量 > 500万行(MySQL)/ > 1亿(PostgreSQL) 单库磁盘 > 500GB 单库 QPS > 5000(读)/ > 1000(写) 单表索引大小 > 内存容量
2.2 分片策略 策略 实现 优点 缺点 哈希分片 hash(key) % N数据均匀 扩容需迁移数据 范围分片 按 ID 或时间区间 范围查询友好 热点风险 列表分片 按枚举值映射 灵活可控 需人工维护 一致性哈希 哈希环 + 虚拟节点 扩容迁移少 实现复杂
// 按用户 ID 哈希分表
String tableName = "order_" + ( userId % 8 ); // order_0 ~ order_7
// 按月份分表
String tableName = "log_" + YearMonth . now (). format ( DateTimeFormatter . ofPattern ( "yyyyMM" ));
// log_202601, log_202602, ...
2.3 ShardingSphere 实战 # shardingsphere-config.yaml
dataSources :
ds_0 : !!com.zaxxer.hikari.HikariDataSource
jdbcUrl : jdbc:mysql://db0:3306/shop
ds_1 : !!com.zaxxer.hikari.HikariDataSource
jdbcUrl : jdbc:mysql://db1:3306/shop
shardingRule :
tables :
orders :
actualDataNodes : ds_${0..1}.orders_${0..7}
tableStrategy :
inline :
shardingColumn : user_id
algorithmExpression : orders_${user_id % 8}
databaseStrategy :
inline :
shardingColumn : user_id
algorithmExpression : ds_${user_id % 2}
keyGenerator :
type : SNOWFLAKE
column : order_id
3. 分布式主键 方案 原理 优点 缺点 Snowflake 41位时间+10位机器+12位序列 趋势递增、高性能 依赖时钟 Leaf (美团)号段模式 稳定、可控 需维护号段表 Redis 自增 INCR 简单 单点、网络开销 数据库步长 auto_increment_offset 简单 扩展性差
// Snowflake 实现(简化)
public class SnowflakeIdGenerator {
private final long workerId ;
private long sequence = 0 ;
private long lastTimestamp = - 1 ;
public synchronized long nextId () {
long timestamp = System . currentTimeMillis ();
if ( timestamp < lastTimestamp ) {
throw new RuntimeException ( "Clock moved backwards" );
}
if ( timestamp == lastTimestamp ) {
sequence = ( sequence + 1 ) & 0xFFF ; // 12 bit
if ( sequence == 0 ) timestamp = tilNextMillis ();
} else {
sequence = 0 ;
}
lastTimestamp = timestamp ;
return (( timestamp - EPOCH ) << 22 ) | ( workerId << 12 ) | sequence ;
}
}
4. 数据迁移 双写迁移方案(不停机):
阶段 1:双写
应用同时写入旧库和新库(新库异步)
读仍然走旧库
阶段 2:数据校验 + 补平
对比旧库和新库数据差异
修复不一致数据
阶段 3:灰度切读
1% 流量读新库 → 10% → 50% → 100%
监控错误率和延迟
阶段 4:切写
写操作切换到新库
停止旧库写入
阶段 5:下线旧库
保留一段时间后归档删除
延伸阅读 继续阅读
探索更多技术文章 浏览归档,发现更多关于系统设计、工具链和工程实践的内容。