首页 > 其他分享 >ShardingJDBC:轻松应对海量数据挑战

ShardingJDBC:轻松应对海量数据挑战

时间:2024-11-09 22:49:34浏览次数:3  
标签:海量 SQL 轻松 order 分片 分表 new ShardingJDBC public

前言

在当今大数据时代,海量数据的存储和访问成为了系统设计的瓶颈。单一数据库实例往往难以承受如此巨大的负载,从而导致性能下降甚至服务崩溃。为了解决这个问题,分库分表成为了一种常见的解决方案。它将数据分散存储到多个数据库实例或表中,从而有效地提升了系统的容量和性能。而 ShardingJDBC 正是为此而生的一个优秀框架,它简化了分库分表的开发流程,并提供了丰富的功能支持。

一、分库分表:

分库分表,顾名思义,就是将数据库中的数据拆分到不同的数据库实例或表中。它主要分为垂直拆分和水平拆分两种方式:

  • 垂直拆分: 按照业务模块将不同的表拆分到不同的数据库中。例如,电商系统可以将用户相关的信息存储在一个数据库中,商品相关的信息存储在另一个数据库中,订单相关的信息存储在第三个数据库中。

    • 优点: 拆分后每个库的数据量更小,数据库的性能更好;不同模块的数据库可以独立维护和升级,方便管理。
    • 缺点: 不同模块之间的数据关联查询比较复杂,需要跨库操作;事务处理也更加复杂,需要分布式事务的支持。
    • 适用场景: 系统模块之间耦合度低,且不同模块的数据量差异较大的情况。
  • 水平拆分: 按照一定的规则将同一张表的数据拆分到多个数据库或表中。例如,可以按照用户 ID 的尾号将用户数据分散到不同的数据库中。

    • 优点: 可以有效地缓解单一数据库的压力,提升系统的容量和性能;方便进行数据扩展,只需新增数据库或表即可。
    • 缺点: 数据分散存储,跨库或跨表查询比较复杂;数据迁移和备份也比较复杂。
    • 适用场景: 单表数据量非常大,单一数据库无法承受的情况。

常用的水平分库分表策略:

  • 范围分片: 按照某个字段的范围进行分片,例如按照时间范围或 ID 范围。
  • 哈希分片: 对某个字段进行哈希计算,然后根据哈希值进行分片。
  • 一致性哈希分片: 在哈希分片的基础上,增加了虚拟节点,可以有效地解决数据倾斜问题。
  • 预分片: 预先规划好分片的数量和规则,提前创建好数据库或表。

二、ShardingJDBC:

ShardingJDBC 是 Apache ShardingSphere 生态圈中的一个轻量级 Java 框架,它定位为轻量级 Java 数据库中间件,在 Java 的 JDBC 层提供的额外服务。它使用客户端直连数据库,以 JDBC API 为基础,封装了分库分表的操作,提供了一套简洁易用的 API,使得开发者可以像操作单库一样操作分库分表的数据。

ShardingJDBC 的核心功能:

  • 数据分片: 支持多种分片策略,包括范围分片、哈希分片、一致性哈希分片等。
  • 读写分离: 支持将读操作路由到从库,写操作路由到主库,从而提升系统的读性能。
  • 分布式事务: 支持 XA 和 BASE 两种分布式事务解决方案。
  • 数据治理: 提供数据脱敏、数据加密等数据治理功能。
  • SQL 兼容性: 高度兼容各种 SQL 方言,无需修改原有 SQL 即可实现分库分表。
  • 易用性: 提供简洁易用的 API,方便开发者快速上手。

ShardingJDBC 的优势:

  • 透明化分库分表: 开发者无需感知底层的分库分表逻辑,可以像操作单库一样操作分库分表的数据。
  • 高性能: 采用客户端直连数据库的方式,避免了额外的网络开销,性能更高。
  • 轻量级: jar 包体积小,占用资源少。
  • 易于集成: 可以轻松地集成到现有的 Java 项目中。

总而言之,分库分表是解决海量数据存储和访问问题的有效方案,而 ShardingJDBC 则是简化分库分表开发的利器,它提供了丰富的功能和易于使用的 API,帮助开发者轻松构建高性能、高可用的分布式数据库应用。


