分布式 ID 生成方案。
雪花算法的核心思想是将一个64位的ID分成多个部分,每个部分代表不同的含义。
具体分为四个部分:
第一部分,占用 1 个 bit,其值永远是 0 ,因为 ID 永远都是正数 0 表示正数。
第二部分是时间戳,占用 41 个 bit ,单位为毫秒,最大可以容纳约 69 年的时间,这里的时间戳不会从 1970 年开始计算,我们可以其从任意时间开始计算。
第三部分是工作机器的 ID ,占用 10 个 bit,高五位为数据中心 ID,低五位为工作节点 ID 最多可以容纳 1024 个节点。
第四部分为序列号,占用 12 个 bit,用来记录同毫秒内产生的不同 ID 。每个节点每毫秒从 0 开始累加,最多可以累加到 4095,同一毫秒最多可以产生 4096 个 ID。
实现逻辑如下:
首先实例化我们的节点对象,实例化参数分别为集群中统一使用的时间和节点的 ID 值。
因为存在并发的情况,所以生成 ID 的操作必须使用锁。
生成 ID 时,比对当前时间和记录的时间是否一致,如果一致则序列号+1。如果序列号满了则等待下一毫秒。如果不一致则序列号保持为 0。
记录新的时间戳。
拼接三段二进制的数据,使用位移和或运算拼接。
返回生成的 ID。
使用 Java 语言简单实现该算法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 package com.example.demo.snowflake;public class Snowflake { private long baseTimestamp = 0L ; private long lastTimestamp = 0L ; private long workerID = 0L ; private long sequence = 0L ; private static final long MAX_SEQUENCE = 4096L ; private static final long SEQUENCE_BITS = 12L ; private static final long WORKER_ID_BITS = 10L ; private static final long MAX_WORKER_ID = 1024L ; private static final long WORKER_ID_SHIFT = SEQUENCE_BITS; private static final long TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS; public Snowflake (long baseTimestamp, long workerID) { if (System.currentTimeMillis() < baseTimestamp) { throw new RuntimeException ("Base timestamp must be in the past" ); } if (workerID < 0 || workerID > MAX_WORKER_ID) { throw new RuntimeException ("Worker ID must be between 0 and " + MAX_WORKER_ID); } this .baseTimestamp = baseTimestamp; this .workerID = workerID; } public synchronized long generateID () { long now = System.currentTimeMillis(); long timestamp = now - this .baseTimestamp; if (timestamp <= this .lastTimestamp) waitUntilClockCatchUp(this .lastTimestamp); if (timestamp == this .lastTimestamp) { this .sequence = (this .sequence + 1 ) & MAX_SEQUENCE; if (this .sequence == 0 ) { try { Thread.sleep(1 ); } catch (InterruptedException e) { e.printStackTrace(); throw new RuntimeException (e); } now = System.currentTimeMillis(); timestamp = now - this .baseTimestamp; } } else { this .sequence = 0L ; } this .lastTimestamp = timestamp; return (timestamp << TIMESTAMP_LEFT_SHIFT) | (this .workerID << WORKER_ID_SHIFT) | this .sequence; } private void waitUntilClockCatchUp (long lastTimestamp) { long currentTimestamp; while ((currentTimestamp = System.currentTimeMillis() - this .baseTimestamp) <= lastTimestamp) { long sleepTime = lastTimestamp - currentTimestamp + 1 ; try { Thread.sleep(sleepTime); } catch (InterruptedException e) { e.printStackTrace(); throw new RuntimeException (e); } } } }