我是靠谱客的博主 香蕉月饼,最近开发中收集的这篇文章主要介绍snowflake主键生成策略1.snowflake简介2.snowflake算法原理,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

1.snowflake简介

在分布式系统中,我们需要各种各样的ID,既然是ID那么必然是要保证全局唯一,除此之外,不同当业务还需要不同的特性,比如像并发巨大的业务要求ID生成效率高,吞吐大;比如某些银行类业务,需要按每日日期制定交易流水号;又比如我们希望用户的ID是随机的,无序的,纯数字的,且位数长度是小于10位的。等等,不同的业务场景需要的ID特性各不一样,于是,衍生了各种ID生成器,但大多数利用数据库控制ID的生成,性能受数据库并发能力限制,那么有没有一款不需要依赖任何中间件(如数据库,分布式缓存服务等)的ID生成器呢?本着取之于开源,用之于开源的原则,今天,特此介绍Twitter开源的一款分布式自增ID算法snowflake,并附上算法原理推导和演算过程!

snowflake算法是一款本地生成的(ID生成过程不依赖任何中间件,无网络通信),保证ID全局唯一,并且ID总体有序递增,性能每秒生成300w+。

Snowflake是我见过生成唯一主键id最快的方法,它是生成的是一个64位的数字,其中42位时间戳,接下来10位是自定义的数,其作用就是区分集群中的所有机器,最后12位是毫秒内序列,集群内每个机器能够在1毫秒内生成2^12 - 1个ID

2.snowflake算法原理

snowflake生产的ID二进制结构表示如下(每部分用-分开):

0 - 00000000 00000000 00000000 00000000 00000000 0 - 00000 - 00000 - 00000000 0000

第一位未使用,接下来的41位为毫秒级时间(41位的长度可以使用69年,从1970-01-01 08:00:00),然后是5位datacenterId(最大支持2^5=32个,二进制表示从00000-11111,也即是十进制0-31),和5位workerId(最大支持2^5=32个,原理同datacenterId),所以datacenterId*workerId最多支持部署1024个节点,最后12位是毫秒内的计数(12位的计数顺序号支持每个节点每毫秒产生2^12=4096个ID序号).

所有位数加起来共64位,恰好是一个Long型(转换为字符串长度为18).

单台机器实例,通过时间戳保证前41位是唯一的,分布式系统多台机器实例下,通过对每个机器实例分配不同的datacenterId和workerId避免中间的10位碰撞。最后12位每毫秒从0递增生产ID,再提一次:每毫秒最多生成4096个ID,每秒可达4096000个。理论上,只要CPU计算能力足够,单机每秒可生产400多万个,实测300w+,效率之高由此可见。下面是一个demo,通过测试可以直接使用。


1 package com.test;

2

3 import java.util.ArrayList;

4 import java.util.List;

5

