分布式id生成器 SnowFlake

分布式id生成器

简单讲解下二进制

1.1 二进制概念

1
2
3

二进制是计算技术中广泛采用的一种数制。二进制数据是用0和1两个数码来表示的数。 它的基数为2,进位规则是“逢二进一”,借位规则是“借一当二”,由18世纪德国数理哲学大师莱布尼兹发现。 当前的计算机系统使用的基本上是二进制系统,数据在计算机中主要是以补码的形式存储的。 计算机中的二进制则是一个非常微小的开关,用“开”来表示1,“关”来表示0。

1.2 运算法则

1
2
3
4
5

二进制的运算算术运算 二进制的加法:0+0=0,0+1=1 ,1+0=1, 1+1=10(向高位进位);例:7=111;10=1010;3=11 二进制的减法:0-0=0,0-1=1(向高位借位) 1-0=1,1-1=0 (模二加运算或异或运算) ; 二进制的乘法:0 * 0 = 0; 0 * 1 = 0; 1 * 0 = 0; 1 * 1 = 1; 二进制的除法:0÷0 = 0,0÷1 = 0,1÷0 = 0 (无意义),1÷1 = 1 ;

逻辑运算二进制的或运算:遇1得1 二进制的与运算:遇0得0 二进制的非运算:各位取反。

1.3 位

1
2
3

数据存储的最小单位。每个二进制数字0或者1就是1个位;

1.4 字节

1
2
3
4
5
6
7
8
9
10
11

8个位构成一个字节;即:1 byte (字节)= 8 bit(位);

1 KB = 1024 B(字节);

1 MB = 1024 KB; (2^10 B)

1 GB = 1024 MB; (2^20 B)

1 TB = 1024 GB; (2^30 B)

1.5 字符

1
2
3
4
5

一般 utf-8 编码下,一个汉字 字符 占用 3 个 字节;

一般 gbk 编码下,一个汉字 字符 占用 2 个 字节;

1.6 java语音的8大基本数据类型

1
2
3
4
5
6
7
8
9
10
11
12

| 数据类型 | 字节长度(单位:byte) | 位长度(单位:bit) |
| -------- | -------------------- | ------------------ |
| int | 4 | 32 |
| byte | 1 | 8 |
| short | 2 | 16 |
| long | 8 | 64 |
| float | 4 | 32 |
| double | 8 | 64 |
| boolean | 1 | 8 |
| char | 2 | 16 |

1.7 二进制原码、反码、补码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

对于有符号数而言:

​ (1)二进制的最高位是符号位:0表示正数,1表示负数

​ (2)正数的原码、反码、补码都一样;

​ (3)负数的反码 = 它的原码符号位不变,其他位取反(0 ->1 ; 1->0 );

​ (4)负数的补码 = 它的反码 +1;

​ (5)0的反码、补码都是0;

​ (6)在计算机运算的时候,都是以补码的方式来运算的;

1.8 二进制转十进制

1
2
3
4
5
6
7

要从右到左用二进制的每个数去乘以2的相应次方(次方要从0开始算起);

