Twitter的分散式自增ID演算法snowflake(有改動Java版)
阿新 • • 發佈:2019-02-13
分散式ID生成器
全域性唯一ID生成
分散式純數字ID
其實這也不是Twitter獨有的,mongodb也採用類似的方法生產自增ID。對於全域性唯一ID的說明請參考我另一篇文章 : 高併發分散式環境中獲取全域性唯一ID[分散式資料庫全域性唯一主鍵生成]
該演算法最大的好處就是:純數字;基本有序遞增
為了簡單起見,我這裡對snowflake演算法進行了一點點修改,修改後的格式為:41位時間戳 |10位程序號 |12位計數器
。共計63位(為什麼不是64位:第一位是符號位
加鎖實現
具體邏輯情況先忙程式碼中的註釋:
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 64位生成規則【首位是符號位,代表正副,所以實際有效是63位】:
* <p>
* 41位時間戳 |10位程序號 |12位計數器
* <p>
* 41位時間戳:
* 注意:這裡沒有直接使用時間戳,因為直接使用的話只能用到2039-09-07 23:47:35
* 這裡採用(當前時間戳-初始時間戳)。最多這樣可以使用69年【(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69】
* 10位機器:最多1023臺機器
* 12位計數器:每毫秒最多生成4095條記錄
* <p>
* 這裡可以根據專案中實際情況,調整每個位置的長度,比如分散式叢集機器數量比較少,可以縮減機器的位數,增加計數器位數
* <p>
* <p>
* 使用注意事項:1、必須關閉時鐘同步;2、currentTimeMillis一經定義,不可修改;3、併發量太高的時候,超過了4095,未做處理;4、最大不超過64位
*<p>
* 自測效能:一秒能有三四十萬的資料產生。
*<p>
* 無鎖沒寫出來......
*<p>
* Created by hzliubenlong on 2017/6/28.
*/
public class IDGeneratorUtil {
/**
* 初始時間戳:2017-1-1 0:0:0
* 一經定義,不可修改
*/
private static final long initTimeMillis = 1483200000929L;
/**
* 程序。
* 這裡也可以手動指定每臺例項的ID號;或者通過ZK的臨時遞增節點自動獲取。
* 固定值
*/
private static final int pid = 3;
/**
* 計數器
* 需要保證執行緒安全
*/
private static volatile long counter;
private static volatile long currentTimeMillis = System.currentTimeMillis() - initTimeMillis;
private static volatile long lastTimeMillis = currentTimeMillis;
/**
* 無鎖模式暫時沒有寫出來
*
* @return
*/
public static synchronized long nextId() {
long series = counter++;
if (series >= (1 << 12) - 1) {//單位毫秒內計時器滿了,需要重新計時
while (lastTimeMillis == currentTimeMillis) {//等待到下一秒
currentTimeMillis = System.currentTimeMillis() - initTimeMillis;
}
lastTimeMillis = currentTimeMillis;
counter = 0;
series = counter++;
}
return (currentTimeMillis << 22) | (pid << 12) | series;
}
}
測試程式碼:
static Set<Long> set = new ConcurrentSkipListSet<>();
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
fixedThreadPool.execute(() -> {
while (true) {
long l = nextId();
boolean add = set.add(l);
if (!add) {
System.out.println(System.currentTimeMillis() + " " + l + " : " + Long.toBinaryString(l));
System.exit(0);
}
}
});
}
這種加鎖的實現,自測,每秒鐘可以產生30W條記錄。足夠了
.
時間回撥,時鐘同步問題
snowflake演算法時間回撥問題思考
由於存在時間回撥問題,但是他又是那麼快和簡單,我們思考下是否可以解決呢? 零度在網上找了一圈沒有發現具體的解決方案,但是找到了一篇美團不錯的文章:Leaf——美團點評分散式ID生成系統(https://tech.meituan.com/MT_Leaf.html)文章很不錯,可惜並沒有提到時間回撥如何具體解決。下面看看零度的一些思考:
分析時間回撥產生原因
第一:人物操作,在真實環境一般不會有那個傻逼幹這種事情,所以基本可以排除。
第二:由於有些業務等需要,機器需要同步時間伺服器(在這個過程中可能會存在時間回撥,查了下我們伺服器一般在10ms以內(2小時同步一次))。
上面也說了,這個演算法存在一個問題就是時鐘同步的問題,尤其時鐘回撥。百度的uid-generator給出了一種解決辦法,大家可以直接拿來使用: https://github.com/baidu/uid-generator