快取作為磁碟以外的一種儲存資料的方式,它有著比磁碟更快的存取效率,因此,可以有效提高系統的效能。在單體系統中,一般會用到本地快取。但在分散式系統中,本地快取就顯得不夠用了,這時往往要用到分散式快取。
本地快取因為就在應用系統程序的記憶體裡面,不需要網路和物件拷貝的開銷所以效能非常高,不過也正因為資料儲存在應用系統程序的記憶體中也有了一些侷限。例如,多個獨立應用系統之間難於共用快取、快取服務不利於擴充套件等。為了解決這些問題,以Jboss Cache、Memcache、Redis為代表的分散式快取也開始出現,它們以不同的方式實現了分散式快取的特性。
1、快取共用
分散式系統是多個單體系統的集合,分散式快取可以讓這些單體系統共用快取資料。分散式快取實現快取共用一般有兩種方式,分別是同步資料和獨立部署。
同步資料
這種方式的實現原理是,快取存在於單體系統應用程序的記憶體中(和本地快取一樣),當一個單體系統的快取發生變化時,把變化的內容同步到其他單體系統,從而達到快取共用的效果。這也就是Jboss Cache的思想,這種方式的確解決了共用的問題,但是這種快取同步的操作如果太多那麼本身就會成為一個很大的效能問題。
獨立部署
這種方式是把快取服務作為一個元件單獨部署,所有的單體系統都可以通過網路請求從快取伺服器存取資料。MemCache和Redis就是這種方式,不過這種方式會有額外的網路開銷,相比於直接在本地就能獲取的資料,自然會有額外的效能開銷。
2、快取擴充套件
當快取資料量規模很大時,單臺伺服器的記憶體是無法容納的,那麼此時就需要支援快取的節點擴充套件。以獨立部署方式實現快取共用的分散式快取服務因為是獨立部署,因此,更有利於做擴充套件。一般會按照一定的演演算法,把快取的資料分配到多個節點。
3、支援高可用
分散式快取一般應用於分散式系統,分散式系統之所以要採用分散式架構,往往是因為系統要應對高並行的場景,而高並行場景又容易造成伺服器負載過重從而導致崩潰。因此,分散式快取作為分散式架構的重要元件,必須要支援高可用。當一個快取服務節點掛掉,可以馬上切換到另外的快取服務節點,以保證系統能正常執行。
問題一:快取一致性問題
快取的資料來源於資料庫,可能會出現資料庫資料跟快取資料不一致的情況,這就是快取一致性的問題。快取一致性問題主要原因是在於修改資料庫的同時還需要把最新的資料更新到快取中去,但是這個過程出現一些問題從而導致快取資料與資料庫的資料不一致。
譬如A、B兩個請求先後修改一個資料a。正常情況下,A修改資料庫-->A修改快取-->B修改資料-->B修改快取。這時候沒有問題,如下圖:
但是,在並行的情況下(A、B兩個請求同時發起)很有可能產生如下情況,A修改資料庫-->B修改資料庫-->B修改快取-->A修改快取。這時候資料庫a=3,而快取a=2,快取資料與資料庫的資料不一致,如下圖:
解決方案一:先修改資料庫、再刪除快取。
這種策略也稱為Cache Aside(旁路快取),其核心在於:
修改資料時,先修改資料庫,然後不對快取進行修改而是直接刪除快取。
讀取資料時,先從快取讀取。如果快取沒有則從資料庫讀取資料,最後把讀取到的資料寫入到快取。
只要保證資料變更時快取一定會刪除,那麼查詢快取的時候每次都會從資料庫載入資料,所以也就避免了修改快取資料導致的不一致問題,如下圖:
解決方案二:資料標識版本號(樂觀鎖)
方案一其實只是一定程度上解決了一致性問題。為什麼這麼說呢?如下圖:
按照上面的步驟,資料庫最終結果是a=3,但快取最終結果是a=2。因此,旁路快取這種策略並沒有完全解決問題。另一個解決方案,可以給資料標識版本號,也就是樂觀鎖。在設計資料表的時候,就應該設定一個記錄資料版本號的欄位,版本號欄位型別可以是整型,版本號越大代表資料越新。快取資料的時候,也把版本號一起快取。這樣,並行修改資料的過程,如下圖:
當寫入快取的資料版本比當前快取的資料版本要低階,就放棄寫入。這個方案在快取過期情況下同樣適用,如下圖:
如果上圖中Step6寫入快取失敗,那麼Step7就可以把a=2寫進快取,這樣也會產生資料不一致。這時候,就要引入訊息佇列。把寫入快取失敗的請求放進訊息佇列,然後有一個執行緒在消費訊息,完成寫入快取的工作,當然寫入快取時要先校驗資料版本。
問題二:快取擊穿問題
快取擊穿就是在某一熱點資料快取失效後,查詢資料的請求不得不進入到資料庫查詢資料,這個過程中如果請求並行量非常大,有可能資料庫負載過重而崩潰。
問題三:快取穿透問題
通常情況,一個請求查詢資料,會先從快取查詢,如果在快取裡找不到,就到資料庫查詢。而快取穿透就是請求系統里根本不存在的資料,導致請求總是進入到資料庫。如果大量的請求同時進入到資料庫,就有可能導致資料庫崩潰。譬如,我們通過文章的id來查詢文章資訊,但是請求的id在資料庫根本不存在。
問題四:快取雪崩問題
快取雪崩是指快取資料某一時刻出現大量失效或者快取服務不可用的情況,導致大量請求進入到資料庫,可能導致資料庫崩潰。
問題五:熱key問題
如果某個key被大量存取,就會導致某個快取服務節點流量暴增,等存取超出單節點負載,就可能會出現單點故障,單點故障後轉移該key的資料到其他節點,單點問題依舊存在,則可能繼續會讓被轉移到的節點也出現故障,最終影響整個快取服務叢集。
目前比較常用的分散式快取中介軟體有Redis、MemCache,因此,在這裡對這兩者做了一個對比。對於這兩者,各有各的特點,因此,不一定要二選一,可以混合使用。
對比項 | Redis | Memcached |
---|---|---|
高可用 | 支援主從節點複製設定,從節點可使用RDB和快取的AOF命令進行同步和恢復;支援Sentinel和Cluster(從3.0版本開始)等高可用叢集方案 | memcached伺服器互不通訊,分散式部署取決於memcached使用者端,需要二次開發 |
佇列 | 支援lpush/brpop、publish/subscribe/psubscribe等佇列和訂閱模式 | 不支援佇列,可通過第三方MemcachQ來實現 |
適用場景 | 複雜的資料結構,有持久化、高可用需求 | 只需key-value資料結構,數量量非常大,並行量非常大的業務 |
過期策略 | 主動過期 +惰性過期 | 懶淘汰機制,每次往快取放入資料的時候,都會存一個時間,在讀取的時候要和設定的時間做 TTL 比較來判斷是否過期。 |
虛擬記憶體使用 | 有自己的VM機制,理論上能夠儲存比實體記憶體更多的資料,當資料超量時,為引發swap,把冷資料刷到磁碟上 | 所有的資料儲存在實體記憶體裡 |
網路模型 | 非阻塞I/O模型 ,提供一些非KV儲存之外的排序,集合功能,在執行這些功能時,複雜的CPU計算會阻塞整個I/O排程 | 非阻塞I/O模型 ,但是使用了多執行緒,不會出現一個邏輯複雜的請求阻塞對其它請求的響應的場景 |
資料結構 | key-value,雜湊,列表,集合,有序集合 | 純key-value |
持久化 | 有,RDB和AOF | 無 |
儲存value容量 | 最大512M | 最大1M |
多執行緒 | 支援單執行緒 | 支援多執行緒,CPU利用方面優於Redis |
單機QPS | 約10W | 約60W |