# 雪花算法
**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.

#### 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