一、京東緩存中間件架構(gòu)
1、背景
在當(dāng)今高并發(fā)、分布式的系統(tǒng)架構(gòu)中,緩存已成為提升應(yīng)用性能、降低數(shù)據(jù)庫負(fù)載的核心組件。隨著業(yè)務(wù)規(guī)模的擴(kuò)大與系統(tǒng)復(fù)雜度的增加,緩存的使用和管理面臨諸多挑戰(zhàn):部署模式多樣、容災(zāi)策略不一、數(shù)據(jù)一致性保障困難等問題日益凸顯。
在京東內(nèi)部,大量業(yè)務(wù)系統(tǒng)依賴JIMDB作為緩存解決方案。然而,在實(shí)際使用與運(yùn)維過程中,我們發(fā)現(xiàn)了以下幾個(gè)普遍存在的痛點(diǎn):
?部署架構(gòu)不統(tǒng)一:?jiǎn)渭簡(jiǎn)螜C(jī)房部署、單集群跨機(jī)房部署、多集群多機(jī)房部署。
?容災(zāi)策略不統(tǒng)一:由于部署架構(gòu)的差異,各業(yè)務(wù)團(tuán)隊(duì)需要自行制定和維護(hù)不同的故障預(yù)案。在發(fā)生大規(guī)模故障時(shí),這些分散的預(yù)案難以被高效、協(xié)調(diào)地執(zhí)行,容災(zāi)效果參差不齊,系統(tǒng)整體可用性面臨挑戰(zhàn)。
?缺乏保障數(shù)據(jù)一致性的公共組件:緩存與持久化存儲(chǔ)之間的數(shù)據(jù)一致性通常由各業(yè)務(wù)方自行實(shí)現(xiàn)。這些方案往往與業(yè)務(wù)邏輯深度耦合,實(shí)現(xiàn)復(fù)雜且難以復(fù)用。
為了解決上述問題,我們?cè)O(shè)計(jì)并實(shí)現(xiàn)了統(tǒng)一緩存中間件:DongDAL for KV(簡(jiǎn)稱DongKV)。
?標(biāo)準(zhǔn)化部署架構(gòu):收斂多種部署場(chǎng)景,提供統(tǒng)一的部署模式。
?標(biāo)準(zhǔn)化容災(zāi)策略:實(shí)現(xiàn)集群級(jí)、機(jī)房級(jí)故障無需切換或一鍵切換。
?統(tǒng)一緩存訪問架構(gòu):提供標(biāo)準(zhǔn)化的緩存訪問中間件,封裝復(fù)雜性,降低業(yè)務(wù)方的使用與維護(hù)成本。
2、統(tǒng)一緩存高可用架構(gòu)
當(dāng)前JIMDB的容災(zāi)能力:
?單個(gè)實(shí)例、物理機(jī)、交換機(jī)故障:屬于日常高頻故障場(chǎng)景。JIMDB具備自動(dòng)故障發(fā)現(xiàn)與Failover能力。
?機(jī)房故障:發(fā)生概率較低,但影響范圍大。JIMDB雖支持主從倒換,但涉及大面積集群結(jié)構(gòu)變更,效率與可靠性難以保障。
?集群故障:偶發(fā)性強(qiáng),危害極大。JIMDB缺乏標(biāo)準(zhǔn)化的容災(zāi)方案,依賴各業(yè)務(wù)自行定制切換或降級(jí)策略,風(fēng)險(xiǎn)較高。
針對(duì)機(jī)房故障和集群故障的場(chǎng)景遇到的問題,DongK提供了雙JIMDB集群,雙機(jī)房部署的標(biāo)準(zhǔn)化高可用架構(gòu)。
2.1、主備模式
在主備模式中,業(yè)務(wù)在每個(gè)機(jī)房各部署一個(gè)JIMDB集群,其中一個(gè)集群被定義為主集群,承載日常所有的讀寫流量;另一個(gè)集群作為備集群(Failover集群),處于熱備狀態(tài)。當(dāng)主集群或主集群所在機(jī)房故障時(shí),可以把應(yīng)用訪問JIMDB的流量一鍵切換到備集群。
需要注意的是,備集群因日常無寫流量,無數(shù)據(jù)。為避免切換后大量請(qǐng)求因緩存未命中而“穿透”到底層數(shù)據(jù)庫造成雪崩,用戶可以主備集群之間通過DTS建立數(shù)據(jù)同步。這樣,備集群能近乎實(shí)時(shí)地同步主集群的數(shù)據(jù),確保切換時(shí)數(shù)據(jù)的“熱度”,實(shí)現(xiàn)平滑切換。
主備模式適用于對(duì)緩存讀寫一致性要求極高的業(yè)務(wù),例如庫存預(yù)占、限購搶購等。

