public class SnowFlakeHelper
{
private static readonly object _obj = new object();
private static SnowFlakeHelper Instance;
private SnowFlakeHelper()
{
}
/// <summary>
/// 獲取雪花幫助類對象
/// </summary>
/// <param name="datacenterId">數據中心ID</param>
/// <param name="workerId">工作機器Id</param>
/// <returns></returns>
public static SnowFlakeHelper GetSnowInstance(long datacenterId = 1, long workerId = 1)
{
//雙if 加鎖
if (Instance == null)
{
lock (_obj)
{
if (Instance == null)
{
Instance = new SnowFlakeHelper(datacenterId, workerId);
}
}
}
return Instance;
}
// 開始時間截((new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Utc)-Jan1st1970).TotalMilliseconds)
private const long twepoch = 1577836800000L;
// 機器id所占的位數
private const int workerIdBits = 5;
// 數據標識id所占的位數
private const int datacenterIdBits = 5;
// 支持的最大機器id,結果是31 (這個移位算法可以很快的計算出幾位二進制數所能表示的最大十進制數)
private const long maxWorkerId = -1L ^ (-1L << workerIdBits);
// 支持的最大數據標識id,結果是31
private const long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
// 序列在id中占的位數
private const int sequenceBits = 12;
// 數據標識id向左移17位(12+5)
private const int datacenterIdShift = sequenceBits + workerIdBits;
// 機器ID向左移12位
private const int workerIdShift = sequenceBits;
// 時間截向左移22位(5+5+12)
private const int timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
// 生成序列的掩碼,這里為4095 (0b111111111111=0xfff=4095)
private const long sequenceMask = -1L ^ (-1L << sequenceBits);
// 數據中心ID(0~31)
public long datacenterId { get; private set; }
// 工作機器ID(0~31)
public long workerId { get; private set; }
// 毫秒內序列(0~4095)
public long sequence { get; private set; }
// 上次生成ID的時間截
public long lastTimestamp { get; private set; }
/// <summary>
/// 雪花ID
/// </summary>
/// <param name="datacenterId">數據中心ID</param>
/// <param name="workerId">工作機器ID</param>
private SnowFlakeHelper(long datacenterId, long workerId)
{
if (datacenterId > maxDatacenterId || datacenterId < 0)
{
throw new Exception(string.Format("datacenter Id can't be greater than {0} or less than 0", maxDatacenterId));
}
if (workerId > maxWorkerId || workerId < 0)
{
throw new Exception(string.Format("worker Id can't be greater than {0} or less than 0", maxWorkerId));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
this.sequence = 0L;
this.lastTimestamp = -1L;
}
/// <summary>
/// 獲得下一個ID
/// </summary>
/// <returns></returns>
public long NextId()
{
lock (this)
{
long timestamp = GetCurrentTimestamp();
if (timestamp > lastTimestamp) //時間戳改變,毫秒內序列重置
{
sequence = 0L;
}
else if (timestamp == lastTimestamp) //如果是同一時間生成的,則進行毫秒內序列
{
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) //毫秒內序列溢出
{
timestamp = GetNextTimestamp(lastTimestamp); //阻塞到下一個毫秒,獲得新的時間戳
}
}
else //當前時間小於上一次ID生成的時間戳,證明系統時鍾被回撥,此時需要做回撥處理
{
sequence = (sequence + 1) & sequenceMask;
if (sequence > 0)
{
timestamp = lastTimestamp; //停留在最后一次時間戳上,等待系統時間追上后即完全度過了時鍾回撥問題。
}
else //毫秒內序列溢出
{
timestamp = lastTimestamp + 1; //直接進位到下一個毫秒
}
//throw new Exception(string.Format("Clock moved backwards. Refusing to generate id for {0} milliseconds", lastTimestamp - timestamp));
}
lastTimestamp = timestamp; //上次生成ID的時間截
//移位並通過或運算拼到一起組成64位的ID
var id = ((timestamp - twepoch) << timestampLeftShift)
| (datacenterId << datacenterIdShift)
| (workerId << workerIdShift)
| sequence;
return id;
}
}
/// <summary>
/// 解析雪花ID
/// </summary>
/// <returns></returns>
public static string AnalyzeId(long Id)
{
StringBuilder sb = new StringBuilder();
var timestamp = (Id >> timestampLeftShift);
var time = Jan1st1970.AddMilliseconds(timestamp + twepoch);
sb.Append(time.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss:fff"));
var datacenterId = (Id ^ (timestamp << timestampLeftShift)) >> datacenterIdShift;
sb.Append("_" + datacenterId);
var workerId = (Id ^ ((timestamp << timestampLeftShift) | (datacenterId << datacenterIdShift))) >> workerIdShift;
sb.Append("_" + workerId);
var sequence = Id & sequenceMask;
sb.Append("_" + sequence);
return sb.ToString();
}
/// <summary>
/// 阻塞到下一個毫秒,直到獲得新的時間戳
/// </summary>
/// <param name="lastTimestamp">上次生成ID的時間截</param>
/// <returns>當前時間戳</returns>
private static long GetNextTimestamp(long lastTimestamp)
{
long timestamp = GetCurrentTimestamp();
while (timestamp <= lastTimestamp)
{
timestamp = GetCurrentTimestamp();
}
return timestamp;
}
/// <summary>
/// 獲取當前時間戳
/// </summary>
/// <returns></returns>
private static long GetCurrentTimestamp()
{
return (long)(DateTime.UtcNow - Jan1st1970).TotalMilliseconds;
}
private static readonly DateTime Jan1st1970 = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
}