本文介紹了什么是分布式ID,分布式ID的業(yè)務(wù)場景以及9種分布式ID的實現(xiàn)方式,同時基于vivo內(nèi)部IT的業(yè)務(wù)場景,介紹了自研魯班分布式ID服務(wù)的實踐。
一、方案背景
1.1 分布式ID應(yīng)用的場景
隨著系統(tǒng)的業(yè)務(wù)場景復(fù)雜化、架構(gòu)方案的優(yōu)化演進,我們在克服問題的過程中,也總會延伸出新的技術(shù)訴求。分布式ID也是誕生于這樣的IT發(fā)展過程中,在不同的關(guān)聯(lián)模塊內(nèi),我們需要一個全局唯一的ID來讓模塊既能并行地解耦運轉(zhuǎn),也能輕松地進行整合處理。以下,首先讓我們一起回顧這些典型的分布式ID場景。
1.1.1 系統(tǒng)分庫分表
隨著系統(tǒng)的持續(xù)運作,常規(guī)的單庫單表 在支撐更高規(guī)模的數(shù)量級時,無論是在性能或穩(wěn)定性上都已經(jīng)難以為繼,需要我們對目標(biāo)邏輯數(shù)據(jù)表進行合理的物理拆分,這些同一業(yè)務(wù)表數(shù)據(jù)的拆分,需要有一套完整的 ID生成方案來保證拆分后的各物理表中同一業(yè)務(wù)ID不相沖突,并能在后續(xù)的合并分析中可以方便快捷地計算。
以公司的營銷系統(tǒng)的訂單為例,當(dāng)前不但以分銷與零售的目標(biāo)組織區(qū)別來進行分庫存儲,來實現(xiàn)多租戶的數(shù)據(jù)隔離,并且會以訂單的業(yè)務(wù)屬性(訂貨單、退貨單、調(diào)拔單等等)來進一步分拆訂單數(shù)據(jù) 。在訂單創(chuàng)建的時候,根據(jù)這些規(guī)則去構(gòu)造全局唯一ID,創(chuàng)建訂單單據(jù)并保存在對應(yīng)的數(shù)據(jù)庫中;在通過訂單號查詢時,通過ID的規(guī)則,快速路由到對應(yīng)的庫表中查詢;在BI數(shù)倉的統(tǒng)計業(yè)務(wù)里,又需要匯總這些訂單數(shù)據(jù)進行報表分析。
1.1.2 系統(tǒng)多活部署
無論是面對著全球化的各國數(shù)據(jù)合規(guī)訴求 ,還是針對容災(zāi)高可用的架構(gòu)設(shè)計 ,我們都會對同一套系統(tǒng)進行多活部署。多活部署架構(gòu)的各單元化服務(wù),存儲的單據(jù)(如訂單/出入庫單/支付單等)均帶有部署區(qū)域?qū)傩缘腎D結(jié)構(gòu)去構(gòu)成全局唯一ID,創(chuàng)建單據(jù)并保存在對應(yīng)單元的數(shù)據(jù)庫中,在前端根據(jù)單據(jù)號查詢的場景,通過ID的規(guī)則,可快速路由到對應(yīng)的單元區(qū)域進行查詢。對應(yīng)多活部署架構(gòu)的中心化服務(wù),同步各單元的單據(jù)數(shù)據(jù)時,單據(jù)的ID是全局唯一,避免了匯聚數(shù)據(jù)時的ID沖突。
在公司的系統(tǒng)部署中,公共領(lǐng)域的 BPM 、待辦、營銷領(lǐng)域的系統(tǒng)都大范圍地實施多活部署。
1.1.3 鏈路跟蹤技術(shù)
在微服務(wù)架構(gòu)流行的大背景下,此類微服務(wù)的應(yīng)用對比單體應(yīng)用的調(diào)用鏈路會更長、更復(fù)雜,對問題的排查帶來了挑戰(zhàn),應(yīng)對該場景的解決方案,會在流量入口處產(chǎn)生全局唯一的TraceID,并在各微服務(wù)之間進行透傳,進行流量染色與關(guān)聯(lián),后續(xù)通過該全局唯一的TraceID ,可快速地查詢與關(guān)聯(lián)全鏈路的調(diào)用關(guān)系與狀態(tài),快速定位根因問題。
在公司的各式各樣的監(jiān)控系統(tǒng)、灰度管理平臺、跨進程鏈路日志中,都會伴隨著這么一個技術(shù)組件進行支撐服務(wù)。
1.2 分布式ID核心的難點
唯一性 : 保持生成的ID全局唯一,在任何情況下也不會出現(xiàn)重復(fù)的值(如防止時間回拔,時鐘周期問題)。
高性能 : ID的需求場景多,中心化生成組件后,需要高并發(fā)處理,以接近 0ms的響應(yīng)大規(guī)模并發(fā)執(zhí)行。
高可用 : 作為ID的生產(chǎn)源頭,需要100%可用,當(dāng)接入的業(yè)務(wù)系統(tǒng)多的時候,很難調(diào)整出各方都可接受的停機發(fā)布窗口,只能接受無損發(fā)布。
易接入 : 作為邏輯上簡單的分布式ID要推廣使用,必須強調(diào)開箱即用,容易上手。
規(guī)律性 : 不同業(yè)務(wù)場景生成的ID有其特征,例如有固定的前后綴,固定的位數(shù),這些都需要配置化管理。
1.3 分布式ID常見的方案
常用系統(tǒng)設(shè)計中主要有下圖9種ID生成的方式:
1.4 分布式ID魯班的方案
我們的系統(tǒng)跨越了公共、生產(chǎn)制造、營銷、供應(yīng)鏈、財經(jīng) 等多個領(lǐng)域。在分布式ID訴求下還有如下的特點 :
在業(yè)務(wù)場景上除了常規(guī)的Long類型 ID,也需要支持“String類型 ”、“MixId類型 ”(后詳述)等多種類型的ID生成,每一種類型也需要支持不同的長度的ID。
在ID的構(gòu)成規(guī)則上需要涵蓋如操作類型、區(qū)域 、代理 等業(yè)務(wù)屬性的標(biāo)識;需要集中式的配置管理。
在一些特定的業(yè)務(wù)上,基于安全的考慮,還需要在尾部加上隨機數(shù)來保證ID不能被輕易猜測。
綜合參考了業(yè)界優(yōu)秀的開源組件與常用方案均不能滿足,為了統(tǒng)一管理這類基礎(chǔ)技術(shù)組件的訴求,我們選擇基于公司業(yè)務(wù)場景自研一套分布式ID服務(wù):魯班分布式ID服務(wù) 。
二、系統(tǒng)架構(gòu)
2.1 架構(gòu)說明
三、 設(shè)計要點
3.1 支持多種類型的ID規(guī)則
目前魯班分布式ID服務(wù)共提供"Long類型 "、“String類型 ”、“MixId類型 ”等三種主要類型的ID,相關(guān)ID構(gòu)成規(guī)則與說明如下:
3.1.2 Long類型
(1)構(gòu)成規(guī)則
靜態(tài)結(jié)構(gòu)由以下三部分數(shù)據(jù)組成,組成部分共19位 :
固定部分(4位) :
由FixPart+ServerPart組成。
① FixPart(4位) :由大區(qū)zone 1位/代理 agent 1位/項目 project 1位/應(yīng)用 app 1位,組成的4位數(shù)字編碼。
② ServerPart(4位) :用于定義產(chǎn)生全局ID的服務(wù)器標(biāo)識位,服務(wù)節(jié)點部署時動態(tài)分配。
動態(tài)部分DynPart(13位) :
System.currentTimeMillis()-固定配置時間的TimeMillis (可滿足使用100年)。
自增部分SelfIncreasePart(2位) :用于在全局ID的客戶端SDK內(nèi)部自增部分,由客戶端SDK控制,業(yè)務(wù)接入方無感知。共 2位組成。
(2)降級機制
主要自增部分在服務(wù)器獲取初始值后,由客戶端SDK維護,直到自增99后再次訪問服務(wù)端獲取下一輪新的ID以減少服務(wù)端交互頻率,提升性能,服務(wù)端獲取失敗后拋出異常,接入業(yè)務(wù)側(cè)需介入進行處理。
(3)樣例說明
3.1.2 String類型
(1)構(gòu)成規(guī)則
靜態(tài)結(jié)構(gòu)由以下五部分數(shù)據(jù)組成,組成部分共25~27位 :
固定部分操作位op+FixPart(9~11位) :
① 操作位op(2~4位) :2~4位由業(yè)務(wù)方傳入的業(yè)務(wù)類型標(biāo)識字符。
② FixPart(7位) :業(yè)務(wù)接入時申請獲取,由大區(qū)zone 1位,代理 agent 2位,項目 project 2位,應(yīng)用 app 2位組成。
服務(wù)器標(biāo)識部分 ServerPart(1位) : 用于定義產(chǎn)生全局ID的服務(wù)器標(biāo)識位,服務(wù)節(jié)點部署時動態(tài)分配A~Z編碼。
動態(tài)部分DynPart(9位) :
System.currentTimeMillis()-固定配置時間的TimeMillis ,再轉(zhuǎn)換為32進制字符串(可滿足使用100年)。
自增部分SelfIncreasePart(3位) :用于在全局ID的客戶端SDK內(nèi)部自增部分,由客戶端SDK控制,業(yè)務(wù)接入方無感知。
隨機部分secureRandomPart(3位) :用于在全局ID的客戶端SDK的隨機部分,由SecureRandom隨機生成3位0-9,A-Z字母數(shù)字組合的安全隨機數(shù),業(yè)務(wù)接入方無感知。
(2)降級機制
主要自增部分由客戶端SDK內(nèi)部維護,一般情況下只使用001–999 共999個全局ID。也就是每向服務(wù)器請求一次,都在客戶端內(nèi)可以自動維護999個唯一的全局ID。特殊情況下在訪問服務(wù)器連接出問題的時候,可以使用帶字符的自增來做服務(wù)器降級處理,使用產(chǎn)生00A, 00B... 0A0, 0A1,0A2....ZZZ. 共有36 * 36 * 36 - 1000 (999純數(shù)字,000不用)**= 45656個降級使用的全局ID** 。
(3)樣例說明
3.1.3 MixId類型
(1)構(gòu)成規(guī)則
靜態(tài)結(jié)構(gòu)由以下三部分數(shù)據(jù)組成,組成部分共17位 :
固定部分FixPart(4~6位) :
① 操作位op(2~4位) :2~4位由業(yè)務(wù)方傳入的業(yè)務(wù)類型標(biāo)識字符
② FixPart(2位) :業(yè)務(wù)接入時申請獲取由代理 agent 2位組成。
動態(tài)部分DynPart(6位) : 生成ID的時間,年(2位)月(2位)日(2位)。
自增部分SelfIncreasePart(7位) :用于在全局ID的客戶端SDK內(nèi)部自增部分,由客戶端SDK控制,業(yè)務(wù)接入方無感知。
(2)降級機制
無,每次ID產(chǎn)生均需到服務(wù)端請求獲取,服務(wù)端獲取失敗后拋出異常,接入業(yè)務(wù)側(cè)需介入進行處理。
(3)樣例說明
3.2 業(yè)務(wù)自定義ID規(guī)則實現(xiàn)
魯班分布式ID服務(wù)內(nèi)置“Long類型”,“String類型”,“MixId類型”等三種長度與規(guī)則固定的ID生成算法,除以上三種類型的ID生成算法外,業(yè)務(wù)側(cè)往往有自定義ID長度與規(guī)則的場景訴求,在魯班分布式ID服務(wù)內(nèi)置ID生成算法未能滿足業(yè)務(wù)場景時,為了能在該場景快速支持業(yè)務(wù),魯班分布式ID服務(wù)提供了業(yè)務(wù)自定義接口并通過SPI機制在服務(wù)運行時動態(tài)加載,以實現(xiàn)業(yè)務(wù)自定義ID生成算法場景的支持,相關(guān)能力的實現(xiàn)設(shè)計與接入流程 如下:
(1)ID的構(gòu)成部分主要分FixPart、DynPart、SelfIncreasePart三個部分。
(2)魯班分布式ID服務(wù)的客戶端SDK提供
LuBanGlobalIDClient的接口與getGlobalId(...)/ setFixPart(...)/setDynPart(...)/setSelfIncreasePart(...)等四個接口方法 。
(3)業(yè)務(wù)側(cè)實現(xiàn)LuBanGlobalIDClient接口內(nèi)的4個方法,通過SPI機制在業(yè)務(wù)側(cè)服務(wù)進行加載,并向外暴露出HTTP或DUBBO協(xié)議的接口。
(4)用戶在魯班分布式ID服務(wù)管理后臺對自定義ID生成算法的類型名稱與服務(wù)地址信息進行配置,并關(guān)聯(lián)需要使用的AK接入信息。
(5)業(yè)務(wù)側(cè)使用時調(diào)用客戶端SDK提供的LuBanGlobalIDClient的接口與getGlobalId方法,并傳入ID生成算法類型與IdRequest入?yún)ο?,魯班分布式ID服務(wù)接收請求后,動態(tài)識別與路由到對應(yīng)ID生產(chǎn)算法的實現(xiàn)服務(wù),并構(gòu)建對象的ID返回給客戶端,完成整個ID生成與獲取的過程。
3.3 保證ID生成不重復(fù)方案
3.4 ID服務(wù)無狀態(tài)無損管理
服務(wù)部署的環(huán)境在虛擬機上,ip是固定,常規(guī)的做法是在配置表里配置ip與機器碼的綁定關(guān)系(這樣在服務(wù)擴縮容的時候就需要人為介入操作,存在一定的遺漏配置風(fēng)險,也帶來了一定的運維成本),但在容器的部署場景,因為每次部署時IP均是動態(tài)變化的,以前通過配置表里ip與機器碼的映射關(guān)系的配置實現(xiàn)方式顯然不能滿足運行在容器場景的訴求,故在服務(wù)端設(shè)計了通過心跳上報實現(xiàn)機器碼動態(tài)分配的機制,實現(xiàn)服務(wù)端節(jié)點ip與機器碼動態(tài)分配、綁定的能力,達成部署自動化與無損發(fā)布的目的。
相關(guān)流程如下:
【注意】
服務(wù)端節(jié)點可能因為異常,非正常地退出,對于該場景,這里就需要有一個解綁的過程,當(dāng)前實現(xiàn)是通過公司平臺團隊的分布式定時任務(wù)服務(wù),檢查持續(xù)5分鐘(可配置)沒有上報心跳的機器碼分配節(jié)點進行數(shù)據(jù)庫綁定信息清理的邏輯,重置相關(guān)機器碼的位置供后續(xù)注冊綁定使用。
3.5 ID使用方接入SDK設(shè)計
SDK設(shè)計主要以"接入快捷,使用簡單 "的原則進行設(shè)計。
(1)接入時:
魯班分布式ID服務(wù)提供了spring-starter包,應(yīng)用只需再pom文件依賴該starter,在啟動類里添加**@EnableGlobalClient** ,并配置AK/SK 等租戶參數(shù)即可完成接入。
同時魯班分布式ID服務(wù)提供Dubbo & Http的調(diào)用方式,通過在啟動注解配置accessType 為HTTP/DUBBO 來確定,SDK自動加載相關(guān)依賴。
(2)使用時:
根據(jù)"Long "、"String "、"MixId "等三種id類型分別提供GlobalIdLongClient 、
GlobalIdStringClient 、GlobalIdMixIDClient 等三個客戶端對象,并封裝了統(tǒng)一的入?yún)?strong>RequestDTO 對象,業(yè)務(wù)系統(tǒng)使用時只需構(gòu)建對應(yīng)Id類型的RequestDTO對象(支持鏈?zhǔn)綐?gòu)建),并調(diào)用對應(yīng)id類型的客戶端對象getGlobalID (GlobalBaseRequestDTO
globalBaseRequestDTO) 方法,即可完成ID的構(gòu)建。
Long類型Id獲取代碼示例
packagecom.vivo.it.demo.controller; importcom.vivo.it.platform.luban.id.client.GlobalIdLongClient; importcom.vivo.it.platform.luban.id.dto.GlobalLongIDRequestDTO; importorg.springframework.beans.factory.annotation.Autowired; importorg.springframework.web.bind.annotation.RequestMapping; @RequestMapping("/globalId") publicclassGlobalIdDemoController{ @Autowired privateGlobalIdLongClientglobalIdLongClient; @RequestMapping("/getLongId") publicStringgetLongId(){ GlobalLongIDRequestDTOglobalLongIDRequestDTO=GlobalLongIDRequestDTO.Builder() .setAgent("1")//代理,接入申請時確定 .setZone("0")//大區(qū),接入申請時確定 .setApp("8")//應(yīng)用,接入申請時確定 .setProject("7")//項目,接入申請時確定 .setIdNumber(2);//當(dāng)次返回的id數(shù)量,只對getGlobalIDQueue有效,對getGlobalID(...)無效 longlongId=globalIdLongClient.getGlobalID(globalLongIDRequestDTO); returnString.valueOf(longId); } }
3.6 關(guān)鍵運行性能優(yōu)化場景
3.6.1 內(nèi)存使用優(yōu)化
在項目上線初時,經(jīng)常發(fā)生FGC,導(dǎo)致服務(wù)停頓,獲取ID超時,經(jīng)過分析,魯班分布式ID服務(wù)的服務(wù)端主要為內(nèi)存敏感的應(yīng)用,當(dāng)高并發(fā)請求時,過多對象進入老年代從而觸發(fā)FGC,經(jīng)過排查主要是JVM內(nèi)存參數(shù)上線時是使用默認的,沒有經(jīng)過優(yōu)化配置,JVM初始化的內(nèi)存較少,高并發(fā)請求時JVM頻繁觸發(fā)內(nèi)存重分配,相關(guān)的對象也流程老年代導(dǎo)致最終頻繁發(fā)送FGC。
對于這個場景的優(yōu)化思路主要是要相關(guān)內(nèi)存對象在年輕代時就快速經(jīng)過YGC回收,盡量少的對象進行老年代而引起FGC。
基于以上的思路主要做了以下的優(yōu)化:
增大JVM初始化內(nèi)存(-Xms,容器場景里為-XX:InitialRAMPercentage)
增大年輕代內(nèi)存(-Xmn)
優(yōu)化代碼,減少代碼里臨時對象的復(fù)制與創(chuàng)建
3.6.2 鎖顆粒度優(yōu)化
客戶端SDK再自增值使用完或一定時間后會向服務(wù)端請求新的id生成,這個時候需要保證該次請求在多線程并發(fā)時是只請求一次,當(dāng)前設(shè)計是基于用戶申請ID的接入配置,組成為key,去獲取對應(yīng)key的對象鎖,以減少同步代碼塊鎖的粒度,避免不同接入配置去在并發(fā)去遠程獲取新的id時,鎖粒度過大,造成線程的阻塞,從而提升在高并發(fā)場景下的性能。
四、 業(yè)務(wù)應(yīng)用
當(dāng)前魯班分布式ID服務(wù)日均ID生成量億級 ,平均RT在0~1ms 內(nèi),單節(jié)點可支持 萬級 QPS,已全面應(yīng)用在公司IT內(nèi)部營銷訂單、支付單據(jù)、庫存單據(jù)、履約單據(jù)、資產(chǎn)管理編碼 等多個領(lǐng)域的業(yè)務(wù)場景。
五、未來規(guī)劃
在可用性方面,當(dāng)前魯班分布式ID服務(wù)仍對Redis、Mysql等外部DB組件有一定的依賴(如應(yīng)用接入配置信息、MixId類型自增部分ID計數(shù)器),規(guī)劃在該依賴極端宕機的場景下,魯班分布式ID服務(wù)仍能有一些降級策略,為業(yè)務(wù)提供可用的服務(wù)。
同時基于業(yè)務(wù)場景的訴求,支持標(biāo)準(zhǔn)形式的雪花算法等ID類型。
六、 回顧總結(jié)
本文通過對分布式ID的3種應(yīng)用場景,實現(xiàn)難點以及9種分布式ID的實現(xiàn)方式進行介紹,并對結(jié)合vivo業(yè)務(wù)場景特性下自研的魯班分布式id服務(wù)從系統(tǒng)架構(gòu),ID生成規(guī)則與部分實現(xiàn)源碼進行介紹,希望對本文的閱讀者在分布式ID的方案選型或自研提供參考。
編輯:黃飛
-
服務(wù)器
+關(guān)注
關(guān)注
12文章
9160瀏覽量
85415 -
數(shù)據(jù)庫
+關(guān)注
關(guān)注
7文章
3799瀏覽量
64388 -
SDK
+關(guān)注
關(guān)注
3文章
1036瀏覽量
45935 -
vivo
+關(guān)注
關(guān)注
12文章
3303瀏覽量
63305
原文標(biāo)題:談?wù)劮植际?ID 的服務(wù)實踐
文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論