2.2、互備模式
在互備模式中,應(yīng)用也是在每個(gè)機(jī)房部署各一個(gè)JIMDB集群。與主備模式不同的是,這些集群日常各自承接本機(jī)房?jī)?nèi)應(yīng)用的讀寫請(qǐng)求,形成“互為主備”的關(guān)系。若某個(gè)JIMDB集群(非整個(gè)機(jī)房)故障,管理員可通過DongKV管理端,一鍵將該集群的流量全部切換到另一個(gè)健康集群。若整個(gè)機(jī)房故障,部署在故障機(jī)房的應(yīng)用可能不可用,但另一機(jī)房的應(yīng)用與JIMDB集群組成的完整服務(wù)單元依然健壯,無需任何緩存流量切換即可繼續(xù)提供服務(wù),實(shí)現(xiàn)了真正的機(jī)房級(jí)容災(zāi)。
互備模式下的數(shù)據(jù)同步通常由業(yè)務(wù)自身的數(shù)據(jù)流驅(qū)動(dòng)。以一個(gè)典型的商品讀服務(wù)為例,其寫數(shù)據(jù)鏈路(圖中虛線框部分):應(yīng)用將數(shù)據(jù)寫入數(shù)據(jù)庫,然后通過Binlake(數(shù)據(jù)同步中間件)將其“刷入”同機(jī)房的JIMDB集群。這樣,每個(gè)機(jī)房都形成了一個(gè)寫鏈路垂直封閉的單元,數(shù)據(jù)通過數(shù)據(jù)庫的底層同步在機(jī)房之間實(shí)現(xiàn)最終一致。此寫鏈路因業(yè)務(wù)而異,屬于業(yè)務(wù)架構(gòu)的一部分,不包含在DongKV中間件內(nèi)。DongKV在此模式下的核心價(jià)值,是提供對(duì)雙集群的智能路由與故障切換能力。
互備模式別適用于讀寫分離的業(yè)務(wù),例如商品信息查詢、優(yōu)惠券查詢。

3、統(tǒng)一持久化存儲(chǔ)+緩存的解決方案
3.1、背景:數(shù)據(jù)一致性問題
數(shù)據(jù)庫在高并發(fā)場(chǎng)景下存在性能瓶頸。為決絕這一問題,應(yīng)用架構(gòu)需要引入緩存,存儲(chǔ)頻繁訪問的熱點(diǎn)數(shù)據(jù),從而減少對(duì)數(shù)據(jù)庫的直接調(diào)用。這樣形成了數(shù)據(jù)庫+緩存兩級(jí)存儲(chǔ)架構(gòu)。然而,這種架構(gòu)在高并發(fā)場(chǎng)景會(huì)出現(xiàn)數(shù)據(jù)一致性問題。
下面的示意圖解釋了這種不一致發(fā)生的原因。應(yīng)用節(jié)點(diǎn)1訪問緩存的key,沒有讀到數(shù)據(jù),回源到數(shù)據(jù)庫讀取到key = v1。在回寫緩存之前,應(yīng)用節(jié)點(diǎn)1的工作線程被掛起。隨后另一個(gè)應(yīng)用節(jié)點(diǎn)2正好把數(shù)據(jù)庫改成key=v2,然后刪除緩存里的臟數(shù)據(jù)(雖然這時(shí)緩存還沒有該key的數(shù)據(jù))。之后應(yīng)用節(jié)點(diǎn)1恢復(fù)執(zhí)行,把key=1回寫到緩存。這時(shí)候緩存的key=v1,數(shù)據(jù)庫的key=v2,從而造成數(shù)據(jù)不一致。

