凡事涉及到高性能貌似都是高大上的東西,因此嘛我也試試;其實這個時間戳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。學習
以上若有不對之處請留言,你們共同窗習進步!!!this
2017.1.22 > 在生產環境中,忽然又發生了禮券號重複問題,致使我懷疑算法的不嚴謹,後來發現觸發生成的來源是兩個服務進程,哈哈,真是本身找坑跳;故你們在部署時候必定要確保ID生成在一個進程裏,若是分佈式仍是老話,加上必要的分佈標識NOspa