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

时间:2025-03-26 15:55:22
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}"); } } }