3.2、痛點(diǎn)
重復(fù)建設(shè):業(yè)界有一些關(guān)于持久化存儲(chǔ)+緩存的解決方案,如簡(jiǎn)單粗暴的延遲雙刪,如Facebook的基于租約的最終一致性方案。業(yè)務(wù)在各自的系統(tǒng)里實(shí)現(xiàn)這些方案,導(dǎo)致技術(shù)資產(chǎn)無法沉淀和復(fù)用,形成“重復(fù)造輪子”的局面,且方案質(zhì)量參差不齊。
代碼耦合:上述一致性邏輯通常直接嵌入在業(yè)務(wù)代碼中,與核心的業(yè)務(wù)流程緊密耦合。這不僅使得業(yè)務(wù)代碼變得冗長(zhǎng)、復(fù)雜,更導(dǎo)致一致性策略難以獨(dú)立演進(jìn)或優(yōu)化,任何改動(dòng)都可能引發(fā)風(fēng)險(xiǎn)。
代碼復(fù)雜度高:要實(shí)現(xiàn)一個(gè)完善、健壯的數(shù)據(jù)一致性方案,實(shí)現(xiàn)復(fù)雜度極高。許多業(yè)務(wù)因難以駕馭此復(fù)雜度,采取了規(guī)避策略,直接將緩存作為數(shù)據(jù)庫使用。這種做法雖然暫時(shí)繞開了數(shù)據(jù)一致性問題,但付出了高昂的資源成本,并因緩存數(shù)據(jù)的非持久化特性而引入了巨大的數(shù)據(jù)丟失風(fēng)險(xiǎn)。
3.3、解決方案
針對(duì)上述痛點(diǎn),DongKV的核心設(shè)計(jì)思想是將數(shù)據(jù)一致性的復(fù)雜邏輯從業(yè)務(wù)代碼中下沉到緩存中間件層,為業(yè)務(wù)提供標(biāo)準(zhǔn)化的訪問接口與可配置的一致性策略。目前,DongKV主要對(duì)兩種典型業(yè)務(wù)場(chǎng)景提供支持。
3.3.1、數(shù)據(jù)庫+JIMDB的強(qiáng)一致場(chǎng)景
以訂單狀態(tài)為例,應(yīng)用了更新訂單狀態(tài),后續(xù)讀取必須反映最新狀態(tài)。例如,用戶支付完成后查詢訂單,DongKV能嚴(yán)格保證后續(xù)的讀操作返回最新的“已支付”的狀態(tài)。

DongKV強(qiáng)一致性方案基于“版本-租約-狀態(tài)”模型,通過數(shù)據(jù)庫與緩存的協(xié)同機(jī)制保障數(shù)據(jù)一致性。利用版本控制實(shí)現(xiàn)數(shù)據(jù)同步,租約機(jī)制規(guī)避臟寫,狀態(tài)機(jī)驅(qū)動(dòng)流程,最終在分布式場(chǎng)景下兼顧一致性與可用性。
(1) 核心設(shè)計(jì)
?版本號(hào)(Version):數(shù)據(jù)每次更新時(shí)版本號(hào)遞增,作為數(shù)據(jù)變更的唯一標(biāo)識(shí)。
?租約(Lease):緩存節(jié)點(diǎn)通過租約機(jī)制臨時(shí)獨(dú)占數(shù)據(jù)寫入權(quán),租約過期后需續(xù)期或釋放。
?狀態(tài)標(biāo)記(State):數(shù)據(jù)在緩存中的狀態(tài)分為有效(Valid)、過期(Expired)、待更新(Updating)三種,驅(qū)動(dòng)一致性邏輯。
(2)協(xié)同機(jī)制
?寫操作:數(shù)據(jù)庫更新后版本號(hào)+1,同步失效緩存;持有租約的節(jié)點(diǎn)負(fù)責(zé)將新數(shù)據(jù)回填緩存。
?讀操作:若緩存數(shù)據(jù)狀態(tài)為有效且版本匹配,直接返回;若過期或版本落后,觸發(fā)同步或阻塞等待更新。
?租約管理:租約超時(shí)后自動(dòng)釋放,防止節(jié)點(diǎn)故障導(dǎo)致數(shù)據(jù)鎖死;續(xù)期需驗(yàn)證版本號(hào),避免臟寫。
此外,DongKV還提供了流量降級(jí)到數(shù)據(jù)庫的能力。即使在JIMDB集群故障的情況下,依然保障業(yè)務(wù)的可用性。
3.3.2、JIMKV+JIMDB的最終一致性場(chǎng)景
如商品讀服務(wù),訪問量大,性能要求高,但是接受讀取到一定過期的數(shù)據(jù)。針對(duì)這個(gè)場(chǎng)景,DongKV提供了冷熱分層存儲(chǔ)能力。全量數(shù)據(jù)放在JIMKV(持久化存儲(chǔ)),熱數(shù)據(jù)放JIMDB。用低成本存儲(chǔ)承載全量數(shù)據(jù),僅將最熱的數(shù)據(jù)子集保留在內(nèi)存中,實(shí)現(xiàn)成本與性能的平衡。JIMKV與JIMDB均為KV接口,存量業(yè)務(wù)遷移或接入此架構(gòu)的改造成本很小。