详细解析

部署ShardingJDBC

详见


为什么要分库分表

  1. 数据量角度

单表数据量过大的问题

1. 表数据量过大 (例如超过1000万条):
   - 查询性能下降
   - 索引效率降低
   - 备份恢复时间长

2. 物理存储限制:
   - 单机存储容量限制
   - 文件系统性能限制
   - IOPS瓶颈
  1. 性能角度

    // 单库服务器资源限制
    CPU_USAGE = 100%; // CPU达到瓶颈
    MEMORY_USAGE = 90%; // 内存接近上限
    DISK_IO = “高负载”; // IO负载过高

    // 连接数限制
    MAX_CONNECTIONS = 2000; // 连接数达到上限
    ACTIVE_CONNECTIONS = 1800; // 活跃连接数过高

    // QPS限制
    QUERIES_PER_SECOND = 10000; // 每秒查询数达到瓶颈

  2. 业务角度

    1. 数据隔离需求

      • 商户数据隔离
      • 业务数据隔离
      • 敏感数据隔离
    2. 业务模块解耦

      • 订单独立库
      • 用户独立库
      • 商品独立库
    3. 合规性要求

      • 数据分地域存储
      • 数据分级存储
  3. 扩展性角度

    1. 垂直扩展限制

      • 硬件升级成本高
      • 单机性能天花板
    2. 水平扩展需求

      • 支持动态扩容
      • 负载均衡需求
      • 异地多活部署
  4. 具体场景示例

// 订单系统分库分表场景
public class OrderSystemExample {
    
    @Test
    public void businessScenario() {
        // 1. 数据量预估
        long dailyOrders = 1000000L;    // 日订单量100万
        long yearlyOrders = 365 * dailyOrders;  // 年订单量3.65亿
        long orderDataSize = 2048L;     // 单条订单数据2KB
        long yearlyDataSize = yearlyOrders * orderDataSize;  // 年数据量730GB
        
        // 2. 性能需求
        int peakQPS = 5000;             // 峰值QPS 5000
        int responseTime = 100;          // 响应时间100ms
        
        // 3. 分库分表方案
        int dbCount = 8;                // 分8个库
        int tableCount = 32;            // 每库32张表
        long singleTableData = yearlyOrders / (dbCount * tableCount); 
        // 每表约140万条数据
    }
}
  1. 分库分表收益
// 分库分表带来的优势
public class ShardingBenefits {
    
    @Test
    public void performanceImprovement() {
        // 1. 提升查询性能
        beforeSharding.queryTime = 1000ms;
        afterSharding.queryTime = 100ms;
        
        // 2. 提高并发能力
        beforeSharding.maxQPS = 5000;
        afterSharding.maxQPS = 50000;
        
        // 3. 降低单库负载
        beforeSharding.dbLoad = 90%;
        afterSharding.dbLoad = 30%;
    }
}
  1. 注意事项
// 分库分表需要注意的问题
public class ShardingConsiderations {
    
    /*
    1. 分片键选择
       - 数据分布均匀
       - 避免跨分片查询
       - 支持后期扩展
    
    2. 事务处理
       - 分布式事务成本
       - 数据一致性保证
       - 性能影响评估
    
    3. 运维复杂度
       - 扩容方案
       - 数据迁移
       - 监控告警
    */
}
  1. 实施建议
// 分库分表实施建议
public class ImplementationSuggestions {
    
    public void bestPractices() {
        // 1. 提前规划
        long futureDataSize = estimateDataSize(3); // 预估3年数据量
        
        // 2. 循序渐进
        String[] steps = {
            "单库垂直分表",
            "单库水平分表",
            "多库水平分表"
        };
        
        // 3. 预留扩展空间
        int initialShards = 32;    // 初始分片数
        int expandableShards = 128; // 可扩展到128分片
    }
}

ShardingJDBC核心原理

