架構師必備,了解分層架構中緩存那點事兒
無論是CDN緩存加速,還是CPU的三級緩存,又或者是在如今互聯網時代流量紅利所帶來的高并發結構客戶端,而不得不使用緩存架構。緩存,對于技術人來說,是一個必須直面的名詞。 然而,如何清晰明了的選擇緩存服務以及如何在設計架構時使用緩存去優化業務,對于我們很多人來說,一直以來都比較迷惑,本文從這一點出發,簡單介紹了緩存概念和分布式緩存服務的一些應用場景。

緩存的必要性
一般而言,互聯網的典型架構可以分為三層模式,客戶端層,站點層,數據層。而架構分層的本質是一個“數據移動”的過程,然后“被處理”和“被呈現”的過程。用戶請求從界面(瀏覽器或App界面)到網絡轉發、應用服務再到存儲(數據庫或文件系統),然后返回到界面呈現內容。

而隨著互聯網的普及與發展,伴隨而來的是內容信息類型日益復雜。同時,由于移動互聯網的流量紅利所帶來的用戶數和訪問量,更是造就了最高10億DAU的“微信神話”。
因此,近幾年爆炸式的互聯網發展也后端架構提出了新的挑戰——如何去平衡應用服務器和數據庫服務器成本和性能之間的矛盾。
資源往往是有限的,同時,關系型數據庫的讀寫能力也受限于磁盤,每秒能夠接收的請求次數也是有限的,如何能夠有效利用有限的資源來提供盡可能大的吞吐量?
引入緩存層,是實現資源的高效利用和降低用戶交互延時的不二法則。
緩存的影響因素和分類
2.1 介質因素
了解緩存在架構設計中的應用,首先我們來看下緩存的分類。最基礎的如CPU緩存,CPU緩存定義為CPU與內存之間的臨時數據交換器,為解決CPU運行處理速度與內存讀寫速度不匹配的矛盾而誕生,一般直接集成在CPU芯片上,這里就不展開細講了。另外就是本地緩存和分布式緩存,聊到這兩者時,我們先來了解下存儲介質。

從硬件介質角度而言,存儲介質廣義上可以分為內存和硬盤,其中內存(RAM)作為“指令中轉器”,只負責臨時性存儲。磁盤作為“外存”,可以持久化存儲。
• 內存:將緩存存儲于內存中是最快的選擇,無需額外的I/O開銷,但是內存的缺點是沒有持久化落地物理磁盤,一旦應用異常break down而重新啟動,數據很難或者無法復原。
• 硬盤:一般來說,很多緩存框架會結合使用內存和硬盤,在內存分配空間滿了或是在異常的情況下,可以被動或主動的將內存空間數據持久化到硬盤中,達到釋放空間或備份數據的目的。
由于馮諾依曼式自身模型原因,就數據傳輸速度而言,CPU緩存 > 內存 > 硬盤。

上圖是一個典型數據“被處理”過程,而我們常說的存儲,依托于硬盤介質,而緩存,更多是需要內存 + 硬盤結合。
2.2 緩存分類
了解了基本的存儲介質知識后,我們接下來認識緩存分類,根據應用架構中的耦合度,分為local cache(本地緩存)和 remote cache(分布式緩存)。
- 本地緩存:也叫進程內緩存,顧名思義,指應用中的緩存組件,優點是應用和緩存在同一進程內部,進程內緩存省去了網絡開銷,所以一來節省了內網帶寬,二來響應時延會更低。缺點就是多個應用無法共享緩存,且難以保持進程緩存的一致性。
- 分布式緩存:也叫進程外緩存,指的是與應用分離的緩存組件或服務,其最大的優點是自身就是一個獨立的應用,與本地應用隔離,多個應用可直接的共享緩存。如我們常見的memcache和Redis數據庫。
而在分層架構設計中,有一條準則:即站點層、服務層需達到無狀態無數據。
其目的是為了當業務需要時,能夠任意的增加節點水平擴展。所以數據和狀態盡量存儲到后端的數據存儲服務,例如數據庫服務或者緩存服務。當然,如果業務處于“極其高并發且業務一定程度允許不一致”的場景,也可以考慮使用本地緩存,其它一般不推薦使用。
主流分布式緩存分析
在對比之前,我們先來了解下分布式緩存數據庫在分層架構中的位置,這樣有助于我們明確的認識到緩存所起到的作用。

