1. 程式人生 > >分散式快取架構基礎

分散式快取架構基礎

一、快取概述

1.1 什麼是快取

     快取在wiki上的定義:用於儲存資料的硬體或軟體的組成部分,以使得後續更快訪問相應的資料。快取中的資料可能是提前計算好的結果、資料的副本等。典型的應用場景:有cpu cache, 磁碟cache等。本文中提及到快取主要是指網際網路應用中所使用的快取元件。

1.2 為什麼引入快取

    傳統的後端業務場景中,訪問量以及對響應時間的要求均不高,通常只使用DB即可滿足要求。這種架構簡單,便於快速部署,很多網站發展初期均考慮使用這種架構。但是隨著訪問量的上升,以及對響應時間的要求提升,單DB無法再滿足要求。這時候通常會考慮DB拆分(sharding)、讀寫分離、甚至硬體升級(SSD)等以滿足新的業務需求。但是這種方式仍然會面臨很多問題,主要體現在:

  •  效能提升有限,很難達到數量級上的提升,尤其在網際網路業務場景下,隨著網站的發展,訪問量經常會面臨十倍、百倍的上漲。
  •  成本高昂,為了承載N倍的訪問量,通常需要N倍的機器,這個代價難以接受。

  單純從DB層進行優化已經無法滿足需求,那是否有更好的方式來解決這個問題呢?首先我們來看圖1的資料,  

圖1 不同操作的耗時

​​    ​從資料上很容易看出,記憶體的訪問效能明顯優於磁碟。把資料放入記憶體中,可以提供更快的讀取效率。但在網際網路業務的場景下,將所有資料資料都裝入記憶體,顯然是不明智的。從圖2的金字塔模型可以看到,從機械硬碟,到SSD硬碟,再到記憶體,速度越來越快,價格越來越貴,單位容量的價格也越來越高。所以,不可能把資料全部裝入記憶體。 

圖2 不同儲存介質效能、容量、價格的對比

​    同時大部分的業務場景下,80%的訪問量都集中在20%的熱資料上(適用二八原則)。因此,通過引入快取元件,將高頻訪問的資料,放入快取中,可以大大提高系統整體的承載能力,原有單層DB的資料儲存結構,也變為Cache+DB的結構,如圖3。 

圖3 Cahce+DB的架構模型

​    在資料層引入快取,有以下幾個好處:

  • 提升資料讀取速度
  • 提升系統擴充套件能力,通過擴充套件快取,提升系統承載能力
  • 降低儲存成本,Cache+DB的方式可以承擔原有需要多臺DB才能承擔的請求量,節省機器成本

    根據業務場景,通常快取有以下幾種使用方式

  • 懶漢式(讀時觸發):寫入DB後, 然後把相關的資料也寫入Cache
  • 飢餓式(寫時觸發):先查詢DB裡的資料, 然後把相關的資料寫入Cache
  • 定期重新整理:適合週期性的跑資料的任務,或者列表型的資料,而且不要求絕對實時性
iiiiiiiiii

​二、常用快取介紹

2.1 快取分類

    快取大致可以分為兩類,一種是應用內快取,比如Map(簡單的資料結構),以及EH Cache(Java第三方庫),另一種就是快取元件,比如Memached,Redis。

2.2 Memcached簡介

2.2.1 概述

Memcached是開源的,高效能的,可分散式部署,用於網站提速,減輕資料庫負載的快取元件,有如下特點:

  • 高效能Key-Value儲存 
  • 協議簡單:簡單文字協議、二進位制協議
  • 支援資料過期
  • LRU剔除演算法
  • 多執行緒
  • slab記憶體管理
  • 客戶端實現分散式       

2.2.2 記憶體管理

    Memcached使用Slab Allocator的機制來實現分配、管理記憶體,按照預先規定的大小,將分配的記憶體分割成特定長度的塊,以此來解決記憶體碎片問題,如圖4: 

圖4 Slab Allocator的機制

​slab是特定大小的chunk的組,並根據增長因子(factor),劃分成不同slab class。分配記憶體時,每次分配一個page(預設1MB)給某個slab,並根據slab的大小,切分成多個chunk,資料儲存在chunk中。Memcached根據收到的資料的大小,選擇最適合資料大小的slab,如圖5。 

圖5 slab class選取

