聊聊本地快取和分散式快取
來源:勇哥java實戰分享
快取,訊息佇列,分庫分表是高併發解決方案三劍客。
快取之所以能夠讓系統“更快”,本質上做到了如下兩點:
減小 CPU 消耗
將原來需要實時計算的內容提前算好、把一些公用的資料進行復用,這可以減少 CPU 消耗,從而提升響應效能。
減小 I/O 消耗
將原來對網路、磁碟等較慢介質的讀寫訪問變為對記憶體等較快介質的訪問,從而提升響應效能。
對於應用系統來講,我們經常將快取劃分為本地快取和分散式快取。
本地快取 :應用中的快取元件,快取元件和應用在同一程式中,快取的讀寫非常快,沒有網路開銷。但各應用或叢集的各節點都需要維護自己的單獨快取,無法共享快取。
分散式快取:和應用分離的快取元件或服務,與本地應用隔離,多個應用可直接共享快取。
這篇文章,聊聊本地快取和分散式快取,希望大家讀完之後,在面對不同的業務場景時,能夠做出合理的快取選型。
1 本地快取 JDK Map
JDK Map 經常用於快取實現:
HashMap
HashMap 是一種基於雜湊表的集合類,它提供了快速的插入、查詢和刪除操作。可以將鍵值對作為快取項的儲存方式,將鍵作為快取項的唯一識別符號,值作為快取項的內容。
ConcurrentHashMap
ConcurrentHashMap 是執行緒安全的 HashMap,它在多執行緒環境下可以保證高效的併發讀寫操作。
LinkedHashMap
LinkedHashMap 是一種有序的 HashMap ,它保留了元素插入的順序,可以按照插入順序或者訪問順序進行遍歷。
TreeMap
TreeMap 是一種基於紅黑樹的有序 Map,它可以按照鍵的順序進行遍歷。
筆者曾經負責藝龍紅包系統,紅包活動就是儲存在 ConcurrentHashMap 中 ,透過定時任務重新整理快取 。
核心流程:
1、紅包系統啟動後,初始化一個 ConcurrentHashMap 作為紅包活動快取 ;
2、資料庫查詢所有的紅包活動 , 並將活動資訊儲存在 Map 中 ;
3、定時任務每隔 30 秒 ,執行快取載入方法,重新整理快取。
為什麼紅包系統會將紅包活動資訊儲存在本地記憶體 ConcurrentHashMap 呢 ?
紅包系統是高併發應用,快速將請求結果響應給前端,大大提升使用者體驗;
紅包活動數量並不多,就算全部放入到 Map 裡也不會產生記憶體溢位的問題;
定時任務重新整理快取並不會影響紅包系統的業務。
筆者見過很多單體應用都使用這種方案,該方案的特點是簡潔易用,工程實現也容易 。
2 本地快取框架
雖然使用 JDK Map 能快捷構建快取,但快取的功能還是比較孱弱的。
因為現實場景裡,我們可能需要給快取新增快取統計、過期失效、淘汰策略等功能。
於是,本地快取框架應運而生。
流行的 Java 快取框架包括:Ehcache , Google Guava , Caffeine Cache 。
下圖展示了 Caffeine 框架的使用示例。
雖然本地快取框架的功能很強大,但是本地快取的缺陷依然明顯。
1、高併發的場景,應用重啟之後,本地快取就失效了,系統的負載就比較大,需要花較長的時間才能恢復;
2、每個應用節點都會維護自己的單獨快取,快取同步比較頭疼。
3 分散式快取
分散式快取是指將快取資料分佈在多臺機器上,以提高快取容量和併發讀寫能力的快取系統。分散式快取通常由多臺機器組成一個叢集,每臺機器上都執行著相同的快取服務程式,快取資料被均勻地分佈在叢集中的各個節點上。
Redis 是分散式快取的首選,甚至我們一提到快取,很多後端工程師首先想到的就它。
下圖是神州專車訂單的 Redis 叢集架構 。將 Redis 叢集拆分成四個分片,每個分片包含一主一從,主從可以切換。應用 A 根據不同的快取 key 訪問不同的分片。
與本地快取相比,分散式快取具有以下優點:
1、容量和效能可擴充套件
透過增加叢集中的機器數量,可以擴充套件快取的容量和併發讀寫能力。同時,快取資料對於應用來講都是共享的。
2、高可用性
由於資料被分佈在多臺機器上,即使其中一臺機器故障,快取服務也能繼續提供服務。
但是分散式快取的缺點同樣不容忽視。
1、網路延遲
分散式快取通常需要透過網路通訊來進行資料讀寫,可能會出現網路延遲等問題,相對於本地快取而言,響應時間更長。
2、複雜性
分散式快取需要考慮序列化、資料分片、快取大小等問題,相對於本地快取而言更加複雜。
舉一個真實的案例,這次案例讓筆者對於分散式快取的認知提上了另一個臺階。
2014年,同事開發了比分直播的系統,所有的請求都是從分散式快取 Memcached 中獲取後直接響應。常規情況下,從快取中查詢資料非常快,但線上使用者稍微多一點,整個系統就會特別卡。
透過 jstat 命令發現 GC 頻率極高,幾次請求就將新生代佔滿了,而且 CPU 的消耗都在 GC 執行緒上。初步判斷是快取值過大導致的,果不其然,快取大小在 300k 到 500k 左右。
解決過程還比較波折,分為兩個步驟:
修改新生代大小,從原來的 2G 修改成 4G,並精簡快取資料大小 (從平均 300k 左右降為 80k 左右); 把快取拆成兩個部分,第一部分是全量資料,第二部分是增量資料(資料量很小)。頁面第一次請求拉取全量資料,當比分有變化的時候,透過 websocket 推送增量資料。
經過這次最佳化,筆者理解到:快取雖然可以提升整體速度,但是在高併發場景下,快取物件大小依然是需要關注的點,稍不留神就會產生事故。另外我們也需要合理地控制讀取策略,最大程度減少 GC 的頻率 , 從而提升整體效能。
4 多級快取
開源中國網站最開始完全是用本地快取框架 Ehcache 。後來隨著訪問量的激增,出現了一個可怕的問題:“因為 Java 程式更新很頻繁,每次更新的時候都要重啟。一旦重啟後,整個 Ehcache 快取裡的資料都被清掉。重啟後若大量訪問進來的話,開源中國的資料庫基本上很快就會崩掉”。
於是,開源中國開發了多級快取框架 J2Cache,使用了多級快取 Ehcache + Redis 。
多級快取有如下優勢:
離使用者越近,速度越快; 減少分散式快取查詢頻率,降低序列化和反序列化的 CPU 消耗; 大幅度減少網路 IO 以及頻寬消耗。
本地快取做為一級快取,分散式快取做為二級快取,首先從一級快取中查詢,若能查詢到資料則直接返回,否則從二級快取中查詢,若二級快取中可以查詢到資料,則回填到一級快取中,並返回資料。若二級快取也查詢不到,則從資料來源中查詢,將結果分別回填到一級快取,二級快取中。
2018年,筆者服務的一家電商公司需要進行 app 首頁介面的效能最佳化。筆者花了大概兩天的時間完成了整個方案,採取的是兩級快取模式,同時利用了 Guava 的惰性載入機制,整體架構如下圖所示:
快取讀取流程如下:
1、業務閘道器剛啟動時,本地快取沒有資料,讀取 Redis 快取,如果 Redis 快取也沒資料,則透過 RPC 呼叫導購服務讀取資料,然後再將資料寫入本地快取和 Redis 中;若 Redis 快取不為空,則將快取資料寫入本地快取中。
2、由於步驟1已經對本地快取預熱,後續請求直接讀取本地快取,返回給使用者端。
3、Guava 配置了 refresh 機制,每隔一段時間會呼叫自定義 LoadingCache 執行緒池(5個最大執行緒,5個核心執行緒)去導購服務同步資料到本地快取和 Redis 中。
最佳化後,效能表現很好,平均耗時在 5ms 左右。最開始我以為出現問題的機率很小,可是有一天晚上,突然發現 app 端首頁顯示的資料時而相同,時而不同。
也就是說:雖然 LoadingCache 執行緒一直在呼叫介面更新快取資訊,但是各個 伺服器本地快取中的資料並非完成一致。說明了兩個很重要的點:
1、惰性載入仍然可能造成多臺機器的資料不一致
2、LoadingCache 執行緒池數量配置的不太合理, 導致了執行緒堆積
最終,我們的解決方案是:
1、惰性載入結合訊息機制來更新快取資料,也就是:當導購服務的配置發生變化時,通知業務閘道器重新拉取資料,更新快取。
2、適當調大 LoadigCache 的執行緒池引數,並線上程池埋點,監控執行緒池的使用情況,當執行緒繁忙時能發出告警,然後動態修改執行緒池引數。
5 總結
Fred Brooks 在 1987 年所發表的一篇關於軟體工程的經典論文《沒有銀彈:軟體工程的本質性與附屬性工作》。
論文強調真正的銀彈並不存在,而所謂的銀彈則是指沒有任何一項技術或方法可以能讓軟體工程的生產力在十年內提高十倍。
通俗來講:在技術領域中沒有一種通用的解決方案可以解決所有問題。技術本質上是為了解決問題而存在的,每個問題都有其獨特的環境和限制條件,沒有一種通用的技術或工具可以完美地解決所有問題。
快取是把雙刃劍,一方面我們享受快取帶來的系統效能提升,另一方面引入快取會提高系統複雜度,因為你要考慮快取的失效、更新、一致性等問題。
在面臨快取選型時,一定要結合業務場景,研發效率,運維成本,人力模型,技術儲備等因素,做出合理的選擇。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024923/viewspace-2950848/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 聊聊分散式快取分散式快取
- 如何在SPRING中同時管理本地快取和分散式快取? - techblogSpring快取分散式
- 分散式快取分散式快取
- 分散式快取 - 快取簡介,常用快取演算法分散式快取演算法
- Redis——快取穿透、快取擊穿、快取雪崩、分散式鎖Redis快取穿透分散式
- redis→分散式快取Redis分散式快取
- 分散式快取方案分散式快取
- JAVA中使用最廣泛的本地快取?Ehcache的自信從何而來3 —— 本地快取變身分散式叢集快取,打破本地快取天花板Java快取分散式
- 分散式系統快取系列一 認識快取分散式快取
- 用Java寫一個分散式快取——快取管理Java分散式快取
- SmartSql Redis 分散式快取SQLRedis分散式快取
- 分散式快取擊穿分散式快取
- 分散式快取NCache使用分散式快取
- 從快取到分散式快取的那些事快取分散式
- 分散式之快取擊穿分散式快取
- k04_分散式快取分散式快取
- 分散式快取 - 概念解釋分散式快取
- 分散式快取基礎教程分散式快取
- 雲上的分散式快取分散式快取
- Masa Framework原始碼解讀-02快取模組(分散式快取進階之多級快取)Framework原始碼快取分散式
- ASP.NET Core - 快取之分散式快取ASP.NET快取分散式
- WEB 應用快取解析以及使用 Redis 實現分散式快取Web快取Redis分散式
- 用Java寫一個分散式快取——快取淘汰演算法Java分散式快取演算法
- 小工匠聊架構 - 分散式快取技術_快取設計架構分散式快取
- 分散式系統關注點(18)——「快取穿透」和「快取雪崩」到底啥區別?分散式快取穿透
- 分散式快取架構綜述分散式快取架構
- 分散式系統中有哪些快取?分散式快取
- 結合Hazelcast和Spring的分散式快取 - reflectoringASTSpring分散式快取
- 【ionic】storage本地快取快取
- 分散式快取--快取與資料庫一致性方案分散式快取資料庫
- 清理 Conda 快取和 Pip 快取快取
- 聊聊web快取那些事!Web快取
- 快取穿透、快取雪崩和快取擊穿是什麼?快取穿透
- 《分散式快取》讀書筆記二分散式快取筆記
- 深入分散式快取 — 學習總結分散式快取
- Gitlab Runner的分散式快取實戰Gitlab分散式快取
- 最強本地快取Caffeine快取
- http快取策略以及強快取和協商快取淺析HTTP快取