0162 雪花分片ID生成算法snowflake

Snowflake算法由Twitter开发,用于解决分布式系统中全局唯一ID的需求。它生成的ID按时间自增排序,且在分布式环境中不发生碰撞。算法采用64位长度,包含时间戳、数据中心ID和工作节点ID,每秒可产生约26万ID。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 提升工作效率利器:

‎Mac App Store 上的“Whale - 任务管理、时间、卡片、高效率”

概述

分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。

有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。

而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移到Cassandra,因为Cassandra没有顺序ID生成机制,所以开发了这样一套全局唯一ID生成服务。

结构

snowflake的结构如下(每部分用-分开):

0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000

第一位为未使用,接下来的41位为毫秒级时间(41位的长度可以使用69年),然后是5位datacenterId和5位workerId(10位的长度最多支持部署1024个节点) ,最后12位是毫秒内的计数(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号)

一共加起来刚好64位,为一个Long型。(转换成字符串后长度最多19)

snowflake生成的ID整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和workerId作区分),并且效率较高。经测试snowflake每秒能够产生26万个ID。

源码

(JAVA版本的源码)

/**
 * Twitter_Snowflake<br>
 * SnowFlake的结构如下(每部分用-分开):<br>
 * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br>
 * 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0<br>
 * 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)
 * 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>
 * 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId<br>
 * 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号<br>
 * 加起来刚好64位,为一个Long型。<br>
 * SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。
 */
public class SnowflakeIdWorker {

    // ==============================Fields===========================================
    /** 开始时间截 (2015-01-01) */
    private final long twepoch = 1420041600000L;

    /** 机器id所占的位数 */
    private final long workerIdBits = 5L;

    /** 数据标识id所占的位数 */
    private final long datacenterIdBits = 5L;

    /** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);

    /** 支持的最大数据标识id,结果是31 */
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);

    /** 序列在id中占的位数 */
    private final long sequenceBits = 12L;

    /** 机器ID向左移12位 */
    private final long workerIdShift = sequenceBits;

    /** 数据标识id向左移17位(12+5) */
    private final long datacenterIdShift = sequenceBits + workerIdBits;

    /** 时间截向左移22位(5+5+12) */
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;

    /** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);

    /** 工作机器ID(0~31) */
    private long workerId;

    /** 数据中心ID(0~31) */
    private long datacenterId;

    /** 毫秒内序列(0~4095) */
    private long sequence = 0L;

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

    //==============================Constructors=====================================
    /**
     * 构造函数
     * @param workerId 工作ID (0~31)
     * @param datacenterId 数据中心ID (0~31)
     */
    public SnowflakeIdWorker(long workerId, long datacenterId) {
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
        }
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    // ==============================Methods==========================================
    /**
     * 获得下一个ID (该方法是线程安全的)
     * @return SnowflakeId
     */
    public synchronized long nextId() {
        long timestamp = timeGen();

        //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
        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) & sequenceMask;
            //毫秒内序列溢出
            if (sequence == 0) {
                //阻塞到下一个毫秒,获得新的时间戳
                timestamp = tilNextMillis(lastTimestamp);
            }
        }
        //时间戳改变,毫秒内序列重置
        else {
            sequence = 0L;
        }

        //上次生成ID的时间截
        lastTimestamp = timestamp;

        //移位并通过或运算拼到一起组成64位的ID
        return ((timestamp - twepoch) << timestampLeftShift) //
                | (datacenterId << datacenterIdShift) //
                | (workerId << workerIdShift) //
                | sequence;
    }

    /**
     * 阻塞到下一个毫秒,直到获得新的时间戳
     * @param lastTimestamp 上次生成ID的时间截
     * @return 当前时间戳
     */
    protected long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    /**
     * 返回以毫秒为单位的当前时间
     * @return 当前时间(毫秒)
     */
    protected long timeGen() {
        return System.currentTimeMillis();
    }

    //==============================Test=============================================
    /** 测试 */
    public static void main(String[] args) {
        SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);
        for (int i = 0; i < 1000; i++) {
            long id = idWorker.nextId();
            System.out.println(Long.toBinaryString(id));
            System.out.println(id);
        }
    }
}

