10. 雪花id

10. 雪花id

前言

雪花算法是Twitter开源的分布式ID生成算法,雪花算法生成后是一个64bit大小的整数(long型),它的优势:

  • 高性能: 每秒可生成数百万的id
  • 生成的id是唯一的,不会重复
  • id是有顺序的

快速入门

单机版雪花id

  1. 新建ASP.NET Core 空项目Assignment.SnowflakeId,并安装Masa.Contrib.Data.IdGenerator.Snowflake(单机版的雪花id)

    1
    2
    3
    dotnet new web -o Assignment.SnowflakeId
    cd Assignment.SnowflakeId
    dotnet add package Masa.Contrib.Data.IdGenerator.Snowflake --version 0.5.0-preview.5
  2. 修改类Program,注册单机版雪花id

    1
    2
    3
    4
    5
    builder.Services.AddSnowflake(options =>
    {
    // options.TimestampType = TimestampType.Seconds;//时间戳使用秒
    // options.EnableMachineClock = true;//启用时钟锁
    });
  3. 如何生成雪花id?修改类Program

    1
    2
    3
    4
    5
    6
    7
    //通过di获取ISnowflakeGenerator创建id
    app.MapGet("/id/generator/ioc", (ISnowflakeGenerator snowflakeGenerator)
    => snowflakeGenerator.Create());

    //通过静态字段获取ISnowflakeGenerator生成雪花id
    app.MapGet("/id/generator", ()
    => IdGeneratorFactory.SnowflakeGenerator.Create());

分布式版本雪花id

  1. 新建ASP.NET Core 空项目Assignment.DistributedSnowflakeId,并安装Masa.Contrib.Data.IdGenerator.Snowflake.Distributed.Redis(分布式雪花id)

    1
    2
    3
    dotnet new web -o Assignment.DistributedSnowflakeId
    cd Assignment.DistributedSnowflakeId
    dotnet add package Masa.Contrib.Data.IdGenerator.Snowflake.Distributed.Redis --version 0.5.0-preview.5
  2. 修改类Program

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //使用Redis,如果有使用MasaRedis,可省略
    builder.Services.AddMasaRedisCache(option =>
    {
    option.Servers = new List<RedisServerOptions>()
    {
    new("localhost", 6379)
    };
    option.DefaultDatabase = 2;
    option.Password = "";
    });
    builder.Services.AddSnowflake(option => {
    //通过Redis实现分布式雪花id
    option.UseRedis();
    });
  3. 修改类Program,新增生成雪花id的服务

    1
    2
    3
    4
    5
    app.MapGet("/id/generator/ioc", (ISnowflakeGenerator snowflakeGenerator)
    => snowflakeGenerator.NewId()); //生成id

    app.MapGet("/id/generator", ()
    => IdGeneratorFactory.SnowflakeGenerator.NewId()); //生成id

    生成雪花id不区分单机版雪花id、分布式雪花id

进阶

雪花id的结构图如下:

雪花id结构图

  • 固定位:
    • 1位, 不需要更改。二进制中最高位为1的都是负数,但是我们生成的id一般都使用整数,所以这个最高位固定是0
  • 时间戳:
    • 41位: 毫秒,可使用0 至 ((2 ^ 41) - 1)的值(默认值)
    • 32位: 秒(并发量低可以使用),可使用0 至 ((2 ^ 32) - 1)的值
  • 工作机器id:
    • 10位:默认值,可使用0 至 ((2 ^ 10) - 1)的值,支持1024个机器id
  • 序列号:
    • 12位:默认值,可使用0 至 ((2 ^ 12) - 1)的值,同一时间节点下同一个工作机器最多生成4096个id

雪花id最多为64位,固定位数不变,确保: 时间戳位数 + 工作机器id位数 + 序列号位数 <= 63 即可

Masa.Contrib.Data.IdGenerator.Snowflake 默认实现的雪花id属于单机版的雪花id,默认工作机器为10位,序列号为12位,时间戳为41位,其中工作机器id默认获取的是环境WORKER_ID变量的值,如果未配置环境WORKER_ID变量的值,则WorkerId的值为0。