6 public class SnowflakeIdWorker {

7

8
// ==============================Fields===========================================

9
/** 开始时间截 (2015-01-01) */
 10
private final long twepoch = 1420041600000L;
 11
 12
/** 机器id所占的位数 */
 13
private final long workerIdBits = 5L;
 14
 15
/** 数据标识id所占的位数 */
 16
private final long datacenterIdBits = 5L;
 17
 18
/** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */
 19
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
 20
 21
/** 支持的最大数据标识id,结果是31 */
 22
private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
 23
 24
/** 序列在id中占的位数 */
 25
private final long sequenceBits = 12L;
 26
 27
/** 机器ID向左移12位 */
 28
private final long workerIdShift = sequenceBits;
 29
 30
/** 数据标识id向左移17位(12+5) */
 31
private final long datacenterIdShift = sequenceBits + workerIdBits;
 32
 33
/** 时间截向左移22位(5+5+12) */
 34
private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
 35
 36
/** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */
 37
private final long sequenceMask = -1L ^ (-1L << sequenceBits);
 38
 39
/** 工作机器ID(0~31) */
 40
private long workerId;
 41
 42
/** 数据中心ID(0~31) */
 43
private long datacenterId;
 44
 45
/** 毫秒内序列(0~4095) */
 46
private long sequence = 0L;
 47
 48
/** 上次生成ID的时间截 */
 49
private long lastTimestamp = -1L;
 50
 51
//==============================Constructors=====================================
 52
/**
 53 
* 构造函数
 54 
* @param workerId 工作ID (0~31)
 55 
* @param datacenterId 数据中心ID (0~31)
 56
*/
 57
public SnowflakeIdWorker(long workerId, long datacenterId) {
 58
if (workerId > maxWorkerId || workerId < 0) {
 59
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
 60 
}
 61
if (datacenterId > maxDatacenterId || datacenterId < 0) {
 62
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
 63 
}
 64
this.workerId = workerId;
 65
this.datacenterId = datacenterId;
 66 
}
 67
 68
// ==============================Methods==========================================
 69
/**
 70 
* 获得下一个ID (该方法是线程安全的)
 71 
* @return SnowflakeId
 72
*/
 73
public synchronized long nextId() {
 74
long timestamp = timeGen();
 75
 76
//如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
 77
if (timestamp < lastTimestamp) {
 78
throw new RuntimeException(
 79
String.format("Clock moved backwards.
Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
 80 
}
 81
 82
//如果是同一时间生成的,则进行毫秒内序列
 83
if (lastTimestamp == timestamp) {
 84
sequence = (sequence + 1) & sequenceMask;
 85
//毫秒内序列溢出
 86
if (sequence == 0) {
 87
//阻塞到下一个毫秒,获得新的时间戳
 88
timestamp = tilNextMillis(lastTimestamp);
 89 
}
 90 
}
 91
//时间戳改变,毫秒内序列重置
 92
else {
 93
sequence = 0L;
 94 
}
 95
 96
//上次生成ID的时间截
 97
lastTimestamp = timestamp;
 98
 99
//移位并通过或运算拼到一起组成64位的ID
100
return ((timestamp - twepoch) << timestampLeftShift) //
101
| (datacenterId << datacenterIdShift) //
102
| (workerId << workerIdShift) //
103
| sequence;
104 
}
105
106
/**
107 
* 阻塞到下一个毫秒,直到获得新的时间戳
108 
* @param lastTimestamp 上次生成ID的时间截
109 
* @return 当前时间戳
110
*/
111
protected long tilNextMillis(long lastTimestamp) {
112
long timestamp = timeGen();
113
while (timestamp <= lastTimestamp) {
114
timestamp = timeGen();
115 
}
116
return timestamp;
117 
}
118
119
/**
120 
* 返回以毫秒为单位的当前时间
121 
* @return 当前时间(毫秒)
122
*/
123
protected long timeGen() {
124
return System.currentTimeMillis();
125 
}
126
127
//==============================Test=============================================
128
/** 测试 */
129
public static void main(String[] args) {
130
SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);
131
List<String> list = new ArrayList<String>();
132
for (int i = 0; i < 100000; i++) {
133
long id = idWorker.nextId();
134 136
String string =new String().valueOf(id);
137 
list.add(string);
138 
}
139
String str1 ="";
140
String str2 ="";
141
for(int j=0;j<100000;j++){
142
str1=list.get(j);
143
for(int n =0;n<100000;n++){
144
str2=list.get(n);
145
if(j==n){
146
break;
147 
}
148
if(str1.equals(str2)){
149
System.out.println("存在相等的id
"+str1+"------->
" +str2);
150 
}
151 
}
152 
}
153
System.out.println("测试成功");
154 
}
155 }

 

转载于:https://www.cnblogs.com/shaoshuai95928/p/shaoshuai.html

最后

以上就是香蕉月饼为你收集整理的snowflake主键生成策略1.snowflake简介2.snowflake算法原理的全部内容,希望文章能够帮你解决snowflake主键生成策略1.snowflake简介2.snowflake算法原理所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(52)

评论列表共有 0 条评论

立即
投稿
返回
顶部