假如:[二进制](http://baike.baidu.com/view/18536.htm)数1101转化成[十进制](http://baike.baidu.com/view/359301.htm) ,那么 1101 = 1*20+0*21+1*22+1*23 = 1+0+4+8 = 13;

注意:任何数的0[次方](http://baike.baidu.com/view/1477879.htm)都是1。

2.位运算符:左移、右移

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

\>> :右移 最高位是0,左边补齐0;最高为是1,左边补齐1

<< :左移 左边最高位丢弃,右边补齐0

\>>>:无符号右移 无论最高位是0还是1,左边补齐0

在数字没有溢出的前提下,对于正数和负数,左移一位都相当于乘以2的1次方,左移n位就相当于乘以2的n次方

右移一位相当于除2,右移n位相当于除以2的n次方。

12>>1 结果:6    12/2^1
12>>2 结果:3    12/2^2



12<<1 结果 :24 ` 12x2^1
12<<2 结果 :48    12x2^2

——————————————————-

在高并发或者分表分库情况下怎么保证数据id的幂等性

经常用到的解决方案有以下几种。

微软公司通用唯一识别码(UUID) Twitter公司雪花算法(SnowFlake) 基于数据库的id自增 对id进行缓存

这里主要谈snowflake算法:

snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号,最后还有一个符号位,永远是0。 snowflake算法所生成的ID结构

整个结构是64位,所以我们在Java中可以使用long来进行存储。 该算法实现基本就是二进制操作,单机每秒内理论上最多可以生成1024*(2^12),也就是409.6万个ID(1024 X 4096 = 4194304)

0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 * 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0

  • 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截) 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L 60 60 24 * 365) = 69

  • 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId

  • 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号

  • 加起来刚好64位,为一个Long型。

  • SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。

SnowFlake算法的优点:

1.生成ID时不依赖于DB,完全在内存生成,高性能高可用。 2.ID呈趋势递增,后续插入索引树的时候性能较好。

2.所有生成的id按时间趋势递增 整个分布式系统内不会产生重复id(因为有datacenterId和workerId来做区分)

SnowFlake算法的缺点:

依赖于系统时钟的一致性。如果某台机器的系统时钟回拨,有可能造成ID冲突,或者ID乱序

算法代码如下:

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135


public class SnowflakeIdWorker {
// ==============================Fields==================
/** 开始时间截 (2019-08-06) */
private final long twepoch = 1565020800000L;

/** 机器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);
}
}
}


快速使用Snowflake算法的方式:

引入hutool依赖

1
2
3
4
5
6
7
8
9
10
11

<dependency>

​ <groupId>cn.hutool</groupId>

<artifactId>hutool-captcha</artifactId>

​ <version>${hutool.version}</version>

</dependency>

ID生成器

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


public class IdGenerator {

private long workerId = 0;

@PostConstruct
void init() {
try {
workerId = NetUtil.ipv4ToLong(NetUtil.getLocalhostStr());
log.info("当前机器 workerId: {}", workerId);
} catch (Exception e) {
log.warn("获取机器 ID 失败", e);
workerId = NetUtil.getLocalhost().hashCode();
log.info("当前机器 workerId: {}", workerId);
}
}

/**
* 获取一个批次号,形如 2019071015301361000101237
* <p>
* 数据库使用 char(25) 存储
*
* @param tenantId 租户ID,5 位
* @param module 业务模块ID,2 位
* @return 返回批次号
*/
public synchronized String batchId(int tenantId, int module) {
String prefix = DateTime.now().toString(DatePattern.PURE_DATETIME_MS_PATTERN);
return prefix + tenantId + module + RandomUtil.randomNumbers(3);
}

@Deprecated
public synchronized String getBatchId(int tenantId, int module) {
return batchId(tenantId, module);
}

/**
* 生成的是不带-的字符串,类似于:b17f24ff026d40949c85a24f4f375d42
*
* @return
*/
public String simpleUUID() {
return IdUtil.simpleUUID();
}

/**
* 生成的UUID是带-的字符串,类似于:a5c8a5e8-df2b-4706-bea4-08d0939410e3
*
* @return
*/
public String randomUUID() {
return IdUtil.randomUUID();
}

private Snowflake snowflake = IdUtil.createSnowflake(workerId, 1);

public synchronized long snowflakeId() {
return snowflake.nextId();
}

public synchronized long snowflakeId(long workerId, long dataCenterId) {
Snowflake snowflake = IdUtil.createSnowflake(workerId, dataCenterId);
return snowflake.nextId();
}

/**
* 生成类似:5b9e306a4df4f8c54a39fb0c
* <p>
* ObjectId 是 MongoDB 数据库的一种唯一 ID 生成策略,
* 是 UUID version1 的变种,详细介绍可见:服务化框架-分布式 Unique ID 的生成方法一览。
*
* @return
*/
public String objectId() {
return ObjectId.next();
}

}


测试类

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


public class IdGeneratorTest {

@Autowired
private IdGenerator idGenerator;

@Test
public void testBatchId() {
for (int i = 0; i < 100; i++) {
String batchId = idGenerator.batchId(1001, 100);
log.info("批次号: {}", batchId);
}
}

@Test
public void testSimpleUUID() {
for (int i = 0; i < 100; i++) {
String simpleUUID = idGenerator.simpleUUID();
log.info("simpleUUID: {}", simpleUUID);
}
}

@Test
public void testRandomUUID() {
for (int i = 0; i < 100; i++) {
String randomUUID = idGenerator.randomUUID();
log.info("randomUUID: {}", randomUUID);
}
}

@Test
public void testObjectID() {
for (int i = 0; i < 100; i++) {
String objectId = idGenerator.objectId();
log.info("objectId: {}", objectId);
}
}

@Test
public void testSnowflakeId() {
ExecutorService executorService = Executors.newFixedThreadPool(20);
for (int i = 0; i < 20; i++) {
executorService.execute(() -> {
log.info("分布式 ID: {}", idGenerator.snowflakeId());
});
}
executorService.shutdown();
}

}



本文转自:https://juejin.im/post/5d8882d8f265da03e369c063