know-engine

基于雪花算法生成唯一的chunkId

项目中,需要针对文档记录分段id,尤其是parentChunkId,需要保证唯一才行,不然会路由错误。我们选择使用雪花算法。 以下是ai帮我生成的雪花算法,基本上没啥问题,用了个双重校验锁的单例,还解决了时钟回拨的问题:

TL;DR

项目中,需要针对文档记录分段id,尤其是parentChunkId,需要保证唯一才行,不然会路由错误。我们选择使用雪花算法。 以下是ai帮我生成的雪花算法,基本上没啥问题,用了个双重校验锁的单例,还解决了时钟回拨的问题:

项目中,需要针对文档记录分段id,尤其是parentChunkId,需要保证唯一才行,不然会路由错误。我们选择使用雪花算法。 以下是ai帮我生成的雪花算法,基本上没啥问题,用了个双重校验锁的单例,还解决了时钟回拨的问题:

package cn.hollis.llm.mentor.know.engine.infra.snowflake;

/**
 * 雪花算法ID生成器
 * 生成的ID是64位长整型,具有以下结构:
 * - 1位符号位(始终为0)
 * - 41位时间戳(毫秒级,可使用约69年)
 * - 10位工作机器ID(0-1023)
 * - 12位序列号(毫秒内自增,每毫秒可生成4096个ID)
 *
 * @author Hollis
 */
public class SnowflakeIdGenerator {

    /**
     * 起始时间戳 (2024-01-01 00:00:00)
     */
    private static final long EPOCH = 1704038400000L;

    /**
     * 机器ID所占的位数
     */
    private static final long WORKER_ID_BITS = 10L;

    /**
     * 序列号所占的位数
     */
    private static final long SEQUENCE_BITS = 12L;

    /**
     * 机器ID的最大值
     */
    private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);

    /**
     * 序列号的最大值
     */
    private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);

    /**
     * 机器ID左移位数
     */
    private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;

    /**
     * 时间戳左移位数
     */
    private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;

    /**
     * 工作机器ID
     */
    private final long workerId;

    /**
     * 序列号
     */
    private long sequence = 0L;

    /**
     * 上次生成ID的时间戳
     */
    private long lastTimestamp = -1L;

    /**
     * 单例实例
     */
    private static volatile SnowflakeIdGenerator instance;

    /**
     * 私有构造函数
     *
     * @param workerId 工作机器ID (0-1023)
     */
    private SnowflakeIdGenerator(long workerId) {
        if (workerId < 0 || workerId > MAX_WORKER_ID) {
            throw new IllegalArgumentException(
                    String.format("workerId must be between 0 and %d", MAX_WORKER_ID));
        }
        this.workerId = workerId;
    }

    /**
     * 获取单例实例
     *
     * @return SnowflakeIdGenerator实例
     */
    public static SnowflakeIdGenerator getInstance() {
        if (instance == null) {
            synchronized (SnowflakeIdGenerator.class) {
                if (instance == null) {
                    // 默认使用进程ID作为workerId
                    long workerId = getWorkerId();
                    instance = new SnowflakeIdGenerator(workerId);
                }
            }
        }
        return instance;
    }

    /**
     * 获取工作机器ID
     * 默认使用进程ID的低10位
     *
     * @return 工作机器ID
     */
    private static long getWorkerId() {
        try {
            String processName = java.lang.management.ManagementFactory.getRuntimeMXBean().getName();
            if (processName != null && processName.contains("@")) {
                String pid = processName.split("@")[0];
                return Long.parseLong(pid) & MAX_WORKER_ID;
            }
        } catch (Exception e) {
            // 忽略异常,使用默认值
        }
        return 1L;
    }

    /**
     * 生成下一个ID(线程安全)
     *
     * @return 唯一ID
     */
    public synchronized long nextId() {
        long timestamp = System.currentTimeMillis();

        // 时钟回拨检测
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(
                    String.format("Clock moved backwards. Refusing to generate id for %d milliseconds",
                            lastTimestamp - timestamp));
        }

        // 同一毫秒内
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & SEQUENCE_MASK;
            // 序列号溢出,等待下一毫秒
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            // 新的毫秒,序列号重置为0
            sequence = 0L;
        }

        lastTimestamp = timestamp;

        // 组装ID
        return ((timestamp - EPOCH) << TIMESTAMP_SHIFT)
                | (workerId << WORKER_ID_SHIFT)
                | sequence;
    }

    /**
     * 阻塞到下一毫秒
     *
     * @param lastTimestamp 上次生成ID的时间戳
     * @return 当前时间戳
     */
    private long tilNextMillis(long lastTimestamp) {
        long timestamp = System.currentTimeMillis();
        while (timestamp <= lastTimestamp) {
            timestamp = System.currentTimeMillis();
        }
        return timestamp;
    }

    /**
     * 生成下一个ID(字符串形式)
     *
     * @return 唯一ID字符串
     */
    public String nextIdStr() {
        return String.valueOf(nextId());
    }
}

用法如下:

版本提示

模型、框架与接口会持续变化。涉及版本号、参数与生产配置时,请在实践前对照对应官方文档。

LLMentor系统化学习大模型应用工程

内容来自个人课程知识库备份,并经过结构化整理。技术版本持续演进,生产使用前请结合官方文档验证。