一、ShardingSphere核心架构与定位
1.1 什么是ShardingSphere?
Apache ShardingSphere是一个开源的分布式数据库中间件生态系统,定位为"Database Plus"——在数据库上层构建标准和生态,补充数据库缺失的分布式能力。它不是一个单一产品,而是一个包含多个组件的解决方案生态圈。
1.2 三大核心产品矩阵
| 产品 | 定位 | 适用场景 | 特点 |
|---|---|---|---|
| ShardingSphere-JDBC | 轻量级Java框架,客户端直连方案 | Spring Boot项目、纯Java技术栈 | 以Jar包形式嵌入应用,性能损耗低(<10%),无独立部署成本 |
| ShardingSphere-Proxy | 独立数据库代理服务 | 多语言混合技术栈、需要集中管理 | 对应用透明,支持MySQL/PostgreSQL协议,兼容各种客户端 |
| ShardingSphere-Sidecar | 云原生Sidecar模式 | Kubernetes环境、服务网格架构 | 与业务应用一同部署,适合云原生场景 |
选型建议:对于Spring Boot项目,ShardingSphere-JDBC是首选方案,因为它无需额外部署、零侵入改造、配置即生效。
1.3 核心功能特性
- 数据分片:支持水平分片(按行拆分)和垂直分片(按表拆分)
- 读写分离:自动将写操作路由到主库,读操作在主从库间负载均衡
- 分布式事务:支持XA强一致性事务和BASE柔性事务(Seata集成)
- 数据加密:透明化数据加密与脱敏
- 弹性伸缩:支持在线添加/删除分片节点
- 多数据库兼容:支持MySQL、PostgreSQL、Oracle、SQL Server等
二、ShardingSphere核心工作原理
2.1 分片处理流程
ShardingSphere的分片处理遵循标准化的6步流程:
- SQL解析:词法解析 + 语法解析,提取解析上下文
- 执行器优化:合并和优化分片条件
- SQL路由:根据分片策略匹配路由路径
- SQL改写:将逻辑SQL改写为真实数据库可执行的SQL
- SQL执行:通过多线程执行器异步执行
- 结果归并:将多个执行结果集归并输出
2.2 分片策略详解
ShardingSphere提供5种分片策略,适应不同业务场景:
| 策略类型 | 分片键数量 | 适用场景 | 复杂度 |
|---|---|---|---|
| 标准分片策略 | 单分片键 | 精确查询(=、IN)和范围查询 | 低 |
| 行表达式分片 | 单分片键 | 简单配置化分片,无需Java代码 | 极低 |
| 复合分片策略 | 多分片键 | 多维度联合分片,复杂业务场景 | 高 |
| Hint分片策略 | 无分片键 | 强制路由或分片键不在SQL中 | 中 |
| 不分片策略 | 无 | 小表、全局表配置 | 无 |
2.3 分片算法类型
- 精确分片算法:处理
=和IN操作符 - 范围分片算法:处理
BETWEEN AND、>、<等范围操作符 - 复合分片算法:处理多分片键的复杂逻辑
- Hint分片算法:通过编程方式指定分片值
三、SpringBoot + ShardingSphere分库分表实战
3.1 环境准备与依赖配置
Maven依赖配置:
<!-- Spring Boot 3.x + ShardingSphere 5.4+ -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.5.2</version> <!-- 使用最新稳定版 -->
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
数据库准备:
-- 创建两个数据库
CREATE DATABASE db0;
CREATE DATABASE db1;
-- 在每个库中创建分表
-- db0库
CREATE TABLE db0.t_order_0 (
order_id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
amount DECIMAL(10,2),
create_time DATETIME
);
CREATE TABLE db0.t_order_1 (
order_id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
amount DECIMAL(10,2),
create_time DATETIME
);
-- db1库
CREATE TABLE db1.t_order_0 (
order_id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
amount DECIMAL(10,2),
create_time DATETIME
);
CREATE TABLE db1.t_order_1 (
order_id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
amount DECIMAL(10,2),
create_time DATETIME
);
3.2 核心配置详解
application.yml配置:
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/db0?useSSL=false&serverTimezone=UTC
username: root
password: 123456
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
maximum-pool-size: 20
ds1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/db1?useSSL=false&serverTimezone=UTC
username: root
password: 123456
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
maximum-pool-size: 20
# 分片规则配置
rules:
sharding:
# 订单表分片配置
tables:
t_order:
# 实际数据节点:2个库 × 2张表 = 4个分片
actual-data-nodes: ds${0..1}.t_order_${0..1}
# 分库策略:按user_id取模分库
database-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: database-inline
# 分表策略:按order_id取模分表
table-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: table-inline
# 分布式ID生成策略(雪花算法)
key-generate-strategy:
column: order_id
key-generator-name: snowflake
# 分片算法定义
sharding-algorithms:
database-inline:
type: INLINE
props:
algorithm-expression: ds${user_id % 2}
table-inline:
type: INLINE
props:
algorithm-expression: t_order_${order_id % 2}
# 分布式ID生成器
key-generators:
snowflake:
type: SNOWFLAKE
props:
worker-id: 123
# 属性配置
props:
sql-show: true # 显示实际执行的SQL,调试用
sql-simple: true # 简化SQL显示
check-table-metadata-enabled: false # 不检查表元数据
3.3 业务代码实现
实体类:
@Data
@TableName("t_order") // MyBatis-Plus注解
public class Order {
@TableId(type = IdType.ASSIGN_ID) // 使用分布式ID
private Long orderId;
private Long userId;
private BigDecimal amount;
private Date createTime;
}
Mapper接口:
@Mapper
public interface OrderMapper extends BaseMapper<Order> {
// 基础CRUD操作由MyBatis-Plus提供
/**
* 根据用户ID查询订单(带分片键)
*/
@Select("SELECT * FROM t_order WHERE user_id = #{userId}")
List<Order> selectByUserId(@Param("userId") Long userId);
/**
* 根据订单ID查询(带分片键)
*/
@Select("SELECT * FROM t_order WHERE order_id = #{orderId}")
Order selectByOrderId(@Param("orderId") Long orderId);
/**
* 范围查询示例(需要配置范围分片算法)
*/
@Select("SELECT * FROM t_order WHERE user_id = #{userId} AND create_time BETWEEN #{startTime} AND #{endTime}")
List<Order> selectByUserIdAndTimeRange(@Param("userId") Long userId,
@Param("startTime") Date startTime,
@Param("endTime") Date endTime);
}
Service层:
@Service
@Slf4j
public class OrderService {
@Autowired
private OrderMapper orderMapper;
/**
* 创建订单(自动路由到对应分片)
*/
@Transactional
public Long createOrder(Long userId, BigDecimal amount) {
Order order = new Order();
order.setUserId(userId);
order.setAmount(amount);
order.setCreateTime(new Date());
// 插入时,ShardingSphere会根据userId和orderId自动路由
orderMapper.insert(order);
log.info("订单创建成功,订单ID:{},用户ID:{}", order.getOrderId(), userId);
return order.getOrderId();
}
/**
* 查询用户订单(带分片键查询,高效)
*/
public List<Order> getUserOrders(Long userId) {
return orderMapper.selectByUserId(userId);
}
/**
* 根据订单ID查询(带分片键查询,高效)
*/
public Order getOrderById(Long orderId) {
return orderMapper.selectByOrderId(orderId);
}
/**
* 批量插入测试数据
*/
public void batchInsertTestData(int count) {
for (int i = 1; i <= count; i++) {
Order order = new Order();
order.setUserId((long) i); // 用户ID从1开始
order.setAmount(new BigDecimal("100.00"));
order.setCreateTime(new Date());
orderMapper.insert(order);
if (i % 100 == 0) {
log.info("已插入 {} 条测试数据", i);
}
}
}
}
3.4 测试验证
单元测试类:
@SpringBootTest
@Slf4j
class OrderServiceTest {
@Autowired
private OrderService orderService;
@Test
void testShardingRouting() {
// 测试数据分布
List<Long> userIds = Arrays.asList(1L, 2L, 3L, 4L, 5L, 6L);
for (Long userId : userIds) {
Long orderId = orderService.createOrder(userId, new BigDecimal("199.99"));
log.info("用户ID: {} -> 订单ID: {} -> 预期路由: ds{} -> t_order_{}",
userId, orderId, userId % 2, orderId % 2);
}
// 验证查询路由
Long testUserId = 3L;
List<Order> orders = orderService.getUserOrders(testUserId);
log.info("用户 {} 的订单数量: {}", testUserId, orders.size());
// 验证跨分片查询(不推荐,仅演示)
// 注意:不带分片键的查询会路由到所有分片
List<Order> allOrders = orderMapper.selectList(null);
log.info("全表查询结果数量: {}", allOrders.size());
}
@Test
void testPerformance() {
int testCount = 1000;
long startTime = System.currentTimeMillis();
orderService.batchInsertTestData(testCount);
long endTime = System.currentTimeMillis();
log.info("插入 {} 条数据耗时: {} ms", testCount, endTime - startTime);
}
}
四、高级配置与优化
4.1 自定义分片算法
精确分片算法实现:
public class UserPreciseShardingAlgorithm implements PreciseShardingAlgorithm<Long> {
@Override
public String doSharding(Collection<String> availableTargetNames,
PreciseShardingValue<Long> shardingValue) {
// availableTargetNames: 可用的数据源或表名集合
// shardingValue: 分片键的值
Long userId = shardingValue.getValue();
String logicTableName = shardingValue.getLogicTableName();
// 自定义分片逻辑:按用户ID的哈希值分片
int hash = Math.abs(userId.hashCode());
int index = hash % availableTargetNames.size();
// 返回目标表名
return logicTableName + "_" + index;
}
}
范围分片算法实现:
public class TimeRangeShardingAlgorithm implements RangeShardingAlgorithm<Date> {
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames,
RangeShardingValue<Date> shardingValue) {
// 处理时间范围查询
Range<Date> range = shardingValue.getValueRange();
Date lower = range.lowerEndpoint();
Date upper = range.upperEndpoint();
// 根据时间范围确定需要查询哪些表
Set<String> result = new LinkedHashSet<>();
// 示例:按月分表,计算需要查询的月份表
Calendar calendar = Calendar.getInstance();
calendar.setTime(lower);
while (!calendar.getTime().after(upper)) {
int month = calendar.get(Calendar.MONTH) + 1;
int year = calendar.get(Calendar.YEAR);
String tableName = String.format("t_order_%d%02d", year, month);
if (availableTargetNames.contains(tableName)) {
result.add(tableName);
}
calendar.add(Calendar.MONTH, 1);
}
return result;
}
}
4.2 绑定表配置(优化关联查询)
spring:
shardingsphere:
rules:
sharding:
# 绑定表配置:订单表和订单明细表使用相同的分片规则
binding-tables:
- t_order, t_order_item
tables:
t_order:
actual-data-nodes: ds${0..1}.t_order_${0..1}
database-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: database-inline
table-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: table-inline
t_order_item:
actual-data-nodes: ds${0..1}.t_order_item_${0..1}
database-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: database-inline
table-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: table-inline
4.3 读写分离配置
spring:
shardingsphere:
rules:
# 读写分离配置
readwrite-splitting:
data-sources:
readwrite_ds:
type: Static
props:
write-data-source-name: master_ds
read-data-source-names: slave_ds_0, slave_ds_1
load-balancer-name: round_robin
load-balancers:
round_robin:
type: ROUND_ROBIN
五、生产环境最佳实践
5.1 分片键选择原则
- 高基数原则:选择值域范围大的字段(如user_id、order_id),避免使用status、gender等低基数字段
- 业务关联原则:选择查询频繁的字段,避免跨分片查询
- 稳定性原则:选择变化频率低的字段,避免频繁数据迁移
- 均匀分布原则:确保数据能均匀分布到各个分片
5.2 分布式ID生成策略
@Component
public class DistributedIdGenerator {
// 使用ShardingSphere内置的雪花算法
@Autowired
private KeyGenerateAlgorithm keyGenerateAlgorithm;
// 或自定义ID生成器
private final Snowflake snowflake = IdUtil.getSnowflake(1, 1);
public Long generateOrderId() {
return snowflake.nextId();
}
public Long generateUserId() {
// 可根据业务需求定制ID生成规则
return System.currentTimeMillis() * 1000 + ThreadLocalRandom.current().nextInt(1000);
}
}
5.3 监控与运维
启用SQL日志监控:
spring:
shardingsphere:
props:
sql-show: true
sql-simple: false # 显示详细SQL
check-table-metadata-enabled: true # 检查表元数据
集成Prometheus监控:
management:
endpoints:
web:
exposure:
include: health,metrics,prometheus
metrics:
export:
prometheus:
enabled: true
5.4 常见问题与解决方案
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 全表扫描性能差 | 查询条件不包含分片键 | 1. 确保查询包含分片键 2. 使用Hint强制路由 3. 建立合适的索引 |
| 跨分片查询结果不准确 | 分页查询跨多个分片 | 1. 使用ShardingSphere的分页修正 2. 业务层做结果合并 3. 避免深度分页 |
| 分布式事务超时 | 跨多个分片事务时间过长 | 1. 合理设置事务超时时间 2. 使用Seata AT模式 3. 考虑最终一致性方案 |
| 数据倾斜 | 分片键选择不当 | 1. 选择高基数字段 2. 使用复合分片键 3. 定期数据重平衡 |
六、性能测试与调优
6.1 基准测试指标
- 单点查询响应时间:带分片键查询应<10ms
- 范围查询性能:时间范围查询效率对比单表
- 并发压力测试:模拟高并发下的系统表现
- 长时间稳定性测试:持续运行观察系统稳定性
6.2 性能优化建议
- 索引优化:分片键必须建立索引,复合查询建立联合索引
- 连接池配置:合理配置HikariCP连接池参数
- 批量操作:使用批量插入/更新减少网络开销
- 缓存策略:对热点数据使用Redis缓存
- SQL优化:避免
SELECT *,只查询必要字段
ShardingSphere为SpringBoot项目提供了完整的分库分表解决方案,通过合理的配置和优化,可以支撑千万级甚至亿级数据量的业务场景。关键成功因素包括:
- 合理分片设计:根据业务特点选择合适的分片键和分片策略
- 渐进式实施:先单库分表,再分库分表,逐步验证
- 全面测试:包括功能测试、性能测试、压力测试
- 监控告警:建立完善的监控体系,及时发现和处理问题
- 团队培训:确保开发团队理解分库分表的原理和最佳实践