ShardingJDBC的核心原理可以分为以下几个关键部分:

  1. SQL解析
  • 使用Antlr实现SQL解析,将SQL转换为抽象语法树(AST)
  • 遍历AST,提取分片键、表名等关键信息
  • 生成SQL路由所需的上下文信息
  1. SQL路由
  • 根据分片规则计算SQL要路由到哪些数据源和表
  • 支持分库分表、仅分库、仅分表等多种路由方式
  • 生成真实的物理SQL
  1. SQL改写
  • 将逻辑SQL改写为在真实表上执行的实际SQL
  • 包括表名替换、字段补列、分页改写等
  • 处理GROUP BY、ORDER BY、聚合函数等场景
  1. SQL执行
  • 并行执行路由后的真实SQL
  • 多线程并发执行提升性能
  • 控制事务的原子性
  1. 结果归并
  • 将多个分片的执行结果进行合并
  • 支持流式归并和内存归并两种方式
  • 处理排序、分组、分页等场景的结果集归并
  1. 分片策略
  • 支持标准分片和复合分片
  • 提供多种分片算法:哈希、范围、时间等
  • 支持自定义分片算法

核心执行流程:

1. 获取数据源 → 解析SQL → 生成路由上下文
2. 根据分片规则路由SQL
3. SQL改写生成真实SQL
4. 并行执行真实SQL
5. 归并多个分片结果
6. 返回最终结果

关键特性:

  1. 分片配置
// 配置分片规则
TableRuleConfiguration orderTableRuleConfig = new TableRuleConfiguration();
orderTableRuleConfig.setLogicTable("t_order");
orderTableRuleConfig.setActualDataNodes("ds${0..1}.t_order${0..1}");

// 配置分片策略
orderTableRuleConfig.setTableShardingStrategyConfig(
    new StandardShardingStrategyConfiguration("order_id", 
    new ModuloShardingTableAlgorithm())
);
  1. 分片算法
public final class ModuloShardingTableAlgorithm 
    implements PreciseShardingAlgorithm<Long> {
    
    @Override
    public String doSharding(
        final Collection<String> tableNames, 
        final PreciseShardingValue<Long> shardingValue) {
        for (String each : tableNames) {
            if (each.endsWith(shardingValue.getValue() % 2 + "")) {
                return each;
            }
        }
        throw new UnsupportedOperationException();
    }
}
  1. 使用方式
// 创建DataSource
DataSource dataSource = ShardingDataSourceFactory.createDataSource(
    dataSourceMap, // 数据源配置
    shardingRuleConfig, // 分片规则
    props  // 属性配置
);

// 执行SQL
String sql = "SELECT * FROM t_order WHERE order_id = ?";
try (
    Connection conn = dataSource.getConnection();
    PreparedStatement ps = conn.prepareStatement(sql)) {
    ps.setLong(1, 1);
    try (ResultSet rs = ps.executeQuery()) {
        while(rs.next()) {
            // 处理结果集
        }
    }
}

优势:

  1. 对应用透明
  • 可以像使用普通JDBC一样使用
  • 不需要修改已有代码
  1. 性能优化
  • 并行执行提升性能
  • 只查询必要的分片
  • 智能缓存优化
  1. 扩展性好
  • 支持自定义分片算法
  • 支持多种数据库
  • 易于扩展新功能
  1. 运维友好
  • 无需额外部署
  • 配置简单直观
  • 监控完善

注意事项:

  1. 分片键选择
  • 尽量选择分布均匀的字段
  • 避免频繁修改的字段
  • 考虑查询场景的覆盖
  1. 分片粒度
  • 权衡分片数量和性能
  • 预留足够扩展空间
  • 避免过度分片
  1. 事务处理
  • 考虑分布式事务的一致性
  • 合理控制事务边界
  • 注意性能影响
  1. 查询优化
  • 避免跨分片的关联查询
  • 减少全库全表扫描
  • 合理使用索引

扩展

6.分布式主键生成

ShardingJDBC提供了多种分布式主键生成策略:

// 雪花算法配置
TableRuleConfiguration orderTableRuleConfig = new TableRuleConfiguration();
orderTableRuleConfig.setKeyGeneratorConfig(
    new KeyGeneratorConfiguration("SNOWFLAKE", "order_id")
);

// 自定义主键生成器
public class CustomKeyGenerator implements KeyGenerator {
    @Override
    public Comparable<?> generateKey() {
        // 自定义生成逻辑
        return System.currentTimeMillis();
    }
}
  1. 读写分离

支持配置主从数据源,实现读写分离:

