C# .NET实现雪花ID生成器的具体方案

 更新时间:2026年01月05日 08:33:19   作者:东百牧码人  
雪花ID是Twitter开源的分布式ID生成算法,核心是将64位长整型ID按规则拆分,保证全局唯一且趋势递增,你希望在.NET中实现一个完整、线程安全且能处理时钟回拨问题的雪花ID算法,下面先讲解核心结构,再给出可直接使用的.NET实现代码,需要的朋友可以参考下

引言

你希望在.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分库分表项目中,作为分布式主键生成方案。

到此这篇关于C# .NET实现雪花ID生成器的具体方案的文章就介绍到这了,更多相关C# .NET雪花ID生成器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 如何在datatable中使用groupby进行分组统计

    如何在datatable中使用groupby进行分组统计

    如何在datatable中进行分组,并且计算分组后每组的数量,考虑了一下,可以使用LINQ来实现datatable分组,需要的朋友可以参考下
    2015-08-08
  • C#文件加密方法汇总

    C#文件加密方法汇总

    这篇文章主要介绍了C#文件加密方法,实例汇总了常见的加密方法如AES加密类、文件加密类、文件夹加密类等,最后给出完整的实例源码下载供大家参考借鉴,需要的朋友可以参考下
    2014-11-11
  • 详解c# 类型转换

    详解c# 类型转换

    这篇文章主要介绍了c# 类型转换的相关资料,文中讲解非常细致,代码帮助大家更好的理解和学习,感兴趣的朋友可以学习
    2020-07-07
  • C#对NULL的简化赋值的方法总结

    C#对NULL的简化赋值的方法总结

    在C#中,对null赋值意味着将引用类型的变量设置为无引用或者说空引用,当你声明一个引用类型的变量,如果没有给它初始化具体的对象,那么它的值就是null,本文给大家介绍了C#对NULL的简化赋值的方法总结,需要的朋友可以参考下
    2024-11-11
  • C#数据导入到EXCEL的方法

    C#数据导入到EXCEL的方法

    今天小编就为大家分享一篇关于C#数据导入到EXCEL的方法,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-01-01
  • C# 中文简体转繁体实现代码

    C# 中文简体转繁体实现代码

    C# 中文简体转繁体实现代码,需要的朋友可以参考一下
    2013-02-02
  • 在Parallel中使用DbSet.Add()发现的一系列多线程问题和解决思路详解

    在Parallel中使用DbSet.Add()发现的一系列多线程问题和解决思路详解

    这篇文章主要介绍了在Parallel中使用DbSet.Add()发现的一系列多线程问题和解决过程的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2016-11-11
  • c#获取字符串宽度的示例代码(字节数方法)

    c#获取字符串宽度的示例代码(字节数方法)

    本篇文章主要介绍了c#获取字符串宽度的示例代码(字节数方法)。需要的朋友可以过来参考下,希望对大家有所帮助
    2014-01-01
  • WPF实现页面的切换的示例代码

    WPF实现页面的切换的示例代码

    本文主要介绍了WPF实现页面的切换的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-01-01
  • C# 串口扫描枪读取数据的实现

    C# 串口扫描枪读取数据的实现

    本文主要介绍了C# 串口扫描枪读取数据的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-04-04

最新评论