分布式唯一ID極簡教程

      網友投稿 867 2022-05-30

      一,題記

      所有的業務系統,都有生成ID的需求,如訂單id,商品id,文章ID等。這個ID會是數據庫中的唯一主鍵,在它上面會建立聚集索引!

      ID生成的核心需求有兩點:

      全局唯一

      趨勢有序

      著名的例子就是身份證號碼,身份證號碼確實是對人唯一的,然而一個人是可以辦理多個身份證的,例如你身份證丟了,又重新補辦了一張,號碼不變。

      問題來了,因為系統是按照身份證號碼做唯一主鍵的。此時,如果身份證是被盜的情況下,你是沒有辦法在系統里面注銷的,因為新舊2個身份證的“主鍵”都是身份證號碼。

      也就是說,舊的身份證仍然逍遙在外,完全有效。這個時候,還好有一個身份證有效時間的東西,只有靠身份證有效期來辨識了。不過,這就是現在這么多銀行,電信詐騙的由來,撿到一張身份證,去很多銀行,手機,酒店都可以使用!身份證缺乏注銷機制!

      所以,經驗告訴我們。不要相信自己的直覺,業務上所謂的唯一往往都是不靠譜的,經不起時間的考研的。所以需要單獨設置一個和業務無關的主鍵,專業術語叫做代理主鍵(surrogate key)。

      這也是為什么數據庫設計范式,唯一主鍵是第一范式!

      以mysql為例,InnoDB引擎表是基于B+樹的索引組織表(IOT);每個表都需要有一個聚集索引(clustered index);所有的行記錄都存儲在B+樹的葉子節點(leaf pages of the tree);基于聚集索引的增、刪、改、查的效率相對是最高的;如下圖:

      如果我們定義了主鍵(PRIMARY KEY),那么InnoDB會選擇其作為聚集索引;

      如果沒有顯式定義主鍵,則InnoDB會選擇第一個不包含有NULL值的唯一索引作為主鍵索引;

      如果也沒有這樣的唯一索引,則InnoDB會選擇內置6字節長的ROWID作為隱含的聚集索引(ROWID隨著行記錄的寫入而主鍵遞增,這個ROWID不像ORACLE的ROWID那樣可引用,是隱含的)。

      綜上總結,如果InnoDB表的數據寫入順序能和B+樹索引的葉子節點順序一致的話,這時候存取效率是最高的,也就是下面這幾種情況的存取效率最高

      使用自增列(INT/BIGINT類型)做主鍵,這時候寫入順序是自增的,和B+數葉子節點分裂順序一致;

      該表不指定自增列做主鍵,同時也沒有可以被選為主鍵的唯一索引(上面的條件),這時候InnoDB會選擇內置的ROWID作為主鍵,寫入順序和ROWID增長順序一致;

      除此以外,如果一個InnoDB表又沒有顯示主鍵,又有可以被選擇為主鍵的唯一索引,但該唯一索引可能不是遞增關系時(例如字符串、UUID、多字段聯合唯一索引的情況),該表的存取效率就會比較差。)

      這就是為什么我們的分布式ID一定要是趨勢遞增的!那么在開發當中,面對這種分布式ID需求,常見的處理方案有哪些呢?

      最常見的方式。利用數據庫,全數據庫唯一。

      優點:

      1)簡單,代碼方便,性能可以接受。

      2)數字ID天然排序,對分頁或者需要排序的結果很有幫助。

      缺點:

      1)不同數據庫語法和實現不同,數據庫遷移的時候或多數據庫版本支持的時候需要處理。

      2)在單個數據庫或讀寫分離或一主多從的情況下,只有一個主庫可以生成。有單點故障的風險。

      3)在性能達不到要求的情況下,比較難于擴展。

      4)如果遇見多個系統需要合并或者涉及到數據遷移會相當痛苦。

      5)分表分庫的時候會有麻煩。

      優化方案:

      1)針對主庫單點,如果有多個Master庫,則每個Master庫設置的起始數字不一樣,步長一樣,可以是Master的個數。比如:Master1 生成的是 1,4,7,10,Master2生成的是2,5,8,11 Master3生成的是 3,6,9,12。這樣就可以有效生成集群中的唯一ID,也可以大大降低ID生成數據庫操作的負載。

      常見的方式。可以利用數據庫也可以利用程序生成,一般來說全球唯一。

      優點:

      1)簡單,代碼方便。

      2)生成ID性能非常好,基本不會有性能問題。

      3)全球唯一,在遇見數據遷移,系統數據合并,或者數據庫變更等情況下,可以從容應對。

      缺點:

      1)沒有排序,無法保證趨勢遞增。

      2)UUID往往是使用字符串存儲,查詢的效率比較低。

      3)存儲空間比較大,如果是海量數據庫,就需要考慮存儲量的問題。

      4)傳輸數據量大

      5)不可讀。

      當使用數據庫來生成ID性能不夠要求的時候,我們可以嘗試使用Redis來生成ID。這主要依賴于Redis是單線程的,所以也可以用生成全局唯一的ID。可以用Redis的原子操作 INCR和INCRBY來實現。

      可以使用Redis集群來獲取更高的吞吐量。假如一個集群中有5臺Redis。可以初始化每臺Redis的值分別是1,2,3,4,5,然后步長都是5。各個Redis生成的ID為:

      A:1,6,11,16,21

      B:2,7,12,17,22

      C:3,8,13,18,23

      D:4,9,14,19,24

      E:5,10,15,20,25

      這個,隨便負載到哪個機確定好,未來很難做修改。但是3-5臺服務器基本能夠滿足器上,都可以獲得不同的ID。但是步長和初始值一定需要事先需要了。使用Redis集群也可以方式單點故障的問題。

      另外,比較適合使用Redis來生成每天從0開始的流水號。比如訂單號=日期+當日自增長號。可以每天在Redis中生成一個Key,使用INCR進行累加。

      優點:

      1)不依賴于數據庫,靈活方便,且性能優于數據庫。

      2)數字ID天然排序,對分頁或者需要排序的結果很有幫助。

      缺點:

      1)如果系統中沒有Redis,還需要引入新的組件,增加系統復雜度。

      2)需要編碼和配置的工作量比較大。

      twitter在把存儲系統從MySQL遷移到Cassandra的過程中由于Cassandra沒有順序ID生成機制,于是自己開發了一套全局唯一ID生成服務:Snowflake。

      1 41位的時間序列(精確到毫秒,41位的長度可以使用69年)

      2 10位的機器標識(10位的長度最多支持部署1024個節點)

      3 12位的計數順序號(12位的計數順序號支持每個節點每毫秒產生4096個ID序號) 最高位是符號位,始終為0。

      優點:

      高性能,低延遲;獨立的應用;

      按時間有序。

      缺點:

      需要獨立的開發和部署。

      強依賴時鐘,如果主機時間回撥,則會造成重復ID,會產生

      ID雖然有序,但是不連續

      原理

      MongoDB的ObjectId和snowflake算法類似。它設計成輕量型的,不同的機器都能用全局唯一的同種方法方便地生成它。MongoDB 從一開始就設計用來作為分布式數據庫,處理多個節點是一個核心要求。使其在分片環境中要容易生成得多。

      ObjectId使用12字節的存儲空間,其生成方式如下:

      |0|1|2|3|4|5|6 |7|8|9|10|11|

      分布式唯一ID極簡教程

      |時間戳 |機器ID|PID|計數器 |

      前四個字節時間戳是從標準紀 元開始的時間戳,單位為秒,有如下特性:

      1 時間戳與后邊5個字節一塊,保證秒級別的唯一性;

      2 保證插入順序大致按時間排序;

      3 隱含了文檔創建時間;

      4 時間戳的實際值并不重要,不需要對服務器之間的時間進行同步(因為加上機器ID和進程ID已保證此值唯一,唯一性是ObjectId的最終訴求)。

      機器ID是服務器主機標識,通常是機器主機名的散列值。

      同一臺機器上可以運行多個mongod實例,因此也需要加入進程標識符PID。

      前9個字節保證了同一秒鐘不同機器不同進程產生的ObjectId的唯一性。后三個字節是一個自動增加的計數器(一個mongod進程需要一個全局的計數器),保證同一秒的ObjectId是唯一的。同一秒鐘最多允許每個進程擁有(256^3 = 16777216)個不同的ObjectId。

      總結一下:時間戳保證秒級唯一,機器ID保證設計時考慮分布式,避免時鐘同步,PID保證同一臺服務器運行多個mongod實例時的唯一性,最后的計數器保證同一秒內的唯一性(選用幾個字節既要考慮存儲的經濟性,也要考慮并發性能的上限)。

      "_id"既可以在服務器端生成也可以在客戶端生成,在客戶端生成可以降低服務器端的壓力。

      國內有很多廠家基于snowflake算法進行了國產化,例如

      百度的uid-generator:

      https://github.com/baidu/uid-generator

      美團Leaf:

      https://github.com/zhuzhong/idleaf

      基本是對snowflake的進一步優化,比如解決時鐘 回撥問題!

      總體而言,分布式唯一ID需要滿足以下條件:

      高可用性:不能有單點故障。

      全局唯一性:不能出現重復的ID號,既然是唯一標識,這是最基本的要求。

      趨勢遞增:在MySQL InnoDB引擎中使用的是聚集索引,由于多數RDBMS使用B-tree的數據結構來存儲索引數據,在主鍵的選擇上面我們應該盡量使用有序的主鍵保證寫入性能。

      時間有序:以時間為序,或者ID里包含時間。這樣一是可以少一個索引,二是冷熱數據容易分離。

      分片支持:可以控制ShardingId。比如某一個用戶的文章要放在同一個分片內,這樣查詢效率高,修改也容易。

      單調遞增:保證下一個ID一定大于上一個ID,例如事務版本號、IM增量消息、排序等特殊需求。

      長度適中:不要太長,最好64bit。使用long比較好操作,如果是96bit,那就要各種移位相當的不方便,還有可能有些組件不能支持這么大的ID。

      信息安全:如果ID是連續的,惡意用戶的扒取工作就非常容易做了,直接按照順序下載指定URL即可;如果是訂單號就更危險了,競爭對手可以直接知道我們一天的單量。所以在一些應用場景下,會需要ID無規則、不規則。

      分布式 存儲 數據庫

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:錘煉,才會更強大
      下一篇:微軟Windows操作系統全面兼容機器人操作系統ROS1和ROS2
      相關文章
      中文字幕亚洲综合久久2| 无码欧精品亚洲日韩一区夜夜嗨| 偷自拍亚洲视频在线观看| 亚洲三级视频在线观看| 亚洲自偷自拍另类12p| 亚洲女久久久噜噜噜熟女| 久久精品国产亚洲精品| 亚洲欧洲自拍拍偷精品 美利坚| 亚洲国产精品综合久久网络| 国产亚洲漂亮白嫩美女在线| 国产精品亚洲色婷婷99久久精品| 亚洲日韩av无码中文| 亚洲av无码片vr一区二区三区| 亚洲成a人无码亚洲成www牛牛 | 亚洲乱码一二三四区麻豆| 亚洲最新在线视频| 91亚洲国产成人久久精品| 亚洲乱码一区av春药高潮| 亚洲中文字幕无码mv| 亚洲а∨精品天堂在线| 精品国产日韩亚洲一区在线| 久久精品国产亚洲av天美18| 国产精品亚洲色图| 久久久久亚洲精品男人的天堂| 久久久久亚洲精品中文字幕| 亚洲男人的天堂在线va拉文| 国产亚洲av片在线观看18女人| 亚洲中文字幕无码久久2017| 亚洲国产精品成人久久| 亚洲尹人九九大色香蕉网站| 亚洲毛片基地日韩毛片基地| 亚洲国产品综合人成综合网站| 国产91在线|亚洲| 亚洲AV日韩AV一区二区三曲 | 亚洲AV无码一区二区三区系列| 亚洲国产精品lv| 亚洲人成激情在线播放| 亚洲一久久久久久久久| 美国毛片亚洲社区在线观看| 亚洲中文字幕伊人久久无码| 亚洲宅男天堂在线观看无病毒|