引言
“ShardingSphere你用过,那你讲讲它有哪些分片策略?各自在什么场景下使用?”
这是很多Java面试中必然会遇到的一道高频题。很多开发者能说出inline、standard这些名词,但当面试官追问“inline表达式里${user_id % 8}和ds${user_id % 2}.order_${order_id % 16}这两种配置有什么区别?”“复合分片策略什么时候用?”时,就开始支支吾吾了。
分片策略的配置是ShardingSphere落地的第一步,也是区分“用过”和“真正掌握”的分水岭。本文将从面试高频考点出发,结合电商订单系统的实战案例,手把手带你掌握ShardingSphere-JDBC五种分片策略的完整配置方式,并提供可以直接复制使用的配置模板。
一、分片策略的本质:分片键 + 分片算法
在深入配置之前,先建立正确的认知模型。
ShardingSphere的分片策略由两部分组成:分片键和分片算法。分片键决定依据哪个字段拆分,分片算法决定按什么规则计算目标分片。
核心公式:
分片策略 = 分片键 + 分片算法
ShardingSphere对外提供了五种分片策略:standard、complex、hint、inline、none。不同的分片策略搭配不同的分片算法,灵活应对复杂业务场景。
二、环境准备与依赖配置
2.1 Maven依赖
这是最容易踩坑的一步。很多开发者引入ShardingSphere-JDBC的Spring Boot Starter后,项目启动就会报ClassNotFoundException或NoSuchMethodError,往往是因为snakeyaml版本冲突。
推荐使用以下依赖配置(基于ShardingSphere-JDBC 5.3.2),特别注意exclusions部分:
<dependencies>
<!-- ShardingSphere JDBC核心依赖 -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.3.2</version>
<exclusions>
<exclusion>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 显式指定snakeyaml版本,避免冲突 -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>2.0</version>
</dependency>
<!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
</dependencies>
2.2 数据源配置
spring:
shardingsphere:
datasource:
names: ds0, ds1
ds0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/db_order_0
username: root
password: 123456
ds1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/db_order_1
username: root
password: 123456
三、五种分片策略详解与实战
3.1 Inline策略:最常用,轻量级表达式
适用场景:分片规则简单,单一分片键,仅支持=和IN操作符的场景。这是入门分库分表的首选策略。
核心配置:通过Groovy表达式直接定义分片逻辑,无需编写Java代码。
库分片配置示例(按用户ID取模):
spring:
shardingsphere:
rules:
sharding:
tables:
t_order:
actual-data-nodes: ds$->{0..1}.t_order
database-strategy:
inline:
sharding-column: user_id
algorithm-expression: ds${user_id % 2}
库+表复合分片示例(用户ID分库 + 订单ID分表):
spring:
shardingsphere:
rules:
sharding:
tables:
t_order:
actual-data-nodes: ds$->{0..1}.t_order_$->{0..15}
database-strategy:
inline:
sharding-column: user_id
algorithm-expression: ds${user_id % 2}
table-strategy:
inline:
sharding-column: order_id
algorithm-expression: t_order_${order_id % 16}
⚠️ 踩坑警告:
-
不支持范围查询:Inline策略仅支持
=和IN操作符。如果SQL中包含BETWEEN AND、>、<等范围条件,ShardingSphere会忽略分片策略,进行全库表路由,性能急剧下降。 -
分片键不能为NULL:NULL值无法参与数学运算(如取余),ShardingSphere无法决定数据放在哪个分片,导致分片失败。必须在应用层确保分片键非空。
-
数据倾斜风险:如果分片键分布不均(如新注册用户ID集中在某些区间),取模后可能导致部分分片数据量远超其他分片。
3.2 Standard策略:支持范围查询的标准分片
适用场景:单一分片键,但SQL中包含BETWEEN AND、>、<等范围操作符的场景。
Standard策略支持精确分片和范围分片两种模式。需要实现StandardShardingAlgorithm接口,包含两个方法:
-
精确分片:处理
=和IN操作符 -
范围分片:处理
BETWEEN AND、>、<等范围操作符
自定义算法实现:
public class OrderStandardShardingAlgorithm implements StandardShardingAlgorithm<Long> {
private Properties props = new Properties();
@Override
public String doSharding(Collection<String> availableTargetNames,
PreciseShardingValue<Long> shardingValue) {
// 精确分片:处理 = 和 IN
Long orderId = shardingValue.getValue();
String suffix = String.valueOf(orderId % 16);
return "t_order_" + suffix;
}
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames,
RangeShardingValue<Long> shardingValue) {
// 范围分片:处理 BETWEEN AND
Set<String> result = new LinkedHashSet<>();
Range<Long> valueRange = shardingValue.getValueRange();
Long lower = valueRange.lowerEndpoint();
Long upper = valueRange.upperEndpoint();
// 计算需要查询的分片范围
for (Long i = lower; i <= upper; i++) {
String suffix = String.valueOf(i % 16);
result.add("t_order_" + suffix);
}
return result;
}
@Override
public Properties getProps() {
return props;
}
@Override
public void init(Properties props) {
this.props = props;
}
}
YAML配置:
spring:
shardingsphere:
rules:
sharding:
tables:
t_order:
actual-data-nodes: ds$->{0..1}.t_order_$->{0..15}
table-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: order-standard
sharding-algorithms:
order-standard:
type: CLASS_BASED
props:
strategy: STANDARD
algorithmClassName: com.example.OrderStandardShardingAlgorithm
面试加分点:面试官问“Standard策略和Inline策略有什么区别”时,不仅要说出范围查询的支持差异,还要点出实现上的区别——Standard需要自定义算法类,更灵活但代码量更大;Inline配置简单但不支持范围查询。
3.3 Complex策略:多分片键复合分片
适用场景:需要根据多个字段共同决定分片路由的场景,如user_id和order_type复合路由。
自定义算法实现:
public class OrderComplexShardingAlgorithm implements ComplexKeysShardingAlgorithm<Long> {
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames,
ComplexKeysShardingValue<Long> shardingValue) {
// 获取多个分片键的值
Map<String, Collection<Long>> columnNameAndShardingValuesMap =
shardingValue.getColumnNameAndShardingValuesMap();
Collection<Long> userIds = columnNameAndShardingValuesMap.get("user_id");
Collection<Long> orderTypes = columnNameAndShardingValuesMap.get("order_type");
Set<String> result = new LinkedHashSet<>();
for (Long userId : userIds) {
for (Long orderType : orderTypes) {
// 复合路由逻辑:库 = user_id % 2,表 = order_type
String db = "ds" + (userId % 2);
String table = "t_order_" + orderType;
result.add(db + "." + table);
}
}
return result;
}
}
YAML配置:
spring:
shardingsphere:
rules:
sharding:
tables:
t_order:
actual-data-nodes: ds$->{0..1}.t_order_$->{0..3}
database-strategy:
complex:
sharding-columns: user_id, order_type
sharding-algorithm-name: order-complex
sharding-algorithms:
order-complex:
type: CLASS_BASED
props:
strategy: COMPLEX
algorithmClassName: com.example.OrderComplexShardingAlgorithm
⚠️ 注意:Complex策略的性能开销高于单一分片键策略。在多分片键场景下,需要评估是否真的需要复合分片,很多时候可以通过设计单分片键+二级索引来替代。
3.4 Hint策略:强制路由
适用场景:SQL中不包含分片键字段,但又需要精准路由到特定分片的场景。最典型的例子是表结构中没有分片键字段,但业务逻辑要求按某个维度路由。
Hint策略无需从SQL中解析分片键,分片键值由外部指定。
自定义算法实现:
public class OrderHintShardingAlgorithm implements HintShardingAlgorithm<Long> {
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames,
HintShardingValue<Long> shardingValue) {
Collection<Long> values = shardingValue.getValues();
Set<String> result = new LinkedHashSet<>();
for (Long value : values) {
String db = "ds" + (value % 2);
String table = "t_order_" + (value % 16);
result.add(db + "." + table);
}
return result;
}
}
业务代码中指定分片信息:
@Service
public class OrderService {
public void queryOrderWithHint(Long userId) {
// 通过HintManager指定分片路由
try (HintManager hintManager = HintManager.getInstance()) {
// 指定数据库分片
hintManager.addDatabaseShardingValue("t_order", userId);
// 指定表分片
hintManager.addTableShardingValue("t_order", userId);
// 执行SQL时,会强制路由到Hint指定的分片
orderMapper.selectOrders();
}
}
}
YAML配置:
spring:
shardingsphere:
rules:
sharding:
tables:
t_order:
actual-data-nodes: ds$->{0..1}.t_order_$->{0..15}
database-strategy:
hint:
sharding-algorithm-name: order-hint
sharding-algorithms:
order-hint:
type: CLASS_BASED
props:
strategy: HINT
algorithmClassName: com.example.OrderHintShardingAlgorithm
⚠️ 关键提醒:使用完HintManager后,必须调用close()方法或使用try-with-resources释放资源,否则会影响后续SQL的路由。
3.5 None策略:不分片
适用场景:某些表不需要分片,但与其他分片表存在关联查询的场景。最常见的是将小表设置为广播表,每个分片库中都有完整副本,避免跨库JOIN。
spring:
shardingsphere:
rules:
sharding:
tables:
t_dict:
actual-data-nodes: ds$->{0..1}.t_dict
database-strategy:
none: # 不分片策略
四、绑定表与广播表:关联查询的优化利器
4.1 绑定表:解决大表与大表的关联问题
当订单表和订单明细表都按user_id分片,且分片规则完全一致时,可以通过绑定表让ShardingSphere将关联查询路由到同一分片内执行,避免笛卡尔积查询。
配置方式:
spring:
shardingsphere:
rules:
sharding:
binding-tables:
- t_order, t_order_item
面试加分点:面试官问“绑定表如何提升查询性能”时,回答核心在于避免跨分片笛卡尔积。没有绑定时,两张分片表关联查询会产生M×N次查询;绑定了同一分片键后,只会产生max(M,N)次查询,性能提升量级巨大。
4.2 广播表:解决小表与大表的关联问题
像字典表、配置表这类数据量小但需要与所有分片表频繁关联查询的表,应该设置为广播表。每个分片库中都有该表的完整副本,关联查询时直接在本地完成,高效又简单。
配置方式:
spring:
shardingsphere:
rules:
sharding:
broadcast-tables:
- t_dict
⚠️ 踩坑警告:广播表适合数据量小、更新频率低的场景。如果把一张每天更新上万次的表设为广播表,每次DML操作都会在所有分片上执行,写放大效应会带来巨大的性能开销。
五、分布式ID生成策略
分库分表后,数据库自增ID无法保证全局唯一。ShardingSphere内置了多种分布式主键生成算法。
5.1 雪花算法配置
雪花算法生成的ID是64位长整型,由时间戳、工作机器ID和序列号组成,趋势递增,对数据库索引友好。
spring:
shardingsphere:
rules:
sharding:
tables:
t_order:
key-generate-strategy:
column: order_id
key-generator-name: snowflake
key-generators:
snowflake:
type: SNOWFLAKE
props:
worker-id: 1 # 工作机器ID,不同节点配置不同
5.2 自定义ID生成器
如果内置策略不满足需求(如需要在ID中注入分片键信息实现基因法),可以通过实现KeyGenerateAlgorithm接口自定义:
public class CustomKeyGenerateAlgorithm implements KeyGenerateAlgorithm {
private Properties props = new Properties();
@Override
public Comparable<?> generateKey() {
// 自定义ID生成逻辑,例如:时间戳 + 机器ID + 序列号 + 用户ID后4位(基因)
return null;
}
@Override
public Properties getProps() {
return props;
}
@Override
public void init(Properties props) {
this.props = props;
}
}
六、五种策略速查对比表
| 策略 | 分片键数量 | 支持范围查询 | 是否需要编码 | 典型场景 |
|---|---|---|---|---|
| Inline | 单个 | ❌ 不支持 | 否 | 简单取模分片,入门首选 |
| Standard | 单个 | ✅ 支持 | 是 | 需要范围查询的单一分片键场景 |
| Complex | 多个 | ✅ 支持 | 是 | 多字段复合路由,如用户+类型 |
| Hint | 无限制 | ✅ 支持 | 是 | SQL中无分片键,需强制指定分片 |
| None | 无 | - | 否 | 不分片的小表(广播表) |
七、面试常见追问与加分表述
Q1:Inline策略不支持范围查询,那项目中遇到范围查询需求怎么办?
加分表述:这是个很好的问题。Inline策略确实只支持等值查询,范围查询时会退化为全库表路由,性能极差。我在项目中有三种应对策略:
第一,将范围查询拆分到代码层完成,先确定分片键值的范围,再对每个值分别查询,最后合并结果。比如查询order_id BETWEEN 1000 AND 2000,可以先计算出1000~2000范围内所有order_id,再对每个id执行单点查询。
第二,对于时间范围的场景,可以配合时间维度的分表策略,通过Standard策略实现精准的范围路由。
第三,也是最推荐的方案,将范围查询类业务卸载到ES宽表,主数据库只承担等值查询,这从根本上避免了范围查询的性能问题。
Q2:Composite策略和Standard策略都可以实现多分片键,有什么区别?
加分表述:这两种策略的设计理念完全不同。Standard策略的核心是“单分片键+多种算法”,它只处理一个分片键,但可以同时支持精确分片和范围分片两种算法。而Complex策略的核心是“多分片键+单一算法”,它处理多个分片键,但只运行一个复合算法。
实用建议:能用Standard解决的场景尽量不要用Complex。Complex策略需要遍历多个分片键的组合,性能开销更大。很多场景下,通过设计单分片键配合基因法(在ID中注入另一个维度的信息),就可以避免使用Complex策略。
Q3:分片键为NULL时,如何保证分片策略不崩溃?
加分表述:ShardingSphere的Inline策略对NULL值的处理确实比较脆弱,NULL值无法参与数学运算,会直接导致分片失败。我的处理思路分为三个层面:
应用层:在插入数据前校验分片键,若为空则赋予默认值或直接拒绝请求。
数据库层:设置分片键字段为NOT NULL,从数据库层面杜绝NULL值。
兜底方案:如果业务确实存在分片键为空但必须落地的场景,可以使用Hint策略绕过分片键解析,在代码中手动指定路由目标。
Q4:你们是如何选择分片策略的?
加分表述:选择分片策略有一个清晰的决策链:
第一步,看是否需要分片。如果是字典表、配置表等小表,直接使用None策略配合广播表。
第二步,看分片键数量。单分片键优先考虑Inline,配置简单、性能好。如果确实需要范围查询,才考虑Standard。
第三步,看是否有多分片键需求。原则上尽量避免,但如果业务必须,使用Complex。同时,在分片键中通过基因法嵌入辅助查询维度,减少对复合分片的依赖。
第四步,考虑特殊场景。如果SQL中不包含分片键,使用Hint策略强制路由。
八、总结
配置ShardingSphere的数据分片策略,本质上是在回答三个问题:用什么字段分片?用什么规则计算?特殊场景如何兜底?
-
Inline:简单粗暴,入门首选,但不支持范围查询
-
Standard:支持范围查询,需要编码,单分片键场景的终极方案
-
Complex:多分片键复合路由,性能开销大,谨慎使用
-
Hint:需要手动管理生命周期
-
None:小表的归宿,配合广播表使用
掌握了这五种策略的适用场景和配置方式,你不仅能在面试中从容应对追问,更能在实际项目中做出正确的技术选型。分片策略配置是ShardingSphere落地的第一块基石,一块一块铺好,才能走得更远。
3015



被折叠的 条评论
为什么被折叠?



