C# 雪花ID实现方案

你希望在.NET中实现一个完整、线程安全且能处理时钟回拨问题的雪花ID(Snowflake)算法,用于分库分表场景下生成全局唯一的分布式ID,避免不同库/表的主键重复。

雪花ID是Twitter开源的分布式ID生成算法,核心是将64位长整型ID按规则拆分,保证全局唯一且趋势递增。下面先讲解核心结构,再给出可直接使用的.NET实现代码。


一、雪花ID核心结构(64位long型)

位段 长度 作用
符号位 1位 固定为0(保证ID为正数)
时间戳位 41位 记录生成ID的毫秒级时间戳(相对于一个起始时间),可支持约69年(2^41-1)
机器/节点位 10位 分为5位数据中心ID + 5位机器ID,最多支持1024个节点部署
序列号位 12位 同一毫秒内的自增序列,每毫秒最多生成4096个ID(2^12=4096)

二、.NET实现雪花ID生成器

以下实现包含线程安全、时钟回拨检测、参数合法性校验,可直接在生产环境使用:

using System; using System.Threading; /// <summary> /// 雪花ID生成器(线程安全、支持时钟回拨检测) /// </summary> public class SnowflakeIdGenerator { #region 常量定义 // 起始时间戳(UTC时间:2024-01-01 00:00:00),可自行调整,建议使用项目上线前的时间 private const long Epoch = 1704067200000L; // 各部分的位长度 private const int TimestampBits = 41; // 时间戳位长度 private const int DataCenterIdBits = 5; // 数据中心ID位长度 private const int MachineIdBits = 5; // 机器ID位长度 private const int SequenceBits = 12; // 序列号位长度 // 最大值(位运算计算) private const long MaxDataCenterId = (1L << DataCenterIdBits) - 1; // 31 private const long MaxMachineId = (1L << MachineIdBits) - 1; // 31 private const long MaxSequence = (1L << SequenceBits) - 1; // 4095 // 位移量 private const int MachineIdShift = SequenceBits; // 12 private const int DataCenterIdShift = SequenceBits + MachineIdBits; // 17 private const int TimestampShift = SequenceBits + MachineIdBits + DataCenterIdBits; // 22 #endregion #region 私有字段 private readonly long _dataCenterId; // 数据中心ID private readonly long _machineId; // 机器ID private long _lastTimestamp = -1L; // 上一次生成ID的时间戳 private long _sequence = 0L; // 当前毫秒内的序列号 private readonly object _lockObj = new(); // 锁对象(保证线程安全) #endregion /// <summary> /// 构造函数 /// </summary> /// <param name="dataCenterId">数据中心ID(0-31)</param> /// <param name="machineId">机器ID(0-31)</param> /// <exception cref="ArgumentException">参数超出范围时抛出</exception> public SnowflakeIdGenerator(long dataCenterId, long machineId) { // 校验参数合法性 if (dataCenterId < 0 || dataCenterId > MaxDataCenterId) { throw new ArgumentException($"数据中心ID必须在0-{MaxDataCenterId}之间", nameof(dataCenterId)); } if (machineId < 0 || machineId > MaxMachineId) { throw new ArgumentException($"机器ID必须在0-{MaxMachineId}之间", nameof(machineId)); } _dataCenterId = dataCenterId; _machineId = machineId; } /// <summary> /// 生成下一个雪花ID /// </summary> /// <returns>全局唯一的雪花ID</returns> /// <exception cref="Exception">时钟回拨超出容忍范围时抛出</exception> public long NextId() { lock (_lockObj) // 加锁保证多线程安全 { // 1. 获取当前时间戳(毫秒) long currentTimestamp = GetCurrentTimestamp(); // 2. 处理时钟回拨问题 if (currentTimestamp < _lastTimestamp) { long timeDiff = _lastTimestamp - currentTimestamp; // 容忍500ms内的时钟回拨(等待直到时间追上) if (timeDiff <= 500) { // 自旋等待,直到时间戳追上上次的时间戳 while (currentTimestamp < _lastTimestamp) { currentTimestamp = GetCurrentTimestamp(); } } else { // 超出容忍范围,抛出异常(避免ID重复) throw new Exception($"时钟回拨异常!当前时间戳:{currentTimestamp},上次时间戳:{_lastTimestamp},差值:{timeDiff}ms"); } } // 3. 处理同一毫秒内的序列号 if (currentTimestamp == _lastTimestamp) { // 序列号自增,超过最大值则等待下一毫秒 _sequence = (_sequence + 1) & MaxSequence; if (_sequence == 0) { // 自旋等待下一毫秒 while (currentTimestamp <= _lastTimestamp) { currentTimestamp = GetCurrentTimestamp(); } } } else { // 新的毫秒,序列号重置为0 _sequence = 0; } // 4. 更新上次时间戳 _lastTimestamp = currentTimestamp; // 5. 拼接各部分生成最终ID return ((currentTimestamp - Epoch) << TimestampShift) // 时间戳部分 | (_dataCenterId << DataCenterIdShift) // 数据中心ID部分 | (_machineId << MachineIdShift) // 机器ID部分 | _sequence; // 序列号部分 } } /// <summary> /// 获取当前UTC时间戳(毫秒) /// </summary> /// <returns>毫秒级时间戳</returns> private long GetCurrentTimestamp() { return (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds; } #region 便捷方法(可选) /// <summary> /// 生成雪花ID的字符串形式 /// </summary> /// <returns>ID字符串</returns> public string NextIdString() { return NextId().ToString(); } /// <summary> /// 解析雪花ID,返回各部分信息(用于调试) /// </summary> /// <param name="id">雪花ID</param> /// <returns>解析后的信息</returns> public (DateTime GenerateTime, long DataCenterId, long MachineId, long Sequence) ParseId(long id) { long timestamp = (id >> TimestampShift) + Epoch; DateTime generateTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(timestamp); long dataCenterId = (id >> DataCenterIdShift) & MaxDataCenterId; long machineId = (id >> MachineIdShift) & MaxMachineId; long sequence = id & MaxSequence; return (generateTime.ToLocalTime(), dataCenterId, machineId, sequence); } #endregion }