​    Memcached的Slab Allocator記憶體分配方式會存在記憶體浪費的問題,主要有以下幾種情況:

  • chunk浪費: 快取資料沒有填充滿chunk
  • page浪費: 一個page的容量不能被slab的大小整除。
  • slab浪費: 某個slab的記憶體沒有完全被利用,只儲存了少量資料,卻佔用一個page。

2.2.3 剔除演算法

    Memcached採用LRU淘汰演算法,在容量滿的時候進行資料剔除。不過該淘汰演算法只在Slab內部進行,也就是說,某個Slab容量已滿時,只會在該Slab內部進行資料剔除,而不會影響其它Slab。這種區域性剔除的策略也帶來了一個問題:Slab鈣化。

2.2.4 Slab鈣化

某個Memcached例項的Slab狀態如下:

​如果此時,業務資料大小出現變更,比如超過152位元組。就會出現以下問題:

​​    新的資料(需要使用chunk size為192),只能最多使用500M,而原有slab class(120和152)沒釋放,儘管資料都已過期,因為淘汰策略是淘汰相同slab class的資料,所以一直利用不上120和152的記憶體,這種情況會導致快取命中率急劇下降。

如果發生這種情況,有以下幾種解決方案:

  • 重啟Memcached例項

    簡單粗暴,需要避免單點問題,避免出現雪崩

  • 隨機過期

     過期淘汰策略也支援淘汰其他slab class的資料,twitter和facebook等均作了類似支援 

  • 通過slab_reassign、slab_authmove

    官方1.4.11版開始支援此功能

2.3 Redis簡介

Redis是開源的,高效能的,支援分散式,支援多資料結構的快取元件。特點如下

  • 高效能Key-Value儲存
  • 豐富的資料結構:string、list、hash、set、zset、hypeloglog
  • 支援資料過期:主動過期+惰性過期
  • 支援多種LRU策略:volatile-lru、volatile-ttl 等
  • 記憶體管理:tcmaloc、jemalloc
  • 記憶體儲存+磁碟持久化: rdb、aof
  • 支援主從複製
  • 單執行緒

    微博使用的是內部定製的redis版本,支援熱升級、基於aof的增量複製、新資料型別longset和bloomfilter等特性。但在微博的場景下,redis更多被用儲存和計數器場景,快取主要以memcached為主,因此這裡不再過多介紹redis。

iiiiiiiiii

三、分散式快取實現

3.1 概述

    構建大型網際網路系統會面臨很多的挑戰,主要有: 

  • 百萬級QPS的資源呼叫 (高併發)
  • 99.99%的可用性 (高可用)
  • 毫秒級的核心請求響應時間 (高效能)

    設計這樣的網際網路系統,不可避免的要考慮使用分散式快取,並從可用性、併發性、效能多個方面進行綜合考量。

3.2 分散式快取的實現方式

3.2.1 資料分片

資料分片就是把資料均勻分散到多個例項中。資料分片可以採用以下幾種規則:區間分片、hash分片、 slot分片。對於hash分片,主要的雜湊演算法有靜態雜湊和一致性雜湊,靜態雜湊和一致性雜湊對比如下:

  • 靜態雜湊(取模求餘)

    優點:演算法簡單  

    缺點:加減節點時震盪厲害, 命中率下降厲害

  • 一致性雜湊

    優點:加減節點時震盪較小, 保持較高命中率

    缺點:自動rehash場景下會資料不一致的問題(同一份資料的請求在不同節點漂移)

    資料分片的實現方式分為三種:

  • 客戶端實現:memcached,redis 2.x

客戶端實現資料分片

    優點:簡單,容易實現

     缺點:擴縮容需要重新上線,手動資料遷移;

  • proxy實現:twemproxy,codis,微博內部實現了CacheService

    通過引入一層代理,將資料分片策略放在代理層實現,客戶端通過代理來訪問資料。

    優點:邏輯在proxy實現,客戶端使用簡單,支援多語言

    缺點:資料訪問多一跳,有一定的效能損耗

  • 服務端實現:redis 3.x,cassandra

    由快取元件本身,實現資料分片機制。

    優點:擴縮容方便,自動資料遷移

    缺點:資料儲存與分散式邏輯耦合在一起,服務端複雜

3.2.2 可用性

    線上使用過程中,如果出現某些快取例項不可用,大量請求穿透會給DB帶來巨大的壓力,極端情況會導致雪崩場景,這需要有更好的方式保證快取的高可用。於是我們採用用主從(Master/Slave)的架構,如圖6。也就是在原有單層快取的結構下,增加一層Slave,來保證即使某個Master節點宕機,整個快取層依然是可用的,不會出現大量請求穿透DB的情況。

