订单服务分库分表设计思维全景

视频

image-d12b16c9

一、需求分析:订单服务的特殊性

1.1 业务场景梳理

订单服务需求分析-139a7621

1.2 实体关系分析

订单服务实体关系-ef6b5683

二、问题建模:订单服务的核心挑战

2.1 双维度高频查询困境

订单服务核心挑战:双维度高频查询-b94c7b7b

2.2 与用户服务的区别

用户服务_vs_订单服务:多维查询对比-0eaf57a2

三、策略选择:为什么选择基因法

3.1 策略对比决策

订单服务策略选择-c87ba386

3.2 基因法原理图解

基因法原理详解-ca3fc828

四、方案设计:订单服务分库分表架构

4.1 整体架构

订单服务分库分表架构-65115870

4.2 分片策略设计

分片策略详解-d46884e8

4.3 绑定表设计

绑定表(Binding_Table)-76b1e18b

五、算法实现深度解析

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;
    }
}
分表算法位运算图解-19acea1d

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));
    }
}
分库算法执行过程图解-0baecb77

5.3 HashMap 优化技巧解析

_HashMap 优化技巧在分片中的应用-b111dc24

六、业务流程设计

6.1 创建订单流程(基因法核心)

创建订单流程-4655f05b

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 查询订单流程

查询订单流程-6728f426

七、设计验证与数据分布

7.1 数据分布验证

数据分布验证-ee7973b9

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 条数据路由一致性验证通过!");
}

八、与其他服务的设计对比

四个服务分库分表设计对比-d8959304

九、设计思维总结

订单服务设计思维总结-b8896279基因法适用场景判断-c6caa9f8


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