1. 程式人生 > >每秒生成一千萬個【可視有序】分散式ID的簡單方案

每秒生成一千萬個【可視有序】分散式ID的簡單方案

去年做了一個產品,會經常匯入匯出大量的外部資料,這些資料的ID有的是GUID型別,有的是字串,也有的是自增。GUID型別沒有順序,結果要排序得藉助其它業務欄位,整體查詢效率比較低;字串ID本來是用來轉換GUID的或者數字ID的,結果有些字串ID不符合規範,常常有特殊資料需要處理;自增主鍵ID的資料匯入合併經常有衝突。

為了避免GUID主鍵的“索引頁分裂”問題,提高查詢效率,同時為了解決分散式環境下的資料匯入合併問題,強烈需要一種分散式的,有序的ID生成方案。我參考了雪花ID(Twitter-Snowflake,64位自增ID演算法)實現方案,設計一個更容易肉眼觀察數值連續有序的分散式ID方案。

跟雪花ID方案一樣,都是使用時間資料做為生成ID的基礎,不同的在於對資料的具體處理方式。另外,為了確保每臺機器ID的不同,可以配置指定此ID,在應用程式配置檔案中如下配置:

<!--分散式ID標識,3位整數,範圍101-999 大小--> 
<add key="SOD_MachineID" value="101"/>

如果不配置分散式ID,預設將根據當前機器IP隨機生成3位分散式機器ID。

該演算法的實現比雪花演算法簡單不少,詳細的不多說,先直接看程式碼:

        /// <summary>
        /// 獲取一個新的有序GUID整數
        
