1. 程式人生 > 其它 >分散式系統唯一ID的生成方案

分散式系統唯一ID的生成方案

系統唯一ID是我們在設計一個系統的時候常常會遇見的問題,也常常為這個問題而糾結。生成ID的方法有很多,適應不同的場景、需求以及效能要求。所以有些比較複雜的系統會有多個ID生成的策略。下面就介紹一些常見的ID生成策略。

1. 資料庫自增長序列或欄位

最常見的方式。利用資料庫,全資料庫唯一。

優點:

1)簡單,程式碼方便,效能可以接受。

2)數字ID天然排序,對分頁或者需要排序的結果很有幫助。

缺點:

1)不同資料庫語法和實現不同,資料庫遷移的時候或多資料庫版本支援的時候需要處理。

2)在單個數據庫或讀寫分離或一主多從的情況下,只有一個主庫可以生成。有單點故障的風險。

3)在效能達不到要求的情況下,比較難於擴充套件。(不適用於海量高併發)

4)如果遇見多個系統需要合併或者涉及到資料遷移會相當痛苦。

5)分表分庫的時候會有麻煩。

6)並非一定連續,類似MySQL,當生成新ID的事務回滾,那麼後續的事務也不會再用這個ID了。這個在效能和連續性的折中。如果為了保證連續,必須要在事務結束後才能生成ID,那效能就會出現問題。

7)在分散式資料庫中,如果採用了自增主鍵的話,有可能會帶來尾部熱點。分散式資料庫常常使用range的分割槽方式,在大量新增記錄的時候,IO會集中在一個分割槽上,造成熱點資料。

優化方案:

1)針對主庫單點,如果有多個Master庫,則每個Master庫設定的起始數字不一樣,步長一樣,可以是Master的個數。比如:Master1 生成的是 1,4,7,10,Master2生成的是2,5,8,11 Master3生成的是 3,6,9,12。這樣就可以有效生成叢集中的唯一ID,也可以大大降低ID生成資料庫操作的負載。

2. UUID

常見的方式。可以利用資料庫也可以利用程式生成,一般來說全球唯一。UUID是由32個的16進位制數字組成,所以每個UUID的長度是128位(16^32 = 2^128)。UUID作為一種廣泛使用標準,有多個實現版本,影響它的因素包括時間、網絡卡MAC地址、自定義Namesapce等等。

優點:

1)簡單,程式碼方便。

2)生成ID效能非常好,基本不會有效能問題。

3)全球唯一,在遇見資料遷移,系統資料合併,或者資料庫變更等情況下,可以從容應對。

缺點:

1)沒有排序,無法保證趨勢遞增。

2)UUID往往是使用字串儲存,查詢的效率比較低。

3)儲存空間比較大,如果是海量資料庫,就需要考慮儲存量的問題。

4)傳輸資料量大

5)不可讀。

3. UUID的變種

1)為了解決UUID不可讀,可以使用UUID to Int64的方法。及

/// <summary>
/// 根據GUID獲取唯一數字序列
/// </summary>
public static long GuidToInt64()
{
    byte[] bytes = Guid.NewGuid().ToByteArray();
    return BitConverter.ToInt64(bytes, 0);
}
?
1

2)為了解決UUID無序的問題,NHibernate在其主鍵生成方式中提供了Comb演算法(combined guid/timestamp)。保留GUID的10個位元組,用另6個位元組表示GUID生成的時間(DateTime)。

