五个id 生成器性能比较记录

Ulid

ULID :Universally Unique Lexicographically Sortable Identifier(通用唯一词典分类标识符)

c# 实现库 : https://github.com/Cysharp/Ulid

ULID特性

ulid() # 01ARZ3NDEKTSV4RRFFQ69G5FAV

  • 与UUID的128位兼容性
  • 每毫秒1.21e + 24个唯一ULID
  • 按字典顺序(也就是字母顺序)排序!
  • 规范地编码为26个字符串,而不是UUID的36个字符
  • 使用Crockford的base32获得更好的效率和可读性(每个字符5位)
  • 不区分大小写
  • 没有特殊字符(URL安全)
  • 单调排序顺序(正确检测并处理相同的毫秒)

二进制布局和字节顺序

组件被编码为16个八位位组。每个组件都以最高有效字节在前(网络字节顺序)进行编码。


0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                      32_bit_uint_time_high                    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|     16_bit_uint_time_low      |       16_bit_uint_random      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                       32_bit_uint_random                      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                       32_bit_uint_random                      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

应用场景

替换数据库自增id,无需DB参与主键生成
分布式环境下,替换UUID,全局唯一且毫秒精度有序
比如要按日期对数据库进行分区分表,可以使用ULID中嵌入的时间戳来选择正确的分区分表
如果毫秒精度是可以接受的(毫秒内无序),可以按照ULID排序,而不是单独的created_at字段

IdGenerator

Seata 优化的雪花算法

Seata基于改良版雪花算法的分布式UUID生成器分析

关于新版雪花算法的答疑

csharp 移植代码

    public class IdGenerator
    {
        private readonly long twepoch = 1588435200000L;
        private const int workerIdBits = 10;
        private const int timestampBits = 41;
        private const int sequenceBits = 12;
        private const int maxWorkerId = ~(-1 << workerIdBits);
        private long workerId;
        private long timestampAndSequence;
        private readonly long timestampAndSequenceMask = ~(-1L << (timestampBits + sequenceBits));

        public static readonly IdGenerator Instance = new IdGenerator(GenerateWorkerId());

        public IdGenerator(long workerId)
        {
            InitTimestampAndSequence();
            InitWorkerId(workerId);
        }

        private void InitTimestampAndSequence()
        {
            long timestamp = GetNewestTimestamp();
            long timestampWithSequence = timestamp << sequenceBits;
            this.timestampAndSequence = timestampWithSequence;
        }

        private void InitWorkerId(long workerId)
        {
            if (workerId > maxWorkerId || workerId < 0)
            {
                string message = string.Format("worker Id can't be greater than {0} or less than 0", maxWorkerId);
                throw new ArgumentException(message);
            }
            this.workerId = workerId << (timestampBits + sequenceBits);
        }

        public long NextId()
        {
            WaitIfNecessary();
            long next = Interlocked.Increment(ref timestampAndSequence);
            long timestampWithSequence = next & timestampAndSequenceMask;
            return workerId | timestampWithSequence;
        }

        public static long NewId()
        {
            return Instance.NextId();
        }

        private void WaitIfNecessary()
        {
            long currentWithSequence = timestampAndSequence;
            long current = currentWithSequence >> sequenceBits;
            long newest = GetNewestTimestamp();
            if (current >= newest)
            {
                Thread.Sleep(5);
            }
        }

        private long GetNewestTimestamp()
        {
            return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - twepoch;
        }

        public static long GenerateWorkerId()
        {
            try
            {
                return GenerateWorkerIdBaseOnK8S();
            }
            catch (Exception)
            {
                try
                {
                    return GenerateWorkerIdBaseOnMac();
                }
                catch (Exception)
                {
                    return GenerateRandomWorkerId();
                }
            }
        }

        public static long GenerateWorkerIdBaseOnMac()
        {
            IEnumerable<NetworkInterface> all = NetworkInterface.GetAllNetworkInterfaces();
            foreach (NetworkInterface networkInterface in all)
            {
                bool isLoopback = networkInterface.NetworkInterfaceType == NetworkInterfaceType.Loopback;
                //bool isVirtual = networkInterface.;
                //if (isLoopback || isVirtual)
                if (isLoopback)
                {
                    continue;
                }
                byte[] mac = networkInterface.GetPhysicalAddress().GetAddressBytes();
                return ((mac[4] & 0B11) << 8) | (mac[5] & 0xFF);
            }
            throw new Exception("no available mac found");
        }

        public static long GenerateWorkerIdBaseOnK8S()
        {
            return GenerateWorkerIdBaseOnString(Environment.GetEnvironmentVariable("K8S_POD_ID"));
        }

        public static long GenerateWorkerIdBaseOnString(string str)
        {
            ArgumentNullException.ThrowIfNull(str, nameof(str));
            int hashValue = 0;
            int cc = 2 << (workerIdBits - 1);
            foreach (char c in str)
            {
                hashValue = (hashValue * 31 + c) % cc;
            }
            return hashValue + 1;
        }

        public static long GenerateRandomWorkerId()
        {
            return Random.Shared.NextInt64(maxWorkerId + 1);
        }
    }

