发布于2026-07-18 阅读(0)
扫一扫,手机访问
先说清楚:C# 里用雪花算法生成 ID,优先选 `SnowflakeId`(来自 `Microsoft.Extensions.TimeProvider`,.NET 8.0+ 自带),或者 `IdGen`、`TwitterSnowflake` 这些已经经过大量验证的库。手写?不是不行,但代价极高,尤其是在时钟回拨、多实例部署、worker ID 分配这几个关键点上,稍有不慎就出问题。
### 为什么不能直接照抄网上的“10行雪花算法”
网上那些所谓的“10行代码实现雪花算法”,看着简洁,但压根没考虑生产环境里的硬伤:
- 没有时钟回拨检测 —— 服务器时间被 NTP 校正后,`GetTimestamp()` 返回值变小,ID 直接重复或阻塞
- worker ID 硬编码或靠随机数生成 —— 多进程、多容器场景下,冲突概率极高,ID 不唯一是家常便饭
- 没考虑 `sequence` 溢出重置时机 —— 同一毫秒内超 4096 次调用,要么抛异常,要么静默截断,都不靠谱
- 依赖 `DateTime.Now` 而非单调时钟 —— 虚拟机休眠、系统暂停后,时间跳变,ID 直接乱套
### 推荐方案:用 IdGen 库(.NET Standard 2.0+ 兼容性最好)
`IdGen` 是社区验证最久、文档最清晰的 C# 雪花兼容实现。它支持自定义 epoch、ID 长度、worker ID 分配策略,还内置了时钟保护机制,省心很多。
安装:
```bash
dotnet add package IdGen
```
基础用法:
```csharp
var options = new IdGenOptions(1); // worker ID = 1
var idgen = new IdGenerator(options);
long id = idgen.CreateId(); // 返回 long 类型 ID
```
关键注意点:
- `IdGenOptions` 构造时传入的 `workerId` 必须全局唯一 —— 推荐从配置中心读取,或者用 Kubernetes Downward API 注入 `POD_NAME` 做哈希
- 默认 epoch 是 `2020-01-01`,如果需要兼容老系统,显式设置 `options.Epoch = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Utc);`
- 不建议在 Web API 的每个请求里 new 一个 `IdGenerator` —— 它是线程安全的,应该注册为 Singleton
### 如果必须手写:至少守住这三条底线
仅限学习或极简嵌入场景。下面是最简可用骨架(省略了日志、配置注入等):
```csharp
public class SimpleSnowflake
{
private const long TWEPOCH = 1288834974657L; // 2010-11-04
private const int WORKER_ID_BITS = 5;
private const int DATA_CENTER_ID_BITS = 5;
private const int SEQUENCE_BITS = 12;
private const long MAX_WORKER_ID = -1L ^ (-1L << WORKER_ID_BITS);
private readonly long _workerId;
private long _sequence = 0L;
private long _lastTimestamp = -1L;
public SimpleSnowflake(long workerId)
{
if (workerId > MAX_WORKER_ID || workerId < 0)
throw new ArgumentException($"worker Id can't be greater than {MAX_WORKER_ID} or less than 0");
_workerId = workerId;
}
public long NextId()
{
var timestamp = CurrentTime();
if (timestamp < _lastTimestamp)
throw new InvalidOperationException($"Clock moved backwards. Refusing to generate id for {_lastTimestamp - timestamp} milliseconds");
if (_lastTimestamp == timestamp)
{
_sequence = (_sequence + 1) & 0xfff;
if (_sequence == 0)
timestamp = WaitNextMillis(_lastTimestamp);
}
else _sequence = 0;
_lastTimestamp = timestamp;
return ((timestamp - TWEPOCH) << 22) | (_workerId << 17) | _sequence;
}
private long CurrentTime() => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
private long WaitNextMillis(long lastTimestamp)
{
var timestamp = CurrentTime();
while (timestamp <= lastTimestamp) timestamp = CurrentTime();
return timestamp;
}
}
```
必须检查的几点:
- 构造函数传入的 `workerId` 是否真的不重复 —— 本地测试用 1 没问题,上线必须动态分配
- `CurrentTime()` 不能替换成 `DateTime.Now`,必须用 `DateTimeOffset.UtcNow` 或 .NET 8+ 的 `TimeProvider.GetUtcNow()`
- `WaitNextMillis` 是阻塞等待,高并发下会拖慢吞吐 —— 生产环境应改用拒绝(抛异常)或降级(返回 Guid)
真正难的不是位运算,而是让多个服务实例在分布式环境下协同生成不重复、趋势递增、无时钟依赖的 ID。worker ID 的分发机制、时钟漂移的容忍策略、以及 ID 解析(反解时间戳/机器号)能力,才是落地时反复卡住的地方。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8