生成順序良好肉眼方便查看的分散式ID的方案,比雪花ID演算法實現更簡單,理論上每秒可以生成接近1000萬個不重覆的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">是否包含毫秒,生成更加有序的數字,但這會增加重覆率</param> /// <returns></returns> protected internal static long InnerNewSequenceGUID(DateTime dt, bool haveMs) { //日期以 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 = (long)dt.Millisecond * 1000; long dateTiePart = (datePart + timePart + msPart) * 10000; int mid = MachineID * 10000; //得到總數= 4(日期)+5(時間)+3(毫秒)+7(GUID) long seq = dateTiePart + mid; //線程安全的自增並且不超過最大值10000 int startValue = System.Threading.Interlocked.Increment(ref SeqNum); while (startValue >= 10000) { SeqNum = 0; startValue = 0; //可能此時別的線程再次更改了 SeqNum while (startValue != SeqNum) { startValue = System.Threading.Interlocked.Increment(ref SeqNum); } } seq = seq + startValue; return seq; }
每秒不重覆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形式的長整數,速度較NewSequenceGUID 稍慢,但線程不安全 /// </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:快速(每毫秒一萬個不重覆ID)模式 1455630735488320001,1455630735498320002,1455630735498320003,1455630735498320004,1455630735498320005,1455630735498320006,1455630735498320007,1455630735498320008,1455630735498320009,1455630735498320010,1455630735508320011,1455630735508320012,1455630735508320013,1455630735508320014,1455630735508320015,1455630735508320016,1455630735508320017,1455630735508320018,1455630735508320019,1455630735508320020,1455630735508320021,1455630735508320022,1455630735508320023,1455630735508320024,1455630735508320025,1455630735508320026,1455630735508320027,1455630735508320028,1455630735508320029,1455630735508320030,1455630735508320031,1455630735508320032,1455630735508320033,1455630735518320034,1455630735518320035,1455630735518320036,1455630735518320037,1455630735518320038,1455630735518320039,1455630735518320040,1455630735528320041,1455630735528320042,1455630735528320043,1455630735528320044,1455630735528320045,1455630735528320046,1455630735528320047,1455630735528320048,1455630735528320049,1455630735558320050, 測試分散式ID:唯一模式 1455630735578320051,1455630735598320052,1455630735598320053,1455630735598320054,1455630735598320055,1455630735608320056,1455630735608320057,1455630735608320058,1455630735608320059,1455630735608320060,1455630735608320061,1455630735608320062,1455630735608320063,1455630735618320064,1455630735618320065,1455630735618320066,1455630735618320067,1455630735618320068,1455630735618320069,1455630735618320070,1455630735618320071,1455630735628320072,1455630735628320073,1455630735628320074,1455630735628320075,1455630735628320076,1455630735628320077,1455630735628320078,
註:本文生成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框架!