去年作了一個產品,會常常導入導出大量的外部數據,這些數據的ID有的是GUID類型,有的是字符串,也有的是自增。GUID類型沒有順序,結果要排序得藉助其它業務字段,總體查詢效率比較低;字符串ID原本是用來轉換GUID的或者數字ID的,結果有些字符串ID不符合規範,經常有特殊數據須要處理;自增主鍵ID的數據導入合併常常有衝突。git
爲了不GUID主鍵的「索引頁分裂」問題,提升查詢效率,同時爲了解決分佈式環境下的數據導入合併問題,強烈須要一種分佈式的,有序的ID生成方案。我參考了雪花ID(Twitter-Snowflake,64位自增ID算法)實現方案,設計一個更容易肉眼觀察數值連續有序的分佈式ID方案。github
跟雪花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。dom
從上面的程序代碼中,得知 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(); }
最後,像下面這樣使用便可:spa
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的方法已經在產品中大量使用,運行狀況良好。
要使用本程序,你能夠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框架!