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

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

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

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

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

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

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

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

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

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

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

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

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

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