C# .NET實現(xiàn)雪花ID生成器的具體方案
引言
你希望在.NET中實現(xiàn)一個完整、線程安全且能處理時鐘回?fù)軉栴}的雪花ID(Snowflake)算法,用于分庫分表場景下生成全局唯一的分布式ID,避免不同庫/表的主鍵重復(fù)。
雪花ID是Twitter開源的分布式ID生成算法,核心是將64位長整型ID按規(guī)則拆分,保證全局唯一且趨勢遞增。下面先講解核心結(jié)構(gòu),再給出可直接使用的.NET實現(xiàn)代碼。
一、雪花ID核心結(jié)構(gòu)(64位long型)
| 位段 | 長度 | 作用 |
|---|---|---|
| 符號位 | 1位 | 固定為0(保證ID為正數(shù)) |
| 時間戳位 | 41位 | 記錄生成ID的毫秒級時間戳(相對于一個起始時間),可支持約69年(2^41-1) |
| 機(jī)器/節(jié)點位 | 10位 | 分為5位數(shù)據(jù)中心ID + 5位機(jī)器ID,最多支持1024個節(jié)點部署 |
| 序列號位 | 12位 | 同一毫秒內(nèi)的自增序列,每毫秒最多生成4096個ID(2^12=4096) |
二、.NET實現(xiàn)雪花ID生成器
以下實現(xiàn)包含線程安全、時鐘回?fù)軝z測、參數(shù)合法性校驗,可直接在生產(chǎn)環(huán)境使用:
using System;
using System.Threading;
/// <summary>
/// 雪花ID生成器(線程安全、支持時鐘回?fù)軝z測)
/// </summary>
public class SnowflakeIdGenerator
{
#region 常量定義
// 起始時間戳(UTC時間:2024-01-01 00:00:00),可自行調(diào)整,建議使用項目上線前的時間
private const long Epoch = 1704067200000L;
// 各部分的位長度
private const int TimestampBits = 41; // 時間戳位長度
private const int DataCenterIdBits = 5; // 數(shù)據(jù)中心ID位長度
private const int MachineIdBits = 5; // 機(jī)器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; // 數(shù)據(jù)中心ID
private readonly long _machineId; // 機(jī)器ID
private long _lastTimestamp = -1L; // 上一次生成ID的時間戳
private long _sequence = 0L; // 當(dāng)前毫秒內(nèi)的序列號
private readonly object _lockObj = new(); // 鎖對象(保證線程安全)
#endregion
/// <summary>
/// 構(gòu)造函數(shù)
/// </summary>
/// <param name="dataCenterId">數(shù)據(jù)中心ID(0-31)</param>
/// <param name="machineId">機(jī)器ID(0-31)</param>
/// <exception cref="ArgumentException">參數(shù)超出范圍時拋出</exception>
public SnowflakeIdGenerator(long dataCenterId, long machineId)
{
// 校驗參數(shù)合法性
if (dataCenterId < 0 || dataCenterId > MaxDataCenterId)
{
throw new ArgumentException($"數(shù)據(jù)中心ID必須在0-{MaxDataCenterId}之間", nameof(dataCenterId));
}
if (machineId < 0 || machineId > MaxMachineId)
{
throw new ArgumentException($"機(jī)器ID必須在0-{MaxMachineId}之間", nameof(machineId));
}
_dataCenterId = dataCenterId;
_machineId = machineId;
}
/// <summary>
/// 生成下一個雪花ID
/// </summary>
/// <returns>全局唯一的雪花ID</returns>
/// <exception cref="Exception">時鐘回?fù)艹鋈萑谭秶鷷r拋出</exception>
public long NextId()
{
lock (_lockObj) // 加鎖保證多線程安全
{
// 1. 獲取當(dāng)前時間戳(毫秒)
long currentTimestamp = GetCurrentTimestamp();
// 2. 處理時鐘回?fù)軉栴}
if (currentTimestamp < _lastTimestamp)
{
long timeDiff = _lastTimestamp - currentTimestamp;
// 容忍500ms內(nèi)的時鐘回?fù)埽ǖ却钡綍r間追上)
if (timeDiff <= 500)
{
// 自旋等待,直到時間戳追上上次的時間戳
while (currentTimestamp < _lastTimestamp)
{
currentTimestamp = GetCurrentTimestamp();
}
}
else
{
// 超出容忍范圍,拋出異常(避免ID重復(fù))
throw new Exception($"時鐘回?fù)墚惓#‘?dāng)前時間戳:{currentTimestamp},上次時間戳:{_lastTimestamp},差值:{timeDiff}ms");
}
}
// 3. 處理同一毫秒內(nèi)的序列號
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) // 數(shù)據(jù)中心ID部分
| (_machineId << MachineIdShift) // 機(jī)器ID部分
| _sequence; // 序列號部分
}
}
/// <summary>
/// 獲取當(dāng)前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,返回各部分信息(用于調(diào)試)
/// </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)
{
// 初始化生成器(數(shù)據(jù)中心ID=1,機(jī)器ID=1,根據(jù)實際部署環(huán)境調(diào)整)
var idGenerator = new SnowflakeIdGenerator(1, 1);
// 單線程生成ID
long id1 = idGenerator.NextId();
Console.WriteLine($"生成的雪花ID:{id1}");
// 解析ID(調(diào)試用)
var parseResult = idGenerator.ParseId(id1);
Console.WriteLine($"生成時間:{parseResult.GenerateTime}");
Console.WriteLine($"數(shù)據(jù)中心ID:{parseResult.DataCenterId}");
Console.WriteLine($"機(jī)器ID:{parseResult.MachineId}");
Console.WriteLine($"序列號:{parseResult.Sequence}");
// 多線程測試(驗證線程安全)
Parallel.For(0, 1000, i =>
{
long id = idGenerator.NextId();
Console.WriteLine($"線程{Thread.CurrentThread.ManagedThreadId}生成ID:{id}");
});
}
}
四、關(guān)鍵說明
- 起始時間戳(Epoch):建議設(shè)置為項目上線前的時間(如示例中的2024-01-01),可延長ID使用周期(默認(rèn)能用到2093年)。
- 機(jī)器/數(shù)據(jù)中心ID:部署時需保證每個節(jié)點的(數(shù)據(jù)中心ID+機(jī)器ID)唯一(比如通過配置文件/環(huán)境變量設(shè)置),避免不同節(jié)點生成重復(fù)ID。
- 時鐘回?fù)芴幚?/strong>:代碼中容忍500ms內(nèi)的時鐘回?fù)埽ㄗ孕却?,超出則拋異常,可根據(jù)業(yè)務(wù)調(diào)整容忍閾值。
- 性能:單節(jié)點每秒可生成約400萬+ ID(1ms生成4096個,1秒=1000ms → 4096*1000=4,096,000),滿足絕大多數(shù)業(yè)務(wù)場景。
總結(jié)
- 雪花ID核心是通過位運算將時間戳、機(jī)器ID、序列號拼接成64位唯一ID,保證全局唯一且趨勢遞增;
- 實現(xiàn)時必須保證線程安全(加鎖)和時鐘回?fù)軝z測(避免ID重復(fù));
- 使用時需保證每個節(jié)點的機(jī)器/數(shù)據(jù)中心ID唯一,起始時間戳建議根據(jù)項目實際調(diào)整。
這個實現(xiàn)可直接集成到你的.NET分庫分表項目中,作為分布式主鍵生成方案。
到此這篇關(guān)于C# .NET實現(xiàn)雪花ID生成器的具體方案的文章就介紹到這了,更多相關(guān)C# .NET雪花ID生成器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

