效能優化之快取篇
1. 前言:為什麼要用快取?
使用者數增長,架構演變,資料量增大,開始考慮怎麼去做效能優化。
而效能優化的第一定律就是:優先考慮使用快取。
2. 快取的基本原理
2.1 快取的作用
1、加快資料訪問速度;
2、減輕後端應用和資料儲存的負載壓力。
2.2 快取的特徵
1、命中率:命中率 = 命中數 / 請求數。
這是衡量快取有效性的重要指標。命中率越高,表明快取的使用率越高。
2、最大元素(最大空間)。
一旦快取中元素數量超過這個值(或者快取資料空間超過其最大支 持空間),將會觸發淘汰策略
3、淘汰策略。
這個我前文其實已經說過。
FIFO(First In First Out) 先進先出,淘汰最早資料。
判斷儲存時間,離目前最遠的資料優先淘汰。
LRU (Least Recently Used)剔除最近最少使用。
判斷最近使用時間,離目前最遠的資料優先淘汰。
LFU (Least Frequently Used)剔除最近使用頻率最低的資料。
在一段時間內,資料被使用次數最少的,優先淘汰。
具體可以看這篇文章常見的快取剔除策略 & LRU與LFU的區別。
3. 快取的分類
快取的主要手段有:瀏覽器快取、CDN、反向代理、本地快取、分散式快取、資料庫快取。
在解讀《大型網站技術架構》一文中,其實已經說到過。
我們一般說做效能優化時是指後三個:本地快取、分散式快取、資料庫快取。
前面三個快取策略屬於網站前端的範疇。
從硬體介質上來看,快取分為記憶體和硬碟兩種。
但從技術上,又可以分成記憶體、硬碟檔案、資料庫。
我們通常意義上說的快取一般都是基於記憶體的。
因為只有記憶體,才足夠快。
資料庫快取一般也是基於記憶體的,但這個活一般是DBA在配置資料庫的時候就設定好了。
對於大部分開發人員來說,我們一般所說的快取優化都是基於本地快取(ocal cache)和遠端快取(remote cache)。
而現在遠端快取這個詞一般也被分散式快取這個常用方案所代指。
4. 什麼時候使用快取?
4.1 快取的使用判斷
什麼時候使用快取的判斷其實比較簡單,抓住兩點就行了:
1、是不是熱點資料?
所謂熱點,一般是遵循二八定律,即百分之八十的訪問集中在百分之二十的資料上。
2、是不是讀比寫多?
這個比例一般為2:1。
4.2 什麼時候不應該使用快取?
反過來就是了。
1、沒有熱點資料不要使用快取,也沒什麼意義。
因為記憶體資源是比較寶貴的。
2、頻繁修改的資料不要使用快取。
因為可能寫入後還來不及讀取就已失效或被淘汰,並且容易產生髒讀。
4.3 合理使用快取
最後,最重要的是確認是否需要使用快取?
確定了後,再選擇合適的快取工具及使用快取的方式。
5. 快取時常見的一些問題
使用快取優點很多,但也存在一些很常見的問題。雙刃之劍,就看怎麼用了。
列舉一些我們工作中常見的一些快取問題,並給出至少一種解決方案。
5.1 快取更新帶來的資料不一致與髒讀
快取更新的常見策略有:
1、先更新資料庫再更新快取;
2、先更新資料庫再刪除快取;
3、先刪除快取再更新資料庫;
4、定時清理快取;
5、有請求訪問資料時,判斷快取是否過期,過期從資料庫中重新整理快取。
在這幾種方案中,如果修改快取與資料庫不在同一個事物中,就帶來了資料不一致和髒讀的問題。
對應方案1:先刪除快取再更新資料庫,並且在同一個事物中。
對應方案2:快取自動失效後,另外的非同步執行緒進行快取更新。
對應方案3:快取更新在併發、分散式要考慮鎖,redis天生就是單執行緒,比較有優勢。
5.2 怎麼做快取預熱
快取預熱是指在使用者可訪問服務之前,將熱點資料載入到快取的操作,這樣可以有效避免上線後瞬時大流量造成系統不可用。
快取預熱的一般性策略:
1、開發個快取重新整理功能,手工重新整理;
2、專案啟動的時候自動進行載入(一般為字典表等資料量不大的資料);
3、設定個定時器,自動重新整理快取;
4、提前統計熱點資料,事先批量載入到如redis這樣快取工具中。
5.3 快取重建
快取失效後,重建熱點快取,如果耗時較長,在重建過程中,效能、負載不好。
對應方案:
1、正常情況下,交錯快取失效時間,減輕快取壓力;
2、崩潰失效的情況下,可以使用帶持久化功能的快取來恢復,比如Redis;
3、如果是MongoDB則不太一樣,它是採用mmap來將資料檔案對映到記憶體中,所以當MongoDB重啟時,這些對映的記憶體並不會清掉,不需要進行快取重建與預熱。
5.4 快取雪崩與可用性
快取雪崩:快取在同一時間失效時,訪問直達資料庫層,可能導致DB掛掉、系統崩潰。
對應方案1:交錯快取失效時間或隨機快取失效時間。
對應方案2:主從熱備(Redis Sentinel)。
對應方案3:叢集/水平切分(Redis Cluster、一致性雜湊)。
5.5 快取穿透
快取穿透:持續高併發訪問某個不存在的Key。
對應方案1:空值快取。
對應方案2:布隆過濾器(bloom filter) + bitmap。窮舉可能訪問的資料放入bitmap中,使用hash訪問。
5.6 快取擊穿
快取擊穿:熱點Key失效,高併發請求,直擊資料庫。
快取擊穿與快取穿透很相似,不同點是是快取擊穿前訪問的是真實的熱點資料,只是在某一剎那失效了,造成了擊穿的效果。
這樣看,它其實也是快取雪崩的一個特例。與雪崩的區別即在於擊穿是對於特定的熱點資料,而雪崩是全部資料。
對應方案:多級快取及交錯失效時間 + LRU 淘汰演算法。
對於熱點資料進行二級或多級快取,並對於不同級別的快取設定不同的失效時間,緩解雪崩。
此外可使用LRU的變種演算法LRU-K快取資料。
5.7 快取降級
快取降級是服務降級中的一環。
在訪問量劇增,導致服務出現問題時,為了保證核心服務可用,防止發生快取雪崩,可進行服務降級。
以redis為例,比較常見的做法就是,不去資料庫查詢,而是直接返回預設值給使用者。
快取降級也可根據日誌級別進行預案設定。
6. 分散式快取的選型
說了這麼多快取的原理與策略,說說我們在實際工作中應該怎麼去做快取選型。
以下就是常用的幾種快取工具。
6.1 Ehcache
Ehcache是純Java開源的快取框架,最早從hibernate發展而來,現在算是springboot中的官配快取工具,整合簡單。特點如下:
快速,針對大型高併發系統場景,Ehcache的多執行緒機制有相應的優化改善;
簡單,很小的jar包,簡單配置就可直接使用,單機場景下無需過多的其他服務依賴;
支援多種的快取策略,靈活;
快取資料有兩級:記憶體和磁碟,與一般的本地記憶體快取相比,有了磁碟的儲存空間,將可以支援更大量的資料快取需求;
具有快取和快取管理器的偵聽介面,能更簡單方便的進行快取例項的監控管理;
支援多快取管理器例項,以及一個例項的多個快取區域。
6.2 Guava Cache
Guava Cache是Google開源的Java重用工具集庫Guava裡的一款快取工具,特點如下:
自動將entry節點載入進快取結構中;
當快取的資料超過設定的最大值時,使用LRU演算法移除;
具備根據entry節點上次被訪問或者寫入時間計算它的過期機制;
快取的key被封裝在WeakReference引用內;
快取的Value被封裝在WeakReference或SoftReference引用內;
統計快取使用過程中命中率、異常率、未命中率等統計資料。
6.3 Memcache
memcache本身不支援分散式,是通過客戶端的路由處理來達到分散式解決方案的目的。特點如下:
memcache使用預分配記憶體池的方式管理記憶體;
所有資料儲存在實體記憶體裡;
非阻塞IO複用模型,純KV存取操作;
多執行緒,效率高,會遇到鎖等上下文切換問題;
只支援簡單KV資料型別;
資料不支援持久化。
6.4 Redis
Redis是當前主流的高效能記憶體資料庫,多用於儲存快取資料,並能實現輕量級的MQ功能。特點如下:
臨時申請空間,可能導致碎片;
有VM機制,能儲存更多資料,超過記憶體空間後會導致swap,降低效率;
非阻塞IO複用模型,支援額外CPU計算:排序、聚合,會影響IO效能;
單執行緒,無鎖,無上下文切換,單例項無法利用多核效能;
支援多種資料型別:string / hash / list / set / sorted set;
資料支援持久化:AOF(語句增量)/RDB(fork全量);
天然支援高可用分散式方案sentinel +;
cluster(故障自動轉移+叢集)。
6.5 推薦
通常情況下,單機我們會用Ehcache,甚至java自己的concurrenthashmap來實現快取。
分散式一般選擇redis。
07 附錄:參考資料
快取那些事 | 美團明輝
什麼是快取預熱,快取更新,快取降級的概念?