// 读写分离配置
MasterSlaveRuleConfiguration masterSlaveRuleConfig = 
    new MasterSlaveRuleConfiguration(
        "ds_master_slave",
        "ds_master",
        Arrays.asList("ds_slave0", "ds_slave1")
    );

// 负载均衡策略
masterSlaveRuleConfig.setLoadBalanceAlgorithm(
    new RoundRobinMasterSlaveLoadBalanceAlgorithm()
);
  1. 分片规则

详细的分片规则配置:

 标准分片策略
StandardShardingStrategyConfiguration standardStrategy = 
    new StandardShardingStrategyConfiguration(
        "order_id",
        new PreciseOrderShardingAlgorithm(),
        new RangeOrderShardingAlgorithm()
    );

 复合分片策略
ComplexShardingStrategyConfiguration complexStrategy = 
    new ComplexShardingStrategyConfiguration(
        "order_id,user_id",
        new ComplexOrderShardingAlgorithm()
    );

 Hint分片策略
HintShardingStrategyConfiguration hintStrategy = 
    new HintShardingStrategyConfiguration(
        new HintOrderShardingAlgorithm()
    );
  1. 分片算法实现示例
 精确分片算法
public class PreciseOrderShardingAlgorithm 
    implements PreciseShardingAlgorithm<Long> {
    
    @Override
    public String doSharding(
        Collection<String> availableTargetNames, 
        PreciseShardingValue<Long> shardingValue) {
        
        for (String tableName : availableTargetNames) {
            if (tableName.endsWith(shardingValue.getValue() % 2 + "")) {
                return tableName;
            }
        }
        throw new UnsupportedOperationException();
    }
}

 范围分片算法
public class RangeOrderShardingAlgorithm 
    implements RangeShardingAlgorithm<Long> {
    
    @Override
    public Collection<String> doSharding(
        Collection<String> availableTargetNames, 
        RangeShardingValue<Long> shardingValue) {
        
        Collection<String> result = new LinkedHashSet<>();
        Range<Long> range = shardingValue.getValueRange();
        for (Long i = range.lowerEndpoint(); 
             i <= range.upperEndpoint(); i++) {
            for (String tableName : availableTargetNames) {
                if (tableName.endsWith(i % 2 + "")) {
                    result.add(tableName);
                }
            }
        }
        return result;
    }
}
  1. 事务支持
 本地事务
TransactionTypeHolder.set(TransactionType.LOCAL);

 XA事务
TransactionTypeHolder.set(TransactionType.XA);

 BASE事务
TransactionTypeHolder.set(TransactionType.BASE);

 使用事务管理器
ShardingTransactionManager transactionManager = 
    ShardingTransactionManagerEngine.getTransactionManager(
        TransactionType.XA
    );

transactionManager.begin();
try {
    // 业务逻辑
    transactionManager.commit();
} catch (Exception ex) {
    transactionManager.rollback();
}
  1. 配置项详解
# 打印SQL日志
spring.shardingsphere.props.sql.show=true

# 工作线程数量
spring.shardingsphere.props.executor.size=16

# 最大连接数量
spring.shardingsphere.props.max.connections.size.per.query=1

# 是否在内存归并结果集
spring.shardingsphere.props.max.connections.size.per.query=true
  1. 性能优化建议

  2. 分片策略优化

  • 选择合适的分片键
  • 避免跨分片查询
  • 使用本地索引
  1. 连接池优化
  • 合理配置连接池大小
  • 使用高性能连接池
  • 监控连接使用情况
  1. SQL优化
  • 避免全表扫描
  • 使用索引覆盖
  • 控制单次查询数据量
  1. 缓存优化
  • 使用二级缓存
  • 合理设置缓存大小
  • 及时清理无用缓存
  1. 监控指标
// 注册监控指标收集器
MetricsTrackerFacade.getInstance().register(
    new PrometheusMetricsTrackerFactory()
);

// 重要监控指标
- SQL执行次数
- 路由节点数量
- 执行时间分布
- 结果集大小
- 连接池状态
  1. 常见问题处理

    1. 跨库关联查询
    • 使用冗余字段
    • 应用层关联
    • 数据同步方案
    1. 分布式事务
    • 选择合适事务模型
    • 控制事务边界
    • 考虑性能影响
    1. 数据迁移
    • 制定迁移方案
    • 验证数据一致性
    • 控制迁移影响
    1. 扩容缩容
    • 在线扩容方案
    • 数据再平衡
    • 避免性能抖动