二、JIMDB內(nèi)核新特性
1、大熱key的自動(dòng)識(shí)別與處置
大熱key的自動(dòng)識(shí)別與處置是JIMDB的一個(gè)自研特性。
1.1、背景
大熱key訪問是JIMDB線上故障的第一大根因。發(fā)生大熱key訪問的具體表現(xiàn)是:CPU資源耗盡,拒絕服務(wù);帶寬打滿,數(shù)據(jù)積壓在內(nèi)存導(dǎo)致容器OOM。
1.2、什么是大熱key
這里以超市收銀員的工作來做個(gè)類比。現(xiàn)在我們網(wǎng)上購物比較多,很久以前我經(jīng)常會(huì)去一家超市線下購物。這個(gè)超市只有一個(gè)收銀員,需要做商品掃碼、計(jì)算總額、把商品打包到購物袋的事情。這個(gè)收銀員非常熟練,即便在排隊(duì)的高峰期,我們也能在幾分鐘內(nèi)完成結(jié)賬。有一天我排了10分鐘,發(fā)現(xiàn)隊(duì)伍沒怎么動(dòng)。上前看了眼,每個(gè)人購物車?yán)锒际菨M滿的商品,據(jù)說是活動(dòng)打折。收銀員忙的不亦樂乎,估計(jì)最少還得等半小時(shí),我只好放下東西走了,默默走了。

和這個(gè)超市收銀員一樣,JIMDB是單線程處理請(qǐng)求。如果遇到一個(gè)幾千個(gè)元素大key訪問,JIMDB需要遍歷幾千次,序列化數(shù)據(jù),最后將這些元素拷貝到輸出緩沖區(qū)等待發(fā)送給客戶端,處理耗時(shí)長(zhǎng)。只是少數(shù)這樣的大key訪問,影響還好,一旦發(fā)生了熱點(diǎn)訪問,如同上面在超市排隊(duì)的場(chǎng)景,排在大key后面的請(qǐng)求長(zhǎng)時(shí)間得不到處理,最后全部超時(shí)。