YitIdHelper

开源库 https://github.com/yitter/IdGenerator

❄ 这是优化的雪花算法(雪花漂移),它生成的ID更短、速度更快。

❄ 支持 k8s 等容器环境自动扩容(自动注册 WorkerId),可在单机或分布式环境生成数字型唯一ID。

Guid

https://learn.microsoft.com/zh-cn/dotnet/api/system.guid?view=net-7.0

全局唯一标识符(GUID,Globally Unique Identifier)是一种由算法生成的二进制长度为128位的数字标识符。GUID主要用于在拥有多个节点、多台计算机的网络或系统中。

Nanoid

开源库 https://github.com/codeyu/nanoid-net

一个小巧、安全、URL友好、唯一的字符串ID生成器。

“一个惊人的无意义的完美主义水平,这简直让人无法不敬佩。”

  • 小巧. 130字节 (经过压缩和gzip处理)。没有依赖。Size Limit 控制大小。
  • 安全. 它使用硬件随机生成器。可在集群中使用。
  • 紧凑. 它使用比 UUID(A-Za-z0-9_-)更大的字母表。因此,ID 大小从36个符号减少到21个符号。
  • 可移植. Nano ID 已被移植到 20种编程语言。

Nano ID 与 UUID v4 (基于随机数) 相当。 它们在 ID 中有相似数量的随机位 (Nano ID 为126,UUID 为122),因此它们的碰撞概率相似::

要想有十亿分之一的重复机会, 必须产生103万亿个版本4的ID.

Nano ID 和 UUID v4之间有两个主要区别:

Nano ID 使用更大的字母表,所以类似数量的随机位 被包装在21个符号中,而不是36个。
Nano ID 代码比 uuid/v4 包少 4倍: 130字节而不是423字节.

性能测试代码

[AllStatisticsColumn]
public class IdGeneratorTest
{
    public IdGeneratorTest()
    {
        var options = new IdGeneratorOptions()
        {
            WorkerId = 55,
            WorkerIdBitLength = 6,
            SeqBitLength = 12,
        };
        YitIdHelper.SetIdGenerator(options);
    }

    [Benchmark]
    public long IdGeneratorNewId() => IdGenerator.NewId();


    [Benchmark]
    public long YitIdHelperNextId() => YitIdHelper.NextId();

    [Benchmark]
    public string NewGuid() => Guid.NewGuid().ToString();

    [Benchmark]
    public string NanoidGenerate() => Nanoid.Generate();

    [Benchmark]
    public string NewUlid() => Ulid.NewUlid().ToString();
}

结果


BenchmarkDotNet v0.13.12, Windows 11 (10.0.22000.2538/21H2/SunValley)
Intel Core i7-10700 CPU 2.90GHz, 1 CPU, 16 logical and 8 physical cores
.NET SDK 8.0.100
  [Host]     : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX2
  DefaultJob : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX2


Method Mean Error StdDev StdErr Median Min Q1 Q3 Max Op/s
IdGeneratorNewId 243.39 ns 3.468 ns 3.244 ns 0.837 ns 244.00 ns 237.82 ns 241.23 ns 245.76 ns 247.94 ns 4,108,554.1
SIdGeneratorNewId 90.68 ns 0.645 ns 0.571 ns 0.153 ns 90.62 ns 89.52 ns 90.30 ns 91.07 ns 91.83 ns 11,027,339.7
YitIdHelperNextId 241.24 ns 66.403 ns 195.790 ns 19.579 ns 431.44 ns 42.29 ns 42.47 ns 432.25 ns 433.03 ns 4,145,275.0
NewGuid 74.77 ns 0.575 ns 0.480 ns 0.133 ns 74.56 ns 74.03 ns 74.48 ns 75.05 ns 75.66 ns 13,374,344.2
NanoidGenerate 174.51 ns 2.040 ns 1.909 ns 0.493 ns 173.68 ns 171.78 ns 173.11 ns 175.80 ns 178.33 ns 5,730,341.9
NewUlid 54.37 ns 0.190 ns 0.169 ns 0.045 ns 54.36 ns 54.17 ns 54.25 ns 54.44 ns 54.71 ns 18,391,966.4

ps: 之前少了 ulid,现已补上, 换用 .net8之后 guid 有明显性能变化,而其余算法可能由于自身设计,没有明显变化

posted @ 2023-11-10 13:12  victor.x.qu  阅读(1354)  评论(2编辑  收藏  举报