数据库分片实战:从理论到ShardingSphere落地
数据库分片实战从理论到ShardingSphere落地一、数据库分片概述1.1 什么是数据库分片数据库分片Database Sharding是一种水平扩展数据库的技术通过将大型数据库表分割成更小、更快、更容易管理的部分称为分片并将这些分片分布到多个服务器上从而实现数据库的水平扩展。1.2 为什么需要分片随着业务的增长单一数据库会面临以下瓶颈存储瓶颈单库存储容量有限性能瓶颈单库处理能力有限查询响应变慢可用性瓶颈单点故障风险运维瓶颈备份恢复时间过长1.3 分片策略分类分片类型描述适用场景垂直分片按业务功能拆分如用户库、订单库、商品库业务模块清晰、耦合度低水平分片按行拆分将同一表的数据分散到多个节点单表数据量过大混合分片结合垂直和水平分片大型复杂系统二、分片键选择策略2.1 选择分片键的原则选择合适的分片键是分片设计的关键需要遵循以下原则均匀分布分片键的值应均匀分布避免数据倾斜业务相关性分片键应与查询模式匹配稳定性分片键应相对稳定避免频繁变更易扩展支持后续扩容需求2.2 常见分片键选择// 用户ID分片 - 适合用户相关数据 Data Entity public class User { Id private Long id; // 作为分片键 private String username; private String email; // ... } // 订单ID分片 - 适合订单数据 Data Entity public class Order { Id private Long id; // 作为分片键 private Long userId; private BigDecimal amount; // ... }三、分片算法详解3.1 范围分片Range Sharding基于分片键的范围值进行分片如按日期、数值范围等。spring: shardingsphere: rules: sharding: tables: order: actual-data-nodes: ds_${0..1}.order_${2024..2026} database-strategy: standard: sharding-column: order_time sharding-algorithm-name: range-sharding-algorithm sharding-algorithms: range-sharding-algorithm: type: CLASS_BASED props: strategy: COMPLEX algorithm-class-name: com.example.sharding.RangeShardingAlgorithm优点易于理解和维护支持范围查询优化缺点热点数据问题如最新月份数据访问频繁扩容困难3.2 哈希分片Hash Sharding基于分片键的哈希值进行分片如取模运算。public class HashShardingAlgorithm implements StandardShardingAlgorithmLong { Override public String doSharding(CollectionString availableTargetNames, ShardingValueLong shardingValue) { Long value shardingValue.getValue(); int index (int) (value % availableTargetNames.size()); return availableTargetNames.stream() .sorted() .skip(index) .findFirst() .orElseThrow(() - new IllegalArgumentException(No available data source)); } }优点数据分布均匀扩容相对简单缺点不支持范围查询需要额外处理数据迁移3.3 一致性哈希Consistent Hashing一致性哈希算法解决了传统哈希算法在节点增减时的数据迁移问题。public class ConsistentHashShardingAlgorithm implements ShardingAlgorithm { private final TreeMapLong, String virtualNodes new TreeMap(); private static final int VIRTUAL_NODES 100; Override public void init() { // 初始化虚拟节点 ListString dataSources getDataSourceNames(); for (String dataSource : dataSources) { for (int i 0; i VIRTUAL_NODES; i) { long hash hash(dataSource _ i); virtualNodes.put(hash, dataSource); } } } private long hash(String key) { return MurmurHash.hash64(key.getBytes()); } Override public String doSharding(CollectionString availableTargetNames, ShardingValue? shardingValue) { String key shardingValue.getValue().toString(); long hash hash(key); // 找到大于等于该hash的第一个虚拟节点 Map.EntryLong, String entry virtualNodes.ceilingEntry(hash); if (entry null) { // 如果没有找到取第一个 entry virtualNodes.firstEntry(); } return entry.getValue(); } }优点节点增减时数据迁移量小数据分布均匀缺点实现复杂需要维护虚拟节点四、ShardingSphere实战配置4.1 依赖配置dependency groupIdorg.apache.shardingsphere/groupId artifactIdshardingsphere-jdbc-core-spring-boot-starter/artifactId version5.4.1/version /dependency4.2 数据源配置spring: shardingsphere: datasource: names: ds_0, ds_1 ds_0: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3306/shard_db_0?useSSLfalseserverTimezoneUTC username: root password: password ds_1: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3307/shard_db_1?useSSLfalseserverTimezoneUTC username: root password: password4.3 分片规则配置spring: shardingsphere: rules: sharding: binding-tables: - t_order,t_order_item broadcast-tables: - t_config tables: t_order: actual-data-nodes: ds_${0..1}.t_order_${0..3} 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..3} database-strategy: standard: sharding-column: user_id sharding-algorithm-name: database-inline table-strategy: standard: sharding-column: order_id sharding-algorithm-name: table-inline sharding-algorithms: database-inline: type: INLINE props: algorithm-expression: ds_${user_id % 2} table-inline: type: INLINE props: algorithm-expression: t_order_${order_id % 4}4.4 读写分离配置spring: shardingsphere: rules: readwrite-splitting: >Service public class OrderService { Autowired private OrderRepository orderRepository; Transactional public Order createOrder(Order order) { // ShardingSphere自动路由到正确的分片 return orderRepository.save(order); } public ListOrder getOrdersByUserId(Long userId) { // 自动路由到对应数据库分片 return orderRepository.findByUserId(userId); } Transactional public void batchInsert(ListOrder orders) { // 批量插入自动分发到各分片 orderRepository.saveAll(orders); } }5.2 跨分片查询Service public class AnalyticsService { Autowired private JdbcTemplate jdbcTemplate; public BigDecimal getTotalRevenue(LocalDate startDate, LocalDate endDate) { // ShardingSphere自动合并跨分片结果 String sql SELECT SUM(amount) FROM t_order WHERE order_time BETWEEN ? AND ?; return jdbcTemplate.queryForObject(sql, BigDecimal.class, startDate, endDate); } public ListOrderStats getDailyStats(LocalDate date) { // 跨分片聚合查询 String sql SELECT DATE(order_time) as date, COUNT(*) as count, SUM(amount) as total FROM t_order WHERE order_time ? AND order_time ? GROUP BY DATE(order_time) ; return jdbcTemplate.query(sql, new OrderStatsRowMapper(), date, date.plusDays(1)); } }六、分片常见问题与解决方案6.1 数据倾斜问题问题描述某些分片数据量远大于其他分片解决方案// 使用复合分片键 public class CompositeShardingAlgorithm implements ComplexShardingAlgorithmLong { Override public CollectionString doSharding(CollectionString availableTargetNames, ComplexShardingValueLong shardingValue) { // 结合多个字段进行分片 Long userId shardingValue.getColumnNameAndShardingValuesMap().get(user_id).get(0); Long orderId shardingValue.getColumnNameAndShardingValuesMap().get(order_id).get(0); // 使用组合哈希 int hash (userId.hashCode() ^ orderId.hashCode()) % availableTargetNames.size(); return Collections.singleton( availableTargetNames.stream().sorted().skip(hash).findFirst().get() ); } }6.2 跨分片事务问题问题描述分布式事务难以保证一致性解决方案Configuration public class TransactionConfig { Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { // 使用XA事务或Seata return new DataSourceTransactionManager(dataSource); } Bean public GlobalTransactionScanner globalTransactionScanner() { // Seata分布式事务扫描器 return new GlobalTransactionScanner(my-sharding-app, my_tx_group); } }6.3 分片键变更问题问题描述分片键值变更需要迁移数据解决方案Component public class DataMigrationService { Transactional public void migrateData(Long oldUserId, Long newUserId) { // 1. 在新分片插入数据 ListOrder orders orderRepository.findByUserId(oldUserId); orders.forEach(order - order.setUserId(newUserId)); orderRepository.saveAll(orders); // 2. 删除旧分片数据 orderRepository.deleteByUserId(oldUserId); } }七、分片扩容策略7.1 垂直扩容Scale Up升级单节点硬件配置适合小规模数据增长。7.2 水平扩容Scale Out增加分片节点需要进行数据迁移。Component public class ShardExpansionService { public void expandShards(int newShardCount) { // 1. 创建新的分片数据库 createNewShards(newShardCount); // 2. 重新分配数据 rebalanceData(); // 3. 更新分片配置 updateShardingConfig(newShardCount); } private void rebalanceData() { // 使用一致性哈希最小化数据迁移 // 只迁移受影响的分片数据 } }八、性能优化建议8.1 索引优化-- 分片键必须建立索引 CREATE INDEX idx_user_id ON t_order(user_id); CREATE INDEX idx_order_id ON t_order(order_id); -- 复合索引优化查询 CREATE INDEX idx_user_time ON t_order(user_id, order_time);8.2 查询优化Service public class OptimizedQueryService { // 避免跨分片全表扫描 Query(SELECT o FROM Order o WHERE o.userId :userId AND o.status :status) ListOrder findByUserIdAndStatus(Param(userId) Long userId, Param(status) String status); // 使用分片键进行过滤 Query(SELECT o FROM Order o WHERE o.userId IN :userIds) ListOrder findByUserIds(Param(userIds) ListLong userIds); }8.3 读写分离spring: shardingsphere: rules: readwrite-splitting: >Component public class ShardingMonitor { Autowired private DataSource dataSource; public MapString, Long getShardDataSize() { MapString, Long result new HashMap(); // 查询各分片数据量 // ... return result; } public ListString getSlowQueries(int thresholdMs) { // 监控慢查询 // ... return List.of(); } }9.2 备份恢复Component public class BackupService { public void backupShard(String shardName) { // 在线热备份 // 使用mysqldump或其他工具 } public void restoreShard(String shardName, String backupPath) { // 恢复分片数据 } }十、总结数据库分片是大型系统架构设计中不可或缺的技术通过合理的分片策略和工具选择可以实现数据库的水平扩展。在实际应用中需要注意分片键选择选择分布均匀、业务相关的字段算法选择根据业务场景选择合适的分片算法事务处理考虑分布式事务解决方案运维监控建立完善的监控体系ShardingSphere作为国内成熟的分片中间件提供了完整的分片解决方案是企业级应用的首选。