/// <summary> 
/// Generate a new <see cref="Guid"/> using the comb algorithm. 
/// </summary> 
private Guid GenerateComb()
{
    byte[] guidArray = Guid.NewGuid().ToByteArray();
DateTime baseDate </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> DateTime(<span style="color: rgba(128, 0, 128, 1)">1900</span>, <span style="color: rgba(128, 0, 128, 1)">1</span>, <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">);
DateTime now </span>=<span style="color: rgba(0, 0, 0, 1)"> DateTime.Now;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Get the days and milliseconds which will be used to build    
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">the byte string    </span>
TimeSpan days = <span style="color: rgba(0, 0, 255, 1)">new</span> TimeSpan(now.Ticks -<span style="color: rgba(0, 0, 0, 1)"> baseDate.Ticks);
TimeSpan msecs </span>=<span style="color: rgba(0, 0, 0, 1)"> now.TimeOfDay;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Convert to a byte array        
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Note that SQL Server is accurate to 1/300th of a    
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> millisecond so we divide by 3.333333    </span>
<span style="color: rgba(0, 0, 255, 1)">byte</span>[] daysArray =<span style="color: rgba(0, 0, 0, 1)"> BitConverter.GetBytes(days.Days);
</span><span style="color: rgba(0, 0, 255, 1)">byte</span>[] msecsArray = BitConverter.GetBytes((<span style="color: rgba(0, 0, 255, 1)">long</span><span style="color: rgba(0, 0, 0, 1)">)
  (msecs.TotalMilliseconds </span>/ <span style="color: rgba(128, 0, 128, 1)">3.333333</span><span style="color: rgba(0, 0, 0, 1)">));

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Reverse the bytes to match SQL Servers ordering    </span>

Array.Reverse(daysArray);
Array.Reverse(msecsArray);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Copy the bytes into the guid    </span>
Array.Copy(daysArray, daysArray.Length - <span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">, guidArray,
  guidArray.Length </span>- <span style="color: rgba(128, 0, 128, 1)">6</span>, <span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">);
Array.Copy(msecsArray, msecsArray.Length </span>- <span style="color: rgba(128, 0, 128, 1)">4</span><span style="color: rgba(0, 0, 0, 1)">, guidArray,
  guidArray.Length </span>- <span style="color: rgba(128, 0, 128, 1)">4</span>, <span style="color: rgba(128, 0, 128, 1)">4</span><span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Guid(guidArray);

}


用上面的演算法測試一下,得到如下的結果:作為比較,前面3個是使用COMB演算法得出的結果,最後12個字串是時間序(統一毫秒生成的3個UUID),過段時間如果再次生成,則12個字串會比圖示的要大。後面3個是直接生成的GUID。

如果想把時間序放在前面,可以生成後改變12個字串的位置,也可以修改演算法類的最後兩個Array.Copy。

4. Redis生成ID

當使用資料庫來生成ID效能不夠要求的時候,我們可以嘗試使用Redis來生成ID。這主要依賴於Redis是單執行緒的,所以也可以用生成全域性唯一的ID。可以用Redis的原子操作 INCR和INCRBY來實現。

可以使用Redis叢集來獲取更高的吞吐量。假如一個叢集中有5臺Redis。可以初始化每臺Redis的值分別是1,2,3,4,5,然後步長都是5。各個Redis生成的ID為:

A:1,6,11,16,21

B:2,7,12,17,22

C:3,8,13,18,23

D:4,9,14,19,24

E:5,10,15,20,25

這個,隨便負載到哪個機確定好,未來很難做修改。但是3-5臺伺服器基本能夠滿足器上,都可以獲得不同的ID。但是步長和初始值一定需要事先需要了。使用Redis叢集也可以方式單點故障的問題。

另外,比較適合使用Redis來生成每天從0開始的流水號。比如訂單號=日期+當日自增長號。可以每天在Redis中生成一個Key,使用INCR進行累加。

優點:

1)不依賴於資料庫,靈活方便,且效能優於資料庫。

2)數字ID天然排序,對分頁或者需要排序的結果很有幫助。

缺點:

1)如果系統中沒有Redis,還需要引入新的元件,增加系統複雜度。

2)需要編碼和配置的工作量比較大。

5. Twitter的snowflake演算法

snowflake是Twitter開源的分散式ID生成演算法,結果是一個long型的ID。其核心思想是:使用41bit作為毫秒數,10bit作為機器的ID(5個bit是資料中心,5個bit的機器ID),12bit作為毫秒內的流水號(意味著每個節點在每毫秒可以產生 4096 個 ID),最後還有一個符號位,永遠是0。具體實現的程式碼可以參看https://github.com/twitter/snowflake。雪花演算法支援的TPS可以達到419萬左右(2^22*1000)。

