前言
在当今大数据时代,海量数据的存储和访问成为了系统设计的瓶颈。单一数据库实例往往难以承受如此巨大的负载,从而导致性能下降甚至服务崩溃。为了解决这个问题,分库分表成为了一种常见的解决方案。它将数据分散存储到多个数据库实例或表中,从而有效地提升了系统的容量和性能。而 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. 表数据量过大 (例如超过1000万条):
- 查询性能下降
- 索引效率降低
- 备份恢复时间长
2. 物理存储限制:
- 单机存储容量限制
- 文件系统性能限制
- IOPS瓶颈
-
性能角度
// 单库服务器资源限制
CPU_USAGE = 100%; // CPU达到瓶颈
MEMORY_USAGE = 90%; // 内存接近上限
DISK_IO = “高负载”; // IO负载过高// 连接数限制
MAX_CONNECTIONS = 2000; // 连接数达到上限
ACTIVE_CONNECTIONS = 1800; // 活跃连接数过高// QPS限制
QUERIES_PER_SECOND = 10000; // 每秒查询数达到瓶颈 -
业务角度
-
数据隔离需求
- 商户数据隔离
- 业务数据隔离
- 敏感数据隔离
-
业务模块解耦
- 订单独立库
- 用户独立库
- 商品独立库
-
合规性要求
- 数据分地域存储
- 数据分级存储
-
-
扩展性角度
-
垂直扩展限制
- 硬件升级成本高
- 单机性能天花板
-
水平扩展需求
- 支持动态扩容
- 负载均衡需求
- 异地多活部署
-
-
具体场景示例
// 订单系统分库分表场景
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万条数据
}
}
- 分库分表收益
// 分库分表带来的优势
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%;
}
}
- 注意事项
// 分库分表需要注意的问题
public class ShardingConsiderations {
/*
1. 分片键选择
- 数据分布均匀
- 避免跨分片查询
- 支持后期扩展
2. 事务处理
- 分布式事务成本
- 数据一致性保证
- 性能影响评估
3. 运维复杂度
- 扩容方案
- 数据迁移
- 监控告警
*/
}
- 实施建议
// 分库分表实施建议
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的核心原理可以分为以下几个关键部分:
- SQL解析
- 使用Antlr实现SQL解析,将SQL转换为抽象语法树(AST)
- 遍历AST,提取分片键、表名等关键信息
- 生成SQL路由所需的上下文信息
- SQL路由
- 根据分片规则计算SQL要路由到哪些数据源和表
- 支持分库分表、仅分库、仅分表等多种路由方式
- 生成真实的物理SQL
- SQL改写
- 将逻辑SQL改写为在真实表上执行的实际SQL
- 包括表名替换、字段补列、分页改写等
- 处理GROUP BY、ORDER BY、聚合函数等场景
- SQL执行
- 并行执行路由后的真实SQL
- 多线程并发执行提升性能
- 控制事务的原子性
- 结果归并
- 将多个分片的执行结果进行合并
- 支持流式归并和内存归并两种方式
- 处理排序、分组、分页等场景的结果集归并
- 分片策略
- 支持标准分片和复合分片
- 提供多种分片算法:哈希、范围、时间等
- 支持自定义分片算法
核心执行流程:
1. 获取数据源 → 解析SQL → 生成路由上下文
2. 根据分片规则路由SQL
3. SQL改写生成真实SQL
4. 并行执行真实SQL
5. 归并多个分片结果
6. 返回最终结果
关键特性:
- 分片配置
// 配置分片规则
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())
);
- 分片算法
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();
}
}
- 使用方式
// 创建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()) {
// 处理结果集
}
}
}
优势:
- 对应用透明
- 可以像使用普通JDBC一样使用
- 不需要修改已有代码
- 性能优化
- 并行执行提升性能
- 只查询必要的分片
- 智能缓存优化
- 扩展性好
- 支持自定义分片算法
- 支持多种数据库
- 易于扩展新功能
- 运维友好
- 无需额外部署
- 配置简单直观
- 监控完善
注意事项:
- 分片键选择
- 尽量选择分布均匀的字段
- 避免频繁修改的字段
- 考虑查询场景的覆盖
- 分片粒度
- 权衡分片数量和性能
- 预留足够扩展空间
- 避免过度分片
- 事务处理
- 考虑分布式事务的一致性
- 合理控制事务边界
- 注意性能影响
- 查询优化
- 避免跨分片的关联查询
- 减少全库全表扫描
- 合理使用索引
扩展
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();
}
}
- 读写分离
支持配置主从数据源,实现读写分离:
// 读写分离配置
MasterSlaveRuleConfiguration masterSlaveRuleConfig =
new MasterSlaveRuleConfiguration(
"ds_master_slave",
"ds_master",
Arrays.asList("ds_slave0", "ds_slave1")
);
// 负载均衡策略
masterSlaveRuleConfig.setLoadBalanceAlgorithm(
new RoundRobinMasterSlaveLoadBalanceAlgorithm()
);
- 分片规则
详细的分片规则配置:
标准分片策略
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()
);
- 分片算法实现示例
精确分片算法
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;
}
}
- 事务支持
本地事务
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();
}
- 配置项详解
# 打印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
-
性能优化建议
-
分片策略优化
- 选择合适的分片键
- 避免跨分片查询
- 使用本地索引
- 连接池优化
- 合理配置连接池大小
- 使用高性能连接池
- 监控连接使用情况
- SQL优化
- 避免全表扫描
- 使用索引覆盖
- 控制单次查询数据量
- 缓存优化
- 使用二级缓存
- 合理设置缓存大小
- 及时清理无用缓存
- 监控指标
// 注册监控指标收集器
MetricsTrackerFacade.getInstance().register(
new PrometheusMetricsTrackerFactory()
);
// 重要监控指标
- SQL执行次数
- 路由节点数量
- 执行时间分布
- 结果集大小
- 连接池状态
-
常见问题处理
- 跨库关联查询
- 使用冗余字段
- 应用层关联
- 数据同步方案
- 分布式事务
- 选择合适事务模型
- 控制事务边界
- 考虑性能影响
- 数据迁移
- 制定迁移方案
- 验证数据一致性
- 控制迁移影响
- 扩容缩容
- 在线扩容方案
- 数据再平衡
- 避免性能抖动
ShardingJDBC实战:
- 项目背景
- 电商订单系统
- 日订单量1000w+
- 历史订单数据100亿+
- 需要支持实时查询和历史查询
- 分片方案设计
分库分表配置
@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;
}
}
- 业务代码实现
@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);
}
- 读写分离配置
@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()
);
}
}
- 性能优化配置
# 数据源配置
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
- 监控告警实现
@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);
}
}
- 分布式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;
}
}
- 缓存优化实现
@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);
}
}
- 异常处理
@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("系统异常");
}
}
- 数据迁移方案
@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;
}
}
}
- 压测验证
@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);
}
对比分库分表常用的几种解决方案:
-
ShardingJDBC vs MyCat
ShardingJDBC特点与优势:
-
无中间件部署
- 以jar包形式提供
- 无需额外服务器
- 维护成本低
-
性能高
- 直连数据库
- 无SQL解析成本
- 无网络开销
-
对业务零侵入
- 完全兼容JDBC
- 支持任意实现JDBC规范的数据库
劣势:
- 需要引入到每个应用
- 升级需要重启应用
- 无法独立管理和监控
-
MyCat特点
优势:
-
独立部署
- 集中式管理
- 统一监控
- 配置热生效
-
支持异构数据库
- 可连接不同类型数据库
- 跨库查询能力强
-
对应用透明
- 应用当做普通数据库使用
劣势:
-
部署维护成本高
-
性能有损耗
-
存在单点风险
-
实际应用场景对比
适合使用ShardingJDBC的场景:
-
单一数据库类型
database.type = “MySQL”; -
性能要求高
requirements.qps = 10000;
requirements.latency = “ms级”; -
分片规则相对固定
shardingRule.changeFrequency = “低”; -
团队技术栈
team.familiarWith = “Java”;
适合使用MyCat的场景:
-
异构数据库
database.types = {“MySQL”, “Oracle”, “SQL Server”}; -
需要统一管理
management.requirement = “集中式”; -
分片规则频繁变化
shardingRule.changeFrequency = “高”; -
DBA团队运维
team.maintainBy = “DBA”;
Apache TDDL
优势:
- 淘宝内部大规模验证
- 动态数据源切换
- 丰富的监控功能
劣势:
- 文档和社区支持较弱
- 配置复杂
- 依赖较重
Vitess
优势:
- YouTube大规模验证
- 支持MySQL协议
- 支持Kubernetes部署
劣势:
-
部署复杂
-
学习成本高
-
国内应用案例少
-
选型建议
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";
}
}
- 性能对比
// 性能测试对比
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;
}
}
- 成本对比
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 = "高";
}
}
- 使用建议
public class UsageSuggestions {
public void suggestions() {
// 1. 充分评估需求
assessRequirements();
// 2. 考虑团队能力
evaluateTeamCapability();
// 3. 性能压测验证
performLoadTesting();
// 4. 制定容灾方案
prepareDRPlan();
}
private void assessRequirements() {
// 评估维度
String[] dimensions = {
"性能要求",
"扩展需求",
"运维能力",
"成本预算"
};
}
}
总结:
- ShardingJDBC适合:
- 性能要求高的场景
- Java技术栈项目
- 运维能力一般的团队
- 成本敏感的项目
- MyCat适合:
- 需要统一管理的场景
- 异构数据库的项目
- 运维能力强的团队
- 需要热配置的场景
- 选型建议:
- 优先考虑ShardingJDBC
- 特殊需求选择MyCat
- 根据团队情况决定
- 提前做好验证