太贊!滴滴開源了一套分佈式ID的生成服務...

ID Generator id生成器 分佈式id生成系統,簡單易用、高性能、高可用的id生成系統

簡介

Tinyid是用Java開發的一款分佈式id生成系統,基於數據庫號段算法實現,關於這個算法可以參考美團leaf或者tinyid原理介紹。Tinyid擴展了leaf-segment算法,支持了多db(master),同時提供了java-client(sdk)使id生成本地化,獲得了更好的性能與可用性。Tinyid在滴滴客服部門使用,均通過tinyid-client方式接入,每天生成億級別的id。

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)方法產生。

性能與可用性

性能

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

可用性

  1. 依賴db,當db不可用時,因爲server有緩存,所以還可以使用一段時間,如果配置了多個db,則只要有1個db存活,則服務可用
  2. 使用tiny-client,只要server有一臺存活,則理論上可用,server全掛,因爲client有緩存,也可以繼續使用一段時間

Tinyid的特性

  1. 全局唯一的long型id
  2. 趨勢遞增的id,即不保證下一個id一定比上一個大
  3. 非連續性
  4. 提供http和java client方式接入
  5. 支持批量獲取id
  6. 支持生成1,3,5,7,9…序列的id
  7. 支持多個db的配置,無單點
    適用場景:只關心id是數字,趨勢遞增的系統,可以容忍id不連續,有浪費的場景 不適用場景:類似訂單id的業務(因爲生成的id大部分是連續的,容易被掃庫、或者測算出訂單量)

推薦使用方式

  1. tinyid-server推薦部署到多個機房的多臺機器
    多機房部署可用性更高,http方式訪問需使用方考慮延遲問題
  2. 推薦使用tinyid-client來獲取id,好處如下:
    id爲本地生成(調用AtomicLong.addAndGet方法),性能大大增加
    client對server訪問變的低頻,減輕了server的壓力
    因爲低頻,即便client使用方和server不在一個機房,也無須擔心延遲
    即便所有server掛掉,因爲client預加載了號段,依然可以繼續使用一段時間 注:使用tinyid-client方式,如果client機器較多頻繁重啓,可能會浪費較多的id,這時可以考慮使用http方式
  3. 推薦db配置兩個或更多:
    db配置多個時,只要有1個db存活,則服務可用 多db配置,如配置了兩個db,則每次新增業務需在兩個db中都寫入相關數據

tinyid的原理

Id生成系統要點
在簡單系統中,我們常常使用db的id自增方式來標識和保存數據,隨着系統的複雜,數據的增多,分庫分表成爲了常見的方案,db自增已無法滿足要求。這時候全局唯一的id生成系統就派上了用場。當然這只是id生成其中的一種應用場景。那麼id生成系統有哪些要求呢?

  1. 全局唯一的id:無論怎樣都不能重複,這是最基本的要求了
  2. 高性能:基礎服務儘可能耗時少,如果能夠本地生成最好
  3. 高可用:雖說很難實現100%的可用性,但是也要無限接近於100%的可用性
  4. 簡單易用: 能夠拿來即用,接入方便,同時在系統設計和實現上要儘可能的簡單

Tinyid的實現原理
我們先來看一下最常見的id生成方式,db的auto_increment,相信大家都非常熟悉,我也見過一些同學在實戰中使用這種方案來獲取一個id,這個方案的優點是簡單,缺點是每次只能向db獲取一個id,性能比較差,對db訪問比較頻繁,db的壓力會比較大。那麼是不是可以對這種方案優化一下呢,可否一次向db獲取一批id呢?答案當然是可以的。
一批id,我們可以看成是一個id範圍,例如(1000,2000],這個1000到2000也可以稱爲一個"號段",我們一次向db申請一個號段,加載到內存中,然後採用自增的方式來生成id,這個號段用完後,再次向db申請一個新的號段,這樣對db的壓力就減輕了很多,同時內存中直接生成id,性能則提高了很多。那麼保存db號段的表該怎設計呢?

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,能夠保證併發更新的正確性  那麼我們可以通過如下幾個步驟來獲取一個可用的號段,
  5. A.查詢當前的max_id信息:select id, biz_type, max_id, step, version from tiny_id_info where biz_type='test';
  6. B.計算新的max_id: new_max_id = max_id + step
  7. 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}
  8. D.如果更新成功,則可用號段獲取成功,新的可用號段爲(max_id, new_max_id]
  9. E.如果更新失敗,則號段可能被其他線程獲取,回到步驟A,進行重試

號段生成方案的簡單架構
如上我們已經完成了號段生成邏輯,那麼我們的id生成服務架構可能是這樣的

id生成系統向外提供http服務,請求經過我們的負載均衡router,到達其中一臺tinyid-server,從事先加載好的號段中獲取一個id,如果號段還沒有加載,或者已經用完,則向db再申請一個新的可用號段,多臺server之間因爲號段生成算法的原子性,而保證每臺server上的可用號段不重,從而使id生成不重
可以看到如果tinyid-server如果重啓了,那麼號段就作廢了,會浪費一部分id;同時id也不會連續;每次請求可能會打到不同的機器上,id也不是單調遞增的,而是趨勢遞增的,不過這對於大部分業務都是可接受的。

簡單架構的問題
到此一個簡單的id生成系統就完成了,那麼是否還存在問題呢?回想一下我們最開始的id生成系統要求,高性能、高可用、簡單易用,在上面這套架構裏,至少還存在以下問題:

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

優化辦法如下:
(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. tinyid提供http和tinyid-client兩種方式接入
  2. tinyid-server內部緩存兩個號段
  3. 號段基於db生成,具有原子性
  4. db支持多個
  5. tinyid-server內置easy-router選擇db

    項目地址

    github地址:https://github.com/didi/tinyid

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章