雪花演算法在工程實現上有單機版本和分散式版本。單機版本如下,分散式版本可以參看美團leaf演算法:https://github.com/Meituan-Dianping/Leaf

C#程式碼如下:

/// <summary>
    /// From: https://github.com/twitter/snowflake
    /// An object that generates IDs.
    /// This is broken into a separate class in case
    /// we ever want to support multiple worker threads
    /// per process
    /// </summary>
    public class IdWorker
    {
        private long workerId;
        private long datacenterId;
        private long sequence = 0L;
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">long</span> twepoch = <span style="color: rgba(128, 0, 128, 1)">1288834974657L</span><span style="color: rgba(0, 0, 0, 1)">;

    </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">long</span> workerIdBits = <span style="color: rgba(128, 0, 128, 1)">5L</span><span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">long</span> datacenterIdBits = <span style="color: rgba(128, 0, 128, 1)">5L</span><span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">long</span> maxWorkerId = -<span style="color: rgba(128, 0, 128, 1)">1L</span> ^ (-<span style="color: rgba(128, 0, 128, 1)">1L</span> &lt;&lt; (<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)">)workerIdBits);
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">long</span> maxDatacenterId = -<span style="color: rgba(128, 0, 128, 1)">1L</span> ^ (-<span style="color: rgba(128, 0, 128, 1)">1L</span> &lt;&lt; (<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)">)datacenterIdBits);
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">long</span> sequenceBits = <span style="color: rgba(128, 0, 128, 1)">12L</span><span style="color: rgba(0, 0, 0, 1)">;

    </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">long</span> workerIdShift =<span style="color: rgba(0, 0, 0, 1)"> sequenceBits;
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">long</span> datacenterIdShift = sequenceBits +<span style="color: rgba(0, 0, 0, 1)"> workerIdBits;
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">long</span> timestampLeftShift = sequenceBits + workerIdBits +<span style="color: rgba(0, 0, 0, 1)"> datacenterIdBits;
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">long</span> sequenceMask = -<span style="color: rgba(128, 0, 128, 1)">1L</span> ^ (-<span style="color: rgba(128, 0, 128, 1)">1L</span> &lt;&lt; (<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)">)sequenceBits);

    </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">long</span> lastTimestamp = -<span style="color: rgba(128, 0, 128, 1)">1L</span><span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">object</span> syncRoot = <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 0, 255, 1)">object</span><span style="color: rgba(0, 0, 0, 1)">();

    </span><span style="color: rgba(0, 0, 255, 1)">public</span> IdWorker(<span style="color: rgba(0, 0, 255, 1)">long</span> workerId, <span style="color: rgba(0, 0, 255, 1)">long</span><span style="color: rgba(0, 0, 0, 1)"> datacenterId)
    {

        </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> sanity check for workerId</span>
        <span style="color: rgba(0, 0, 255, 1)">if</span> (workerId &gt; maxWorkerId || workerId &lt; <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">)
        {
            </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> ArgumentException(<span style="color: rgba(0, 0, 255, 1)">string</span>.Format(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">worker Id can't be greater than %d or less than 0</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, maxWorkerId));
        }
        </span><span style="color: rgba(0, 0, 255, 1)">if</span> (datacenterId &gt; maxDatacenterId || datacenterId &lt; <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">)
        {
            </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> ArgumentException(<span style="color: rgba(0, 0, 255, 1)">string</span>.Format(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">datacenter Id can't be greater than %d or less than 0</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, maxDatacenterId));
        }
        </span><span style="color: rgba(0, 0, 255, 1)">this</span>.workerId =<span style="color: rgba(0, 0, 0, 1)"> workerId;
        </span><span style="color: rgba(0, 0, 255, 1)">this</span>.datacenterId =<span style="color: rgba(0, 0, 0, 1)"> datacenterId;
    }

    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">long</span><span style="color: rgba(0, 0, 0, 1)"> nextId()
    {
        </span><span style="color: rgba(0, 0, 255, 1)">lock</span><span style="color: rgba(0, 0, 0, 1)"> (syncRoot)
        {
            </span><span style="color: rgba(0, 0, 255, 1)">long</span> timestamp =<span style="color: rgba(0, 0, 0, 1)"> timeGen();

            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (timestamp &lt;<span style="color: rgba(0, 0, 0, 1)"> lastTimestamp)
            {
                </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> ApplicationException(<span style="color: rgba(0, 0, 255, 1)">string</span>.Format(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Clock moved backwards.  Refusing to generate id for %d milliseconds</span><span style="color: rgba(128, 0, 0, 1)">"</span>, lastTimestamp -<span style="color: rgba(0, 0, 0, 1)"> timestamp));
            }

            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (lastTimestamp ==<span style="color: rgba(0, 0, 0, 1)"> timestamp)
            {
                sequence </span>= (sequence + <span style="color: rgba(128, 0, 128, 1)">1</span>) &amp;<span style="color: rgba(0, 0, 0, 1)"> sequenceMask;
                </span><span style="color: rgba(0, 0, 255, 1)">if</span> (sequence == <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">)
                {
                    timestamp </span>=<span style="color: rgba(0, 0, 0, 1)"> tilNextMillis(lastTimestamp);
                }
            }
            </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">
            {
                sequence </span>= <span style="color: rgba(128, 0, 128, 1)">0L</span><span style="color: rgba(0, 0, 0, 1)">;
            }

            lastTimestamp </span>=<span style="color: rgba(0, 0, 0, 1)"> timestamp;

            </span><span style="color: rgba(0, 0, 255, 1)">return</span> ((timestamp - twepoch) &lt;&lt; (<span style="color: rgba(0, 0, 255, 1)">int</span>)timestampLeftShift) | (datacenterId &lt;&lt; (<span style="color: rgba(0, 0, 255, 1)">int</span>)datacenterIdShift) | (workerId &lt;&lt; (<span style="color: rgba(0, 0, 255, 1)">int</span>)workerIdShift) |<span style="color: rgba(0, 0, 0, 1)"> sequence;
        }
    }

    </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">long</span> tilNextMillis(<span style="color: rgba(0, 0, 255, 1)">long</span><span style="color: rgba(0, 0, 0, 1)"> lastTimestamp)
    {
        </span><span style="color: rgba(0, 0, 255, 1)">long</span> timestamp =<span style="color: rgba(0, 0, 0, 1)"> timeGen();
        </span><span style="color: rgba(0, 0, 255, 1)">while</span> (timestamp &lt;=<span style="color: rgba(0, 0, 0, 1)"> lastTimestamp)
        {
            timestamp </span>=<span style="color: rgba(0, 0, 0, 1)"> timeGen();
        }
        </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> timestamp;
    }

    </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">long</span><span style="color: rgba(0, 0, 0, 1)"> timeGen()
    {
        </span><span style="color: rgba(0, 0, 255, 1)">return</span> (<span style="color: rgba(0, 0, 255, 1)">long</span>)(DateTime.UtcNow - <span style="color: rgba(0, 0, 255, 1)">new</span> DateTime(<span style="color: rgba(128, 0, 128, 1)">1970</span>, <span style="color: rgba(128, 0, 128, 1)">1</span>, <span style="color: rgba(128, 0, 128, 1)">1</span>, <span style="color: rgba(128, 0, 128, 1)">0</span>, <span style="color: rgba(128, 0, 128, 1)">0</span>, <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">, DateTimeKind.Utc)).TotalMilliseconds;
    }
}</span></pre>

測試程式碼如下:

private static void TestIdWorker()
        {
            HashSet<long> set = new HashSet<long>();
            IdWorker idWorker1 = new IdWorker(0, 0);
            IdWorker idWorker2 = new IdWorker(1, 0);
            Thread t1 = new Thread(() => DoTestIdWoker(idWorker1, set));
            Thread t2 = new Thread(() => DoTestIdWoker(idWorker2, set));
            t1.IsBackground = true;
            t2.IsBackground = true;
        t1.Start();
        t2.Start();
        </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
        {
            Thread.Sleep(</span><span style="color: rgba(128, 0, 128, 1)">30000</span><span style="color: rgba(0, 0, 0, 1)">);
            t1.Abort();
            t2.Abort();
        }
        </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e)
        {
        }

        Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">done</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
    }

    </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> DoTestIdWoker(IdWorker idWorker, HashSet&lt;<span style="color: rgba(0, 0, 255, 1)">long</span>&gt; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">)
    {
        </span><span style="color: rgba(0, 0, 255, 1)">while</span> (<span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">)
        {
            </span><span style="color: rgba(0, 0, 255, 1)">long</span> id =<span style="color: rgba(0, 0, 0, 1)"> idWorker.nextId();
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">.Add(id))
            {
                Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">duplicate:</span><span style="color: rgba(128, 0, 0, 1)">"</span> +<span style="color: rgba(0, 0, 0, 1)"> id);
            }

            Thread.Sleep(</span><span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">);
        }
    }</span></pre>

snowflake演算法可以根據自身專案的需要進行一定的修改。比如估算未來的資料中心個數,每個資料中心的機器數以及統一毫秒可以能的併發數來調整在演算法中所需要的bit數。

優點:

1)不依賴於資料庫,靈活方便,且效能優於資料庫。

2)ID按照時間在單機上是遞增的。

缺點:

1)在單機上是遞增的,但是由於涉及到分散式環境,每臺機器上的時鐘不可能完全同步,在演算法上要解決時間回撥的問題。

6. 利用zookeeper生成唯一ID

zookeeper主要通過其znode資料版本來生成序列號,可以生成32位和64位的資料版本號,客戶端可以使用這個版本號來作為唯一的序列號。
很少會使用zookeeper來生成唯一ID。主要是由於需要依賴zookeeper,並且是多步呼叫API,如果在競爭較大的情況下,需要考慮使用分散式鎖。因此,效能在高併發的分散式環境下,也不甚理想。

7. MongoDB的ObjectId
MongoDB的ObjectId和snowflake演算法類似。它設計成輕量型的,不同的機器都能用全域性唯一的同種方法方便地生成它。MongoDB 從一開始就設計用來作為分散式資料庫,處理多個節點是一個核心要求。使其在分片環境中要容易生成得多。

其格式如下:

前4 個位元組是從標準紀元開始的時間戳,單位為秒。時間戳,與隨後的5 個位元組組合起來,提供了秒級別的唯一性。由於時間戳在前,這意味著ObjectId 大致會按照插入的順序排列。這對於某些方面很有用,如將其作為索引提高效率。這4 個位元組也隱含了文件建立的時間。絕大多數客戶端類庫都會公開一個方法從ObjectId 獲取這個資訊。
接下來的3 位元組是所在主機的唯一識別符號。通常是機器主機名的雜湊值。這樣就可以確保不同主機生成不同的ObjectId,不產生衝突。


為了確保在同一臺機器上併發的多個程序產生的ObjectId 是唯一的,接下來的兩位元組來自產生ObjectId 的程序識別符號(PID)。


前9 位元組保證了同一秒鐘不同機器不同程序產生的ObjectId 是唯一的。後3 位元組就是一個自動增加的計數器,確保相同程序同一秒產生的ObjectId 也是不一樣的。同一秒鐘最多允許每個程序擁有2563(16 777 216)個不同的ObjectId。

實現的原始碼可以到MongoDB官方網站下載。

8. TiDB的主鍵

TiDB預設是支援自增主鍵的,對未宣告主鍵的表,會提供了一個隱式主鍵_tidb_rowid,因為這個主鍵大體上是單調遞增的,所以也會出現我們前面說的“尾部熱點”問題。

TiDB也提供了UUID函式,而且在4.0版本中還提供了另一種解決方案AutoRandom。TiDB 模仿MySQL的 AutoIncrement,提供了AutoRandom關鍵字用於生成一個隨機ID填充指定列。

所謂迷茫,就是清醒的看著自己沉淪。 不過總會遇到那束光,或早,或晚。