.NET 实现雪花算法:高效生成分布式唯一 ID
雪花算法(Snowflake)
Twitter 开源的一种分布式 ID 生成算法
能够生成全局唯一的 64 位整数 ID。在分布式系统中,唯一 ID 的生成至关重要,它广泛应用于数据库主键、消息队列、订单号等场景。
具体实现可参考NetCoreKevin中的Kevin.SnowflakeId模块
一个基于NET8搭建DDD-微服务-现代化Saas企业级WebAPI前后端分离架构:前端Vue3、IDS4单点登录、多级缓存、自动任务、分布式、AI智能体、一库多租户、日志、授权和鉴权、CAP事件、SignalR、领域事件、MCP协议服务、IOC模块化注入、Cors、Quartz自动任务、多短信、AI、AgentFramework、SemanticKernel集成、RAG检索增强+Qdrant矢量数据库、OCR识别、API多版本、单元测试、RabbitMQ
项目地址:github:https://github.com/junkai-li/NetCoreKevin
Gitee: https://gitee.com/netkevin-li/NetCoreKevin
本文将详细介绍如何在 .NET 中实现雪花算法,并分析其核心逻辑。
雪花算法(Snowflake)简介
- 分布式 ID 生成算法的背景与需求
- 雪花算法的核心特点(全局唯一、趋势递增、高性能)
- 典型应用场景(数据库主键、消息队列、订单系统等)
雪花算法的核心结构
- 64 位 ID 的组成(符号位 + 时间戳 + 数据中心 ID + 机器 ID + 序列号)
- 各部分的位数分配及作用
- 时间回拨问题的处理机制
.NET 实现雪花算法的关键步骤
- 定义 ID 生成器的类结构(SnowflakeIdGenerator)
- 实现时间戳、数据中心 ID、机器 ID 的初始化逻辑
- 序列号的自增与溢出处理
- 处理系统时钟回拨的容错机制
| 时间戳(41 位) | 机器 ID(5 位) | 数据中心 ID(5 位) | 序列号(12 位) |
时间戳(41 位):记录生成 ID 的毫秒级时间,从自定义起始时间(如 2020-01-01)开始计算,可支持约 69 年。
机器 ID(5 位):标识不同的物理机器,范围是 0-31。
数据中心 ID(5 位):标识不同的数据中心,范围是 0-31。
序列号(12 位):同一毫秒内同一机器生成的 ID 的序列号,范围是 0-4095。
这种结构确保了:时间趋势性:ID 随时间递增,有利于数据库索引性能。
分布式唯一性:通过机器 ID 和数据中心 ID 区分不同节点。
高并发支持:序列号解决同一毫秒内的并发问题。
.NET 实现代码
核心类定义
csharp
Copy Code
using System;
using System.Threading;
public class SnowflakeIdGenerator
{
// 起始时间戳 (2020-01-01 00:00:00)
private const long TWEPOCH = 1577808000000L;
// 机器 ID 位数
private const int WORKER_ID_BITS = 5;
// 数据中心 ID 位数
private const int DATACENTER_ID_BITS = 5;
// 序列号位数
private const int SEQUENCE_BITS = 12;
// 最大机器 ID (0-31)
private const long MAX_WORKER_ID = -1L ^ (-1L << WORKER_ID_BITS);
// 最大数据中心 ID (0-31)
private const long MAX_DATACENTER_ID = -1L ^ (-1L << DATACENTER_ID_BITS);
// 机器 ID 左移位数
private const int WORKER_ID_SHIFT = SEQUENCE_BITS;
// 数据中心 ID 左移位数
private const int DATACENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
// 时间戳左移位数
private const int TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS;
// 序列号掩码 (0-4095)
private const long SEQUENCE_MASK = -1L ^ (-1L << SEQUENCE_BITS);
private long _lastTimestamp = -1L;
private long _sequence = 0L;
public SnowflakeIdGenerator(long workerId, long datacenterId)
{
if (workerId > MAX_WORKER_ID || workerId < 0)
{
throw new ArgumentException($"Worker ID 必须在 0 到 {MAX_WORKER_ID} 之间");
}
if (datacenterId > MAX_DATACENTER_ID || datacenterId < 0)
{
throw new ArgumentException($"Datacenter ID 必须在 0 到 {MAX_DATACENTER_ID} 之间");
}
WorkerId = workerId;
DatacenterId = datacenterId;
}
public long WorkerId { get; private set; }
public long DatacenterId { get; private set; }
private readonly object _lock = new object();
public long NextId()
{
lock (_lock)
{
long timestamp = TimeGen();
// 处理时钟回拨
if (timestamp < _lastTimestamp)
{
throw new Exception($"时钟回拨 detected. 拒绝生成 ID 直到 {_lastTimestamp}");
}
if (_lastTimestamp == timestamp)
{
_sequence = (_sequence + 1) & SEQUENCE_MASK;
if (_sequence == 0)
{
timestamp = TilNextMillis(_lastTimestamp);
}
}
else
{
_sequence = 0;
}
_lastTimestamp = timestamp;
return ((timestamp - TWEPOCH) << TIMESTAMP_LEFT_SHIFT) |
(DatacenterId << DATACENTER_ID_SHIFT) |
(WorkerId << WORKER_ID_SHIFT) |
_sequence;
}
}
private long TilNextMillis(long lastTimestamp)
{
long timestamp = TimeGen();
while (timestamp <= lastTimestamp)
{
timestamp = TimeGen();
}
return timestamp;
}
private long TimeGen()
{
return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
}
}
关键逻辑说明 初始化参数:
workerId 和 datacenterId 必须唯一,范围是 0-31。
起始时间戳 TWEPOCH 可自定义,确保时间戳部分足够长。
生成 ID 的步骤:
获取当前时间戳(毫秒)。
如果时间戳小于上一次生成 ID 的时间,抛出异常(时钟回拨)。
如果时间戳相同,递增序列号;如果序列号溢出,等待到下一毫秒。
组合时间戳、机器 ID、数据中心 ID 和序列号。
线程安全:
使用 lock 确保多线程环境下序列号的正确递增。
使用示例
csharp
Copy Code
public static void Main(string[] args)
{
// 创建生成器实例 (workerId: 1, datacenterId: 1)
var idGenerator = new SnowflakeIdGenerator(1, 1);
// 生成 10 个 ID
for (int i = 0; i < 10; i++)
{
long id = idGenerator.NextId();
Console.WriteLine($"生成的雪花 ID: {id} (二进制: {Convert.ToString(id, 2)})");
Thread.Sleep(1); // 确保不同毫秒
}
}

浙公网安备 33010602011771号