一.前言
最近有一個生成 APM TraceId 的需求,公司的APM系統的 TraceId 的格式為:APM AgentId+毫秒級時間戳+自增數字
,根據此規則生成的 Id 可以保證全域性唯一(有 NTP 時間同步),前兩個欄位好說,最後一個欄位也不復雜,我的想法是按秒來進行自增。比如說1秒的時候,自增計數為100,在2秒的時候會重置為0,然後進行自增。其實這個思想就是固定時間視窗演算法,這個演算法一般常用在限流、Id生成器等場景。
二.程式碼實現
long _currentTime;
long _current;
public long FixedWindow()
{
var now = DateTimeOffset.Now.ToUnixTimeSeconds();
var ct = Interlocked.Read(ref _currentTime);
if (now > ct)
{
if (Interlocked.CompareExchange(ref _currentTime, now, ct)==ct)
{
return Interlocked.Exchange(ref _current, 0);
}
}
return Interlocked.Increment(ref _current);
}
程式碼沒多少,每呼叫一次就返回計數,採用的 C# CAS API Interlocked ,保證每個計數操作都是原子操作,從而達到無鎖。
測試程式碼,使用10個執行緒併發呼叫,每個執行緒呼叫 1w次,最終期望計數應該是10w。
private static long _currentTime;
private static long _current;
private static Semaphore _semaphore = new Semaphore(0, 10);
static void Main(string[] args)
{
_currentTime = DateTimeOffset.Now.ToUnixTimeSeconds();
_current = 0;
for (int i = 0; i < 10; i++)
{
Task.Factory.StartNew(() =>
{
for (int j = 0; j < 10000; j++)
{
FixedWindow();
}
_semaphore.Release(1);
});
}
for (int i = 0; i < 10; i++)
{
_semaphore.WaitOne();
}
Console.WriteLine(_current);
Console.WriteLine("sleep 2s");
Thread.Sleep(2000);
Console.WriteLine(FixedWindow());
}
執行結果:
符合預期,10執行緒的計數在 1s 內能執行完畢,所以最終計數是10w,然後sleep 2s,重置計數,再次呼叫就返回了 1
三.資料
本文 Demo 程式碼:github