ShardingJDBC实战:

  1. 项目背景
  • 电商订单系统
  • 日订单量1000w+
  • 历史订单数据100亿+
  • 需要支持实时查询和历史查询
  1. 分片方案设计
 分库分表配置
@Configuration
public class ShardingConfig {
    
    @Bean
    public DataSource getDataSource() {
         配置数据源
        Map<String, DataSource> dataSourceMap = new HashMap<>();
         配置8个数据源
        for (int i = 0; i < 8; i++) {
            dataSourceMap.put("ds" + i, createDataSource("ds" + i));
        }
        
         配置分片规则
        ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
        
         订单表分片
        TableRuleConfiguration orderTableRule = new TableRuleConfiguration(
            "t_order", 
            "ds${0..7}.t_order_${0..31}"
        );
        
         按订单ID分库 - 8个库
        orderTableRule.setDatabaseShardingStrategyConfig(
            new StandardShardingStrategyConfiguration(
                "order_id", 
                new DatabaseShardingAlgorithm()
            )
        );
        
         按订单ID分表 - 每库32张表
        orderTableRule.setTableShardingStrategyConfig(
            new StandardShardingStrategyConfiguration(
                "order_id", 
                new TableShardingAlgorithm()
            )
        );
        
        shardingRuleConfig.getTableRuleConfigs().add(orderTableRule);
        
        return ShardingDataSourceFactory.createDataSource(
            dataSourceMap, 
            shardingRuleConfig,
            new Properties()
        );
    }
}

// 分库算法
public class DatabaseShardingAlgorithm 
    implements PreciseShardingAlgorithm<Long> {
    
    @Override
    public String doSharding(
        Collection<String> databaseNames, 
        PreciseShardingValue<Long> shardingValue) {
        
        Long orderId = shardingValue.getValue();
        // 按订单ID取模确定库
        long dbIndex = orderId % 8;
        return "ds" + dbIndex;
    }
}

// 分表算法
public class TableShardingAlgorithm 
    implements PreciseShardingAlgorithm<Long> {
    
    @Override
    public String doSharding(
        Collection<String> tableNames, 
        PreciseShardingValue<Long> shardingValue) {
        
        Long orderId = shardingValue.getValue();
        // 按订单ID取模确定表
        long tableIndex = orderId % 32;
        return "t_order_" + tableIndex;
    }
}
  1. 业务代码实现
@Service
public class OrderService {
    
    @Autowired
    private OrderMapper orderMapper;
    
    // 创建订单
    @Transactional
    public void createOrder(Order order) {
        // 生成分布式ID
        long orderId = SnowflakeIdGenerator.generateId();
        order.setOrderId(orderId);
        
        // 保存订单
        orderMapper.insert(order);
        
        // 发送MQ消息
        sendOrderMessage(order);
    }
    
    // 查询订单
    public Order getOrder(Long orderId) {
        return orderMapper.selectById(orderId);
    }
    
    // 分页查询订单
    public PageInfo<Order> listOrders(OrderQuery query) {
        // 设置分页
        PageHelper.startPage(query.getPageNum(), query.getPageSize());
        
        // 构建查询条件
        QueryWrapper<Order> wrapper = new QueryWrapper<>();
        wrapper.eq(query.getUserId() != null, "user_id", query.getUserId())
               .ge(query.getStartTime() != null, "create_time", query.getStartTime())
               .le(query.getEndTime() != null, "create_time", query.getEndTime())
               .orderByDesc("create_time");
               
        // 执行查询
        List<Order> orders = orderMapper.selectList(wrapper);
        
        return new PageInfo<>(orders);
    }
}

// Mapper接口
@Mapper
public interface OrderMapper extends BaseMapper<Order> {
    
    // 使用MyBatis-Plus
    
    @Select("SELECT * FROM t_order WHERE order_id = #{orderId}")
    Order selectById(@Param("orderId") Long orderId);
    
