0%

分布式唯一id(数据库主键):snowflake算法

简介

项目中大家喜欢用java自带的UUID工具类去生成唯一的数据库主键,这样做的原因是简单。但mysql的innodb存储引擎的主键是聚簇索引(一种索引类型,数据与索引数据放在一起),既然数据和索引数据放在一起,那么在数据插入或者更新的时候,那么在数据插入或者更新的时候,我们需要找到要插入的位置,再把数据写到特定的位置上,这就产生了随机的 IO,而innodb存储引擎采用的是B+树的结果存储索引,一旦发生了页分裂,就不可避免会做数据的移动,也会极大地损耗写入性能,但是如果使用mysql的自增,则在分库分表下无法保证主键id的唯一性,因此我们采用twitter的snowflake算法来生成分布式的唯一id。

snowflake算法

snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。41为的时间戳大概可以生成69年((2^41)/1000/60/60/24/365),10bit的工作机器id可以支持1024(2^10)台机器,序列号支持1毫秒产生4096(2^12)个自增序列id
snowflake

算法java实现

Twitter在github上提供了scala的实现版本,以下版本是根据scala转换成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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
package com.liu;
public class IdWorker{

private long workerId;
private long datacenterId;
private long sequence;

//开始时间,比如我想从2000-1-1开始生成日期就填入2000-1-1的时间
private long twepoch = 1288834974657L;
/**
*
* @param workerId 机器id(占用5比特,最大为31)
* @param datacenterId 机房id(占用5比特)
* @param sequence (12位序列号起始编号)
* long twepoch 参数开始时间,比如我想从2000-1-1开始生成日期就填入2000-1-1的时间
*/
public IdWorker(long workerId, long datacenterId, long sequence){
// sanity check for workerId
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));
}
System.out.printf("worker starting. timestamp left shift %d, datacenter id bits %d, worker id bits %d, sequence bits %d, workerid %d",
timestampLeftShift, datacenterIdBits, workerIdBits, sequenceBits, workerId);

this.workerId = workerId;
this.datacenterId = datacenterId;
this.sequence = sequence;
}


private long workerIdBits = 5L;
private long datacenterIdBits = 5L;
private long maxWorkerId = -1L ^ (-1L << workerIdBits);
private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
private long sequenceBits = 12L;

private long workerIdShift = sequenceBits;
private long datacenterIdShift = sequenceBits + workerIdBits;
private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
private long sequenceMask = -1L ^ (-1L << sequenceBits);

private long lastTimestamp = -1L;

public long getWorkerId(){
return workerId;
}

public long getDatacenterId(){
return datacenterId;
}

public long getTimestamp(){
return System.currentTimeMillis();
}

public synchronized long nextId() {
long timestamp = timeGen();

if (timestamp < lastTimestamp) {
System.err.printf("clock is moving backwards. Rejecting requests until %d.", 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 = 0;
}

lastTimestamp = timestamp;
return ((timestamp - twepoch) << timestampLeftShift) |
(datacenterId << datacenterIdShift) |
(workerId << workerIdShift) |
sequence;
}

private long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}

private long timeGen(){
return System.currentTimeMillis();
}

//---------------测试---------------
public static void main(String[] args) {
IdWorker worker = new IdWorker(3,1,1);
for (int i = 0; i < 30; i++) {
System.out.println(worker.nextId());
}
}

}