1、前言
snowflake算法是Twitter技術團隊在2010年開源的分佈式ID的生成算法,後續美團和百度都相應的根據該算法進行了改進,並且開源了其分佈式ID生成算法。本文將詳細介紹snowflake算法的數據結構以及其工作原理
2、數據結構
snowflake算法生成的分佈式ID是一個一個64bit大小的整數,其結構如下:
- 1位,佔位符不用,我們知道一般第一位表示正負數的符號,所以固定位爲0
- 41位,用來記錄時間的時間戳(毫秒)
41位可以表示的最大的數字爲,其可以使用的最長的時間爲
年 - 10位,工作機器ID,可以部署在個結點,其中5位爲workerId,5位爲datacenterId
- 12位,序列號,用來記錄同毫秒內產生的ID,可以表示個序列號
3、代碼詳解
Twitter的官方是用Scala寫的,在這裏我改成了Java版
public class IdWorker {
//worker ID
private long workerId;
/**
* 數據中心ID
*/
private long datacenterId;
/**
* 系統起始時間
* 2010/11/4 9:42:54
*/
private long twepoch = 1288834974657L;
/**
* workerId位數
*/
private long workerIdBits = 5L;
/**
* datacenter位數
*/
private long datacenterIdBits = 5L;
/**
* 最大的workerId 0x1f = 31
*/
private long maxWorkerId = -1L ^ (-1L << workerIdBits);
/**
* 最大的數據中心ID 0x1f = 31
*/
private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
/**
* 序列位數
*/
private long sequenceBits = 12L;
/**
* workerId位移 12位
*/
private long workerIdShift = sequenceBits;
/**
* datacenter位移 17位
*/
private long datacenterIdShift = sequenceBits + workerIdBits;
/**
* 時間位移 22位
*/
private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
/**
* 序列掩碼,便於求餘
*/
private long sequenceMask = -1L ^ (-1L << sequenceBits);
private long lastTimestamp = -1L;
private long sequence = 0L;
public IdWorker(Long workerId, Long datacenterId) {
if (workerId > maxWorkerId || workerId < 0) {
System.out.println("worker Id can't be greater than %d or less than 0");
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
System.out.println("datacenter Id can't be greater than %d or less than 0");
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
private Long getWorkerId() {
return workerId;
}
private Long getDataCenterId() {
return datacenterId;
}
private Long getTimestamp() {
return System.currentTimeMillis();
}
public synchronized Long getId() {
//獲取當前時間戳
Long timestamp = getTimeGen();
if (timestamp < lastTimestamp) {
System.out.println("error,clock is moving backwards. Rejecting requests until ");
}
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
//阻塞到下一個毫秒
timestamp = getNextMills(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
//整合時間戳-數據中心ID-workerID-序列號
return ((timestamp - twepoch) << timestampLeftShift) |
(datacenterId << datacenterIdShift) |
(workerId << workerIdShift) |
sequence;
}
/**
* 阻塞到下一個毫秒
* @param lastTimestamp
* @return
*/
protected Long getNextMills(Long lastTimestamp) {
Long timestamp = getTimeGen();
while (timestamp <= lastTimestamp) {
timestamp = getTimeGen();
}
return timestamp;
}
protected Long getTimeGen() {
return System.currentTimeMillis();
}
}
該算法的整體思路如下:
- 獲取ID的時候,先獲取當前的時間戳,和上一個時間戳比較,如果小,則產生了時鐘回撥,錯誤。
- 如果時間戳相等則序列號加1和序列號最大值求餘,當序列號爲0的時候則阻塞到下個毫秒
- 如果時間戳大於上一個時間戳,則序列號賦值爲0
- 通過位運算生成分佈式ID結果