有哪些缺点及如何解决?

  • 不支持k8s(k8s多Pod共享同一个环境环境,会导致获取到同一个WorkerId,高并发场景下,可能生成重复id)
    • 可以使用Masa.Contrib.Data.IdGenerator.Snowflake.Distributed.Redis, 其借助Redis做到了WorkerId的分配,可以确保WorkerId不会重复
    • 自定义实现IWorkerProvider,并在AddSnowflake之前提前将新的WorkerProvider注册到服务集合中(其生命周期为单例)
  • 时钟回拨(时钟回拨会导致重复id出现)
    • 使用可靠的NTP服务器,确保时钟的稳定性
    • 关闭NTP同步
    • 启用时钟锁 (options.EnableMachineClock = true,代码修改,以首次运行获取到的当前时间作为基准,后续不再获取当前时间)

如何定义符合自己业务的雪花id

雪花id是一个64bit的整数,其具有很强的灵活性,默认雪花id由固定位 + 时间戳 + 机器id + 序列号生成,但这不是固定的,我们可以更改的除固定位以及时间戳意外的其他组成,而时间戳可以用毫秒也可以用秒,因此机器id + 序列号的位数在22到31位。

  • 机器id的意义在于多副本部署时,副本之间在同一时间点时不会生成相同的id
  • 序列号的意义在于同一个机器、同一个时间点下最多生成的id数,其值是递增的,如果你的业并发很高,在同一机器、时间点的请求超过4096时,则需要将序列号的位数增大,如果并发不高,则可以将序列号的位数降低。

明确自己的业务情况,确定id的组成部分,这样一来我们接下来就可以针对性的做适合我们的雪花id

  1. 新建类库IdGenerator.Snowflake,并安装Masa.BuildingBlocks.Data

    1
    2
    3
    dotnet new web -o IdGenerator.Snowflake
    cd IdGenerator.Snowflake
    dotnet add package Masa.BuildingBlocks.Data --version 0.5.0-preview.5
  2. 新建类SnowflakeIdGenerator, 并实现ISnowflakeGenerator

    1
    2
    3
    4
    5
    6
    7
    8
    public class SnowflakeIdGenerator : ISnowflakeGenerator
    {
    public long Create()
    {
    //todo: 生成雪花id,具体可参考 https://github.com/masastack/MASA.Contrib/blob/main/src/Data/IdGenerator/Masa.Contrib.Data.IdGenerator.Snowflake/Internal/SnowflakeIdGenerator.cs
    throw new NotImplementedException();
    }
    }
  3. 新建类ServiceCollectionExtensions, 新增AddSnowflake方法扩展服务集合

    1
    2
    3
    4
    5
    6
    7
    8
    public static IServiceCollection AddSnowflake(this IServiceCollection services)
    {
    services.TryAddSingleton<ISnowflakeGenerator, SnowflakeIdGenerator>();
    services.TryAddSingleton<IIdGenerator<System.Snowflake, long>>(serviceProvider
    => serviceProvider.GetRequiredService<ISnowflakeGenerator>());
    IdGeneratorFactory.SetSnowflakeGenerator(services.BuildServiceProvider().GetRequiredService<ISnowflakeGenerator>());//方便通过 IdGeneratorFactory.SnowflakeGenerator.Create() 获取id
    return services;
    }

性能测试

通过运行Benchmark查看生成id的性能

  1. TimestampType为1(毫秒)

BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19043.1023 (21H1/May2021Update) 11th Gen Intel Core i7-11700 2.50GHz, 1 CPU, 16 logical and 8 physical cores .NET SDK=7.0.100-preview.4.22252.9 [Host] : .NET 6.0.5 (6.0.522.21309), X64 RyuJIT DEBUG Job-JPQDWN : .NET 6.0.5 (6.0.522.21309), X64 RyuJIT Job-BKJUSV : .NET 6.0.5 (6.0.522.21309), X64 RyuJIT Job-UGZQME : .NET 6.0.5 (6.0.522.21309), X64 RyuJIT

Runtime=.NET 6.0 RunStrategy=ColdStart