    @Insert("INSERT INTO t_order(order_id,user_id,amount,status,create_time) " +
            "VALUES(#{orderId},#{userId},#{amount},#{status},#{createTime})")
    void insert(Order order);
}
  1. 读写分离配置
@Configuration
public class MasterSlaveConfig {
    
    @Bean
    public DataSource getMasterSlaveDataSource() {
        // 主库
        Map<String, DataSource> masterDataSourceMap = new HashMap<>();
        for (int i = 0; i < 8; i++) {
            masterDataSourceMap.put("master" + i, createMasterDataSource(i));
        }
        
        // 从库
        Map<String, DataSource> slaveDataSourceMap = new HashMap<>();
        for (int i = 0; i < 8; i++) {
            for (int j = 0; j < 2; j++) {
                slaveDataSourceMap.put("slave" + i + j, createSlaveDataSource(i, j));
            }
        }
        
        // 读写分离配置
        MasterSlaveRuleConfiguration masterSlaveRuleConfig = 
            new MasterSlaveRuleConfiguration();
            
        // 每个主库配置2个从库    
        for (int i = 0; i < 8; i++) {
            masterSlaveRuleConfig.getSlaveDataSourceNames().add(
                new MasterSlaveRuleConfiguration(
                    "ds" + i,
                    "master" + i,
                    Arrays.asList("slave" + i + "0", "slave" + i + "1")
                )
            );
        }
        
        return MasterSlaveDataSourceFactory.createDataSource(
            masterDataSourceMap,
            slaveDataSourceMap, 
            masterSlaveRuleConfig,
            new Properties()
        );
    }
}
  1. 性能优化配置
# 数据源配置
spring.datasource.druid.initial-size=5
spring.datasource.druid.min-idle=5
spring.datasource.druid.max-active=20
spring.datasource.druid.max-wait=60000

# ShardingSphere配置
spring.shardingsphere.props.sql.show=true
spring.shardingsphere.props.executor.size=32
spring.shardingsphere.props.max.connections.size.per.query=2

# MyBatis配置
mybatis-plus.configuration.map-underscore-to-camel-case=true
mybatis-plus.configuration.cache-enabled=true
mybatis-plus.configuration.lazy-loading-enabled=true
  1. 监控告警实现
@Aspect
@Component
public class SqlMonitorAspect {
    
    @Autowired
    private MetricsService metricsService;
    
    @Around("execution(* com.xxx.mapper.*.*(..))")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long startTime = System.currentTimeMillis();
        String methodName = point.getSignature().getName();
        
        try {
            Object result = point.proceed();
            long costTime = System.currentTimeMillis() - startTime;
            
            // 记录执行时间
            metricsService.recordExecuteTime(methodName, costTime);
            
            // 慢SQL告警
            if (costTime > 1000) {
                sendSlowSqlAlert(methodName, costTime);
            }
            
            return result;
        } catch (Exception e) {
            // 异常告警
            sendErrorAlert(methodName, e);
            throw e;
        }
    }
}

// 监控指标服务
@Service
public class MetricsService {
    
    private final Counter sqlCounter = Counter.build()
        .name("sql_execute_total")
        .help("SQL execution count")
        .register();
        
    private final Histogram sqlDuration = Histogram.build()
        .name("sql_execute_duration")
        .help("SQL execution time")
        .register();
        
    public void recordExecuteTime(String method, long time) {
        sqlCounter.inc();
        sqlDuration.observe(time);
    }
}
  1. 分布式ID生成
@Component
public class SnowflakeIdGenerator {
    
    private final SnowflakeIdWorker idWorker;
    
    public SnowflakeIdGenerator(
        @Value("${snowflake.worker-id}") long workerId,
        @Value("${snowflake.datacenter-id}") long datacenterId) {
        this.idWorker = new SnowflakeIdWorker(workerId, datacenterId);
    }
    
    public synchronized long generateId() {
        return idWorker.nextId();
    }
}

public class SnowflakeIdWorker {
    
    private final long workerId;
    private final long datacenterId;
    private long sequence = 0L;
    private long lastTimestamp = -1L;
    
    public synchronized long nextId() {
        long timestamp = timeGen();
        
        if (timestamp < lastTimestamp) {
            throw new RuntimeException("Clock moved backwards");
        }
        
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }
        
