凡事涉及到高性能貌似都是高大上的東西,所以嘛我也試試;其實這個時間戳ID的生成主要為了解決我們公司內部的券號生成,估計有小伙伴認為券號生成有這麼麻煩嘛,搞個自增ID完全可以用起來,或者時間取毫微米時間戳等。
如果以上真是這樣簡單的話,那我要說道說道;首先自增ID資源耗盡的時候,特別禮券號生成的越頻繁,畢竟bigInt也有耗盡那天(當然如果有更長數字字段就是慢慢耗呗),而且依靠數據庫進行被動生成,在有些業務上比較軟肋;我還有一個同事說搞一張表定時去自增生成ID,這樣就能隨時取已經存在的ID資源數據,我只能說這是一種笨辦法,而且你都不知道外部業務對券號的需要量有多少,萬一立馬要個1000萬,你來得及?
還有就是毫微米時間戳也是會出問題,因為在多並發請求下也會大概率出現同樣ID,大家不信可以去試試,想想我們的CPU運算有多快;當然防止重復可以考慮休眠例如一毫秒,但這樣就會丟失性能,想想雪花算法一毫秒能產生4095個不重復ID,我們好歹也可以向它學習吧,以上說了這麼多少就明白了這個要求也是蠻苛刻的,當中我也想過用雪花算法,但由於生成的ID比較長(後面我會說為什麼不適宜)!
下面來看看我對這個唯一時間戳ID生成的代碼算法,借鑒了點雪花算法:
/// <summary> /// 時間戳ID /// </summary> public class TimestampID { private long _lastTimestamp; private long _sequence; //計數從零開始 private readonly DateTime? _initialDateTime; private static TimestampID _timestampID; private const int MAX_END_NUMBER = 9999; private TimestampID(DateTime? initialDateTime) { _initialDateTime = initialDateTime; } /// <summary> /// 獲取單個實例對象 /// </summary> /// <param name="initialDateTime">最初時間,與當前時間做個相差取時間戳</param> /// <returns></returns> public static TimestampID GetInstance(DateTime? initialDateTime = null) { if (_timestampID == null) Interlocked.CompareExchange(ref _timestampID, new TimestampID(initialDateTime), null); return _timestampID; } /// <summary> /// 最初時間,作用時間戳的相差 /// </summary> protected DateTime InitialDateTime { get { if (_initialDateTime == null || _initialDateTime.Value == DateTime.MinValue) return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); return _initialDateTime.Value; } } /// <summary> /// 獲取時間戳ID /// </summary> /// <returns></returns> public string GetID() { long temp; var timestamp = GetUniqueTimeStamp(_lastTimestamp, out temp); return $"{timestamp}{Fill(temp)}"; } private string Fill(long temp) { var num = temp.ToString(); IList<char> chars = new List<char>(); for (int i = 0; i < MAX_END_NUMBER.ToString().Length - num.Length; i++) { chars.Add('0'); } return new string(chars.ToArray()) + num; } /// <summary> /// 獲取一個時間戳字符串 /// </summary> /// <returns></returns> public long GetUniqueTimeStamp(long lastTimeStamp, out long temp) { lock (this) { temp = 1; var timeStamp = GetTimestamp(); if (timeStamp == _lastTimestamp) { _sequence = _sequence + 1; temp = _sequence; if (temp >= MAX_END_NUMBER) { timeStamp = GetTimestamp(); _lastTimestamp = timeStamp; temp = _sequence = 1; } } else { _sequence = 1; _lastTimestamp = timeStamp; } return timeStamp; } } /// <summary> /// /// </summary> /// <returns></returns> private long GetTimestamp() { if (InitialDateTime >= DateTime.Now) throw new Exception("最初時間比當前時間還大,不合理"); var ts = DateTime.UtcNow - InitialDateTime; return (long)ts.TotalMilliseconds; } }
當中我加了一點補位算法,保證每次出來的ID長度一致,之前提到了是用在禮券號上的,那就應該不能這麼長,後續我又繼續進行了32進制計算,縮短到8-10位左右,但大家估計覺的還是長,那就看取決你把相差時間應該縮短。但如果直接用雪花算法生成的ID進行32位進制縮短也是在10位以上,所以我沒有用到。
對了,忘記說了性能問題,一毫秒預計能生成1000個,呵呵,還算過得去
接下來談談禮券這塊業務,類似我們初創電商公司這種需要去互聯網上大量拉攏會員,所以也相對需要大量的推廣禮券號,如果成熟的電商如京東和天貓等,他們所有禮券都已經綁定到自己會員身上,在使用上根本不用去關注填寫什麼禮券號,也是他們的禮券體系相對完整和成熟,故我們對禮券號的的生成需求也是一塊心病。
下面再說說雪花算法生成的ID,比較適合使用一些流水數據,如果分布式上生成時就需要考慮一台吞吐量好的服務統一生成ID,或者也可以進行多台服務器+負載均衡,當然每台機器出的ID還是需要標識補位(比如機器自定義的編號ID)增加長度防止同一時間重復ID。
以上如有不對之處請留言,大家共同學習進步!!!