那些用於產生唯一ID的演算法,以CSharp為例


Buy Me a Coffee

在現代的分布式系統中,如何產生全局唯一的ID是一個非常關鍵的問題。從數據庫的主鍵,到分佈式消息隊列的消息ID,唯一ID的生成方案必須高效、穩定且能夠避免重複。今天我們就來聊聊幾種常見的唯一ID生成演算法,並看看如何在CSharp中實現它們。

什麼是唯一ID?

唯一ID,顧名思義,就是一個在整個系統中絕對唯一的識別碼。想像一下,如果你在超市買了一堆東西,每樣商品都有一個條碼,這個條碼就是唯一ID。對於分布式系統來說,唯一ID更是至關重要。它可以用來識別數據庫中的每一條記錄,標記每一條消息,甚至追踪每一個用戶行為。

常見的唯一ID生成演算法

這裡我們介紹幾種常見的唯一ID生成演算法,每一種都有它的特點和適用場景。讓我們逐一看看它們的優缺點,並附上CSharp實現代碼。

1. UUID(Universally Unique Identifier)

UUID 是一種標準的128位唯一標識符。它有多種版本,最常用的是基於隨機數的版本4(UUIDv4)。UUID的長度是固定的,這使得它在很多情況下都很適用。

優點:

  • 全球唯一性,無需中心化協調。
  • 標準化,廣泛支持。

缺點:

  • 長度較長,占用存儲空間大。
  • 無序性,不適合索引和排序。

CSharp範例:

using System;

public class UUIDExample
{
    public static void Main()
    {
        Guid uniqueId = Guid.NewGuid();
        Console.WriteLine(uniqueId);
    }
}

2. ULID(Universally Unique Lexicographically Sortable Identifier)

ULID 是一種基於時間的唯一ID生成演算法,生成的ID是按字典順序排序的,適合索引和排序。它比UUID更短且更具可讀性。

優點:

  • 按時間排序。
  • 簡單易用。

缺點:

  • 需要依賴庫或實現代碼。

CSharp範例:

using System;
using System.Text;

public class ULIDExample
{
    private static readonly char[] _encoding = "0123456789ABCDEFGHJKMNPQRSTVWXYZ".ToCharArray();
    private static readonly int _base32Length = 26;

    public static string NewULID()
    {
        byte[] buffer = new byte[16];
        new Random().NextBytes(buffer);
        
        long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
        byte[] timeBytes = BitConverter.GetBytes(timestamp);
        Array.Reverse(timeBytes);

        StringBuilder result = new StringBuilder(_base32Length);
        result.Append(EncodeTime(timeBytes));
        result.Append(EncodeRandom(buffer));

        return result.ToString();
    }

    private static string EncodeTime(byte[] timeBytes)
    {
        return new string(_encoding, 0, 10);
    }

    private static string EncodeRandom(byte[] randomBytes)
    {
        return new string(_encoding, 10, 16);
    }

    public static void Main()
    {
        string uniqueId = NewULID();
        Console.WriteLine(uniqueId);
    }
}

3. Snowflake

Snowflake 算法由 Twitter 開發,是一種高效的分布式唯一ID生成算法。它使用時間戳、數據中心ID、機器ID和序列號組合生成唯一ID。

優點:

  • 高效性、唯一性。
  • 按時間排序,適合索引和排序。

缺點:

  • 實現相對複雜,需要時間同步。

CSharp範例:

using System;

public class Snowflake
{
    private readonly long _epoch = 1577836800000L;
    private readonly long _dataCenterId;
    private readonly long _machineId;
    private long _sequence = 0L;
    private long _lastTimestamp = -1L;

    public Snowflake(long dataCenterId, long machineId)
    {
        _dataCenterId = dataCenterId;
        _machineId = machineId;
    }

    public long NextId()
    {
        lock (this)
        {
            long timestamp = TimeGen();

            if (timestamp < _lastTimestamp)
            {
                throw new Exception("Clock moved backwards. Refusing to generate id");
            }

            if (_lastTimestamp == timestamp)
            {
                _sequence = (_sequence + 1) & 4095;
                if (_sequence == 0)
                {
                    timestamp = TilNextMillis(_lastTimestamp);
                }
            }
            else
            {
                _sequence = 0;
            }

            _lastTimestamp = timestamp;
            return ((timestamp - _epoch) << 22) | (_dataCenterId << 17) | (_machineId << 12) | _sequence;
        }
    }

    private long TimeGen()
    {
        return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
    }

    private long TilNextMillis(long lastTimestamp)
    {
        long timestamp = TimeGen();
        while (timestamp <= lastTimestamp)
        {
            timestamp = TimeGen();
        }
        return timestamp;
    }
}

public class SnowflakeExample
{
    public static void Main()
    {
        Snowflake snowflake = new Snowflake(1, 1);
        Console.WriteLine(snowflake.NextId());
    }
}

4. NanoID

NanoID 是一種輕量級的唯一ID生成庫,支持自定義字符集和長度,生成的ID通常較短,適合各種應用場景。

優點:

  • 高度可定制化。
  • 簡單易用。

缺點:

  • 無序性,不適合索引和排序。

CSharp範例:

using System;
using System.Security.Cryptography;
using System.Text;

public class NanoID
{
    private static readonly char[] _alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray();
    private static readonly int _alphabetLength = _alphabet.Length;

    public static string Generate(int size = 21)
    {
        byte[] data = new byte[size];
        using (var crypto = new RNGCryptoServiceProvider())
        {
            crypto.GetBytes(data);
        }

        StringBuilder result = new StringBuilder(size);
        foreach (byte b in data)
        {
            result.Append(_alphabet[b % _alphabetLength]);
        }

        return result.ToString();
    }
}

public class NanoIDExample
{
    public static void Main()
    {
        string uniqueId = NanoID.Generate();
        Console.WriteLine(uniqueId);
    }
}

比較表格

演算法優點缺點CSharp 實現
UUID全球唯一性,標準化長度較長,無序性Guid.NewGuid()
ULID按時間排序,簡單易用需要依賴庫或實現代碼NewULID()
Snowflake高效性、唯一性,按時間排序實現複雜,需要時間同步Snowflake.NextId()
NanoID高度可定制化,簡單易用無序性,不適合索引和排序NanoID.Generate()

總結來說,不同的唯一ID生成演算法有各自的優勢和適用場景。選擇合適的演算法需要根據具體的應用需求,比如是否需要排序、生成速度、ID的長度等。希望這篇文章能幫助你更好地理解這些演算法,並在實際開發中找到最適合的解決方案。