订单服务分库分表设计思维全景
视频
一、需求分析:订单服务的特殊性
1.1 业务场景梳理
1.2 实体关系分析
二、问题建模:订单服务的核心挑战
2.1 双维度高频查询困境
2.2 与用户服务的区别
三、策略选择:为什么选择基因法
3.1 策略对比决策
3.2 基因法原理图解
四、方案设计:订单服务分库分表架构
4.1 整体架构
4.2 分片策略设计
4.3 绑定表设计
五、算法实现深度解析
5.1 分表算法解析
/**
* 订单分表算法
*
* 核心逻辑:使用 order_number 或 user_id 对分表数量取模
* 由于基因法保证了两者低位相同,所以结果一致
*/
public class TableOrderComplexGeneArithmetic
implements ComplexKeysShardingAlgorithm<Long> {
private int shardingCount; // 分表数量 = 4
@Override
public void init(Properties props) {
// 从配置读取分表数量
shardingCount = Integer.parseInt(
props.getProperty("sharding-count"));
}
@Override
public Collection<String> doSharding(
Collection<String> allActualSplitTableNames,
ComplexKeysShardingValue<Long> complexKeysShardingValue) {
List<String> actualTableNames = new ArrayList<>();
String logicTableName = complexKeysShardingValue.getLogicTableName();
// 获取查询条件中的分片键值
Map<String, Collection<Long>> columnNameAndShardingValuesMap =
complexKeysShardingValue.getColumnNameAndShardingValuesMap();
// 如果没有分片键条件,返回所有表(全路由)
if (CollectionUtil.isEmpty(columnNameAndShardingValuesMap)) {
return allActualSplitTableNames;
}
// 优先使用 order_number,其次使用 user_id
Collection<Long> orderNumberValues =
columnNameAndShardingValuesMap.get("order_number");
Collection<Long> userIdValues =
columnNameAndShardingValuesMap.get("user_id");
Long value = null;
if (CollectionUtil.isNotEmpty(orderNumberValues)) {
value = orderNumberValues.stream().findFirst().orElseThrow();
} else if (CollectionUtil.isNotEmpty(userIdValues)) {
value = userIdValues.stream().findFirst().orElseThrow();
}
if (Objects.nonNull(value)) {
// 🔥 核心:位运算取模(等价于 value % shardingCount)
// shardingCount = 4,shardingCount - 1 = 3 = 0b11
// value & 0b11 = 取 value 的低 2 位
int tableIndex = (int) ((shardingCount - 1) & value);
actualTableNames.add(logicTableName + "_" + tableIndex);
return actualTableNames;
}
return allActualSplitTableNames;
}
}
5.2 分库算法解析
/**
* 订单分库算法
*
* 核心逻辑:提取分片基因,做哈希扰动后对分库数量取模
*/
public class DatabaseOrderComplexGeneArithmetic
implements ComplexKeysShardingAlgorithm<Long> {
private int shardingCount; // 分库数量 = 2
private int tableShardingCount; // 分表数量 = 4
@Override
public void init(Properties props) {
this.shardingCount = Integer.parseInt(
props.getProperty("sharding-count"));
this.tableShardingCount = Integer.parseInt(
props.getProperty("table-sharding-count"));
}
@Override
public Collection<String> doSharding(
Collection<String> allActualSplitDatabaseNames,
ComplexKeysShardingValue<Long> complexKeysShardingValue) {
List<String> actualDatabaseNames = new ArrayList<>();
Map<String, Collection<Long>> columnNameAndShardingValuesMap =
complexKeysShardingValue.getColumnNameAndShardingValuesMap();
if (CollectionUtil.isEmpty(columnNameAndShardingValuesMap)) {
return allActualSplitDatabaseNames;
}
// 获取分片键值(与分表算法相同)
Collection<Long> orderNumberValues =
columnNameAndShardingValuesMap.get("order_number");
Collection<Long> userIdValues =
columnNameAndShardingValuesMap.get("user_id");
Long value = null;
if (CollectionUtil.isNotEmpty(orderNumberValues)) {
value = orderNumberValues.stream().findFirst().orElseThrow();
} else if (CollectionUtil.isNotEmpty(userIdValues)) {
value = userIdValues.stream().findFirst().orElseThrow();
}
if (Objects.nonNull(value)) {
// 🔥 计算分库索引
long databaseIndex = calculateDatabaseIndex(
shardingCount, value, tableShardingCount);
String databaseIndexStr = String.valueOf(databaseIndex);
for (String dbName : allActualSplitDatabaseNames) {
if (dbName.contains(databaseIndexStr)) {
actualDatabaseNames.add(dbName);
break;
}
}
return actualDatabaseNames;
}
return allActualSplitDatabaseNames;
}
/**
* 计算分库索引
*
* @param databaseCount 分库数量(2)
* @param splicingKey 分片键值
* @param tableCount 分表数量(4)
*/
public long calculateDatabaseIndex(
Integer databaseCount, Long splicingKey, Integer tableCount) {
// 1. 转换为二进制字符串
String splicingKeyBinary = Long.toBinaryString(splicingKey);
// 2. 计算基因长度 = log2(tableCount) = log2(4) = 2
long replacementLength = log2N(tableCount);
// 3. 截取基因(二进制字符串的最后 N 位)
String geneBinaryStr = splicingKeyBinary.substring(
splicingKeyBinary.length() - (int) replacementLength);
if (StringUtil.isNotEmpty(geneBinaryStr)) {
// 4. 哈希扰动(HashMap 的优化思想)
int h;
int geneOptimizeHashCode =
(h = geneBinaryStr.hashCode()) ^ (h >>> 16);
// 5. 对分库数量取模
return (databaseCount - 1) & geneOptimizeHashCode;
}
throw new DaMaiFrameException(BaseCode.NOT_FOUND_GENE);
}
public long log2N(long count) {
return (long)(Math.log(count) / Math.log(2));
}
}
5.3 HashMap 优化技巧解析
六、业务流程设计
6.1 创建订单流程(基因法核心)
6.2 订单号生成器实现
/**
* 订单号生成器 - 嵌入分片基因
*/
public class OrderNumberGenerator {
// 分表数量(必须是 2 的幂)
private static final int TABLE_COUNT = 4;
// 基因位数 = log2(TABLE_COUNT)
private static final int GENE_BITS = 2;
// 基因掩码
private static final long GENE_MASK = TABLE_COUNT - 1; // 0b11
/**
* 生成订单号
*
* @param userId 用户ID
* @return 嵌入基因的订单号
*/
public static long generate(long userId) {
// 1. 计算分片基因
long gene = userId & GENE_MASK; // 等价于 userId % TABLE_COUNT
// 2. 生成基础序列(雪花算法)
long baseSequence = SnowflakeIdGenerator.nextId();
// 3. 清除基础序列的低位,嵌入基因
// 方式一:位运算(推荐)
long orderNumber = (baseSequence << GENE_BITS) | gene;
// 方式二:乘法(更直观)
// long orderNumber = baseSequence * TABLE_COUNT + gene;
return orderNumber;
}
/**
* 从订单号提取基因(用于验证)
*/
public static long extractGene(long orderNumber) {
return orderNumber & GENE_MASK;
}
/**
* 验证订单号和用户ID的基因是否一致
*/
public static boolean validateGene(long orderNumber, long userId) {
return extractGene(orderNumber) == (userId & GENE_MASK);
}
}
6.3 查询订单流程
七、设计验证与数据分布
7.1 数据分布验证
7.2 路由一致性验证
/**
* 验证基因法的正确性
*/
@Test
public void testGeneConsistency() {
int tableCount = 4;
int dbCount = 2;
for (int i = 0; i < 10000; i++) {
long userId = ThreadLocalRandom.current().nextLong(1, 100000000);
long orderNumber = OrderNumberGenerator.generate(userId);
// 验证分表路由一致性
int tableByOrder = (int) (orderNumber & (tableCount - 1));
int tableByUser = (int) (userId & (tableCount - 1));
assertEquals(tableByOrder, tableByUser,
"分表路由不一致!userId=" + userId);
// 验证分库路由一致性
int dbByOrder = calculateDbIndex(orderNumber, dbCount, tableCount);
int dbByUser = calculateDbIndex(userId, dbCount, tableCount);
assertEquals(dbByOrder, dbByUser,
"分库路由不一致!userId=" + userId);
}
System.out.println("✅ 10000 条数据路由一致性验证通过!");
}
八、与其他服务的设计对比
九、设计思维总结


企业级项目导航:⬅️ 04-分布式分库分表:基因法完全解读 | 05-订单服务分库分表设计思维全景 | ➡️ 01-分库分表-用户服务-用户表
💬 评论