IM消息ID技術專題(六):深度解密滴滴的高性能ID生成器(Tinyid)

一、引言

在中大型IM系統中,聊天消息的惟一ID生成策略是個很重要的技術點。不誇張的說,聊天消息ID貫穿了整個聊天生命週期的幾乎每個算法、邏輯和過程,ID生成策略的好壞有可能直接決定系統在某些技術點上的設計難易度。php

有中小型IM場景下,消息ID能夠簡單處理,反正只要惟一就行,而中大型場景下,由於要考慮到分佈式的性能、一致性等,因此要考慮的問題點又是另外一回事。html

總之就是,IM的消息ID生成這件事,可深可淺,看似簡單但實際可探索的邊界能夠很大,這也是爲何即時通信網爲此專門整理了《IM消息ID技術專題》系列文章的緣由。作技術所謂厚積薄發,瞭解的越多,你的技術可操做空間也就越大,但願隨着這個系列文章的閱讀,能夠爲你在ID生成這一塊的技術選型帶來更多有益的啓發。java

另外,由於即時通信網主要關注的是即時通信方面的系統開發,但並不意味着這個系統文章只適用於IM或消息推送等實時通訊系統,它一樣適用於其它須要惟一ID的應用中。git

本文將要分享的是滴滴開源的分佈式ID生成器Tinyid的技術原理、使用方法等等,但願能進一步爲你打開這方面的技術視野。github

學習交流:面試

- 即時通信/推送技術開發交流5羣:215477170[推薦]算法

- 移動端IM開發入門文章:《新手入門一篇就夠:從零開發移動端IM數據庫

- 開源IM框架源碼:https://github.com/JackJiang2011/MobileIMSDK緩存

(本文同步發佈於:http://www.52im.net/thread-3129-1-1.html安全

二、專題目錄

本文是「IM消息ID技術專題」系列文章的第6篇,專題總目錄以下:

IM消息ID技術專題(一):微信的海量IM聊天消息序列號生成實踐(算法原理篇)

IM消息ID技術專題(二):微信的海量IM聊天消息序列號生成實踐(容災方案篇)

IM消息ID技術專題(三):解密融雲IM產品的聊天消息ID生成策略

IM消息ID技術專題(四):深度解密美團的分佈式ID生成算法

IM消息ID技術專題(五):開源分佈式ID生成器UidGenerator的技術實現

IM消息ID技術專題(六):深度解密滴滴的高性能ID生成器(Tinyid)》(* 本文

三、什麼是Tinyid?

Tinyid是滴滴用Java開發的一款分佈式id生成系統,基於數據庫號段算法實現。

Tinyid是在美團的ID生成算法Leaf的基礎上擴展而來,支持數據庫多主節點模式,它提供了REST API和Java客戶端兩種獲取方式,相對來講使用更方便。不過,和美團的Leaf算法不一樣的是,Tinyid只支持號段一種模式(並不支持Snowflake模式)。(有關美團的Leaf算法,能夠詳讀《IM消息ID技術專題(四):深度解密美團的分佈式ID生成算法

Tinyid目前在滴滴客服部門使用,且經過tinyid-client方式接入,天天生成的是億級別的id。性能上,據稱單實例能達到1千萬QPS。

它的開源地址是:

PS:滴滴在Tinyid工程頁面寫了一句話,「tinyid,並非滴滴官方產品,只是滴滴擁有的代碼」,我語文很差,這句該怎麼理解呢?

四、Tinyid的主要技術特性

主要特性總結一下就是:

  • 1)全局惟一的long型ID:即id極限數量是2的64次方;
  • 2)趨勢遞增的id:趨勢遞增的意思是,id是遞增但不必定是連續的(這跟微信的ID生成策略相似);
  • 3)提供 http 和 java-client 方式接入;
  • 4)支持批量獲取ID;
  • 5)支持生成1,3,5,7,9…序列的ID;
  • 6)支持多個db的配置。

適用的場景:只關心ID是數字,趨勢遞增的系統,能夠容忍ID不連續,能夠容忍ID的浪費。

不適用場景:像相似於訂單ID的業務,因生成的ID大部分是連續的,容易被掃庫、或者推算出訂單量等信息。