圖6 master/slave快取模型

​​

3.2.3 擴充套件性

主從結構部署已經能很好的滿足大多數業務場景,但是在微博這種存在突發熱點引起流量驟增的業務場景下仍然存在一定的問題,這種方式並不能很方便的進行橫向擴充套件,如果直接在原有的快取中增加新的節點,就需要涉及到資料遷移等工作。為了解決橫向擴充套件的問題,增加了L1 Cache,實現了多級快取,L1 Cache的容量一般小於Master容量,也就是說L1 Cache中的資料熱度要更高;同時L1 Cache可以有多組,需要橫向擴充套件的時候,只需要成組擴容L1 Cache即可,如圖7。 

圖7 L1 cache

    在L1 Cache這種結構下,Master/Slave的訪問量會小很多,會出現Master/Slave資料變冷的情況,為了改善這種情況,我們把Master/Slave在邏輯上也作為L1 Cache的一組,這樣就保證了Master/Slave的熱度。如圖8,是把Master作為L1的一組。 

圖8 master作為L1 cache

​​​

iiiiiiiiii

四、快取設計實踐

4.1 Memached Multiget-Hole(multigut黑洞)

    在Memcached採用資料分片方式部署的情況下,對於multiget命令來說,部署部署更多的節點,並不能提升multiget的承載量,甚至出現節點數越多,multiget的效率反而會降低,這就是multiget黑洞。這是由於執行multiget命令時,會對每一個節點進行訪問,通常SLA取決於最慢最壞的節點,而且節點數增多,出問題的概率也增大,客戶端處理的壓力也會增大。通常在資料分片時,我們推薦4~8個節點左右。解決multiget黑洞有兩種方式可供參考:

  • 使用多副本的方式擴容,增加multiget的承載量
  • 通過業務層面來控制,multiget的keys儘可能放在同一個節點上,但具體實施時較難操作,可行性不是很高。

4.2 反向Cache

    反向Cache就是將一個不存在的key放在快取中,也就是在快取中存一個空值。在某些場景下,比如微博維度的計數場景,若採用cache+DB的儲存方式,由於大多數的微博並不存在轉發、評論計數,這種場景下,就會出現由於大量訪問不存在計數的mid,導致DB壓力居高不下的情況。通過在cache中存一個null值,可減少對DB的穿透。當然這也存在潛在的風險或問題:

  • 如果每次都是不同的mid,快取效果可能不明顯
  • 需要更多的快取容量

4.3 快取Fail-Fast (快速失敗)

    當快取層某個節點出現故障時,會導致請求持續穿透到儲存層,使請求響應時間長(需要等到讀寫故障快取節點超時),並且儲存層負載居高不下。這就需要在使用快取時考慮快速失敗機制。快速失敗指的是:當出現故障節點時,標識故障節點為不可用節點(策略舉例:連續N次請求都出現超時,標識M時間段內為不可用),讀寫不可用節點快速返回。通過快速失敗策略,解決請求響應時間長問題,保證SLA。

4.4 快取無過期(Cache is Storage)

    快取無過期是指快取中儲存全量資料,不存在資料穿透的情況。 相比於快取+DB的訪問模型,使用記憶體儲存簡單可靠,但相應的記憶體成本也較高。選擇記憶體快取還是記憶體儲存,需要結合具體的業務場景做權衡,比如單純為解決Dog-Pile Effect而採用記憶體儲存的話,記憶體成本可能就無法接受。通常情況下,記憶體儲存模式,適合總體資料量很小,但是訪問量巨大的業務場景,比如微博應用(來自weibo.com,weico等)列表。

4.5 dog-pile effect (狗樁效應)

    狗樁效應是由於極熱訪問的快取資料失效,大量請求發現沒有快取,進而穿透至DB,導致資料庫load瞬間飆高甚至宕機。這是一個典型的併發訪問穿透問題,理想情況下快取失效對資料庫應該只有一次穿透。要解決這個問題,首先從程式碼層面就要考慮到併發穿透的情況,保證一個程序只有一次穿透;同時,可以考慮使用基於mc的分散式鎖來控制。不過使用分散式鎖來實現會較為繁瑣,通常在程式碼層面進行控制,就可以得到很好的效果。