三、使用示例

class Program { static void Main(string[] args) { // 初始化生成器(数据中心ID=1,机器ID=1,根据实际部署环境调整) var idGenerator = new SnowflakeIdGenerator(1, 1); // 单线程生成ID long id1 = idGenerator.NextId(); Console.WriteLine($"生成的雪花ID:{id1}"); // 解析ID(调试用) var parseResult = idGenerator.ParseId(id1); Console.WriteLine($"生成时间:{parseResult.GenerateTime}"); Console.WriteLine($"数据中心ID:{parseResult.DataCenterId}"); Console.WriteLine($"机器ID:{parseResult.MachineId}"); Console.WriteLine($"序列号:{parseResult.Sequence}"); // 多线程测试(验证线程安全) Parallel.For(0, 1000, i => { long id = idGenerator.NextId(); Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId}生成ID:{id}"); }); } }

四、关键说明

  1. 起始时间戳(Epoch):建议设置为项目上线前的时间(如示例中的2024-01-01),可延长ID使用周期(默认能用到2093年)。
  2. 机器/数据中心ID:部署时需保证每个节点的(数据中心ID+机器ID)唯一(比如通过配置文件/环境变量设置),避免不同节点生成重复ID。
  3. 时钟回拨处理:代码中容忍500ms内的时钟回拨(自旋等待),超出则抛异常,可根据业务调整容忍阈值。
  4. 性能:单节点每秒可生成约400万+ ID(1ms生成4096个,1秒=1000ms → 4096*1000=4,096,000),满足绝大多数业务场景。

总结

  1. 雪花ID核心是通过位运算将时间戳、机器ID、序列号拼接成64位唯一ID,保证全局唯一且趋势递增;
  2. 实现时必须保证线程安全(加锁)和时钟回拨检测(避免ID重复);
  3. 使用时需保证每个节点的机器/数据中心ID唯一,起始时间戳建议根据项目实际调整。

这个实现可直接集成到你的.NET分库分表项目中,作为分布式主键生成方案。

本文使用 文章同步助手 同步

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容