1. 为什么需要分库分表?
在业务系统发展初期,单库单表往往能够满足需求。但随着数据量增长和访问压力上升,单一数据库会面临三大典型问题:
-
性能瓶颈:当单表数据超过千万级时,即使有索引,查询性能也会显著下降。我经历过一个订单表达到3亿条记录时,简单的主键查询都需要500ms以上。
-
可用性风险:所有数据集中在一个物理节点,一旦发生硬件故障可能导致全站不可用。去年某电商大促期间就因主库磁盘故障导致服务中断6小时。
-
运维难度:数百GB的数据库备份恢复耗时长达数小时,DDL操作锁表时间不可控。曾有个ALTER TABLE操作锁表47分钟,直接触发了生产事故。
1.1 分库分表的核心思想
分库分表通过水平拆分(Horizontal Partitioning)将数据分散到多个物理节点,其核心设计原则包括:
-
数据分布策略:
- 哈希取模(user_id % 4)
- 范围分片(order_id 1-1000万在分片1,1000-2000万在分片2)
- 时间分片(按年月分表)
-
路由规则:
java复制// 基于用户ID的哈希路由示例 String actualTable = "order_" + (userId.hashCode() % 16); -
全局ID生成:
- Snowflake算法(64位:1位符号+41位时间戳+10位机器ID+12位序列号)
- 数据库自增序列(需中心化服务)
重要提示:分片键的选择直接影响系统性能。应该选择查询频率高且分布均匀的字段,如用户ID。避免使用单调递增的字段作为分片键,这会导致"热点分片"问题。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. ShardingSphere核心架构解析
2.1 三层产品体系
ShardingSphere提供完整的分布式数据库解决方案:
-
ShardingSphere-JDBC(轻量级驱动):
- 直接嵌入应用进程
- 支持所有兼容JDBC的数据库
- 性能损耗<5%(实测TPS 12,000→11,400)
-
ShardingSphere-Proxy(独立中间件):
- 以数据库代理形式部署
- 支持MySQL/PostgreSQL协议
- 适合多语言架构(如同时有Java/Python服务)
-
ShardingSphere-Sidecar(云原生模式):
- 基于Service Mesh
- 对应用完全透明
- 目前仍处于alpha阶段
2.2 核心功能模块
mermaid复制graph TD
A[SQL解析] --> B[路由引擎]
B --> C[改写引擎]
C --> D[执行引擎]
D --> E[归并引擎]
(注:实际输出时应删除mermaid图表,此处仅为说明内部流程)
- 解析引擎:基于ANTLR4实现,支持MySQL/Oracle/PostgreSQL等方言
- 路由引擎:根据分片规则确定物理数据源
- 改写引擎:将逻辑SQL转为真实SQL(如
t_order→t_order_1) - 执行引擎:多线程并发执行(默认线程池大小=CPU核数*2)
- 归并引擎:内存归并(LIMIT合并)、流式归并(ORDER BY)、装饰归并(GROUP BY)
3. 生产级配置实战
3.1 数据源配置示例
yaml复制spring:
shardingsphere:
datasource:
names: ds0,ds1
ds0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://db0:3306/order_db
username: root
password: 123456
ds1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://db1:3306/order_db
username: root
password: 123456
3.2 分片规则配置
yaml复制rules:
sharding:
tables:
t_order:
actualDataNodes: ds$->{0..1}.t_order_$->{0..15}
databaseStrategy:
standard:
shardingColumn: user_id
preciseAlgorithmClassName: com.example.HashModDatabaseShardingAlgorithm
tableStrategy:
standard:
shardingColumn: order_id
preciseAlgorithmClassName: com.example.HashModTableShardingAlgorithm
3.3 自定义分片算法实现
java复制public class HashModDatabaseShardingAlgorithm implements PreciseShardingAlgorithm<Long> {
@Override
public String doSharding(Collection<String> availableTargetNames,
PreciseShardingValue<Long> shardingValue) {
int size = availableTargetNames.size();
for (String each : availableTargetNames) {
if (each.endsWith(shardingValue.getValue() % size + "")) {
return each;
}
}
throw new IllegalArgumentException();
}
}
4. 性能优化与问题排查
4.1 常见性能陷阱
-
全路由问题:
sql复制-- 错误示例(会导致全库扫描) SELECT * FROM t_order WHERE status = 1; -- 优化方案 SELECT * FROM t_order WHERE user_id = 123 AND status = 1; -
分布式事务延迟:
- 默认SEATA事务提交耗时约200ms
- 建议:非核心链路可关闭事务(spring.shardingsphere.props.xa-transaction-manager-type=atomikos)
-
连接池配置:
yaml复制# 建议值 = (最大并发查询数 / 分片数) * 1.2 ds0: maximumPoolSize: 20 minimumIdle: 5
4.2 监控指标
通过Prometheus暴露的关键指标:
code复制shardingsphere_statement_latency_millis{type="SELECT"} 95th=47
shardingsphere_connection_acquire_count total=12453
shardingsphere_routed_result_rows{logic_table="t_order"} sum=1.2e6
4.3 典型错误排查
问题现象:SQLParsingException: No viable alternative at input 'xxx'
解决方案:
- 检查SQL是否符合ShardingSphere支持的语法
- 确认是否使用了保留关键字(如
order需要反引号包裹) - 升级ShardingSphere版本(某些语法在新版本才支持)
问题现象:ShardingSphereRoutingException: Cannot find actual data source for logic table
解决方案:
- 检查
actualDataNodes配置格式(ds$->{0..1}.t_order_$->{0..15}) - 确认物理表是否真实存在
- 检查分片算法返回值是否在availableTargetNames范围内
5. 进阶实践技巧
5.1 动态分片策略
通过Groovy脚本实现按月分表:
yaml复制tableStrategy:
standard:
shardingColumn: create_time
preciseAlgorithmClassName: com.example.MonthShardingAlgorithm
java复制public class MonthShardingAlgorithm implements PreciseShardingAlgorithm<Date> {
private static final SimpleDateFormat format = new SimpleDateFormat("yyyyMM");
@Override
public String doSharding(Collection<String> availableTargetNames,
PreciseShardingValue<Date> shardingValue) {
String suffix = format.format(shardingValue.getValue());
return shardingValue.getLogicTableName() + "_" + suffix;
}
}
5.2 读写分离配置
yaml复制rules:
replica-query:
dataSources:
pr_ds:
primaryDataSourceName: ds0
replicaDataSourceNames: ds0_slave1, ds0_slave2
loadBalancerName: roundRobin
loadBalancers:
roundRobin:
type: ROUND_ROBIN
5.3 数据加密方案
yaml复制rules:
encrypt:
encryptors:
aes_encryptor:
type: AES
props:
aes-key-value: 123456abc
tables:
t_user:
columns:
phone:
plainColumn: phone_plain
cipherColumn: phone_cipher
encryptorName: aes_encryptor
6. 迁移与扩容方案
6.1 在线扩容步骤
-
准备新节点:
bash复制# 使用xtrabackup从现有节点克隆数据 innobackupex --copy-back /path/to/backup --datadir=/var/lib/mysql-new -
修改配置:
yaml复制# 从 ds0,ds1 扩展到 ds0,ds1,ds2,ds3 actualDataNodes: ds$->{0..3}.t_order_$->{0..15} -
数据重分布:
sql复制-- 使用ShardingSphere的Scaling功能 START MIGRATION JOB 'order_migration';
6.2 双写方案设计
java复制// 在数据迁移期间实现双写
@Transactional
public void createOrder(Order order) {
// 写入新分片
orderNewMapper.insert(order);
// 写入旧分片(兼容未迁移的数据)
if(!isMigrated(order.getShardKey())) {
orderOldMapper.insert(order);
}
}
7. 生产环境验证清单
在正式上线前建议完成以下验证:
-
SQL兼容性测试:
- 执行所有业务SQL(特别是JOIN/子查询等复杂语句)
- 验证分布式事务一致性(XA/Seata)
-
性能基准测试:
bash复制# 使用sysbench模拟压力 sysbench --db-driver=mysql oltp_read_write \ --mysql-host=proxy-host --mysql-port=3307 \ --threads=64 --time=300 run -
故障注入测试:
- 随机kill数据库节点
- 模拟网络分区
- 磁盘IO延迟注入
-
监控验证:
- 确认Prometheus指标采集正常
- 设置合理的告警阈值(如查询延迟>500ms)
经过多个项目的实战验证,这套方案能够支撑单日亿级订单量的处理。最关键的是提前做好分片键设计和容量规划,避免后期频繁扩容带来的运维复杂度。