<think>嗯,用户问“雪花算法(Snowflake)生成的是数字吗”,我需要先确认用户对雪花算法的了解程度。可能用户已经听说过这个算法,但不确定其输出形式。首先,我应该明确回答雪花算法生成的确实是数字,通常是64位的整数。 接下来,需要详细解释雪花算法的结构,比如时间戳、工作节点ID、序列号等部分。这部分需要确保用户理解为什么生成ID是数字,以及这些组成部分如何组合成一个唯一的数字ID。 然后,可能需要举例说明生成的数字形式,比如一个长整型数字,如1234567890123456789。同时,要强调数字的优点,比如存储效率高、比较和排序方便,以及在分布式系统中的性能优势。 用户可能有潜在的问题,比如为什么选择数字而不是字符串,或者数字是否足够大以避免重复。这时候需要解释数字的位数(64位)以及各部分的分配,确保在合理配置下ID的唯一性。 另外,可能需要提到雪花算法的应用场景,比如分布式系统生成唯一ID,以及与其他方案(如UUID或数据库自增ID)的对比。这可以帮助用户更好地理解雪花算法的适用场景和优势。 最后,需要检查是否有遗漏点,比如可能的限制或注意事项,例如时钟回拨问题,以及如何解决这些问题。这虽然不是直接回答用户的问题,但能提供更全面的信息,帮助用户更深入地理解雪花算法。</think>**雪花算法(Snowflake)生成的确实是数字**,具体来说是通过64位整数形式表达的全局唯一ID。以下是关键特性解析: --- ### **一、算法结构解析(64位二进制布局)** $$ \underbrace{\text{0}}_{1位} \underbrace{\text{时间戳}}_{41位} \underbrace{\text{工作节点ID}}_{10位} \underbrace{\text{序列号}}_{12位} $$ 1. **时间戳部分** - 41位存储毫秒级时间,可覆盖约 **69年**($2^{41}/(1000 \times 60 \times 60 \times 24 \times 365) ≈ 69.7$年) - 示例:`2023-10-01 00:00:00` 对应时间戳为 `1696089600000` 2. **工作节点ID** - 10位可支持最多 **1024个节点**($2^{10}$) - 典型配置:数据中心ID(5位) + 机器ID(5位) 3. **序列号** - 12位支持单节点每毫秒生成 **4096个ID**($2^{12}$) --- ### **二、数字形式示例** ```python # 典型Snowflake ID的十进制表示 snowflake_id = 1234567890123456789 # 64位整数范围:0 ~ 18,446,744,073,709,551,615 # 转换为二进制观察结构 binary = bin(snowflake_id)[2:].zfill(64) print(f"0{binary[0]} | {binary[1:42]} | {binary[42:52]} | {binary[52:]}") # 输出:0 | 时间戳41位 | 节点ID10位 | 序列号12位 ``` --- ### **三、数字型ID的核心优势** | 特性 | 数字优势 | |---------------------|-------------------------------------------------------------------------| | **存储效率** | 8字节存储(对比UUID的16字节) | | **索引性能** | MySQL中B+树索引数字比字符串快3-5倍 | | **排序能力** | 隐含时间顺序(高位时间戳保证时序性) | | **分布式计算** | 数字分片效率远高于字符串哈希 | | **传输开销** | 网络传输体积减少50%(对比Base64编码的UUID) | --- ### **四、与其他ID形式的对比** | ID类型 | 示例 | 存储字节 | 可读性 | 时序性 | |--------------------|-------------------------|----------|--------|--------| | **Snowflake数字** | 1234567890123456789 | 8 | 低 | 强 | | **UUID字符串** | "550e8400-e29b-41d4..." | 16 | 中 | 无 | | **MongoDB ObjectId**| "507f1f77bcf86cd..." | 12 | 中 | 弱 | | **数据库自增ID** | 1001 | 4/8 | 高 | 无 | --- ### **五、注意事项** 1. **时钟回拨问题** - 若服务器时钟回退可能导致ID重复,需通过NTP同步和本地时钟监控解决 2. **ID溢出风险** - 41位时间戳在初始时间设定为`2020-01-01`时,可安全使用至约 **2089年** 3. **长度限制** - JavaScript等语言处理64位整数时可能丢失精度,建议传输时转为字符串 --- **总结**:雪花算法生成的数字型ID在分布式系统中实现了性能与功能的完美平衡,其数值特性使其成为高并发场景的首选方案,但需要合理配置节点ID和时钟同步策略。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值