        lastTimestamp = timestamp;
        
        return ((timestamp - twepoch) << timestampLeftShift)
                | (datacenterId << datacenterIdShift)
                | (workerId << workerIdShift)
                | sequence;
    }
}
  1. 缓存优化实现
@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private OrderMapper orderMapper;
    
    @Autowired
    private RedisTemplate<String, Order> redisTemplate;
    
    private static final String ORDER_CACHE_KEY = "order:";
    
    @Override
    public Order getOrder(Long orderId) {
        // 先查缓存
        String cacheKey = ORDER_CACHE_KEY + orderId;
        Order order = redisTemplate.opsForValue().get(cacheKey);
        
        if (order != null) {
            return order;
        }
        
        // 缓存未命中查询数据库
        order = orderMapper.selectById(orderId);
        
        if (order != null) {
            // 放入缓存
            redisTemplate.opsForValue().set(cacheKey, order, 1, TimeUnit.HOURS);
        }
        
        return order;
    }
    
    @Override
    @Transactional
    public void updateOrder(Order order) {
        // 更新数据库
        orderMapper.updateById(order);
        
        // 删除缓存
        String cacheKey = ORDER_CACHE_KEY + order.getOrderId();
        redisTemplate.delete(cacheKey);
    }
}
  1. 异常处理
@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ShardingException.class)
    public Result handleShardingException(ShardingException e) {
        log.error("分片异常", e);
        return Result.error("系统繁忙,请稍后重试");
    }
    
    @ExceptionHandler(DuplicateKeyException.class)
    public Result handleDuplicateKeyException(DuplicateKeyException e) {
        log.error("唯一索引冲突", e);
        return Result.error("数据已存在");
    }
    
    @ExceptionHandler(Exception.class)
    public Result handleException(Exception e) {
        log.error("系统异常", e);
        return Result.error("系统异常");
    }
}
  1. 数据迁移方案
@Service
public class DataMigrationService {

    @Autowired
    private OrderMapper sourceMapper;
    
    @Autowired
    private OrderMapper targetMapper;
    
    @Value("${migration.batch-size}")
    private int batchSize;
    
    public void migrateData(Date startTime, Date endTime) {
        long offset = 0;
        while (true) {
            // 分批读取源数据
            List<Order> orders = sourceMapper.selectByTimeRange(
                startTime, endTime, offset, batchSize
            );
            
            if (CollectionUtils.isEmpty(orders)) {
                break;
            }
            
            // 写入目标库
            for (Order order : orders) {
                try {
                    targetMapper.insert(order);
                } catch (Exception e) {
                    log.error("迁移订单失败: {}", order.getOrderId(), e);
                }
            }
            
            offset += batchSize;
        }
    }
}
  1. 压测验证
@Test
public void performanceTest() {
    // 准备测试数据
    List<Order> testOrders = generateTestOrders(10000);
    
    // 并发写入测试
    CountDownLatch latch = new CountDownLatch(10);
    for (int i = 0; i < 10; i++) {
        new Thread(() -> {
            try {
                for (Order order : testOrders) {
                    orderService.createOrder(order);
                }
            } finally {
                latch.countDown();
            }
        }).start();
    }
    
    latch.await();
    
    // 查询性能测试
    long startTime = System.currentTimeMillis();
    for (int i = 0; i < 1000; i++) {
        orderService.getOrder(RandomUtils.nextLong());
    }
    long costTime = System.currentTimeMillis() - startTime;
    
    log.info("平均查询耗时: {}ms", costTime / 1000.0);
}

对比分库分表常用的几种解决方案:

  1. ShardingJDBC vs MyCat

    ShardingJDBC特点与优势:

    1. 无中间件部署

      • 以jar包形式提供
      • 无需额外服务器
      • 维护成本低
    2. 性能高

      • 直连数据库
      • 无SQL解析成本
      • 无网络开销
    3. 对业务零侵入

      • 完全兼容JDBC
      • 支持任意实现JDBC规范的数据库

    劣势:

    1. 需要引入到每个应用
    2. 升级需要重启应用
    3. 无法独立管理和监控

