--- title: "05-订单服务分库分表设计思维全景" created: 2025-12-11 aliases: - 订单服务分库分表设计思维全景 tags: - 项目 --- # 订单服务分库分表设计思维全景 ## 视频 ![[image-d12b16c9.png]] ## **一、需求分析:订单服务的特殊性** ### **1.1 业务场景梳理** ![[订单服务需求分析-139a7621.jpg]] ### **1.2 实体关系分析** ![[订单服务实体关系-ef6b5683.jpg]] ## **二、问题建模:订单服务的核心挑战** ### **2.1 双维度高频查询困境** ![[订单服务核心挑战:双维度高频查询-b94c7b7b.jpg]] ### **2.2 与用户服务的区别** ![[用户服务_vs_订单服务:多维查询对比-0eaf57a2.jpg]] ## **三、策略选择:为什么选择基因法** ### **3.1 策略对比决策** ![[订单服务策略选择-c87ba386.jpg]] ### **3.2 基因法原理图解** ![[基因法原理详解-ca3fc828.jpg]] ## **四、方案设计:订单服务分库分表架构** ### **4.1 整体架构** ![[订单服务分库分表架构-65115870.jpg]] ### **4.2 分片策略设计** ![[分片策略详解-d46884e8.jpg]] ### **4.3 绑定表设计** ![[绑定表(Binding_Table)-76b1e18b.jpg]] ## **五、算法实现深度解析** ### **5.1 分表算法解析** ```java /** * 订单分表算法 * * 核心逻辑:使用 order_number 或 user_id 对分表数量取模 * 由于基因法保证了两者低位相同,所以结果一致 */ public class TableOrderComplexGeneArithmetic implements ComplexKeysShardingAlgorithm { private int shardingCount; // 分表数量 = 4 @Override public void init(Properties props) { // 从配置读取分表数量 shardingCount = Integer.parseInt( props.getProperty("sharding-count")); } @Override public Collection doSharding( Collection allActualSplitTableNames, ComplexKeysShardingValue complexKeysShardingValue) { List actualTableNames = new ArrayList<>(); String logicTableName = complexKeysShardingValue.getLogicTableName(); // 获取查询条件中的分片键值 Map> columnNameAndShardingValuesMap = complexKeysShardingValue.getColumnNameAndShardingValuesMap(); // 如果没有分片键条件,返回所有表(全路由) if (CollectionUtil.isEmpty(columnNameAndShardingValuesMap)) { return allActualSplitTableNames; } // 优先使用 order_number,其次使用 user_id Collection orderNumberValues = columnNameAndShardingValuesMap.get("order_number"); Collection 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; } } ``` ![[分表算法位运算图解-19acea1d.jpg]] ### **5.2 分库算法解析** ```java /** * 订单分库算法 * * 核心逻辑:提取分片基因,做哈希扰动后对分库数量取模 */ public class DatabaseOrderComplexGeneArithmetic implements ComplexKeysShardingAlgorithm { 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 doSharding( Collection allActualSplitDatabaseNames, ComplexKeysShardingValue complexKeysShardingValue) { List actualDatabaseNames = new ArrayList<>(); Map> columnNameAndShardingValuesMap = complexKeysShardingValue.getColumnNameAndShardingValuesMap(); if (CollectionUtil.isEmpty(columnNameAndShardingValuesMap)) { return allActualSplitDatabaseNames; } // 获取分片键值(与分表算法相同) Collection orderNumberValues = columnNameAndShardingValuesMap.get("order_number"); Collection 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)); } } ``` ![[分库算法执行过程图解-0baecb77.jpg]] ### **5.3 HashMap 优化技巧解析** ![[_HashMap 优化技巧在分片中的应用-b111dc24.jpg]] ## **六、业务流程设计** ### **6.1 创建订单流程(基因法核心)** ![[创建订单流程-4655f05b.jpg]] ### **6.2 订单号生成器实现** ```java /** * 订单号生成器 - 嵌入分片基因 */ 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 查询订单流程** ![[查询订单流程-6728f426.jpg]] ## **七、设计验证与数据分布** ### **7.1 数据分布验证** ![[数据分布验证-ee7973b9.jpg]] ### **7.2 路由一致性验证** ```java /** * 验证基因法的正确性 */ @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 条数据路由一致性验证通过!"); } ``` ## **八、与其他服务的设计对比** ![[四个服务分库分表设计对比-d8959304.jpg]] ## **九、设计思维总结** ![[订单服务设计思维总结-b8896279.jpg]]![[基因法适用场景判断-c6caa9f8.jpg]] --- **企业级项目导航**:⬅️ [[04-分布式分库分表:基因法完全解读|04-分布式分库分表:基因法完全解读]] | 05-订单服务分库分表设计思维全景 | ➡️ [[01-分库分表-用户服务-用户表|01-分库分表-用户服务-用户表]]