見上圖,按照經典互聯網架構三層模式,簡單畫出了站點層和數據層的交互邏輯。加入了緩存服務后,這里也定義它為緩存服務層,其處于站點層和數據層的中間,同時依賴于兩者提供雙向的“數據移動”。既然如此,當我們想要加入分布式緩存服務時,那么圖中緩存服務層中的Redis和memcache兩者又該如何去選擇呢?
3.1 使用率分析
Redis和memcache都是互聯網分層架構中,最常用的KV緩存服務。盡管memcache首發(2003年)比Redis首發(2009年)早的多,兩者也都是使用C語言編寫,但是當Redis一經發布,迅速就成為了架構師手中設計分層架構時的優先選擇。

這里只找到一張截止到17年時的使用率對比分析,不難看出Redis使用率一直呈現上升趨勢,到目前更是遠遠的甩下了memcahce。
3.2 功能分析
在對比前,先來了解Redis和memcahce數據庫分別到底是什么以及它們的基本概念。
- Redis:一個開源的、Key-Value型、基于內存運行并支持持久化的NoSQL數據庫;
- memcached:一款完全開源、高性能的、分布式的內存系統;
關鍵詞:內存、持久化。
其實關鍵詞已經為我們涵蓋了Redis和memcahce兩者的核心作用。Redis的持久化+緩存,memcache的緩存。如果把兩者比如成學生,那么“memcache”就像是一名特長生,專項發展。而“Redis”則是一名三好學生,“德智育”全面發展。
接下來我們從不同維度詳細分析下Redis和memcahce數據庫兩者的區別,以便于大家能夠更好的區別并選擇適合自己的緩存數據庫。

一表勝千言,這是來自“特長生”和“三好學生”的較量。根據上圖,下面我們來分析下兩者在什么場景下更加適用。
3.3 應用場景分析
3.3.1 什么時候傾向于適用Redis?
業務需求決定技術選型,當業務有這樣一些特點的時候,選擇Redis會更加適合。
a 存在復雜數據結構
Redis支持5種存儲類型,包含字符串、哈希、列表、集合、有序集合等,而Menmcache只支持KV。
假設當緩存數據類型比較復雜時,推薦使用Redis,這種場景多見于用戶訂單列表,用戶消息,帖子評論列表等。

b 當需要考慮緩存持久化時
Redis支持固化功能,當數據庫崩潰后重啟,內存可以迅速的恢復熱數據。無需主動或被動的預熱,減少因Redis瞬間壓力過大導致的后端數據庫雪崩風險。 Redis的固化模式分為兩種模式,一種是RDB快照模式,另外一種是AOF持久化模式。兩者的用途不同,請看下圖。

這里需要注意的是,RDB定期快照不能保證萬無一失,且AOF會降低Redis的效率。 同時,也別看著Redis有持久化功能,就跟打了雞血一樣想省下Mysql數據庫的錢,記住,讓專業的工具做專業的事情。
ps:如果是云數據庫Redis(阿里云、七牛云)是默認開啟固化的,所以是內存+硬盤形式。
c 當需要高可用時
Redis天然支持集群功能,可以實現主動復制,讀寫分離。Redis在擴展和穩定高可用性能方面都是比較成熟的。

Redis官方也提供了sentinel集群管理工具,能夠實現主從服務監控,故障自動轉移,最重要的是,這些對于客戶端都是透明的,無需程序改動,也無需人工介入。
而Memcache本身并不支持集群,所有的集群形式都是通過客戶端實現。要想要實現高可用,需要進行二次開發,需要例如客戶端的雙讀雙寫或者服務端的集群同步等。
如果業務當有緩存高可用場景需求時,那么使用Redis比memcahce簡便的多。例如在即時通訊業務中,用戶的在線狀態,就有高可用需求。
d 當Vlaue值很大時
前文也說了,Redis和Memcache都是以KV形式存儲,那么除了數據類型因素,選擇Redis,還有什么因素影響呢?
答案是Value值的大小。

在Redis官網的文檔中,我們可以查閱到,Redis支持多種復雜數據結構,也因此,支持Key和Value值大小最大可以到512M。而Memcache的key和Value值大小都被限制在1M以內。