/// </summary> /// <param name="dt">當前時間</param> /// <param name="haveMs">是否包含毫秒,如果不包含,將使用3位隨機數代替</param> /// <returns></returns> protected internal static long InnerNewSequenceGUID(DateTime dt, bool haveMs) { //執行緒安全的自增並且不超過最大值10000
int countNum = System.Threading.Interlocked.Increment(ref SeqNum); if (countNum >= 10000) { while (Interlocked.Exchange(ref signal, 1) != 0)//加自旋鎖 { //黑魔法 } //進入臨界區 if (SeqNum >= 10000) { SeqNum = 0; //達到1萬個數後,延遲10毫秒,重新取系統時間,避免重複 Thread.Sleep(10); dt = DateTime.Now; } countNum = System.Threading.Interlocked.Increment(ref SeqNum); //離開臨界區 Interlocked.Exchange(ref signal, 0); //釋放鎖 } //日期以 2017.3.1日為基準,計算當前日期距離基準日期相差的天數,可以使用20年。 //日期部分使用4位數字表示 int days = (int)dt.Subtract(baseDate).TotalDays; //時間部分表示一天中所有的秒數,最大為 86400秒,共5位 //日期時間總位數= 4(日期)+5(時間)+3(毫秒)=12 int times = dt.Second + dt.Minute * 60 + dt.Hour * 3600; //long 型別最大值 9223 3720 3685 4775 807 //可用隨機位數= 19-12=7 long datePart = ((long)days + 1000) * 1000 * 1000 * 1000 * 100; long timePart = (long)times * 1000 * 1000; long msPart = 0; if (haveMs) { msPart = (long)dt.Millisecond ; } else { msPart = new Random().Next(100, 1000); } long dateTiePart = (datePart + timePart + msPart*1000) * 10000; int mid = MachineID * 10000; //得到總數= 4(日期)+5(時間)+3(毫秒)+7(GUID) long seq = dateTiePart + mid; return seq + countNum; ; }

注意:上面使用了一個模擬的自旋鎖,用來在末尾的順序號超過1萬的時候歸零重新計算,並且睡眠10毫秒從而根本上杜絕重複ID。

每秒不重複ID生成數:

從上面的程式程式碼中,得知 ID總數= 4位(日期)+5位(時間)+3位(毫秒)+7位(GUID)。
其中,7位(GUID)中,除去前3位的分散式機器ID,剩餘4位有序數字,可以表示1萬個數字。
所以,該方面每毫秒最大可以生成1萬個不重複的ID數,每秒最大可以生成1千萬個不重複ID
當然這是理論大小,實際上受到當前機器的計算能力限制。

該方法進行了再次封裝,用於在不同情況下分別使用:

      /// <summary>
       /// 生成一個新的在秒級別有序的長整形“GUID”,在一秒內,資料比較隨機,執行緒安全,
       /// 但不如NewUniqueSequenceGUID 方法結果更有序(不包含毫秒部分)
/// </summary> /// <returns></returns> public static long NewSequenceGUID() { return UniqueSequenceGUID.InnerNewSequenceGUID(DateTime.Now,false); } /// <summary> /// 生成一個唯一的更加有序的GUID形式的長整數,在一秒內,一千萬個不重複ID,執行緒安全。可用於嚴格有序增長的ID /// </summary> /// <returns></returns> public static long NewUniqueSequenceGUID() { return UniqueId.NewID(); } /// <summary> /// 當前機器ID,可以作為分散式ID,如果需要指定此ID,請在應用程式配置檔案配置 SOD_MachineID 的值,範圍大於100,小於1000. /// </summary> /// <returns></returns> public static int CurrentMachineID() { return UniqueSequenceGUID.GetCurrentMachineID(); }

最後,像下面這樣使用即可:

        Console.WriteLine("當前機器的分散式ID:{0}",CommonUtil.CurrentMachineID());
            Console.WriteLine("測試分散式ID:秒級有序");
            for (int i= 0; i < 50; i++)
            {
                Console.Write(CommonUtil.NewSequenceGUID());
                Console.Write(",");
            }
            Console.WriteLine();
            Console.WriteLine("測試分散式ID:唯一且有序");
            for (int i = 0; i < 50; i++)
            {
                Console.Write(CommonUtil.NewUniqueSequenceGUID());
                Console.Write(",");
            }
            Console.WriteLine();

下面是生成的ID數字示例:

當前機器的分散式ID:832
測試分散式ID:秒級有序
1460532991258320201,1460532991258320202,1460532991258320203,1460532991258320204,1460532991258320205,
1460532991258320206,1460532991258320207,1460532991258320208,1460532991258320209,1460532991258320210,
1460532991258320211,1460532991258320212,1460532991258320213,1460532991258320214,1460532991258320215,
1460532991258320216,1460532991258320217,1460532991258320218,1460532991258320219,1460532991258320220,
1460532991258320221,1460532994488320222,1460532994488320223,1460532994488320224,1460532994488320225,
1460532994488320226,1460532994488320227,1460532994488320228,1460532994488320229,1460532994488320230,
1460532994488320231,1460532994488320232,1460532994488320233,1460532994488320234,1460532994488320235,
1460532994488320236,1460532994488320237,1460532994488320238,1460532994488320239,1460532994488320240,
1460532994488320241,1460532994488320242,1460532994488320243,1460532994488320244,1460532994488320245,
1460532994488320246,1460532994488320247,1460532994488320248,1460532993018320249,1460532993018320250, 測試分散式ID:唯一且有序
1460532997708320251,1460532997708320252,1460532997718320253,1460532997718320254,1460532997718320255,
1460532997728320256,1460532997728320257,1460532997728320258,1460532997738320259,1460532997738320260,
1460532997788320261,1460532997788320262,1460532997788320263,1460532997838320264,1460532997838320265,
1460532997838320266,1460532997838320267,1460532997858320268,1460532997858320269,1460532997858320270,
1460532997858320271,1460532997868320272,1460532997868320273,1460532997868320274,1460532997878320275,
1460532997878320276,1460532997878320277,1460532997878320278,1460532997888320279,1460532997888320280,
1460532997888320281,1460532997888320282,1460532997898320283,1460532997908320284,1460532997918320285,
1460532997918320286,1460532997918320287,1460532997918320288,1460532997928320289,1460532997928320290,
1460532997928320291,1460532997928320292,1460532997938320293,1460532997938320294,1460532997948320295,
1460532997948320296,1460532997948320297,1460532998028320298,1460532998028320299,1460532998068320300,

注:本文生成ID的方法已經在產品中大量使用,執行情況良好。

要使用本程式,你可以Nuget 下載SOD的程式包(支援.NET 2.0專案),然後像本文示例這樣使用即可:

Install-Package PDF.NET.SOD.Core

獲取SOD的原始碼,請Fork我們的Github:

原始碼位置在 https://github.com/znlgis/sod/tree/master/src/SOD 目錄下。

有疑問,請加QQ群154224970 諮詢,感謝大家支援SOD框架!