using Microsoft.Extensions.Options; using System; using System.Collections.Generic; using System.Text; namespace TEAMModelOS.SDK.DI { public class SnowflakeId { private readonly IOptionsMonitor _optionsMonitor; // 開始時間截((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); // 毫秒內序列(0~4095) private long sequence = 0L; // 上次生成ID的時間截 private long lastTimestamp = -1L; // 數據中心ID(0~31,China請填8,Global請填1) public long DatacenterId { get; private set; } // 工作機器ID(0~31,依照數據中心,各地區自定) public long WorkerId { get; private set; } /// /// 分散式ID產生器 (Snowflake算法,支援線程安全) /// /// 數據中心ID (0~31) /// 工作機器ID (0~31) public SnowflakeId(IOptionsMonitor optionsMonitor) { if (optionsMonitor == null) throw new ArgumentNullException(nameof(optionsMonitor)); _optionsMonitor = optionsMonitor; this.DatacenterId = optionsMonitor.CurrentValue.DatacenterId; this.WorkerId = optionsMonitor.CurrentValue.WorkerId; } /// /// 獲得下一個ID /// /// 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; } } /// /// 解析分散式ID /// /// public string ParseId(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(); } /// /// 解析分散式ID,并转换为时间字符串。 /// /// public string ParseIdToTimeString(long Id, string format = "yyyy-MM-dd HH:mm:ss") { var timestamp = (Id >> timestampLeftShift); var time = Jan1st1970.AddMilliseconds(timestamp + twepoch); string timeString= time.ToLocalTime().ToString(format); return timeString; } /// /// 解析分散式ID,并转换为时间戳。 /// /// public long ParseIdToTimeStamp(long Id) { var timestamp = (Id >> timestampLeftShift); long stime = timestamp + twepoch; return stime; } /// /// 阻塞到下一個毫秒,直到獲得新的時間戳 /// /// 上次生成ID的時間截 /// 當前時間戳 private static long GetNextTimestamp(long lastTimestamp) { long timestamp = GetCurrentTimestamp(); while (timestamp <= lastTimestamp) { timestamp = GetCurrentTimestamp(); } return timestamp; } /// /// 獲取當前時間戳 /// /// private static long GetCurrentTimestamp() { return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); //return (long)(DateTime.UtcNow - Jan1st1970).TotalMilliseconds; } private static readonly DateTime Jan1st1970 = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); } }