所以,當我們如果有key-value值非常大的緩存服務應用場景時,那么也只能使用Redis了。
3.3.2 什么時候傾向于適用Memcache?
說了這么多關于Redis的好,甚至有種memcahe就是Redis子集的錯覺,而memcache有的功能,似乎Redis都有了。非也,作為“特長生”,當你面臨以下場景時,那么選擇memcache緩存服務,比Redis可能更好一些。
a 數據量大,并發量大的業務
這里的前提是緩存數據類型支持,即純KV場景。如果業務存在數據量大,并發量大的需求,那么使用memcache或許更適合。 這個也和memcache的底層實現原理有關。

如上圖,當在內存分配、線程模型和網絡模型維度考慮時,如果當你的業務符合是數據量大,并發量大的緩存業務場景時,使用memcache比redis能達到訪問更快,同時,延時更低。這個時候,選擇memcache就再恰當不過了。
探討
4.1 保持緩存一致性的方式
前面我們已經分析了Redis和memcache的功能對比以及其衍生出來的場景描述,最后千言萬語不如一句話:業務需求決定技術選型。選擇適合業務的緩存服務最為重要。
既然是緩存服務,我們都知道,用戶訪問到時,站點層先看緩存服務層是否能hit數據,如果miss,則會到后端數據庫拿到數據再原路返回給用戶,同時緩存服務層set。

假設,當緩存服務層存在數據,但是這時候,剛好用戶也在發送寫請求,那么這個用戶hit,則會返回舊數據。出現這種情況,歸根結底還是因為數據庫和緩存主從延時導致。 如何保持緩存一致性,這是個值得深思的問題。也引申出了當用戶發出寫請求時,應該先寫緩存還是數據庫這個疑問。 Cache Aside Pattern:簡稱旁路緩存方案。基本原理就是數據庫有主數據庫(用于寫)、從數據庫(用于讀),另有緩存用于提升讀寫效率;
- 讀請求:標準的用戶訪問模式。站點層-緩存服務層-數據庫層
- 寫請求:先寫主數據庫,再淘汰緩存。

而目前,主流如微軟、臉書等公司都是使用都是Cache-Aside pattern(旁路緩存方案),針對寫請求,即先寫數據庫,然后再淘汰緩存。如果先操作緩存,在讀寫并發時,可能出現數據不一致情況(數據庫主從未同步中的間隔時間)。
這種旁路緩存方案,也是為了保障最終數據庫是正確的,而對于緩存的不一致,有限時間內的不一致是允許的(參考CAP原則和Base理論)。當然,這里也有一個隱藏的坑點,假設當寫入數據庫已經成功的,但是之后淘汰緩存失敗了,針對這種情況,這里也提供一個簡單的思路。

流程如下圖所示:
(1)更新數據庫數據
(2)數據庫會將操作信息寫入binlog日志當中
(3)訂閱程序(DTS或者cannal)提取出所需要的數據以及key
(4)另起一段非業務代碼,獲得該信息
(5)嘗試刪除緩存操作,發現刪除失敗
(6)將這些信息發送至消息隊列
(7)重新從消息隊列中獲得該數據,重試操作。
4.2 使用緩存服務的幾點誤區
a 使用緩存,不考慮雪崩
我們先來認識下什么是緩存雪崩。
- 緩存雪崩:當緩存服務器重啟或者大量緩存集中在某一個時間段失效,這樣在失效的時間段內,站點層會給后端系統(比如DB)帶來很大壓力。甚至直接壓垮數據庫,直接導致系統整體不可用。一般來說,在分層架構中,緩存服務最高能幫數據庫層抗住90%的壓力,如果當緩存數據庫出現崩潰時,如果事先未做好規劃,將直接導致雪崩。

為了預防上述情況,首先要做好容量預估,同時,使用采用高可用緩存集群,最好災備方案,當一個緩存服務器服務掛掉時,能夠做到自動切換服務。
ps:這也是為啥云數據庫受歡迎的原因,簡單,省心。
b 將緩存服務層當做傳遞數據媒介
簡單來說,將緩存服務層當做MQ(消息隊列)使用,通過緩存傳遞數據,從而實現兩個服務通信的目的,如下圖。

先不說專業工具做專業的事情,就一點,如果使用緩存傳遞數據的話,會直接導致服務耦合。 而MQ,作為互聯網架構解耦神器,天然支持集群高可用,而且支持數據落存儲。
ps:使用MQ后,上游不知道彼此存在,也不需要關注哪些下游訂閱了消息,這樣直接達到服務解耦的效果。
參考文獻
1、緩存那些事---美團技術團隊
2、緩存架構設計,從此不再發愁---58沈劍
3、分布式之數據庫和緩存雙寫一致性方案解析--孤獨煙




























