分库分表

如果用 userId 分表,要用merchantId 查询的话,如何查询?

维护索引表:用一张单独的索引表存储 merchantId -> userId,查询时先查索引表拿到 userId

自己手写实现 一致性哈希分表

一、核心原理(3 句话记住)

  1. userId 哈希成一个 0 ~ 2^32-1 的整数
  2. 分表(表名后缀 0、1、2、3...) 也哈希后放到哈希环
  3. userId 顺时针找最近的表 → 得到表序号(0、1、2...)

关键:加表只影响少量数据

二、手写实现一致性哈希(完整可复制)

结构

  1. 哈希环(排序的 Map)
  2. 物理节点(真实表:0、1、2、3)
  3. 虚拟节点(让数据分布更均匀)
  4. 路由方法:输入 userId → 输出表序号

完整代码

import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 自己实现 一致性哈希分表(单库多表)
 * 作用:userId -> 表序号(0、1、2、3...)
 */
public class ConsistentHashTableRouter {

    // 哈希环:key=哈希值,value=节点(真实节点+虚拟节点)
    private final SortedMap<Long, Integer> hashRing = new TreeMap<>();

    // 虚拟节点数量(让数据分布更均匀,推荐 10~20)
    private static final int VIRTUAL_NODE_NUM = 10;

    // 真实表数量(如 4 张表:0、1、2、3)
    private final int tableCount;

    // 构造器:初始化哈希环
    public ConsistentHashTableRouter(int tableCount) {
        this.tableCount = tableCount;
        initRing();
    }

    // 初始化环:加入真实表 + 虚拟节点
    private void initRing() {
        for (int tableIdx = 0; tableIdx < tableCount; tableIdx++) {
            // 加入真实节点
            long realHash = hash("real-node-" + tableIdx);
            hashRing.put(realHash, tableIdx);

            // 加入虚拟节点
            for (int v = 0; v < VIRTUAL_NODE_NUM; v++) {
                long virtualHash = hash("virtual-node-" + tableIdx + "-" + v);
                hashRing.put(virtualHash, tableIdx);
            }
        }
    }

    // 哈希函数(MurmurHash 风格,简单版)
    private long hash(String key) {
        byte[] bytes = key.getBytes(StandardCharsets.UTF_8);
        long h = 0;
        for (byte b : bytes) {
            h = (h * 31 + (b & 0xFF)) & 0xFFFFFFFFL;
        }
        return h;
    }

    // 核心路由:userId -> 表序号
    public int getTableIndex(long userId) {
        String key = String.valueOf(userId);
        long userHash = hash(key);

        // 获取比 userHash 大的所有环节点
        SortedMap<Long, Integer> tailMap = hashRing.tailMap(userHash);

        if (tailMap.isEmpty()) {
            // 环尾 → 回到环头
            return hashRing.get(hashRing.firstKey());
        }

        // 顺时针取第一个
        return tailMap.get(tailMap.firstKey());
    }

    // 测试
    public static void main(String[] args) {
        // 4 张表
        ConsistentHashTableRouter router = new ConsistentHashTableRouter(4);

        long userId = 10086L;
        int tableIndex = router.getTableIndex(userId);

        // 输出:t_order_2
        System.out.println("路由到表:t_order_" + tableIndex);
    }
}

三、这个代码能干什么?

你传入 userId

它直接返回 表序号

例如:

userId=10086 → 表 2
userId=12345 → 表 0
userId=999 → 表 3

最终表名:

"t_order_" + 表序号

四、扩容(加表)怎么操作?

原来 4 张表 → 现在 5 张表

  1. 新建表 t_order_4
  2. 创建新的 ConsistentHashTableRouter (5)
  3. 把新环加载
  4. 迁移 1/5 数据(只需要迁移很少)

90% 的 userId 路由不变!

全部评论

相关推荐

评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务