回到大熱key的概念,其實(shí)就是大key發(fā)生熱點(diǎn)訪問。JIMDB提出了以資源影響為標(biāo)準(zhǔn)定義大熱key:一個(gè)針對(duì)特定Key的、與具體命令和參數(shù)強(qiáng)相關(guān)的操作,因其處理的數(shù)據(jù)量與執(zhí)行頻次的疊加效應(yīng),導(dǎo)致服務(wù)端CPU或網(wǎng)絡(luò)帶寬被耗盡的事件。
1.3、大熱key自動(dòng)識(shí)別
確立了以資源影響為核心的“大熱Key”定義后,JIMDB為此設(shè)計(jì)了一套精密的、多層次的識(shí)別策略。該引擎并非簡(jiǎn)單的閾值監(jiān)控,而是一個(gè)結(jié)合了實(shí)時(shí)計(jì)算、靜態(tài)預(yù)判和機(jī)器學(xué)習(xí)預(yù)測(cè)的智能感知系統(tǒng)。
為了最大限度地降低識(shí)別過程本身對(duì)服務(wù)性能的影響,JIMDB的識(shí)別引擎遵循一個(gè)“由簡(jiǎn)入繁、快速失敗”的瀑布式檢測(cè)原則。當(dāng)一個(gè)命令被執(zhí)行時(shí),服務(wù)端會(huì)依次進(jìn)行以下三個(gè)層面的檢查,一旦滿足任一條件,即判定為大熱Key并中止后續(xù)步驟:
?帶寬瓶頸檢測(cè): 響應(yīng)大小 * ops >= 網(wǎng)絡(luò)帶寬 * 70%。
?集合大小瓶頸檢測(cè): 若未觸及帶寬瓶頸,則對(duì)集合類型進(jìn)行靜態(tài)大小檢查。
?CPU算力瓶頸檢測(cè): 最后,對(duì)命令進(jìn)行基于預(yù)測(cè)模型的CPU負(fù)載分析。結(jié)合了元素個(gè)數(shù)、響應(yīng)大小和ops,利用線上性能數(shù)據(jù),使用機(jī)器學(xué)習(xí)的方法,來預(yù)測(cè)該請(qǐng)求在多少個(gè)ops下可能造成CPU瓶頸的。
1.4、大熱key自動(dòng)處置
在自動(dòng)識(shí)別到大熱key之后,JIMDB在內(nèi)核還提供了從服務(wù)端到客戶端、從自動(dòng)處理到人工干預(yù)多種應(yīng)急機(jī)制。
?服務(wù)端緩存:解決大熱key訪問CPU打滿的問題
識(shí)別到一個(gè)大熱key操作之后,響應(yīng)會(huì)被緩存到服務(wù)端,后續(xù)的請(qǐng)求不再去做遍歷元素、序列化、內(nèi)存拷貝的操作,直接使用服務(wù)端的大熱key緩存,緩解CPU壓力。

?客戶端緩存:解決大熱key訪問帶寬瓶頸問題
客戶端緩存解決了大熱key訪問帶寬瓶頸問題,并且可以保障保障客戶端緩存與服務(wù)端數(shù)據(jù)的一致性。每個(gè)大熱key緩存項(xiàng)都帶有一個(gè)版本號(hào)。一旦該key被修改,JIMDB會(huì)同步修改緩存項(xiàng)的value和version。
當(dāng)服務(wù)端發(fā)現(xiàn)大熱key訪問時(shí),會(huì)生成本地緩存,并通知客戶端??蛻舳嗽L問該key時(shí),服務(wù)端會(huì)返回該key的value和version??蛻舳讼麓卧L問會(huì)帶著key和version去詢問服務(wù)端。如果該key沒有修改,那么服務(wù)端只是返回該key無變更,不會(huì)返回value。如果該key被修改了,服務(wù)端返回value和新的version。在key很少變更的情況,服務(wù)端幾乎不用返回整個(gè)value。這樣既較少了帶寬占用,又保障了緩存數(shù)據(jù)的一致性。

?服務(wù)端熔斷
考慮到可能存在大熱key識(shí)別未覆蓋的情況,JIMDB服務(wù)端提供了key級(jí)別和命令級(jí)別的熔斷能力。在發(fā)生未被自動(dòng)識(shí)別到的大熱key風(fēng)險(xiǎn)時(shí),可以人工介入熔斷該風(fēng)險(xiǎn)訪問,避免影響同一個(gè)實(shí)例的其他請(qǐng)求。
目前這個(gè)版本已經(jīng)覆蓋了線上超60%的JIMDB集群,日均防御超千次大熱key風(fēng)險(xiǎn)訪問。該版本的集群在線上沒有發(fā)生過大熱key訪問導(dǎo)致的故障。
2、異步IO多線程
異步IO多線程是開源社區(qū)引入的新特性。
目前線上JIMDB內(nèi)核絕大多數(shù)是基于Redis2.8基礎(chǔ)二次開發(fā)的,這是一個(gè)單Reactor單線程的模型。

