分布式主键解决方案之--Snowflake雪花算法

229 阅读4分钟
对于分布式系统环境,主键ID的设计很关键,什么自增intID那些是绝对不用的,比较早的时候,大部分系统都用UUID/GUID来作为主键优点是方便又能解决问题,缺点是插入时因为UUID/GUID的不规则导致每插入一条数据就需要重新排列一次,性能低下;也有人提出用UUID/GUID转long的方式,可以很明确的告诉你,这种方式long不能保证唯一,大并发下会有重复long出现,所以也不可取,这个主键设计问题曾经是很多公司系统设计的一个头疼点,所以大部分公司愿意牺牲一部分性能而直接采用简单粗暴的UUID/GUID来作为分布式系统的主键;
  twitter开源了一个snowflake算法,俗称雪花算法;就是为了解决分布式环境下生成不同ID的问题;该算法会生成19位的long型有序数字,MySQL中用bigint来存储(bigint长度为20位);该算法应该是目前分布式环境中主键ID最好的解决方案之一了;

1--snowflake雪花算法实现

  好,废话不多说,直接上算法实现
[url=][/url]

1
package
com.anson;
2
3
import
java.lang.management.ManagementFactory;
4
import
java.net.InetAddress;
5
import
java.net.NetworkInterface;
6
7
//
雪花算法代码实现
8
public
class
IdWorker {
9
//
时间起始标记点,作为基准,一般取系统的最近时间(一旦确定不能变动)
10
private
final
static
long
twepoch = 1288834974657L;
11
//
机器标识位数
12
private
final
static
long
workerIdBits = 5L;
13
//
数据中心标识位数
14
private
final
static
long
datacenterIdBits = 5L;
15
//
机器ID最大值
16
private
final
static
long
maxWorkerId = -1L ^ (-1L << workerIdBits);
17
//
数据中心ID最大值
18
private
final
static
long
maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
19
//
毫秒内自增位
20
private
final
static
long
sequenceBits = 12L;
21
//
机器ID偏左移12位
22
private
final
static
long
workerIdShift = sequenceBits;
23
//
数据中心ID左移17位
24
private
final
static
long
datacenterIdShift = sequenceBits + workerIdBits;
25
//
时间毫秒左移22位
26
private
final
static
long
timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
27
28
private
final
static
long
sequenceMask = -1L ^ (-1L << sequenceBits);
29
/*
上次生产id时间戳
*/
30
private
static
long
lastTimestamp = -1L;
31
//
0,并发控制
32
private
long
sequence = 0L;
33
34
private
final
long
workerId;
35
//
数据标识id部分
36
private
final
long
datacenterId;
37
38
public
IdWorker(){
39
this
.datacenterId = getDatacenterId(maxDatacenterId);
40
this
.workerId = getMaxWorkerId(datacenterId, maxWorkerId);
41
}
42
/**
43
*
@param
workerId
44
* 工作机器ID
45
*
@param
datacenterId
46
* 序列号
47
*/
48
public
IdWorker(
long
workerId,
long
datacenterId) {
49
if
(workerId > maxWorkerId || workerId < 0) {
50
throw
new
IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
51
}
52
if
(datacenterId > maxDatacenterId || datacenterId < 0) {
53
throw
new
IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
54
}
55
this
.workerId = workerId;
56
this
.datacenterId = datacenterId;
57
}
58
/**
59
* 获取下一个ID
60
*
61
*
@return
62
*/
63
public
synchronized
long
nextId() {
64
long
timestamp = timeGen();
65
if
(timestamp < lastTimestamp) {
66
throw
new
RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
67
}
68
69
if
(lastTimestamp == timestamp) {
70
//
当前毫秒内,则+1
71
sequence = (sequence + 1) & sequenceMask;
72
if
(sequence == 0) {
73
//
当前毫秒内计数满了,则等待下一秒
74
timestamp = tilNextMillis(lastTimestamp);
75
}
76
}
else
{
77
sequence = 0L;
78
}
79
lastTimestamp = timestamp;
80
//
ID偏移组合生成最终的ID,并返回ID
81
long
nextId = ((timestamp - twepoch) << timestampLeftShift)
82
| (datacenterId << datacenterIdShift)
83
| (workerId << workerIdShift) | sequence;
84
85
return
nextId;
86
}
87
88
private
long
tilNextMillis(
final
long
lastTimestamp) {
89
long
timestamp =
this
.timeGen();
90
while
(timestamp <= lastTimestamp) {
91
timestamp =
this
.timeGen();
92
}
93
return
timestamp;
94
}
95
96
private
long
timeGen() {
97
return
System.currentTimeMillis();
98
}
99
100
/**
101
* <p>
102
* 获取 maxWorkerId
103
* </p>
104
*/
105
protected
static
long
getMaxWorkerId(
long
datacenterId,
long
maxWorkerId) {
106
StringBuffer mpid =
new
StringBuffer();
107
mpid.append(datacenterId);
108
String name = ManagementFactory.getRuntimeMXBean().getName();
109
if
(!name.isEmpty()) {
110
/*
111
* GET jvmPid
112
*/
113
mpid.append(name.split("@")[0]);
114
}
115
/*
116
* MAC + PID 的 hashcode 获取16个低位
117
*/
118
return
(mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
119
}
120
121
/**
122
* <p>
123
* 数据标识id部分
124
* </p>
125
*/
126
protected
static
long
getDatacenterId(
long
maxDatacenterId) {
127
long
id = 0L;
128
try
{
129
InetAddress ip = InetAddress.getLocalHost();
130
NetworkInterface network = NetworkInterface.getByInetAddress(ip);
131
if
(network ==
null
) {
132
id = 1L;
133
}
else
{
134
byte
[] mac = network.getHardwareAddress();
135
id = ((0x000000FF & (
long
) mac[mac.length - 1])
136
| (0x0000FF00 & (((
long
) mac[mac.length - 2]) << 8))) >> 6;
137
id = id % (maxDatacenterId + 1);
138
}
139
}
catch
(Exception e) {
140
System.out.println(" getDatacenterId: " + e.getMessage());
141
}
142
return
id;
143
}
144
}
[url=]
[/url]



3--测试

[url=]
[/url]

package
com.anson;
/**
* @description: TODO *
@author
: anson * @Date: 2019/10/7 22:16 *
@version
: 1.0
*/
public
class
snow{
public
static
void
main(String[] args)
throws
Exception {
try
{ IdWorker idw =
new
IdWorker(1,1);
long
ids = idw.nextId();
for
(
int
i=0;i<10000;i++) { ids = idw.nextId(); System.out.println(ids); } }
catch
(Exception ex) { } }}
[url=]
[/url]


结果如下图:


程序生成了19位的有序数字,这个既解决了分布式ID生成唯一性问题,也解决了性能问题,建议系统ID设计都采用该算法生成。