另外:微信的聊天消息ID生成算法也是基於號段、趨勢遞增這種邏輯,若是有興趣,能夠詳見:《IM消息ID技術專題(一):微信的海量IM聊天消息序列號生成實踐(算法原理篇)》。

五、Tinyid的技術優點

性能方面:

  • 1)http方式:訪問性能取決於http server的能力,網絡傳輸速度;
  • 2)java-client方式:id爲本地生成,號段長度(step)越長,qps越大,若是將號段設置足夠大,則qps可達1000w+。

可用性方面:

  • 1)當db不可用時,由於server有緩存,因此還可使用一段時間;
  • 2)若是配置了多個db,則只要有1個db存活,則服務可用;
  • 3)使用tiny-client時,只要server有一臺存活,則理論上server全掛,由於client有緩存,也能夠繼續使用一段時間。

六、Tinyid的技術原理詳解

6.1 ID生成系統的技術要點

在簡單系統中,咱們經常使用db的id自增方式來標識和保存數據,隨着系統的複雜,數據的增多,分庫分表成爲了常見的方案,db自增已沒法知足要求。

這時候全局惟一的id生成系統就派上了用場,固然這只是id生成其中的一種應用場景。

那麼,一個成熟的id生成系統應該具有哪些能力呢?

  • 1)惟一性:不管怎樣都不能重複,id全局惟一是最基本的要求;
  • 2)高性能:基礎服務儘量耗時少,若是可以本地生成最好;
  • 3)高可用:雖然說很難實現100%的可用性,可是也要無限接近於100%的可用性;
  • 4)易用性:可以拿來即用,接入方便,同時在系統設計和實現上要儘量的簡單。

6.2 Tinyid的實現原理

咱們先來看一下最多見的id生成方式,db的auto_increment,相信你們都很是熟悉。

我也見過一些同窗在實戰中使用這種方案來獲取一個id,這個方案的優勢是簡單,缺點是每次只能向db獲取一個id,性能比較差,對db訪問比較頻繁,db的壓力會比較大。

那麼,是否是能夠對這種方案優化一下呢?能否一次向db獲取一批id呢?答案固然是能夠的。

一批id,咱們能夠當作是一個id範圍,例如(1000,2000],這個1000到2000也能夠稱爲一個「號段」,咱們一次向db申請一個號段,加載到內存中,而後採用自增的方式來生成id,這個號段用完後,再次向db申請一個新的號段,這樣對db的壓力就減輕了不少,同時內存中直接生成id,性能則提升了不少。

PS:簡單解釋一下什麼是號段模式:

號段模式就是從數據庫批量的獲取自增ID,每次從數據庫取出一個號段範圍,例如 (1,1000] 表明1000個ID,業務服務將號段在本地生成1~1000的自增ID並加載到內存。

那麼保存db號段的表該怎設計呢?咱們繼續往下看。

6.3 DB號段算法描述

如上表,咱們很容易想到的是db直接存儲一個範圍(start_id,end_id],當這批id使用完畢後,咱們作一次update操做,update start_id=2000(end_id), end_id=3000(end_id+1000),update成功了,則說明獲取到了下一個id範圍。仔細想一想,實際上start_id並無起什麼做用,新的號段老是(end_id,end_id+1000]。

因此這裏咱們更改一下,db設計應該是這樣的:

如上表所示:

  • 1)咱們增長了biz_type,這個表明業務類型,不一樣的業務的id隔離;
  • 2)max_id則是上面的end_id了,表明當前最大的可用id;
  • 3)step表明號段的長度,能夠根據每一個業務的qps來設置一個合理的長度;
  • 4)version是一個樂觀鎖,每次更新都加上version,可以保證併發更新的正確性 。

那麼咱們能夠經過以下幾個步驟來獲取一個可用的號段:

A、查詢當前的max_id信息:select id, biz_type, max_id, step, version from tiny_id_info where biz_type='test';

B、計算新的max_id: new_max_id = max_id + step;

C、更新DB中的max_id:update tiny_id_info set max_id=#{new_max_id} , verison=version+1 where id=#{id} and max_id=#{max_id} and version=#{version};

D、若是更新成功,則可用號段獲取成功,新的可用號段爲(max_id, new_max_id];