Method Job IterationCount Mean Error StdDev Median Min Max
SnowflakeByMillisecond Job-JPQDWN 1000 2,096.1 ns 519.98 ns 4,982.3 ns 1,900.0 ns 1,000.0 ns 156,600.0 ns
SnowflakeByMillisecond Job-BKJUSV 10000 934.0 ns 58.44 ns 1,775.5 ns 500.0 ns 200.0 ns 161,900.0 ns
SnowflakeByMillisecond Job-UGZQME 100000 474.6 ns 5.54 ns 532.8 ns 400.0 ns 200.0 ns 140,500.0 ns
  1. TimestampType为2(秒)

BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19043.1023 (21H1/May2021Update) 11th Gen Intel Core i7-11700 2.50GHz, 1 CPU, 16 logical and 8 physical cores .NET SDK=7.0.100-preview.4.22252.9 [Host] : .NET 6.0.5 (6.0.522.21309), X64 RyuJIT Job-RVUKKG : .NET 6.0.5 (6.0.522.21309), X64 RyuJIT Job-JAUDMW : .NET 6.0.5 (6.0.522.21309), X64 RyuJIT Job-LOMSTK : .NET 6.0.5 (6.0.522.21309), X64 RyuJIT

Runtime=.NET 6.0 RunStrategy=ColdStart

Method Job IterationCount Mean Error StdDev Median Min Max
SnowflakeBySecond Job-RVUKKG 1000 1.882 us 0.5182 us 4.965 us 1.5000 us 0.9000 us 158.0 us
SnowflakeBySecond Job-JAUDMW 10000 11.505 us 35.1131 us 1,066.781 us 0.4000 us 0.3000 us 106,678.8 us
SnowflakeBySecond Job-LOMSTK 100000 22.097 us 15.0311 us 1,444.484 us 0.4000 us 0.2000 us 118,139.7 us
  1. TimestampType为1(毫秒)、启用时钟锁

BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19043.1023 (21H1/May2021Update) 11th Gen Intel Core i7-11700 2.50GHz, 1 CPU, 16 logical and 8 physical cores .NET SDK=7.0.100-preview.4.22252.9 [Host] : .NET 6.0.5 (6.0.522.21309), X64 RyuJIT Job-BBZSDR : .NET 6.0.5 (6.0.522.21309), X64 RyuJIT Job-NUSWYF : .NET 6.0.5 (6.0.522.21309), X64 RyuJIT Job-FYICRN : .NET 6.0.5 (6.0.522.21309), X64 RyuJIT

Runtime=.NET 6.0 RunStrategy=ColdStart

Method Job IterationCount Mean Error StdDev Median Min Max
MachineClockByMillisecond Job-BBZSDR 1000 1,502.0 ns 498.35 ns 4,775.1 ns 1,100.0 ns 700.0000 ns 151,600.0 ns
MachineClockByMillisecond Job-NUSWYF 10000 602.0 ns 54.76 ns 1,663.7 ns 200.0 ns 100.0000 ns 145,400.0 ns
MachineClockByMillisecond Job-FYICRN 100000 269.8 ns 5.64 ns 542.4 ns 200.0 ns 0.0000 ns 140,900.0 ns

总结

单机版的雪花id与分布式雪花id的区别在于WorkerId,多副本服务获取到的WorkerId不是唯一的,而分布式雪花id的WorkerId是唯一的,目前Masa提供的分布式雪花id是基于Redis实现的,如果有其他的实现可参考IdGenerator.Snowflake.Distributed.Redis实现,如果需要支持数据中心或者服务可参考如何自定义雪花id

本章源码

Assignment10

https://github.com/zhenlei520/MasaFramework.Practice

开源地址

MASA.BuildingBlocks:https://github.com/masastack/MASA.BuildingBlocks

MASA.Contrib:https://github.com/masastack/MASA.Contrib

MASA.Utils:https://github.com/masastack/MASA.Utils

MASA.EShop:https://github.com/masalabs/MASA.EShop

MASA.Blazor:https://github.com/BlazorComponent/MASA.Blazor

如果你对我们的 MASA Framework 感兴趣,无论是代码贡献、使用、提 Issue,欢迎联系我们

16373211753064.png

作者

MASA

发布于

2022-06-17

更新于

2022-09-26

许可协议