單線程里唯一一個(gè)epoll就是這里的Reactor,負(fù)責(zé)接受連接、網(wǎng)絡(luò)讀寫操作和命令處理的工作。單Reactor單線程的優(yōu)點(diǎn)是無鎖,命令執(zhí)行速度快。缺點(diǎn)也很明顯,性能上限受制于單核CPU。
通過火焰圖我們可以看出,單線程的JIMDB只有少部分CPU在處理命令,大多數(shù)CPU時(shí)間花費(fèi)在網(wǎng)絡(luò)IO上。

異步IO多線程就是來解決這個(gè)問題的,將原來的單Reactor單線程,變成了多Reactor主線程+IO多線程模式,將網(wǎng)絡(luò)讀寫的開銷從主線程卸載到IO線程。

主線程的epoll就是這里的mainReactor,負(fù)責(zé)連接的創(chuàng)建。創(chuàng)建好連接后,主線程會(huì)將連接分配給一IO線程,并注冊(cè)到IO線程的epoll上。每個(gè)IO線程的epoll就是subReactor,負(fù)責(zé)網(wǎng)絡(luò)數(shù)據(jù)讀寫。在多Reactor模型中,subReactor讀取到請(qǐng)求后,將其提交到線程池。線程池分配給一個(gè)worker線程處理命令的解析、執(zhí)行和結(jié)果序列化工作。這里的worker線程就是我們的主線程。每個(gè)IO線程將讀取到的數(shù)據(jù)異步通知主線程處理。這個(gè)主線程就是多Reactor模型的工作線程池。因?yàn)橹骶€程只有一個(gè),所以所有命令處理依然可以在無鎖的情況快速執(zhí)行,同時(shí)主線程也不再承擔(dān)網(wǎng)絡(luò)讀寫的開銷。

通過引入異步IO與多線程優(yōu)化技術(shù),JIMDB單實(shí)例在4核4線程配置下的OPS(每秒操作數(shù))性能較原單線程模式提升超過150%。這一改進(jìn)的核心價(jià)值在于通過提升CPU利用率實(shí)現(xiàn)資源節(jié)約:在單線程架構(gòu)下,每個(gè)實(shí)例無論分配多少內(nèi)存,僅能占用單核CPU算力,導(dǎo)致服務(wù)器整體CPU利用率長(zhǎng)期偏低。而單核處理能力存在物理上限,過去為應(yīng)對(duì)高并發(fā)讀流量,許多集群不得不部署多個(gè)副本分擔(dān)壓力。升級(jí)新版本后,單實(shí)例可充分調(diào)用多核計(jì)算資源,使得相同流量壓力下副本數(shù)量顯著減少,從而降低內(nèi)存資源占用總量。與此同時(shí),新版本通過利用閑置CPU核心,使服務(wù)器整體CPU利用率得到有效提升。
審核編輯 黃宇
-
京東
+關(guān)注
關(guān)注
2文章
1130瀏覽量
50142
發(fā)布評(píng)論請(qǐng)先 登錄
MIMX9302xxxxD不支持多核中間件嗎?
KeepAlive:組件緩存實(shí)現(xiàn)深度解析
以“網(wǎng)關(guān)中間件”實(shí)現(xiàn)充電樁OCPP 1.6安全配置文件無縫升級(jí)
C語言的緩沖區(qū)(緩存)詳解
蜂鳥E203內(nèi)核優(yōu)化方法
串口DMA發(fā)送有緩存嗎?
Redis緩存的經(jīng)典問題和解決方案
緩存之美:萬文詳解 Caffeine 實(shí)現(xiàn)原理(上)
本地緩存 Caffeine 中的時(shí)間輪(TimeWheel)是什么?
STM32U575VGT6在cubeMX中沒有FATFS中間件,是不支持嗎?
高性能緩存設(shè)計(jì):如何解決緩存偽共享問題
中科創(chuàng)達(dá)與ETAS推出預(yù)集成多域中間件解決方案
MCU緩存設(shè)計(jì)
Nginx緩存配置詳解
京東緩存中間件架構(gòu)與緩存內(nèi)核優(yōu)化
評(píng)論