ShardingJDBC分库分表实战:原理、配置与最佳实践
一、分库分表背景与挑战
随着业务规模的指数级增长,单数据库实例在存储容量、并发处理能力和运维复杂度等方面逐渐显现瓶颈。传统单体数据库架构面临三大核心挑战:
- 性能瓶颈:单表数据量突破千万级时,查询性能呈断崖式下降
- 可用性风险:单一故障点导致全站服务不可用
- 运维复杂度:数据迁移、索引维护等操作窗口期越来越短
ShardingJDBC作为Apache ShardingSphere的核心组件,通过客户端直连的轻量级方案,提供透明化的数据库水平扩展能力。
二、ShardingJDBC核心架构
2.1 架构分层
graph TD
A[应用层] --> B[ShardingJDBC]
B --> C[逻辑表]
C --> D[物理数据源1]
C --> E[物理数据源2]
D --> F[实际表_0]
D --> G[实际表_1]
E --> H[实际表_0]
E --> I[实际表_1]
2.2 核心概念
- 逻辑表:应用程序视角的统一表名(如t_order)
- 真实表:物理数据库中的实际表(如ds0.t_order_0)
- 数据节点:由数据源与真实表组成的定位单元(ds0.t_order_0)
- 分片键:决定数据路由的关键字段(如order_id)
- 分片算法:包含精确分片、范围分片等策略
三、Spring Boot集成实战
3.1 Maven依赖配置
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.3.2</version>
</dependency>
3.2 分库分表策略配置
# https://shardingsphere.apache.org/index_zh.html
mode:
# 运行模式类型。可选配置:内存模式 Memory、单机模式 Standalone、集群模式 Cluster - 目前为单机模式
type: Standalone
dataSources:
ds_0:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://192.168.10.130:13306/sharding_db_00?useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&serverTimezone=UTC&useSSL=true
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 15
minPoolSize: 5
ds_1:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://192.168.10.130:13306/sharding_db_01?useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&serverTimezone=UTC&useSSL=true
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 15
minPoolSize: 5
rules:
- !SHARDING
# 声明这是一个分片(分库分表)规则配置
# 库的路由
defaultDatabaseStrategy:
# 定义默认的数据库分片策略
standard:
# 使用标准分片策略,该策略适用于单分片键的场景
shardingColumn: user_id
# 指定分片键为 user_id,即根据 user_id 的值来决定数据存储在哪个数据库中
shardingAlgorithmName: database_inline
# 指定使用的分片算法名称为 database_inline
# 表的路由
tables:
# 配置具体表的分片规则
user_order:
# 配置 user_order 表的分片规则
actualDataNodes: ds_->{0..1}.user_order_->{0..3}
# 定义实际的数据节点,这里表示数据会存储在 ds_0 和 ds_1 两个数据库中,
# 每个数据库中又有 user_order_0 到 user_order_3 共 4 个表
tableStrategy:
# 定义该表的分片策略
standard:
# 使用标准分片策略,同样适用于单分片键的场景
shardingColumn: user_id
# 指定分片键为 user_id,即根据 user_id 的值来决定数据存储在哪个表中
shardingAlgorithmName: user_order_inline
# 指定使用的分片算法名称为 user_order_inline
# 路由算法
shardingAlgorithms:
# 定义具体的分片算法
# 库-路由算法 2是两个库,库的数量。库的数量用哈希模2来计算。
database_inline:
# 定义名为 database_inline 的分片算法
type: INLINE
# 指定算法类型为 INLINE,INLINE 类型的算法可以使用表达式来计算分片结果
props:
algorithm-expression: ds_->{Math.abs(user_id.hashCode()) % 2}
# 定义算法表达式,通过计算 user_id 的哈希值的绝对值并对 2 取模,
# 得到的结果决定数据存储在 ds_0 还是 ds_1 数据库中
# 表-路由算法 4是一个库里,表的数量。4 - 1 为了获得 011 这样的二进制值。不推荐 user_order_->{Math.abs(user_id.hashCode()) % 2} 作为表的路由
user_order_inline:
# 定义名为 user_order_inline 的分片算法
type: INLINE
# 指定算法类型为 INLINE
props:
algorithm-expression: user_order_$->{(user_id.hashCode() ^ (user_id.hashCode()) >>> 16) & (4 - 1)}
# 定义算法表达式,通过对 user_id 的哈希值进行一系列位运算(异或和右移),
# 并与 3(4 - 1)进行按位与运算,得到的结果决定数据存储在 user_order_0 到 user_order_3 中的哪个表中
props:
# 是否在日志中打印 SQL。
# 打印 SQL 可以帮助开发者快速定位系统问题。日志内容包含:逻辑 SQL,真实 SQL 和 SQL 解析结果。
# 如果开启配置,日志将使用 Topic ShardingSphere-SQL,日志级别是 INFO。 false
sql-show: true
# 是否在日志中打印简单风格的 SQL。false
sql-simple: true
# 用于设置任务处理线程池的大小。每个 ShardingSphereDataSource 使用一个独立的线程池,同一个 JVM 的不同数据源不共享线程池。
executor-size: 20
# 查询请求在每个数据库实例中所能使用的最大连接数。1
max-connections-size-per-query: 1
# 在程序启动和更新时,是否检查分片元数据的结构一致性。
check-table-metadata-enabled: false
# 在程序启动和更新时,是否检查重复表。false
check-duplicate-table-enabled: false
四、最佳实践与注意事项
4.1 分片键选择原则
- 选择高基数列(如用户ID、订单号)
- 避免选择频繁更新的字段
- 优先选择业务查询中的必现条件
4.2 常见问题解决方案
分布式ID生成:
// 雪花算法配置
spring.shardingsphere.rules.sharding.key-generators:
snowflake:
type: SNOWFLAKE
props:
worker-id: 123
跨库关联查询:
- 全局表:小规模维度表全库冗余
- 绑定表:确保关联表分片策略一致
spring.shardingsphere.rules.sharding.binding-tables: - t_order,t_order_item
4.3 性能优化建议
- 合理设置分片数量(建议单表不超过5000万行)
- 避免全路由查询(如不带分片键的查询)
- 使用Hint强制路由处理特殊场景
try (HintManager hintManager = HintManager.getInstance()) { hintManager.addDatabaseShardingValue("t_order", 1); hintManager.addTableShardingValue("t_order", 2); // 执行查询 }
五、监控与扩展
-
集成Prometheus监控指标
spring.shardingsphere.metrics: enabled: true name: prometheus host: 127.0.0.1 port: 9090
-
使用ShardingSphere-UI进行可视化管控
-
弹性扩展方案:
- 双写迁移:新旧分片策略并行写入
- 历史数据归档:冷热数据分离存储
六、总结
ShardingJDBC通过以下核心优势成为分库分表首选方案:
- 无侵入性:无需修改业务代码即可实现分片功能
- 灵活扩展:支持动态数据源、在线规则修改
- 生态完善:提供分布式事务、数据加密等企业级功能
当面临以下场景时建议采用分库分表方案:
- 单表数据量预计超过5000万行
- 数据库QPS超过5000
- 需要多地多活部署架构
附录:分片算法性能对比表
算法类型 | 适用场景 | 扩容复杂度 | 查询性能 |
---|---|---|---|
取模分片 | 数据均匀分布 | 高 | 优 |
范围分片 | 时间序列数据 | 低 | 良 |
哈希分片 | 随机分布需求 | 高 | 优 |
自定义复合分片 | 复杂业务规则 | 中 | 中 |