1. 程式人生 > >Twitter的分散式自增ID演算法snowflake(有改動Java版)

Twitter的分散式自增ID演算法snowflake(有改動Java版)

分散式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