10. 雪花id
前言
雪花算法是Twitter开源的分布式ID生成算法,雪花算法生成后是一个64bit大小的整数(long型),它的优势:
- 高性能: 每秒可生成数百万的id
- 生成的id是唯一的,不会重复
- id是有顺序的
快速入门
- 安装.Net 6.0
单机版雪花id
新建ASP.NET Core 空项目
Assignment.SnowflakeId
,并安装Masa.Contrib.Data.IdGenerator.Snowflake
(单机版的雪花id)1
2
3dotnet new web -o Assignment.SnowflakeId
cd Assignment.SnowflakeId
dotnet add package Masa.Contrib.Data.IdGenerator.Snowflake --version 0.5.0-preview.5修改类
Program
,注册单机版雪花id1
2
3
4
5builder.Services.AddSnowflake(options =>
{
// options.TimestampType = TimestampType.Seconds;//时间戳使用秒
// options.EnableMachineClock = true;//启用时钟锁
});如何生成雪花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
新建ASP.NET Core 空项目
Assignment.DistributedSnowflakeId
,并安装Masa.Contrib.Data.IdGenerator.Snowflake.Distributed.Redis
(分布式雪花id)1
2
3dotnet new web -o Assignment.DistributedSnowflakeId
cd Assignment.DistributedSnowflakeId
dotnet add package Masa.Contrib.Data.IdGenerator.Snowflake.Distributed.Redis --version 0.5.0-preview.5修改类
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();
});修改类
Program
,新增生成雪花id的服务1
2
3
4
5app.MapGet("/id/generator/ioc", (ISnowflakeGenerator snowflakeGenerator)
=> snowflakeGenerator.NewId()); //生成id
app.MapGet("/id/generator", ()
=> IdGeneratorFactory.SnowflakeGenerator.NewId()); //生成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
新建类库
IdGenerator.Snowflake
,并安装Masa.BuildingBlocks.Data
1
2
3dotnet new web -o IdGenerator.Snowflake
cd IdGenerator.Snowflake
dotnet add package Masa.BuildingBlocks.Data --version 0.5.0-preview.5新建类
SnowflakeIdGenerator
, 并实现ISnowflakeGenerator1
2
3
4
5
6
7
8public 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();
}
}新建类
ServiceCollectionExtensions
, 新增AddSnowflake
方法扩展服务集合1
2
3
4
5
6
7
8public 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的性能
- 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 |
- 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 |
- 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,欢迎联系我们