一个简单的用C#实现的分布式雪花ID算法

雪花ID是一个依赖时间戳根据算法生成的一个Int64的数字ID,一般用来做主键或者订单号等。以下是一个用C#写的雪花ID的简单实现方法

csharp 复制代码
using System;
using System.Collections.Concurrent;
using System.Diagnostics;

public class SnowflakeIdGenerator
{
    // 配置常量
    private const int SignBits = 1;// 符号位
    private const int TimestampBits = 41;// 时间戳位,最大值2^41-1=2,147,483,647ms,69年
    private const int DataCenterIdBits = 4;// 数据中心ID位,最大值2^4-1=15
    private const int MachineIdBits = 6;// 机器ID位,最大值2^6-1=63
    private const int DefaultSequenceBits = 12;// 自增序号位,最大值2^12-1=4095
    private const int ProcessIdBits = 4;// 进程ID位,最大值2^4-1=15
    private const int SequenceBitsWithProcess = 8;//如果使用进程ID,则为8位,否则为12位,最大值2^8-1=255

    // 位移计算常量
    private const int TimestampShift = 64 - SignBits - TimestampBits;
    private const int DataCenterShift = TimestampShift - DataCenterIdBits;
    private const int MachineShift = DataCenterShift - MachineIdBits;
    private const int ProcessShift = SequenceBitsWithProcess;

    // 基准时间
    private static readonly DateTime Epoch = new(2025, 3, 1, 0, 0, 0, DateTimeKind.Utc);
    private const long MaxTimestamp = (1L << TimestampBits) - 1;

    // 单例存储
    private static readonly ConcurrentDictionary<string, SnowflakeIdGenerator> _instances = new();

    // 各ID最大值
    private readonly long _maxDataCenterId = (1 << DataCenterIdBits) - 1;
    private readonly long _maxMachineId = (1 << MachineIdBits) - 1;
    private readonly long _maxProcessId = (1 << ProcessIdBits) - 1;
    private readonly long _maxSequence;

    // 状态控制
    private readonly object _lock = new object();
    private long _lastTimestamp = -1L;
    private int _sequence = 0;

    // 节点信息
    public int DataCenterId { get; }// 数据中心ID
    public int MachineId { get; }// 机器ID
    public int ProcessId { get; }// 进程ID
    public bool UseProcessId { get; }// 是否使用进程ID

    private SnowflakeIdGenerator(int dataCenterId, int machineId, int processId)
    {
        // 参数校验逻辑
        ValidateId(dataCenterId, (1 << DataCenterIdBits) - 1, nameof(dataCenterId));
        ValidateId(machineId, (1 << MachineIdBits) - 1, nameof(machineId));

        UseProcessId = processId != 0;
        if (UseProcessId)
        {
            ValidateId(processId, (1 << ProcessIdBits) - 1, nameof(processId));
        }

        DataCenterId = dataCenterId;
        MachineId = machineId;
        ProcessId = processId;
    }

    public static SnowflakeIdGenerator GetInstance(int dataCenterId, int machineId, int processId = 0)
    {
        var key = $"{dataCenterId}-{machineId}-{processId}";
        return _instances.GetOrAdd(key, _ => new SnowflakeIdGenerator(dataCenterId, machineId, processId));
    }
    /// <summary>
    /// 产生下一个ID
    /// </summary>
    /// <returns></returns>
    public long NextId()
    {
        lock (_lock)
        {
            var timestamp = GetValidTimestamp();

            if (timestamp == _lastTimestamp)
            {
                _sequence++;
                if (_sequence > MaxSequence)
                {
                    timestamp = WaitNextMillis(timestamp);
                    _sequence = 0;
                }
            }
            else
            {
                _sequence = 0;
            }

            _lastTimestamp = timestamp;

            return BuildId(timestamp);
        }
    }
    /// <summary>
    /// 获取最大序号
    /// </summary>
    private int MaxSequence => UseProcessId ?
       (1 << SequenceBitsWithProcess) - 1 :
       (1 << DefaultSequenceBits) - 1;
    /// <summary>
    /// 拼接生成ID
    /// </summary>
    /// <param name="timestamp"></param>
    /// <returns></returns>
    private long BuildId(long timestamp)
    {
        var id = (timestamp << TimestampShift)
               | ((long)DataCenterId << DataCenterShift)
               | ((long)MachineId << MachineShift);

        if (UseProcessId)
        {
            return id | ((long)ProcessId << ProcessShift) | (uint)_sequence;
        }
        return id | (uint)_sequence;
    }
    /// <summary>
    /// 获取有效的时间戳,防止时间回拨或系统时钟溢出
    /// </summary>
    /// <returns></returns>
    /// <exception cref="InvalidOperationException"></exception>
    private long GetValidTimestamp()
    {
        var timestamp = (DateTimeOffset.UtcNow.Ticks - Epoch.Ticks) / TimeSpan.TicksPerMillisecond;

        if (timestamp < _lastTimestamp)
        {
            throw new InvalidOperationException(
                $"Clock moved backwards. Refusing to generate ID for {_lastTimestamp - timestamp}ms");
        }

        if (timestamp > MaxTimestamp)
        {
            throw new InvalidOperationException(
                $"System clock overflow. Timestamp exceeds {TimestampBits} bits.");
        }

        return timestamp;
    }
    // 等待下一毫秒,产生新的时间戳
    private static long WaitNextMillis(long currentTimestamp)
    {
        long timestamp;
        var spinWait = new SpinWait();
        do
        {
            spinWait.SpinOnce();
            timestamp = (DateTimeOffset.UtcNow.Ticks - Epoch.Ticks) / TimeSpan.TicksPerMillisecond;
        } while (timestamp <= currentTimestamp);

        return timestamp;
    }

    // 校验ID,必须在0到最大值之间
    private static void ValidateId(int value, long maxValue, string paramName)
    {
        if (value < 0 || value > maxValue)
        {
            throw new ArgumentOutOfRangeException(paramName,
                $"Value must be between 0 and {maxValue}");
        }
    }
}

调用方式

csharp 复制代码
 //获取单例实例
 var generator = SnowflakeIdGenerator.GetInstance(dataCenterId: 1, machineId: 5);//推荐
 long id = generator.NextId();
 //或者,加入进程ID
 var generator = SnowflakeIdGenerator.GetInstance(dataCenterId: 1, machineId: 5, processId: 10);
 long id = generator.NextId();
相关推荐
涉密IT资质笔记1 分钟前
涉密资质投入产出分析模型:时间成本、四块成本构成与 ROI 决策框架
java·开发语言·算法
木井巳3 分钟前
【BFS 解决拓扑排序】火星词典
java·算法·leetcode·广度优先·宽度优先·图搜索算法
北风toto14 分钟前
KMP 算法详解:从暴力匹配到 O(n+m) 的字符串搜索
java·开发语言·算法
林澈在路上21 分钟前
哪个写歌软件可以用自己声音?MELO音乐详解
人工智能·python·算法
爱读书的小胖22 分钟前
Codewars Sum of a Sequence Hard Core Version C 解题分析
c语言·数据结构·算法
不会就选b22 分钟前
算法日常・每日刷题--<动态规划>24
算法·动态规划
Shaoshing34 分钟前
二分模板(左右查询)
java·算法·二分
蒲公英eric43 分钟前
线性回归与正则化:从最小二乘到岭回归、LASSO
人工智能·算法·数学建模·线性回归·岭回归·最小二乘·lasso
碧海银沙音频科技研究院5 小时前
炬芯ATS3089X手表的HFP通话不自动路由到设备BT的问题解决
网络·人工智能·嵌入式硬件·算法