E、若是更新失敗,則號段可能被其餘線程獲取,回到步驟A,進行重試。

6.4 號段生成方案的簡單架構

如上述內容,咱們已經完成了號段生成邏輯。

那麼咱們的id生成服務架構多是這樣的:

如上圖,id生成系統向外提供http服務,請求通過咱們的負載均衡router,到達其中一臺tinyid-server,從事先加載好的號段中獲取一個id。

若是號段尚未加載,或者已經用完,則向db再申請一個新的可用號段,多臺server之間由於號段生成算法的原子性,而保證每臺server上的可用號段不重,從而使id生成不重。

能夠看到:

  • 1)若是tinyid-server若是重啓了,那麼號段就做廢了,會浪費一部分id;
  • 2)同時id也不會連續;
  • 3)每次請求可能會打到不一樣的機器上,id也不是單調遞增的,而是趨勢遞增的(不過這對於大部分業務都是可接受的)。

6.5 簡單架構的問題

到此一個簡單的id生成系統就完成了,那麼是否還存在問題呢?

回想一下咱們最開始的id生成系統要求:高性能、高可用、簡單易用。

在上面這套架構裏,至少還存在如下問題:

  • 1)當id用完時須要訪問db加載新的號段,db更新也可能存在version衝突,此時id生成耗時明顯增長;
  • 2)db是一個單點,雖然db能夠建設主從等高可用架構,但始終是一個單點;
  • 3)使用http方式獲取一個id,存在網絡開銷,性能和可用性都不太好。

6.6 優化辦法及最終架構

1)雙號段緩存:

對於號段用完須要訪問db,咱們很容易想到在號段用到必定程度的時候,就去異步加載下一個號段,保證內存中始終有可用號段,則可避免性能波動。

2)增長多db支持:

db只有一個master時,若是db不可用(down掉或者主從延遲比較大),則獲取號段不可用。實際上咱們能夠支持多個db,好比2個db,A和B,咱們獲取號段能夠隨機從其中一臺上獲取。那麼若是A,B都獲取到了同一號段,咱們怎麼保證生成的id不重呢?tinyid是這麼作的,讓A只生成偶數id,B只生產奇數id,對應的db設計增長了兩個字段,以下所示

delta表明id每次的增量,remainder表明餘數,例如能夠將A,B都delta都設置2,remainder分別設置爲0,1則,A的號段只生成偶數號段,B是奇數號段。經過delta和remainder兩個字段咱們能夠根據使用方的需求靈活設計db個數,同時也能夠爲使用方提供只生產相似奇數的id序列。

3)增長tinyid-client:

使用http獲取一個id,存在網絡開銷,是否能夠本地生成id?

爲此咱們提供了tinyid-client,咱們能夠向tinyid-server發送請求來獲取可用號段,以後在本地構建雙號段、id生成,如此id生成則變成純本地操做,性能大大提高,由於本地有雙號段緩存,則能夠容忍tinyid-server一段時間的down掉,可用性也有了比較大的提高。

4)tinyid最終架構:

最終咱們的架構多是這樣的:

下面是更具體的代碼調用邏輯:

如上圖所示,下面是關於這個代碼調用邏輯圖的說明:

  • 1)nextId和getNextSegmentId是tinyid-server對外提供的兩個http接口;
  • 2)nextId是獲取下一個id,當調用nextId時,會傳入bizType,每一個bizType的id數據是隔離的,生成id會使用該bizType類型生成的IdGenerator;
  • 3)getNextSegmentId是獲取下一個可用號段,tinyid-client會經過此接口來獲取可用號段;
  • 4)IdGenerator是id生成的接口;
  • 5)IdGeneratorFactory是生產具體IdGenerator的工廠,每一個biz_type生成一個IdGenerator實例。經過工廠,咱們能夠隨時在db中新增biz_type,而不用重啓服務;
  • 6)IdGeneratorFactory實際上有兩個子類IdGeneratorFactoryServer和IdGeneratorFactoryClient,區別在於,getNextSegmentId的不一樣,一個是DbGet,一個是HttpGet;
  • 7)CachedIdGenerator則是具體的id生成器對象,持有currentSegmentId和nextSegmentId對象,負責nextId的核心流程。nextId最終經過AtomicLong.andAndGet(delta)方法產生。

