談談雪花算法的使用
背景
618來臨之際,為了應對一些突發流量,購買了兩台一個月的ECS用來臨時對部分項目擴容。其中一個項目有用到雪花算法來生成Id,這個還是挺OK的。
不過發現要在配置檔案中手動配置機器碼!!配置的時候還要先知道目前配置了那些,這樣才可以避免重複。
經過了解,除了會有單機單執行個體的情況,還會有單機多執行個體的情況。
這個要人工配置,是徒增工作量的,有點讓人難以接受。
針對這個,老黃就做了一點調整,讓這個機器碼自動生成。
雪花算法基礎
關于雪花算法,大部分文章都可以看到這個圖。這個圖很好的诠釋了雪花算法生成Id的幾個重要組成部分,這裡也不展開具體介紹了。

時間戳,工作機器Id,序列号這些位數是可以根據自己的業務場景來進來調整的。
10bit工作機器Id,其實就是上面說到的機器碼,雪花算法内部并沒有做任何處理,而是交由業務方自己定義,是以業務方需要自己保證這個的唯一性。
大部分情況,會把它分為5bit資料中心辨別和5bit機器Id。這樣的話可以支援32個資料中心和32個機器Id。
換句話說就是,一個業務可以在一個資料中心部署32個執行個體,最多部署的32個資料中心。正常來說,大部分項目,都不會需要部署這麼多執行個體。。。
考慮到内網的IP段基本上是固定的,同一個應用基本上也會在連續的IP上面部署。
是以這裡老黃最後采用的是本地IP位址取餘做為機器Id,機器的HostName取餘做為預設的資料中心Id。
下面來看看具體的實作。
簡單實作
自動擷取機器Id和資料中心Id。
/// <summary> /// 擷取機器Id /// </summary> /// <returns></returns> private int GetWorkerId() { var workerId = 0; try { IPAddress ipaddress = IPAddress.Parse("0.0.0.0"); NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces(); foreach (NetworkInterface ni in interfaces) { if (ni.NetworkInterfaceType == NetworkInterfaceType.Ethernet) { foreach (UnicastIPAddressInformation ip in ni.GetIPProperties().UnicastAddresses) { if (ip.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) { ipaddress = ip.Address; break; } } } } Console.WriteLine($"ip = {ipaddress.ToString()}"); var val = ipaddress.GetAddressBytes().Sum(x => x); // 取餘 workerId = val % (int)MaxWorkerId; } catch { // 異常的話,生成一個随機數 workerId = new Random().Next((int)MaxWorkerId - 1); } return workerId; } /// <summary> /// 擷取資料中心Id /// </summary> /// <returns></returns> private int GetDatacenterId() { var hostName = Dns.GetHostName(); Console.WriteLine($"hostname = {hostName}"); var val = System.Text.Encoding.UTF8.GetBytes(hostName).Sum(x => x); // 取餘 return val % (int)MaxDatacenterId; }
生成器的構造函數
public IdGenerator(long datacenterId = -1) { if (datacenterId == -1) { // default datacenterId = GetDatacenterId(); } if (datacenterId > MaxDatacenterId || datacenterId < 0) { throw new ArgumentException("非法資料标志ID", nameof(datacenterId)); } // 先檢驗再指派 WorkerId = GetWorkerId(); DatacenterId = datacenterId; Console.WriteLine($"w = {WorkerId}"); Console.WriteLine($"d = {DatacenterId}"); }
這裡的資料中心可以讓使用方自己定義,預設-1的話,會根據HostName去生成一個。
這裡給一個自定義的可選辨別,主要還是考慮到了單機多執行個體,即一個IP上面部署多個執行個體。
雖然這個時候還是要考慮人工配置,不過已經從多機變成單機了,也算是一點簡化。畢竟大部分情況下也不會建議在同一個機器部署多個一樣的項目。
預設情況下的使用,IdGenerator對象要全局唯一,做成單例即可。
IdGenerator generator = new IdGenerator(); Parallel.For(0, 20, x => { Console.WriteLine(generator.NextId()); }); Console.WriteLine("Hello World!"); System.Threading.Thread.Sleep(1000 * 60);
下面運作多個容器來模拟。
可以看到機器Id和資料中心Id都是沒有重複的。
在運作一次。
也是同樣的。
不足與展望
目前這種做法在應用執行個體少,機器數量少的情況下是基本可以滿足使用要求的了。老黃公司目前也就不到30台伺服器,是以怎麼都是夠用的。
但是依靠IP和HostName,随着執行個體或機器的數量增多,沒有辦法保證它們取餘算出來的一定是唯一的。
在這種情況下就需要考慮引用第三方存儲(Redis或資料庫)來保證這個的唯一性了。
下面是本文的示例代碼:
SnowflakeDemo
如果您認為這篇文章還不錯或者有所收獲,可以點選右下角的【推薦】按鈕,因為你的支援是我繼續寫作,分享的最大動力!
作者:Catcher Wong ( 黃文清 )
來源:http://catcher1994.cnblogs.com/
聲明:
本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。如果您發現部落格中出現了錯誤,或者有更好的建議、想法,請及時與我聯系!!如果想找我私下交流,可以私信或者加我微信。