上週末考完試,這周正好把工做整理整理,而後也把以前的一些素材,整理一番,也當本身再學習一番。
一方面正好最近看到幾篇這方面的文章,另外一方面也是正好工做上有所涉及,因此決定寫一篇這樣的文章。
先是簡單介紹概念和現有解決方案,而後是我對這些方案的總結,最後是我本身項目的解決思路。node
在複雜分佈式系統中,每每須要對大量的數據和消息進行惟一標識。redis
如在金融、電商、支付、等產品的系統中,數據日漸增加,對數據分庫分表後須要有一個惟一ID來標識一條數據或消息,數據庫的自增ID顯然不能知足需求,此時一個可以生成全局惟一ID的系統是很是必要的。算法
分佈式全局惟一ID(數據庫的分庫分表後須要有一個惟一ID來標識一條數據或消息;特別一點的如訂單、騎手、優惠券也都須要有惟一ID作標識;MQ中消息的高可用性(確認消息是否發送成功,是否已發送等)等)
其實分佈式全局ID是一個比較複雜,重要的分佈式問題(什麼問題涉及真正的分佈式,高併發後都會比較複雜)。常看法決方案有UUID,Snowflake,Flicker,Redis,Zookeeper,Leaf等。sql
生成一個32位16進制字符串(16字節的128位數據,一般以32位長度的字符串表示)(結合機器識別碼(全局惟一的IEEE機器識別號,若是有網卡,從網卡MAC地址得到,沒有網卡以其餘方式得到),當前時間,一個隨機數)。數據庫
不須要考慮空間佔用,不須要生成有遞增趨勢的ID,且不在MySQL中存儲。安全
Twitter開源,生成一個64bit(0和1)字符串(1bit不用,41bit表示存儲時間戳,10bit表示工做機器id(5位數據標示位,5位機器標識位),12bit序列號)服務器
最後生成64位Long型數值(這裏指,通常Long數據就是64位bit的)。網絡
要求高性能,能夠不連續,數據類型爲long型。數據結構
主要思路是涉及單獨的庫表,利用數據庫的自增ID+replace_into,來生成全局ID。架構
replace into跟insert功能相似,不一樣點在於:replace into首先嚐試插入數據列表中,若是發現表中已經有此行數據(根據主鍵或惟一索引判斷)則先刪除,再插入。不然直接插入新數據。
建表:
create table t_global_id( id bigint(20) unsigned not null auto_increment, stub char(1) not null default '', primary key (id), unique key stub (stub) ) engine=MyISAM;
(stub:票根,對應須要生成ID的業務方編碼,能夠是項目名,表名,甚至是服務器IP地址。 MyISAM(MYSQL5.5.8前默認數據庫存儲引擎,5.5.8及以後默認存儲引擎爲InnoDB):(此處應當有MyISAM與InnoDB引擎的區別,乃至其餘引擎)基於ISAM類型。不是事務安全(沒有事務隔離??),不支持外鍵,沒有行級鎖。若是執行大量的select,建議MyISAM。 獲取數據: # 每次業務可使用如下SQL讀寫MySQL獲得ID號
replace into t_golbal_id(stub) values('a');
select last_insert_id();
擴展:爲解決單點問題,啓用多臺服務器,如MySQL,利用給字段設置auto_increment_increment和auto_increment_offset來保證ID自增(如經過設置起始值與步長,生成奇偶數ID)
數據量不大,併發量不大。
因爲Redis的全部命令是單線程的,因此能夠利用Redis的原子操做INCR和INCRBY,來生成全局惟一的ID。
能夠經過集羣來提高吞吐量(能夠經過爲不一樣Redis節點設置不一樣的初始值並贊成步長,從而利用Redis生成惟一且趨勢遞增的ID)(其實這個方法和Flicker一致,只是利用到了Redis的一些特性,如原子操做,內存數據庫讀寫快等)(Incrby:將key中儲存的數字加上指定的增量值。這是一個「INCR AND GET」的原子操做,業務方能夠定義一個本身的key值,經過INCR命令來獲取對應的ID)
不依賴數據庫,靈活方便,且性能優於基於數據庫的Flicker方案。
Redis集羣高可用,併發量高。
利用Redis來生成天天從0開始的流水號。如訂單號=日期+當日自增加號。能夠天天在Redis中生成一個Key,適用INCR進行累加。
經過其znode數據版原本生成序列號,能夠生成32位和64位的數據版本號,客戶端可使用這個版本號來做爲惟一的序列號。
小結:不多會使用zookeeper來生成惟一ID。主要是因爲須要依賴zookeeper,而且是多步調用API,若是在競爭較大的狀況下,須要考慮使用分佈式鎖。所以,性能在高併發的分佈式環境下,也不甚理想。
美團的Leaf分佈式ID生成系統,在Flicker策略與Snowflake算法的基礎上作了兩套優化的方案:Leaf-segment數據庫方案(相比Flicker方案每次都要讀取數據庫,該方案改用proxy server批量獲取,且作了雙buffer的優化)與Leaf-snowflake方案(主要針對時鐘回撥問題作了特殊處理。若發生時鐘回撥則拒絕發號,並進行告警)。
ObjectID能夠算做和snowflake相似方法,經過」時間+機器碼+pid+inc」共12個字節,經過4+3+2+3的方式,最終標識一個24長度的十六進制字符。
其實除了上述方案外,還有ins等的方案,但總的來看,方案主要分爲兩種:第一有中心(如數據庫,包括MYSQL,REDIS等),其中能夠會利用事先的預定來實現集羣(起始步長)。第二種就是無中心,經過生成足夠散落的數據,來確保無衝突(如UUID等)。站在這兩個方向上,來看上述方案的利弊就方便多了。
技術是無窮無盡的,咱們不只須要看到其中體現的思想與原則,在學習新技術或方案時,須要明確其中一些特性,優缺點的來源,從而進行有效的總結概括。
應用角度來講:(一方面想要標示符短,便於處理與存儲,另外一方面想要足夠大,而不會產生衝突。呵呵)。最理想就是追求從0開始,每一個標示符都被使用,且不重複,並且不用擔憂併發。呵呵。徹底應該根據當前業務場景來選擇,畢竟業務場景在當前是肯定的。若是業務變更較大(好比發展初期,業務增加很快),那就須要考慮擴展性,便於往後進行該模塊的更新與技術方案的替換實現(避免一個系統開發一年,用不到一年,那就尷尬了))。
我曾經作過一個「工業物聯網」系統,該系統系統是分爲三個子系統:終端服務器(用於收集終端傳感器數據);企業中控服務器(接收來自多個終端服務器的數據,進行綜合查看與控制);雲平臺服務器(提供上雲)。其中就涉及多個終端服務器的傳感器數據辨識問題,這裏以傾斜傳感器數據爲例。簡述不一樣終端服務器的傾斜數據的如何實現全局惟一標識。
簡單說,就是終端服務器發送一個數據到企業中控室,企業中控服務器就將該數據保存到數據庫中,那麼每一個數據在企業中控服務器數據庫中都有惟一的ID,而且保持了自增。
優勢是實現簡單,只須要作好數據收發,與數據的插入工做便可。惟一須要注意的是數據庫插入時注意資源互斥,防止出現數據插入異常問題(Springframework生成的Bean默認時單例的)。
缺點是須要實時收發數據,防止數據丟失,數據積壓,數據的create_time異常等問題。
簡單說,就是終端服務器要發送的數據賦予UUID這樣的ID,來確保全局惟一。這樣終端服務器就能夠和中控服務器保持一樣且不衝突的ID了。數據的生成是實如今終端服務器的,而中控服務器只是做爲數據的保存與調用(經過統一ID調用)。
優勢是不須要數據的實時收發,避免系統在弱網絡狀況下出現各種異常。
缺點是數據的ID過長,而且沒法保持自增。而且在某種程度上帶來了數據複雜度,從而提升了系統複雜度。
因爲實際業務的需求,如弱網絡,數據交互頻率跨度大等狀況。最終個人實現是先由終端服務器在啓動之初,在企業中控服務器註冊TerminalId,做爲不一樣終端服務器的標識。不一樣終端服務器接收與保存數據時,都會在每條數據中插入TerminalId,便於企業中控服務器的識別。固然,具體實現當中還有一些細節。如終端服務器在註冊時因爲網絡等狀況註冊失敗,會先創建一個相似UUID的TerminalId來先保存監測數據。當註冊成功時(系統會根據TerminalId的長度等特性來判斷是否註冊失敗,是否須要從新註冊),會從新修改全部數據的TerminalId,再容許數據上傳。
優勢是確保了數據在弱網絡狀況下的正確性,而且實現了自動註冊等通用模塊的實現。
缺點是最終數據插入企業中控服務器數據庫時,並無嚴格實現數據符合實際時間的增加(如某終端服務器因爲網絡等狀況無法發送數據,等待一段時間後發送了這段時間堆積的數據),但保持了整體增加的趨勢。
IT沒有銀彈,咱們要作的是多去了解現有的技術方案,再產生符合本身需求的技術方案。由於不一樣的技術方案都由於其使用場景有着各自的特色,而咱們須要瞭解各類特色的技術來源(是什麼技術造就了這一特色,或者說是什麼架構造就了這一特色等),從而構建出最符合本身需求的技術方案。
沒有最好,只有最適合。