# 雪花算法 **Repository Path**: xxacker/snowflake ## Basic Information - **Project Name**: 雪花算法 - **Description**: 雪花算法源码 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-03-30 - **Last Updated**: 2023-03-30 ## Categories & Tags **Categories**: Uncategorized **Tags**: 雪花算法, Java ## README # 雪花算法 #### 1. 介绍 雪花算法是 “推特” 公司采用的一种算法,目的是在分布式系统中,产生全局唯一、并且递增的 ID. ![输入图片说明](images/%E9%9B%AA%E8%8A%B1%E7%AE%97%E6%B3%95%E7%94%9F%E6%88%90%20ID%20.png) #### 2. 组成部分(64bit) 1. 第一位 占用 1bit, 其值始终是 0 ,没有实际作用。 2. 时间戳 占用 41bit, 精确到毫秒,总共可以容纳约 69 年的时间。 3. 工作机器 id 占用 10bit, 其中高位 5bit 是数据中心 ID,低位 5bit 是工作节点 ID,做多可以容纳 1024 个节点。 4. 序列号 占用 12bit, 每个节点每毫秒 0 开始不断累加,最多可以累加到 4095,一共可以产生 4096 个 ID。 SnowFlake 算法在同一毫秒内最多可以生成多少个全局唯一 ID 呢: 同一毫秒的ID数量 = 1024 X 4096 = 4194304(419万个) #### 3. Java 实现 ``` black public class SnowFlake { /** * 起始的时间戳 */ private final static long START_STMP = 1480166465631L; /** * 每一部分占用的位数 */ private final static long SEQUENCE_BIT = 12; //序列号占用的位数 private final static long MACHINE_BIT = 5; //机器标识占用的位数 private final static long DATACENTER_BIT = 5;//数据中心占用的位数 /** * 每一部分的最大值 */ /** 数据中心数量 -- 用位运算计算出最大支持的数据中心数量: 31 */ private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT); /** 机器数量 -- 用位运算计算出最大支持的机器数量: 31 */ private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT); /** 序列号数量 -- 用位运算计算出 12 位能存储的最大正整数: 4095 */ /*-1L == 1111 1111 -1L << 12 也就是: 1111 1111 0000 0000 0000 0000 -1L 异或运算 0^0 和 1^1=0 1^0 和 0^1=1 1111 1111 0000 0000 0000 0000 A 1111 1111 1111 1111 1111 1111 ------------------------------------------------------ 0000 0000 1111 1111 1111 1111 换算成十进制:4095*/ private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT); /** * 每一部分向左的位移 */ /** 机器标识较序列号的偏移量 12位 */ private final static long MACHINE_LEFT = SEQUENCE_BIT; /** 数据中心较机器标识的偏移量 17位 */ private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT; /** 时间戳较数据中心的偏移量 */ private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT; private long datacenterId; //数据中心 private long machineId; //机器标识 private long sequence = 0L; //序列号 private long lastStmp = -1L;//上一次时间戳 public static void main(String[] args) { SnowFlake snowFlake = new SnowFlake(2, 3); // << :位移运算服, <<左移运算,>>右移运算,还有不带符号的位移运算 >>> // 1 << 12:首先把1转为二进制数字 0000 0000 0000 0000 0000 0000 0000 0001 // 然后将上面的二进制数字向左移动 12 位后面补 0 得到 0010 0000 0000 0000 0000 0000 0000 0000 // 最后将得到的二进制数字转回对应类型的十进制 System.out.println(1 << 12); for (int i = 0; i < (1 << 12); i++) { System.out.println(snowFlake.nextId()); } } /** * @param datacenterId * @param machineId */ public SnowFlake(long datacenterId, long machineId) { if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) { throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0(数据中心 ID 不能大于 MAX_DATACENTER_NUM 或小于 0)"); } if (machineId > MAX_MACHINE_NUM || machineId < 0) { throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0(机器 ID 不能大于 MAX_MACHINE_NUM 或小于 0)"); } this.datacenterId = datacenterId; this.machineId = machineId; } /** * 产生下一个ID */ public synchronized long nextId() { // 获取当前的时间戳 long currStmp = getNewstmp(); // 如果当前时间戳小于上次时间戳,则抛出异常 if (currStmp < lastStmp) { throw new RuntimeException("Clock moved backwards. Refusing to generate id(时钟回拨。拒绝生成 id)"); } // 如果当前时间戳等于上次的时间戳,说明是在同一毫秒内调用的雪花算法 if (currStmp == lastStmp) { //相同毫秒内,序列号自增 /* & 算法:1 & 1 =1,其余况都返回 0 (相同为1,其他为0) 4095:0000 0000 1111 1111 1111 1111 & 4096:0000 0001 0000 0000 0000 0000 ------------------------------------ 等于:0000 0000 0000 0000 0000 0000 */ sequence = (sequence + 1) & MAX_SEQUENCE; //同一毫秒的序列数已经达到最大 4095 if (sequence == 0L) { // 获取下一毫秒的时间戳给到当前时间戳 currStmp = getNextMill(); } } else { //不同毫秒内,序列号置为0 sequence = 0L; } // 当前时间戳存档记录,用于下次产生 id 时对比,是否为相同时间戳 lastStmp = currStmp; return (currStmp - START_STMP) << TIMESTMP_LEFT // 时间戳部分 22 | datacenterId << DATACENTER_LEFT // 数据中心部分 17 | machineId << MACHINE_LEFT // 机器标识部分 12 | sequence; // 序列号部分 } /** * @return 获取当前时间戳 */ private long getNewstmp() { return System.currentTimeMillis(); } /** * @return 获取下一时间的时间戳 */ private long getNextMill() { long mill = getNewstmp(); while (mill <= lastStmp) { mill = getNewstmp(); } return mill; } } ``` #### 4. 总结 ##### 考察意图: 雪花算法是一个生成全局唯一 ID 的算法,它主要出现在分库分表场景中,作为业务主键,或者作为订单ID 这一类的 ID生成器。 ##### 问题分析: 雪花算法一般是用来实现全局唯一ID 的业务主键,解决分库分表之后,主键ID 的唯一性问题;
在实际应用中,我们的 主键ID 除了全局唯一,还需要满足有序递增、高性能、高可用,以及带有时间戳,或带有其他的一些特征。
然而,雪花算法就是比较符合这一类特征的全局唯一算法。

它是一个通过 64个bit 位组成的一个 Long类型 的数字,它由 4部分 组成: * 第一部分用一个 bit位 来表示一个符号位,一般情况下就是 0 * 第二部分用 41个bit位 来表示时间戳,这个时间是系统时间的毫秒数 * 第三部分用 10个bit位 来表示工作机器id,这样就可以保证在多个服务器上生成 id 的唯一性;如果是在分机房的情况下,我们还可以将这 10个bit位 分成两部分,前 5个bit 用来表示机房id,后面 5个bit 用来表示机器id。 * 第四部分用 12个bit位 来表示一个递增序列,用来实现同毫秒内,产生不同 ID 的能力。
雪花算法就是通过这四个部分的规则,生成对应 bit位 的一个数据,然后再组装到一起,形成一个全局唯一id