4.6 極熱點資料場景

    微博在遇到一些突發事件時(如文章事件),流量會出現爆發式的增長,大量的熱點數集中訪問,導致某個快取資源遇到效能瓶頸(比如明星的資料所在的埠),最終介面響應變慢影響正常的服務。為了應對這個問題我們在前端使用local cache, 以緩解後端快取的壓力。但是有些業務場景下,由於各種海量業務資料的沖刷,前端使用 local cache,命中率可能不高,效能提升也不明顯,這種業務場景下可以考慮引入L1結構,通過部署多組小容量的L1快取來應對突然的訪問量增長。

4. 7避免雪崩

     雪崩效應是由於快取伺服器宕機等原因導致命中率降低,大量的請求穿透到資料庫,導致資料庫被沖垮,業務系統出現故障,服務很難再短時間內回覆。避免雪崩主要從以下幾方面考慮:

  • 快取高可用

    避免單點故障,保證快取高命中率

  • 降級和流控

    故障期間通過降級非核心功能來保證核心功能可用性

    故障期間通過拒掉部分請求保證有部分請求還能正常響應        

  • 清楚後端資源容量

    更好的預知風險點,提前做好準備

    即使出現問題,也便於更好的流控(具體應該放量多少)

4.8 資料一致性

    我們知道,在CAP理論下,只能取其二,而無法保證全部。在分散式快取中,通常要保證可用性(A)和可擴充套件性(P),並折中採用資料最終一致性,最終一致性包括:

  • Master與副本一致性
  • Cache與Storage一致性
  • 業務各維度快取資料一致性

4.9 快取容量規劃

    進行快取容量規劃時,主要從以下幾個方面進行考慮:

  • 請求量
  • 命中率:預熱,防止雪崩
  • 網路頻寬:網絡卡、交換機
  •  儲存容量:預估儲存大小,過期策略、剔除率
  • 連線數
iiiiiiiiii

五、微博快取中介軟體CacheService

    隨著微博業務資料的增長,快取叢集的規模也越來越大,直接使用裸快取資源的方式也逐漸暴露出來一些問題:

  • 快取資源變更復雜度高

節點變更、擴縮容需要業務方變更配置重新上線,而且資源變更過程中業務方密切關注快取的服務狀況。

  • 高可用策略無法複用

微博平臺使用java語言開發,java client定製了保證快取高可用性的多級快取訪問策略,公司其它使用非java語言(php、go)的部門無法使用。

  • 運維複雜度高

    缺少簡單友好的統一運維管理平臺負責快取資源的申請、分配、部署、變更、回收等操作。運維操作沒有實現全介面化,且自動化程度不高。

  • 缺少SLA指標保證

    快取資源異常導致業務出現問題時,缺少業務快取SLA指標的監控及處理,更多的是依賴使用者投訴後執行後續的運維處理,整個過程週期耗時較長,對業務影響較大。

    為了應對以上問題,2013年底開始推進快取服務化。設計了內部快取服務化體系CacheService, 目前已在微博多個業務場景中使用。整體架構設計如圖9:

圖9 cacheservice架構設計

​    整體框架包括以下幾個元件:

代理層

    代理業務端的請求,並基於設定的路由規則轉發到後端的 cache 資源,它本身是無狀態的。支援配置服務化、多級cache訪問策略、叢集間資料複製、S4LRU等特性。

資源層

    實際的資料儲存引擎,初期支援 memcached,後續又擴充套件了 Redis、SSDCache 元件,其中 SSDCache 是為了降低服務成本,內部開發的基於 SSD 的儲存元件,用於快取介於 memory 和 DB 之間的 warm 資料。

客戶端

    業務只需要簡單配置所使用的服務池名group和業務標識namespace即可使用

java業務方:通過配置中心獲取可訪問的proxy節點 

    php業務方:本地proxy節點 or DNS 

配置中心 ConfigServer

    微博內部的配置服務中心,主要是管理靜態配置和動態命名服務的一個遠端服務,並能夠在配置發生變更的時候實時通知監聽的 ConfigClient。

叢集管理系統 ClusterManager

管理快取的整個生命週期,包括快取資源的申請、上線、變更、下線等;管理叢集中所有元件的執行狀態及業務的SLA指標,出現異常時自動觸發運維操作。

    快取服務化的道路還是很長,未來還需要進一步的對CacheService服務化框架及其相關元件持續優化;需要逐步完善服務化體系中冷熱資料分級儲存機制降低服務成本;需要進一步完善叢集管理元件降低運維複雜度等。