分布式Id生成器
2024/6/3...大约 3 分钟
分布式Id生成器
Id生成的几种方式
- 数据库自增 - 优势 - 简单,无需额外操作
- 保持定长自增
- 保持单表唯一性
 
- 劣势 - 主键生成依赖数据库,高并发下会造成数据库服务器压力较大
- 水平扩展困难,在分布式数据库环境下,无法保证唯一性
 
 
- 优势 
- UUID(GUID) - 优势 - 本地生成,无需远程调用(无需网络通信)
- 全局唯一
- 水平扩展好
 
- 劣势 - ID 128 bits,占用空间大
- 字符串类型,索引效率低
- 无法保证趋势递增
 
 
- 优势 
- 时间戳 - 略 
- Twitter Snowflake 雪花算法 - 优势 - 本地生产,无需远程调用(无需网络通信)
- 单机每秒可生成400w个ID
- ID 64 bits,占用空间小
- long类型,对索引效率有提升
- 趋势递增
 
- 劣势 - 时间回拨问题
- 集群部署时,集群内机器时间同步问题
 
 
- 优势 
基于雪花算法的分布式Id生成器实现
package com.ibi.ptd.aps.core.utils;
import java.util.Calendar;
/**
 * 该生成器采用 Twitter Snowflake 算法实现,生成 64 Bits 的 Long 型编号
 * <pre>
 *     1 bit        41 bits     10 bits     12 bits
 *     sign bit     时间戳       工作ID       序列号ID
 * </pre>
 *
 * @author YL
 */
public final class SnowflakeKeyGenerator {
    /**
     * 基准时间:时间偏移量
     */
    private static final long EPOCH;
    static {
        // 从2017年01月01日零点开始
        Calendar calendar = Calendar.getInstance();
        calendar.set(2017, Calendar.JANUARY, 1);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        EPOCH = calendar.getTimeInMillis();
    }
    /**
     * 序列号ID位数
     */
    private static final long SEQUENCE_BITS = 12L;
    /**
     * 工作ID位数
     */
    private static final long WORKER_ID_BITS = 10L;
    /**
     * 序列号ID最大值
     */
    private static final long SEQUENCE_MASK = (1 << SEQUENCE_BITS) - 1;
    /**
     * 工作ID最大值
     */
    private static final long WORKER_ID_MAX_VALUE = 1L << WORKER_ID_BITS;
    /**
     * 工作ID左移12位
     */
    private static final long WORKER_ID_LEFT_SHIFT_BITS = SEQUENCE_BITS;
    /**
     * 时间戳左移22数
     */
    private static final long TIMESTAMP_LEFT_SHIFT_BITS = WORKER_ID_LEFT_SHIFT_BITS + WORKER_ID_BITS;
    /**
     * 序列号ID
     */
    private long sequence;
    /**
     * 序列号ID偏移量
     */
    private byte sequenceOffset;
    /**
     * 最后生成编号时间戳,单位:毫秒
     */
    private long lastTime;
    /**
     * 工作ID
     */
    private long workerId;
    public SnowflakeKeyGenerator(long workerId) {
        if (workerId >= WORKER_ID_MAX_VALUE || workerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0",
                    WORKER_ID_MAX_VALUE));
        }
        this.workerId = workerId;
    }
    /**
     * 生成 ID
     *
     * @return 返回@{@link Long}类型的Id
     */
    @Override
    public synchronized long nextId() {
        // 保证当前时间大于最后时间。时间回退会导致产生重复id
        long currentMillis = System.currentTimeMillis();
        if (currentMillis < this.lastTime) {
            throw new IllegalArgumentException(String.format("clock moved backwards.Refusing to generate id for %d " +
                            "milliseconds",
                    (this.lastTime - currentMillis)));
        }
        // 获取序列号
        if (this.lastTime == currentMillis) {
            // 当获得序号超过最大值时,归0,并去获得新的时间
            if (0L == (this.sequence = ++this.sequence & SEQUENCE_MASK)) {
                currentMillis = tailNextMillis(currentMillis);
            }
        } else {
            // 1、在跨毫秒时,序列号总是归0,导致序列号为0的ID较多,导致生成的ID取模后不均匀
            // this.sequence = 0;
            // 2、序列号取[0-9]之间的随机数,可以初步解决【1】中的问题,也会导致ID取模不均匀
            // this.sequence = new SecureRandom().nextInt(10);
            // 3、交替使用[0-1]
            this.sequence = vibrateSequenceOffset();
        }
        // 设置最后时间戳
        this.lastTime = currentMillis;
        // 生成编号
        return ((currentMillis - EPOCH) << TIMESTAMP_LEFT_SHIFT_BITS)
                | (this.workerId << WORKER_ID_LEFT_SHIFT_BITS)
                | this.sequence;
    }
    /**
     * 不停获得时间,直到大于最后时间
     * <p>
     * 从 Snowflake 的官方文档 (https://github.com/twitter/snowflake/#system-clock-dependency) 中也可以看到, 它明确要求 "You should
     * use NTP to keep your system clock accurate". 而且最好把 NTP 配置成不会向后调整的模式. 也就是说, NTP 纠正时间时, 不会向后回拨机器时钟.
     * ntpd 和 ntpdate 的区别,使用 ntpd 影响不大
     * todo 如果时间回拨,会导致这个逻辑等待,等待时间可能会很长
     * </p>
     *
     * @param lastTime 最后时间
     *
     * @return 时间
     */
    private long tailNextMillis(final long lastTime) {
        long time = System.currentTimeMillis();
        while (time <= lastTime) {
            time = System.currentTimeMillis();
        }
        return time;
    }
    /**
     * 只会交替返回0和1
     */
    private byte vibrateSequenceOffset() {
        this.sequenceOffset = (byte) (~this.sequenceOffset & 1);
        return this.sequenceOffset;
    }
}