具體的代碼實現,有興趣能夠直接閱讀源碼:

七、Tinyid的最佳實踐

1)tinyid-server推薦部署到多個機房的多臺機器:

多機房部署可用性更高,http方式訪問需使用方考慮延遲問題。

2)推薦使用tinyid-client來獲取id,好處以下:

a、id爲本地生成(調用AtomicLong.addAndGet方法),性能大大增長;

b、client對server訪問變的低頻,減輕了server的壓力;

c、由於低頻,即使client使用方和server不在一個機房,也無須擔憂延遲;

d、即使全部server掛掉,由於client預加載了號段,依然能夠繼續使用一段時間

注:使用tinyid-client方式,若是client機器較多頻繁重啓,可能會浪費較多的id,這時能夠考慮使用http方式。

3)推薦db配置兩個或更多:

db配置多個時,只要有1個db存活,則服務可用 多db配置,如配置了兩個db,則每次新增業務需在兩個db中都寫入相關數據。

八、Tinyid該怎麼調用?

關於怎麼調用。鑑於篇幅緣由,就再也不具體去寫了,有興趣的話,能夠讀一下這篇《Tinyid:滴滴開源千萬級併發的分佈式ID生成器》。

九、參考資料

[1] 面試總被問分佈式ID怎麼辦? 滴滴(Tinyid)甩給他

[2] Tinyid:滴滴開源千萬級併發的分佈式ID生成器

[3] tinyid工程中文readme

[4] 滴滴開源的Tinyid如何天天生成億級別的ID?

附錄:更多IM開發熱門技術文章

新手入門一篇就夠:從零開發移動端IM

移動端IM開發者必讀(一):通俗易懂,理解移動網絡的「弱」和「慢」

移動端IM開發者必讀(二):史上最全移動弱網絡優化方法總結

從客戶端的角度來談談移動端IM的消息可靠性和送達機制

現代移動端網絡短鏈接的優化手段總結:請求速度、弱網適應、安全保障

移動端IM中大規模羣消息的推送如何保證效率、實時性?

移動端IM開發須要面對的技術問題

開發IM是本身設計協議用字節流好仍是字符流好?

請問有人知道語音留言聊天的主流實現方式嗎?

IM消息送達保證機制實現(一):保證在線實時消息的可靠投遞

IM消息送達保證機制實現(二):保證離線消息的可靠投遞

如何保證IM實時消息的「時序性」與「一致性」?

一個低成本確保IM消息時序的方法探討

IM單聊和羣聊中的在線狀態同步應該用「推」仍是「拉」?

IM羣聊消息如此複雜,如何保證不丟不重?

談談移動端 IM 開發中登陸請求的優化

移動端IM登陸時拉取數據如何做到省流量?

淺談移動端IM的多點登陸和消息漫遊原理

徹底自已開發的IM該如何設計「失敗重試」機制?

自已開發IM有那麼難嗎?手把手教你自擼一個Andriod版簡易IM (有源碼)

IM開發基礎知識補課(六):數據庫用NoSQL仍是SQL?讀這篇就夠了!

適合新手:從零開發一個IM服務端(基於Netty,有完整源碼)

拿起鍵盤就是幹:跟我一塊兒徒手開發一套分佈式IM系統

適合新手:手把手教你用Go快速搭建高性能、可擴展的IM系統(有源碼)

IM裏「附近的人」功能實現原理是什麼?如何高效率地實現它?

IM開發基礎知識補課(七):主流移動端帳號登陸方式的原理及設計思路

IM要作手機掃碼登陸?先看看微信的掃碼登陸功能技術原理

IM開發寶典:史上最全,微信各類功能參數和邏輯規則資料彙總

IM開發乾貨分享:我是如何解決大量離線消息致使客戶端卡頓的

IM開發乾貨分享:如何優雅的實現大量離線消息的可靠投遞

IM開發乾貨分享:有贊移動端IM的組件化SDK架構設計實踐

>> 更多同類文章 ……

本文已同步發佈於「即時通信技術圈」公衆號,歡迎關注:

▲ 本文在公衆號上的連接是:點此進入,原文連接是:http://www.52im.net/thread-3129-1-1.html

相關文章
相關標籤/搜索