<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          .NET中對數(shù)據(jù)庫友好的GUID的變種使用方法

          共 11201字,需瀏覽 23分鐘

           ·

          2023-11-11 23:38

          概述

          .NET生成的GUID唯一性很好,用之方便,但是,缺少像雪花算法那樣的有序性。雖然分布式系統(tǒng)中做不到絕對的有序,但是,相對的有序?qū)τ谀壳皵?shù)據(jù)庫而言,索引效率等方面的提升還是有明顯效果的(當然,我認為,這是數(shù)據(jù)庫的問題,而非編程的問題,數(shù)據(jù)庫應該處理好任何類型數(shù)據(jù)作為主鍵索引時的性能,除非在SQL標準中明確寫不支持哪些數(shù)據(jù)類型)。

          在當前數(shù)據(jù)庫無法解決這些問題的時候,除了使用雪花算法,是否能夠改造GUID,利用微軟已經(jīng)相當成熟的GUID的性能與效率的同時,加上序列的特性呢。本文就是做此嘗試。

          GUID與雪花算法

          個人主觀的感受,雪花算法生成簡單,但是,在生產(chǎn)環(huán)境配置需要注意,同時,有范圍限制(時間、數(shù)據(jù)中心、工作機器、單位時間生成個數(shù)都有限制),正常情況下,這些范圍足夠使用,但是,畢竟GUID是沒有限制的,唯一的問題就是有極低的概念會重復,這種重復在插入的時候,通過數(shù)據(jù)庫的Pk是可以及時發(fā)現(xiàn)并處理的,不會產(chǎn)生生產(chǎn)故障。

          claus是這樣評價這兩個算法的:

          GUID 和 Snowflake 都是常見的唯一ID生成方案,主要有以下區(qū)別:

          優(yōu)點:

          1、GUID是完全隨機的,碰撞概率極低,唯一性非常好。

          2、GUID不依賴中心節(jié)點,分布式環(huán)境下也能生成,更適合于分布式系統(tǒng)。

          3、GUID包含版本信息,可以對不同算法生成的GUID進行區(qū)分,向下兼容。

          缺點:

          1、GUID是無序的,不能進行排序,對數(shù)據(jù)庫索引不友好。

          2、GUID較長,作為主鍵會占用更多存儲空間。

          3、GUID包含 BPSK 碼,大小寫字母和短劃線,可讀性較差。

          Snowflake算法:

          優(yōu)點:

          1、可以按時間有序生成ID,對數(shù)據(jù)庫索引更友好。

          2、整體ID更短小,節(jié)省存儲空間。

          3、不包含特殊字符,可讀性更好。

          缺點:

          1、依賴中心節(jié)點時間戳,必須保證節(jié)點時間同步。

          2、不適合大規(guī)模分布式環(huán)境,擴展性較弱。

          3、序列號周期較短,需要自定義優(yōu)化。

          4、不向下兼容,算法變更會導致ID不連續(xù)。

          總之,應根據(jù)分布式需求、時間序需求、存儲空間和可讀性等進行權衡選擇。

          我們需要用到時間值

          (戳?還是不蹭UNIX的概念吧)

          1 毫秒=1000000 納秒

          var dt = DateTime.Now;// 當前時間
          Console.WriteLine(dt.Ticks);// 638322150575422659,這是.net自帶的運算,其它語言可以使用下面的方式生成。
          Console.WriteLine((dt - new DateTime(111)).TotalMilliseconds*10000); // 638322150575422700
          // 通過Ticks,可以取得100ns,即萬分之一毫秒的精度。

          到3023年(1千年以后),Ticks的值也不會進位,其值為953650368000000000。

          了解一下GUID

          GUID 的格式為“xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx”。

          其含義為:【時間值低位 32bit】-【時間值中位 16bit】-【版本號 4bit】【時間值高位 4bit】【時間值高位 8bit】-【變體值 2bit】【時間序列高位 6bit】-【節(jié)點值 48bit】

          位數(shù)為:8hex-4hex-4hex-4hex-12hex。

          var uuidN = Guid.NewGuid().ToString("N"); // e0a953c3ee6040eaa9fae2b667060e09

          時間值+GUID

          時間值本身是一個long類型的數(shù)字,其大小為Int64,即8byte。

          guid本身就是一個byte[],其長度為16位。

          所以,我們生成一個byte[],前8位放時間值,后面放GUID,在比較大小的時候,前端的位置優(yōu)先級更高,所以,后面的GUID的無序特性會被覆蓋。

          8 位 Byte 8 位 Byte 8 位 Byte
          時間值 GUID值

          加在一起,一共24個字節(jié)。

          Base64字符串化

          因為數(shù)據(jù)庫、前端、CSV等環(huán)境下,無法描述所有的Byte(原因是部分AscII是非可見字符),故而,需要將其進行類似Base64的轉(zhuǎn)換。

          轉(zhuǎn)換后,我們會得到一個長度大概24/3*4=32位長度的字符串。這個字符串的字節(jié)數(shù)至少是32,但是,其具體更好的可讀性。

          但是,Base64有一個缺陷,我們來看一下它的碼表:

          可以看到,【大寫字母】<【小寫字母】<【數(shù)字】<【+】<【/】。

          但是,我們的程序進行比較時,并非如此,而是遵循了AscII碼表的次序。我們來看一下AscII碼表的次序:

          在AscII碼表中,【/】<【數(shù)字】<【=】<【大寫字母】<【小寫字母】。

          與上面的base64對比可以發(fā)現(xiàn),如果我們將兩串二進制用base64表示,則他們將無法使用base64字面的字符進行大小比較。所以,我們需要對base64進行一次轉(zhuǎn)換,轉(zhuǎn)換的結果要與ascii對應,起到和ascii大小次序一次的效果。

          BaseSortValue-BSV

          為了解決這個問題,我們需要將base64的二進制拿出來,然后, 給予他們有次序的新的碼表即可。

          但是,我們要做更長遠的考慮,我們的BSV大概率會被用作主鍵,會用來查詢,用出現(xiàn)在URL中,所以,我們應該避開URL的字義字符。URL的轉(zhuǎn)義表如下:

          除了大小寫字母、數(shù)字以外,我們還需要3個字符。除去URL轉(zhuǎn)義字符,ASCII中可用的可視字符只剩下 !"$'()*,-.;<>[\]^_{|}~。其中"'出現(xiàn)在代碼中容易影響代碼本身的轉(zhuǎn)義,故而不可。

          _符號在查詢時,經(jīng)常因為疏忽看不見。

          所以,最好的應該是!$-。因為這三者的中英文區(qū)別較大,具有較高的可識別度。同時,!小于數(shù)字及字母,作為補位可以不影響大小。最終形成的碼表如下:

          生成的結果如下:

          0Bj4hRXIFkDoc$DXPivPF7nPBmO-smcF
          0Bj4hU4f674ny-f0keZnG6VpDZm1b75r
          0Bj4hU4h3WXnCsIKnnrrG7LHBzpH4yMF
          0Bj4hU4h4FhniG-wH57BF6pSCVD$5sGp
          0Bj4hU4h4Za$B5tkH2sIEtjA39M-nGuQ
          0Bj4hU4h4qL0M-4nKRLZFcrU8qF$yezv
          0Bj4hU4h548bDgf6ypAiFvI-HQSzZFeH
          0Bj4hU4h5I47IsKrnkfdF7bfvOjMXWXm
          0Bj4hU4h5V3P7fTSP0lBEcbZbF5h2CXV
          0Bj4hU4h5iCiT-m$R7PfEeko7oaFcIPO
          0Bj4hU4h5vDF6VNYTDSSFsHi1FUQt93p

          實現(xiàn) - .NET

          public static class SeqGuid
          {
              /// <summary>
              /// 生成BSV的GUID。
              /// </summary>
              /// <returns></returns>
              public static string NewGuid()
              {

                  var gid = Guid.NewGuid().ToByteArray();// 獲取唯一的guid,對應uuid的版本應該是v4。此處直接獲取其byte數(shù)組。
                  var dtvalue = DateTime.Now.Ticks;//獲取當前時間到1年1月1日的總ticks數(shù),ticks單位是100ns,即萬分之一毫秒。
                  var dtbytes = BitConverter.GetBytes(dtvalue);// 將ticks時間戳轉(zhuǎn)換為字節(jié)數(shù)組,默認是小端。
                  var bytes = new Byte[gid.Length + dtbytes.Length];// 實例化新的數(shù)字,用以存放時間值和GUID值。

                  // 因為BitConverter.GetBytes獲得的Byte[]是小端,不符合排序要求,所以,要逆序?qū)懭隻ytes數(shù)組中,形成大端的方式。
                  // 將時間值放入bytes數(shù)組中。
                  for (long i = 0; i < dtbytes.Length; i++)
                  {
                      var cvalue = dtbytes[dtbytes.Length - i - 1];
                      bytes[i] = cvalue;
                  }

                  // 將guid的值,放入bytes數(shù)組中。
                  gid.CopyTo(bytes, dtbytes.Length);
                  // 將值轉(zhuǎn)換為base64,主要原因是,前端、數(shù)據(jù)庫比較容易處理字符串類型的數(shù)據(jù)。
                  var b64 = Convert.ToBase64String(bytes);
                  // 將無序的base64轉(zhuǎn)換為有序的偽base64格式。

                  var ss = b64.ToArray();
                  for (var i = 0; i < ss.Length; i++)
                  {
                      ss[i] = dic[ss[i]];
                  }
                  return new string(ss);
              }
              /// <summary>
              /// 仿base64的有序字典,其與base64相似,使用有限的字符,表示6bit的二進制,不足的地方補=。但是,與base64的區(qū)別是,字符串是按從小到大的次序表示000000到111111的數(shù)值的。
              /// </summary>
              public static readonly Dictionary<charchar> dic = new Dictionary<charchar>()
              {
                  {'A','$'},{'B','-'},{'C','0'},{'D','1'},{'E','2'},{'F','3'},{'G','4'},{'H','5'},{'I','6'},{'J','7'},{'K','8'},
                  {'L','9'},{'M','A'},{'N','B'},{'O','C'},{'P','D'},{'Q','E'},{'R','F'},{'S','G'},{'T','H'},{'U','I'},{'V','J'},
                  {'W','K'},{'X','L'},{'Y','M'},{'Z','N'},{'a','O'},{'b','P'},{'c','Q'},{'d','R'},{'e','S'},{'f','T'},{'g','U'},
                  {'h','V'},{'i','W'},{'j','X'},{'k','Y'},{'l','Z'},{'m','a'},{'n','b'},{'o','c'},{'p','d'},{'q','e'},{'r','f'},
                  {'s','g'},{'t','h'},{'u','i'},{'v','j'},{'w','k'},{'x','l'},{'y','m'},{'z','n'},{'0','o'},{'1','p'},{'2','q'},
                  {'3','r'},{'4','s'},{'5','t'},{'6','u'},{'7','v'},{'8','w'},{'9','x'},{'+','y'},{'/','z'},{'=','!'}
              };
          }
          internal class Program
          {
              static void Main(string[] args)
              {
                  var preone = SeqGuid.NewGuid();
                  for(int i = 0; i < 9999999; i++)
                  {
                      var newone = SeqGuid.NewGuid();
                      if (String.CompareOrdinal(newone, preone)<0)//必須使用CompareOrdinal,因為Compare和CompareTo等都受本地的CultureInfo影響,可能會忽略大小寫。
                      {
                          Console.WriteLine($"error ,{newone} < {preone}");

                      }
                      preone = newone;

                  }
                  Console.WriteLine("done...");
                  Console.ReadLine();
              }
          }

          運行結果:

          執(zhí)行1000萬次,沒有大小次序錯誤。單線程 的情況下,每秒生成143萬個。

          多線程并發(fā)測試

          因為我的電腦只有20核,所以,使用15個線程來處理,為了避免受Task的優(yōu)化影響,我們直接使用Thread來模擬。

          static ConcurrentBag<string> exists = new ConcurrentBag<string>();
          static void 測試并發(fā)唯一性()
          {
              Thread[] tks = new Thread[15];
              for(var t = 0; t < 15; t++)
              {
                  tks[t] = new Thread(workThread);
                  tks[t].Start();
                  
              }
              Console.WriteLine("所有任務啟動完成,等待執(zhí)行完成中……");

              for (var t = 0; t < 15; t++)
              {
                  tks[t].Join();
              }
              Console.WriteLine($"全部執(zhí)行完成,總生成guid個數(shù):"+exists.Count);

          }
          static void workThread()
          {
              var count = 1000;
              Console.WriteLine(Thread.CurrentThread.ManagedThreadId + ":啟動");
              for (int i = 0; i < count; i++)
              {
                  var nid = SeqGuid.NewGuid();
                  if (exists.Contains(nid))
                  {
                      Console.WriteLine("出現(xiàn)重復guid:" + nid);
                  }
                  else
                  {
                      exists.Add(nid);
                  }
              }
              Console.WriteLine(Thread.CurrentThread.ManagedThreadId + ":完成");
          }

          測試結果如下:

          說明,借著.NET原本的GUID,完美的避開了并發(fā)情況下唯一的問題。至于說,高并發(fā)情況下次序問題,如果兩個動作在100ns以內(nèi),是否區(qū)分次序或者不是那么重要了。

          并且此GUID也并非是提供強一致次序(這種需求還是需要用Redis之類的來實現(xiàn)),而是提供有限的次序,以便解決數(shù)據(jù)庫優(yōu)化的問題。

          轉(zhuǎn)自:ensleep

          鏈接:cnblogs.com/ensleep/p/17745166.html







          回復 【關閉】學永久關閉App開屏廣告
          回復 【刪除】學自動檢測那個微信好友刪除、拉黑
          回復 【手冊】獲取3萬字.NET、C#工程師面試手冊
          回復 【幫助】獲取100+個常用的C#幫助類庫
          回復 【加群】加入DotNet學習交流群

          瀏覽 1128
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  91亚洲精华 | 国产在线成人免费视频色婷婷 | 丁香五月乱伦五月 | 中文无码熟妇人妻AV在线 | 一级a黄色视频 |