ShardingSphere
Java 高级 · 存储与搜索
当单库单表遇到容量与性能天花板,ShardingSphere 以"生态接入层"方式提供分库分表、读写分离与数据治理,对业务代码近乎无侵入。本文聚焦其分片内核五步(解析→路由→改写→执行→归并)、读写分离、分布式事务与数据脱敏,讲清"一条逻辑 SQL 如何变成多段真实 SQL 并被正确合并"。
一、产品形态:JDBC vs Proxy
- ShardingSphere-JDBC:增强版 JDBC 驱动,直连数据库、无中心化,以 jar 嵌入应用。性能最优(无额外网络跳转),但治理要靠应用侧。
- ShardingSphere-Proxy:独立代理进程,兼容 MySQL/PG 协议,对运维友好(统一入口、集中治理),有少量网络开销。
二者内核完全相同,区别只在部署形态。本文以 JDBC 形态讲内核。
二、分片核心概念
- 逻辑表 / 真实表:业务写
t_order,实际落到t_order_0...t_order_n。 - 分片键(sharding key):决定数据路由的字段,必须高频出现在查询条件中。没带分片键的查询会广播。
- 分片算法:取模(inline
ds$->{user_id%2})、范围、哈希、自定义。常用"分库按user_id取模、分表按order_id取模"组合。 - 分片策略:标准(单键)、复合(多键)、Hint(强制路由,绕过 SQL 解析,用于按外部条件路由)。
三、内核五步:一条 SQL 的旅程
这是 ShardingSphere 最值得读透的部分。逻辑 SQL 经五步变真实 SQL:
3.1 解析(Parsing)
用 ANTLR / 自研 SQL 解析器把 SQL 解析成抽象语法树(AST),提取表名、条件、投影列、分页、排序、聚合。这一步决定"能否识别分片键"。复杂 SQL(嵌套、子查询跨分片)解析可能失败或只能走广播。
3.2 路由(Routing)
按分片键计算目标真实表。路由类型:
- 直接路由:带分片键,命中单/少量真实表(最优)。
- 广播路由(全路由):无分片键,
t_order→ 所有t_order_0..n都查一遍(性能灾难,必须避免)。 - 单播路由:如按主键
IN命中多个精确表。 - 绑定表(Binding Table):存在主外键关系的表(如
t_order/t_order_item同按order_id分片),路由时保持分片规则一致,避免笛卡尔积。
3.3 改写(Rewrite)
把逻辑表名替换为真实表名,并可能补列(如补 order_id 用于后续归并排序)。例如 SELECT * FROM t_order 改写为对 t_order_0、t_order_1... 的多条 SQL。
3.4 执行(Execute)
通过连接池并行下发到多个真实数据源。ShardingSphere 用多线程执行引擎(按 SQL 数创建线程,流式/内存模式不同),并行拉取各分片结果。
3.5 归并(Merge)——最易踩坑的一步
各分片返回的结果要在内存/流式中归并:
- 排序归并:各分片已按
ORDER BY排好,归并时用多路归并(堆) 合并成全局有序。 - 分组/聚合归并:
SUM/COUNT各分片先算局部聚合,归并时再累加;AVG必须SUM/COUNT两列分别归并再相除(不能先算各分片 avg 再平均!)。 - 分页归并(最坑):
LIMIT 10,20需从每个分片各取offset+limit条,在内存中跳offset取limit——深翻页(offset=100000)每个分片都要取 100020 条,极慢。解决:用"上次最大 id"游标翻页替代offset。
spring:
shardingsphere:
datasource:
names: ds0,ds1
ds0: { type: com.zaxxer.hikari.HikariDataSource, jdbc-url: jdbc:mysql://db0/order, username: ***, password: *** }
ds1: { type: com.zaxxer.hikari.HikariDataSource, jdbc-url: jdbc:mysql://db1/order, username: ***, password: *** }
rules:
sharding:
tables:
t_order:
actual-data-nodes: ds$->{0..1}.t_order_$->{0..3}
database-strategy:
standard: { sharding-column: user_id, sharding-algorithm-name: db-mod }
table-strategy:
standard: { sharding-column: order_id, sharding-algorithm-name: tbl-mod }
sharding-algorithms:
db-mod: { type: INLINE, props: { algorithm-expression: ds$->{user_id % 2} } }
tbl-mod: { type: INLINE, props: { algorithm-expression: t_order_$->{order_id % 4} } }
props: { sql-show: true }WARNING
- 分片键未进查询条件:SQL 无分片键触发广播路由(全库全表),性能灾难。设计必须保证高频查询带分片键。
- 跨分片分页/排序代价高:归并需拉取各分片数据再内存排序,深翻页极慢。改用"上次最大 id"游标翻页。
- 分布式主键用自增 ID:各库自增会冲突,须用**雪花算法(SNOWFLAKE)**等全局唯一 ID。
- 分片键随意变更:分片键修改等于数据迁移,极难处理。建模时慎重选稳定字段。
四、读写分离
主库写、从库读,基于 readwrite-splitting 规则路由。ShardingSphere 提供负载均衡(轮询/随机)与从库延迟感知(从库延迟超阈值则避开,防读脏)。一致性要求高的"刚写立读"场景,用 Hint 强制走主库(HintManager.setWriteRouteOnly())。
rules:
readwrite-splitting:
data-sources:
order-ds:
type: Static
props:
write-data-source-name: ds0
read-data-source-names: ds1,ds2
load-balancer-name: round-robin五、分布式事务
ShardingSphere 提供两种模式适配:
- XA(强一致):基于
Atomikos/Narayana的 XA 协议两阶段提交,跨库强一致,但资源锁定久、性能损耗大,慎用于高并发。 - Seata AT(最终一致):把分库分表的事务交给 Seata 管理,低侵入、最终一致,适合大多数业务。
六、数据脱敏与影子库
- 数据脱敏(Encrypt):配置
encrypt-rules,对phone/id_card等字段自动加密存储、查询时透明解密,应用无感知(存储密文、查询仍用明文条件,ShardingSphere 改写时加密)。 - 影子库(Shadow):压测流量打标,自动路由到影子库,隔离真实数据。配合全链路压测。
📚 官方文档与参考资料
- ShardingSphere 官方文档:https://shardingsphere.apache.org/document/current/cn/overview/
- ShardingSphere GitHub:https://github.com/apache/shardingsphere
- ShardingSphere 分片内核:https://shardingsphere.apache.org/document/current/cn/features/sharding/
✏️ 练习与自测
- 给定
t_order按user_id分 2 库、order_id分 4 表,推导user_id=7, order_id=11路由到哪个真实表(ds? . t_order_?)。 - 说明为什么"无分片键的查询"会成为性能瓶颈(广播路由),以及如何用 Hint 路由缓解特定场景。
- 为什么
LIMIT 100000,20在分片下极慢?给出游标翻页(WHERE id > ? ORDER BY id LIMIT 20)的改写方案。 - 比较 ShardingSphere-JDBC 与 Proxy 在部署形态、性能、治理上的差异,何时选 Proxy?
本节小结
ShardingSphere 用"解析→路由→改写→执行→归并"五步透明化分片,业务几乎零改造成本。难点在归并(排序多路归并、AVG 必须 SUM/COUNT 分别归并、深翻页需游标而非 offset)与路由(无分片键即广播)。落地铁律:分片键选稳且高频、全局 ID 用雪花、分页靠游标、读写分离用 Hint 保刚写立读。JDBC 形态性能优,Proxy 形态易运维,按需取舍;分布式事务与脱敏是锦上添花的能力。