Apache Ignite(四):基于Ignite的分布式ID生成器
2016-02-15 18:13:11 來源:李玉玨 評論:0 點擊:
1.Apache Ignite的分布式原子化類型
JDK在1.5版本之后,提供了java.util.concurrent包,其中java.util.concurrent.atomic子包中包含了對于單一變量的線程安全的支持lock-free的編程實現(xiàn)。該包中的類,比如AtomicLong
,提供了和Long類型相對應(yīng)的原子化操作,比如一些increment方法,基于這些功能,是可以開發(fā)出單JVM的序列生成器這樣的功能的,但是對于分布式環(huán)境,則無能為力。
在Ignite中,除了提供標準的基于鍵-值的類似于Map的存儲以外,還提供了一種分布式數(shù)據(jù)結(jié)構(gòu)的實現(xiàn),其中包括:IgniteAtomicLong
, IgniteSet
, IgniteQueue
, IgniteAtomicReference
, IgniteAtomicSequence
, IgniteCountDownLatch
, IgniteSemaphore
,這些類除了提供和JDK相同的功能外,就是增加了對分布式環(huán)境的支持,也就是支持集群范圍內(nèi)的原子化操作。
鑒于本文重點是討論分布式ID生成器,所有下文的重點在于IgniteAtomicSequence。
2.IgniteAtomicSequence
IgniteAtomicSequence接口提供了分布式的原子性序列,類似于分布式原子性的Long類型,但是他的值只能增長,他特有的功能是支持預(yù)留一定范圍的序列值,來避免每次序列獲取下一個值時都需要的昂貴的網(wǎng)絡(luò)消耗和緩存更新,也就是,當在一個原子性序列上執(zhí)行了incrementAndGet()(或者任何其他的原子性操作),數(shù)據(jù)結(jié)構(gòu)會往前預(yù)留一定范圍的序列值,他會保證對于這個序列實例來說跨集群的唯一性。
這個類型的使用是非常簡單的,相關(guān)代碼如下:
Ignite ignite = Ignition.start();IgniteAtomicSequence seq = ignite.atomicSequence("seqName",//序列名 0, //初始值true//如果序列不存在則創(chuàng)建);for (int i = 0; i < 20; i++) { long currentValue = seq.get();//獲取當前值 long newValue = seq.incrementAndGet();//先加1再取值 ...}
這個樣例中創(chuàng)建的seq,初始值從0開始,然后遞增,看上去很完美,但是當系統(tǒng)不管什么原因重啟后,就又會從0開始,這顯然是無法保證唯一性的,因此這個方法還是不能在生產(chǎn)環(huán)境下使用。
3.基于IgniteAtomicSequence的分布式ID生成器
按照前述,直接按照初始值0創(chuàng)建IgniteAtomicSequence,是有很大風(fēng)險的,無法在生產(chǎn)環(huán)境下使用,而且存在長度不固定問題,所以還需要進一步想辦法,研究的重點在于解決初始值的問題。
因為IgniteAtomicSequence的值為long型,而在Java中l(wèi)ong類型的最大值是9223372036854775807,這個數(shù)值長度為19位,對于實際應(yīng)用來說,是一個很大的值,但是對于常見的沒有環(huán)境依賴的ID生成器來說,還是比較短的。因此我們打算在這方面做文章。
因為系統(tǒng)重置的一個重要指標就是時間,那么我們以時間作為參照,然后加上一個擴展,可能是一個比較理想的選擇,我們以如下的規(guī)則作為初始值:
時間的yyyyMMddHHmmss+00000
這個長度正好是19位,然后每次加1,因為現(xiàn)在是2016年,這個規(guī)則在常規(guī)應(yīng)用場景中,是不會超過long類型的最大值的。
但是,這個規(guī)則存在一個風(fēng)險,就是假設(shè)不考慮實際應(yīng)用和實際性能,如果增加操作業(yè)務(wù)量特別大,會使這個序列值快速進位,如果某個時間節(jié)點宕機后瞬間重啟,是有可能存在重啟后的初始值小于原來的最大值的,這時就無法保證唯一性了。下面就對這個理論情況下的最大值做一個計算,然后開發(fā)者就會知道在自己的應(yīng)用中如何改進這個規(guī)則以滿足個性化需求了。
4.理論極限和性能
假定不考慮實際性能,我們以最簡單的情況為例,就是啟動后一秒鐘內(nèi)訪問達到峰值,然后宕機后瞬間重啟這種情況,這個很容易就能看出來,不需要計算,就是5個0對應(yīng)的最大值10萬,以此類推,考慮到時間的進位和十進制進位的不同,我們可以計算出一分鐘后、一小時后、一天后、一月后、一年后宕機換算出的交易量的極大值,如下:
以1分鐘為例進行說明,假設(shè)初始值為2016011815341200000,一分鐘后宕機瞬間重啟,對應(yīng)的初始值為2016011815351200000,這個差額是10000000,對應(yīng)的每秒交易量為16.6萬。
從上圖來看,對于這樣的規(guī)則,能承載的交易量還是很大的,當今世界最繁忙的交易系統(tǒng),也不會超過這個極限情況下的極值,也就是說,這個規(guī)則就目前來說,具有普遍適用性。
而在實際生產(chǎn)中,瞬間重啟是不存在的,隨著重啟時間向后推移,新的初始值會和原來的最大值拉開差距,更不可能出現(xiàn)沖突了。
關(guān)于性能,我在一臺2011年的舊筆記本上進行測試,很容易就能達到50K/s的序列生成速度,這個還是可以的,但是這是在開啟預(yù)留的前提下實現(xiàn)的,如果不開啟預(yù)留,性能可能下降到13K/s。在一個具體的集群環(huán)境下,通常不會拿Ignite單獨建立服務(wù)做ID分發(fā)中心,所以實際環(huán)境下性能能不能滿足需求,開發(fā)者需要自行進行測試,評估然后做選擇。另外,開啟了預(yù)留會導(dǎo)致最終生成的ID可能不是隨時間線性增長的,這個也需要注意。
5.常見分布式ID生成器對比
前述的基于Ignite的分布式ID生成器,優(yōu)點是實現(xiàn)簡單,將一個jar包嵌入應(yīng)用后ID生成系統(tǒng)和應(yīng)用生命周期一致,設(shè)置了備份后不存在單點故障,數(shù)值線性遞增可比較大小,規(guī)則按照業(yè)務(wù)定制后可以做得更短,如果轉(zhuǎn)成十六進制后,會非常短,不依賴數(shù)據(jù)庫,不對數(shù)據(jù)庫產(chǎn)生壓力,缺點可能就是性能以及一些特定的業(yè)務(wù)需求了。
生成全局唯一ID的需求是剛性的,尤其是分布式環(huán)境中,問題顯得尤為復(fù)雜。當前,這方面的實現(xiàn)方案非常多,通用的不通用的,本文不做詳細的論述,只做簡單的列舉:
- UUID
優(yōu)點是性能好,缺點是比較長,128位,無規(guī)則,無法比較大小。 - ID分發(fā)中心
比如twitter的snowflake服務(wù),可以做到高性能,高可用,低延遲,時間上有序,缺點就是使整個系統(tǒng)變得復(fù)雜,維護工作量加大。 - MongoDB的ObjectID(類似UUID)
MongoDB的驅(qū)動中提供了objectId的生成功能,優(yōu)點是相對于UUID要短些,時間上有序,而且這個id包含了很多有用的信息,解碼后就可以獲得。 - 數(shù)據(jù)庫生成
有很多的基于數(shù)據(jù)庫的方案,比如基于Oracle和PostgreSQL的序列,F(xiàn)lickr和Instagram也有基于數(shù)據(jù)庫的比較復(fù)雜的方案。 - 其他
根據(jù)不同的業(yè)務(wù)場景,可以做出各種各樣的、滿足各種需求的ID生成方案,需求越多,實現(xiàn)也會越復(fù)雜。
6.總結(jié)
分布式ID生成策略有很多的實現(xiàn)方案,各有優(yōu)缺點,本文又提出了一個基于Apache Ignite的新方案,應(yīng)該說沒有最完美的,只有最符合實際業(yè)務(wù)需求的,開發(fā)者需要做的就是做詳細的、綜合的比較,然后選擇最適合自己的方案。
相關(guān)熱詞搜索:apache ignite part04 架構(gòu) & 設(shè)計 語言 & 開發(fā) Apache 性能分析 分布式 分布式開發(fā) 性能
上一篇:Golang的包管理之道
下一篇:小故事:架構(gòu)師需要做什么?
