使用雪花算法生成ID

雪花算法

雪花算法(Snowflake)是一种生成分布式全局唯一ID的算法,生成的ID称为Snowflake IDs或snowflakes。这种算法由Twitter创建,并用于推文的ID。DiscordInstagram等其他公司采用了修改后的版本。

格式

一个Snowflake ID有64位元。前41位是时间戳,表示了自选定的时期以来的毫秒数。 接下来的10位代表计算机ID,防止冲突。 其余12位代表每台机器上生成ID的序列号,这允许在同一毫秒内创建多个Snowflake ID。最后以十进制将数字序列化。

SnowflakeID基于时间生成,故可以按时间排序。此外,一个ID的生成时间可以由其自身推断出来,反之亦然。该特性可以用于按时间筛选ID,以及与之联系的对象

范例

2022年六月由@Wikipedia所发的一条推文的雪花ID是1541815603606036480。这个数字被转换成二进制就是0b 0001 0101 0110 0101 1010 0001 0001 1111 0110 0010 00|01 0111 1010|0000 0000 0000,其中以竖线分隔成三个部分。

  • 最前面41位元(加上1位元的0)转换成十进制为367597485448。将这个值加上推特纪年1288834974657(UNIX时间毫秒),这条推特的UNIX时间为1656432460.105:即世界协调时间2022年6月28日16:07:40.105。
  • 中间10位元01 0111 1010是机器ID。
  • 后面12位元解码出来全部为0,代表这条推文是该机器于这一微秒所发出的第一条推文。

Java实现代码

import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.LockSupport;

/**
 * @Description 使用雪花算法生成随机ID
 * @Author hyx
 * @Date 2023-07-31
 */
public class SnowflakeIdGenerator {

    /**
     * 定义起始时间戳, UTC+8:00 2023-07-31 00:00:00
     */
    private static final long START_TIMESTAMP = 1690732800L;

    // 定义每个部分的bit数
    /**
     * 序列号占用的bit数,用于表示在同一毫秒内生成的序列号。
     */
    private static final long SEQUENCE_BIT = 12;
    /**
     * 工作机器ID占用的bit数,用于表示当前工作机器的唯一ID。
     */
    private static final long WORKER_BIT = 10;
    /**
     * 数据中心ID占用的bit数,用于表示当前数据中心的唯一ID。
     */
    private static final long DATACENTER_BIT = 10;

    // 定义每个部分的最大值
    /**
     * 定义了数据中心ID所能表示的最大值。它是一个位运算的结果,将二进制表示的最低DATACENTER_BIT位全部置为1,从而得到最大的数据中心ID。
     */
    private static final long MAX_DATACENTER_NUM = ~(-1L << DATACENTER_BIT);
    /**
     * 定义了工作机器ID所能表示的最大值,用法类似于MAX_DATACENTER_NUM。
     */
    private static final long MAX_WORKER_NUM = ~(-1L << WORKER_BIT);
    /**
     * 定义了序列号所能表示的最大值,用法类似于MAX_DATACENTER_NUM。
     */
    private static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);

    // 定义每个部分向左的位移数
    /**
     * 定义了工作机器ID向左的位移数,用于将工作机器ID占用的bit位移动到正确的位置。
     */
    private static final long WORKER_LEFT = SEQUENCE_BIT;
    /**
     * 定义了数据中心ID向左的位移数,用法类似于WORKER_LEFT。
     */
    private static final long DATACENTER_LEFT = SEQUENCE_BIT + WORKER_BIT;
    /**
     * 定义了时间戳向左的位移数,用法类似于WORKER_LEFT。
     */
    private static final long TIMESTAMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;

    /**
     * 表示当前数据中心的唯一ID
     */
    private final long DATA_CENTER_ID;
    /**
     * 表示当前工作机器的唯一ID
     */
    private final long WORKER_ID;
    /**
     * 用AtomicLong来表示序列号,用于记录在同一毫秒内生成的序列号。初始值为0,每次生成ID时会自增,并通过& MAX_SEQUENCE操作保证在规定位数内循环。
     */
    private final AtomicLong SEQUENCE = new AtomicLong(0L);
    /**
     * 用于记录上次生成ID的时间戳,初始值为-1L。在生成ID时,会检查当前时间戳与上次时间戳的大小关系,以保证生成的ID是单调递增的。
     */
    private volatile long lastTimestamp = -1L;

    /**
     * 构造函数
     *
     * @param datacenterId 数据中心ID
     * @param workerId     工作机器ID
     */
    public SnowflakeIdGenerator(long datacenterId, long workerId) {
        if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
            throw new IllegalArgumentException("数据中心ID不能大于 " + MAX_DATACENTER_NUM + " 或小于 0");
        }
        if (workerId > MAX_WORKER_NUM || workerId < 0) {
            throw new IllegalArgumentException("工作机器ID不能大于 " + MAX_WORKER_NUM + " 或小于 0");
        }
        this.DATA_CENTER_ID = datacenterId;
        this.WORKER_ID = workerId;
        SEQUENCE.set(0L);
    }

    /**
     * 生成ID
     *
     * @return long
     */
    public synchronized long nextId() {
        long currentTimestamp = getTimestamp();

        if (currentTimestamp < lastTimestamp) {
            throw new RuntimeException("时钟向后移动。拒绝为 " + (lastTimestamp - currentTimestamp) + " ms生成ID。");
        }

        if (currentTimestamp == lastTimestamp) {
            long seq = SEQUENCE.getAndIncrement() & MAX_SEQUENCE;
            if (seq == 0) {
                // 当前毫秒的序列号已用完,等待下一毫秒
                currentTimestamp = getNextTimestamp(lastTimestamp);
                SEQUENCE.set(0L);
            }
        } else {
            SEQUENCE.set(0L);
        }

        lastTimestamp = currentTimestamp;

        // 将各个部分拼接为最终的ID
        return ((currentTimestamp - START_TIMESTAMP) << TIMESTAMP_LEFT) |
                (DATA_CENTER_ID << DATACENTER_LEFT) |
                (WORKER_ID << WORKER_LEFT) |
                SEQUENCE.get();
    }

    /**
     * 获取当前时间戳
     *
     * @return long
     */
    private long getTimestamp() {
        // 使用System.nanoTime()获取更高精度的时间戳
        long nanoTime = System.nanoTime();
        // 将纳秒级的时间戳转换为毫秒级,并加上起始时间戳
        long timeStamp = (nanoTime / 1000000L) + START_TIMESTAMP;
        // 返回当前时间戳和上次时间戳中较大的一个,以保证生成的ID是单调递增的
        return Math.max(timeStamp, lastTimestamp);
    }

    /**
     * 等待下一毫秒
     *
     * @param lastTimestamp 最后一次生成ID的时间戳
     * @return long
     */
    private long getNextTimestamp(long lastTimestamp) {
        long currentTimestamp = getTimestamp();
        while (currentTimestamp <= lastTimestamp) {
            // 等待一纳秒,避免循环空转,导致CPU资源浪费
            LockSupport.parkNanos(1L);
            currentTimestamp = getTimestamp();
        }
        return currentTimestamp;
    }

    // 示例用法
    public static void main(String[] args) {
        SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator(0, 1);

        for (int i = 0; i < 100; i++) {
            long id = idGenerator.nextId();
            System.out.println("Generated ID" + i + ": " + id);
        }
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值