凡事涉及到高性能貌似都是高大上的東西,所以嘛我也試試;其實這個時間戳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。
以上如有不對之處請留言,大家共同學習進步!!!