MyCat特点
优势:

  1. 独立部署

    • 集中式管理
    • 统一监控
    • 配置热生效
  2. 支持异构数据库

    • 可连接不同类型数据库
    • 跨库查询能力强
  3. 对应用透明

    • 应用当做普通数据库使用

劣势:

  1. 部署维护成本高

  2. 性能有损耗

  3. 存在单点风险

  4. 实际应用场景对比

适合使用ShardingJDBC的场景:

  1. 单一数据库类型
    database.type = “MySQL”;

  2. 性能要求高
    requirements.qps = 10000;
    requirements.latency = “ms级”;

  3. 分片规则相对固定
    shardingRule.changeFrequency = “低”;

  4. 团队技术栈
    team.familiarWith = “Java”;

适合使用MyCat的场景:

  1. 异构数据库
    database.types = {“MySQL”, “Oracle”, “SQL Server”};

  2. 需要统一管理
    management.requirement = “集中式”;

  3. 分片规则频繁变化
    shardingRule.changeFrequency = “高”;

  4. DBA团队运维
    team.maintainBy = “DBA”;

Apache TDDL
优势:

  1. 淘宝内部大规模验证
  2. 动态数据源切换
  3. 丰富的监控功能

劣势:

  1. 文档和社区支持较弱
  2. 配置复杂
  3. 依赖较重

Vitess

优势:

  1. YouTube大规模验证
  2. 支持MySQL协议
  3. 支持Kubernetes部署

劣势:

  1. 部署复杂

  2. 学习成本高

  3. 国内应用案例少

  4. 选型建议

public class SelectionSuggestions {

public String selectSolution(Requirements req) {
    // 1. 性能要求
    if (req.performanceCritical) {
        return "ShardingJDBC";
    }
    
    // 2. 运维能力
    if (req.hasStrongOps) {
        return "MyCat";
    }
    
    // 3. 异构数据库
    if (req.multipleDbTypes) {
        return "MyCat";
    }
    
    // 4. 默认推荐
    return "ShardingJDBC";
}

}

  1. 性能对比
// 性能测试对比
public class PerformanceComparison {
    
    @Test
    public void comparePerformance() {
        // 直连数据库
        DirectAccess.qps = 10000;
        DirectAccess.latency = 10;
        
        // ShardingJDBC
        ShardingJDBC.qps = 9000;      // 性能损耗约10%
        ShardingJDBC.latency = 12;
        
        // MyCat
        MyCat.qps = 7000;             // 性能损耗约30%
        MyCat.latency = 20;
    }
}
  1. 成本对比
public class CostComparison {
    
    @Test
    public void compareCost() {
        // ShardingJDBC
        ShardingJDBCCost cost1 = new ShardingJDBCCost();
        cost1.development = "中";      // 开发成本
        cost1.operation = "低";        // 运维成本
        cost1.hardware = "低";         // 硬件成本
        cost1.learning = "中";         // 学习成本
        
        // MyCat
        MyCatCost cost2 = new MyCatCost();
        cost2.development = "低";
        cost2.operation = "高";
        cost2.hardware = "中";
        cost2.learning = "高";
    }
}
  1. 使用建议
public class UsageSuggestions {
    
    public void suggestions() {
        // 1. 充分评估需求
        assessRequirements();
        
        // 2. 考虑团队能力
        evaluateTeamCapability();
        
        // 3. 性能压测验证
        performLoadTesting();
        
        // 4. 制定容灾方案
        prepareDRPlan();
    }
    
    private void assessRequirements() {
        // 评估维度
        String[] dimensions = {
            "性能要求",
            "扩展需求",
            "运维能力",
            "成本预算"
        };
    }
}

总结:

  1. ShardingJDBC适合:
  • 性能要求高的场景
  • Java技术栈项目
  • 运维能力一般的团队
  • 成本敏感的项目
  1. MyCat适合:
  • 需要统一管理的场景
  • 异构数据库的项目
  • 运维能力强的团队
  • 需要热配置的场景
  1. 选型建议:
  • 优先考虑ShardingJDBC
  • 特殊需求选择MyCat
  • 根据团队情况决定
  • 提前做好验证

标签:海量,SQL,轻松,order,分片,分表,new,ShardingJDBC,public
From: https://blog.csdn.net/jsjbrdzhh/article/details/143651950

相关文章