離譜!面試為啥都問Redis緩存?趕緊補一下
大家好,我是哪吒。
我第一次接觸緩存的時候,是用map做的,當時做一個實時數(shù)據(jù)同步的功能。
需求看似簡單,一取一傳
- 當時是通過websocket獲取服務(wù)端數(shù)據(jù)。
- 然后根據(jù)數(shù)據(jù)類別,將數(shù)據(jù)緩存到本地map中。
- 做了一個定時任務(wù),通過ftp上傳給第三方服務(wù)器。
當有并發(fā)時,map是不行的,數(shù)據(jù)會錯亂,使用ConcurrentHashMap可以解決并發(fā)數(shù)據(jù)錯亂問題。
- 現(xiàn)場網(wǎng)絡(luò)很不穩(wěn)定,F(xiàn)TP時好時壞。
- 做的是一個安全問題的實時監(jiān)控系統(tǒng),第三方數(shù)據(jù)要求還很嚴格,必須100%準確。
這矛盾怎么解決,無解了。
起初,是通過重啟的方式解決的,哈哈,重啟解決一切煩惱。
- 添加一個心跳功能,實時監(jiān)控FTP服務(wù)的狀態(tài)。
- 如果斷了7秒以上,就采取報警功能,我記得設(shè)置的是火警的音樂,提示現(xiàn)場人員排查FTP網(wǎng)絡(luò)。
- 如果斷了1分鐘以上,就將軟件自動重啟。
但是,又出現(xiàn)了一個新的問題,數(shù)據(jù)丟了。
因為用的是ConcurrentHashMap緩存數(shù)據(jù),也就是本地緩存,你重啟了,數(shù)據(jù)不就沒了嗎?兄弟。
到后來,才發(fā)現(xiàn),當時做的真的是稀爛,本地緩存應(yīng)該具有很多功能,當時這些,壓根就沒有。
- 超過最大限制有對應(yīng)淘汰策略如LRU、LFU。
- 過期時間淘汰如定時、懶式、定期。
- 持久化。
- 統(tǒng)計監(jiān)控。
下面從緩存、本地緩存、Redis緩存、Redis緩存策略幾個維度,全方位、系統(tǒng)的學(xué)習(xí)一下緩存到底是個啥?
一、緩存
緩存就是把訪問量較高的熱點數(shù)據(jù)從傳統(tǒng)的關(guān)系型數(shù)據(jù)庫中加載到內(nèi)存中,當用戶再次訪問熱點數(shù)據(jù)時,是從內(nèi)存中加載,減少了對數(shù)據(jù)庫的訪問量,解決了高并發(fā)場景下容易造成數(shù)據(jù)庫宕機的問題。
緩存有哪些分類:
- 操作系統(tǒng)磁盤緩存,減少磁盤機械操作。
- 數(shù)據(jù)庫緩存,減少文件系統(tǒng) I/O。
- 應(yīng)用程序緩存,減少對數(shù)據(jù)庫的查詢。
- Web 服務(wù)器緩存,減少應(yīng)用程序服務(wù)器請求。
- 客戶端瀏覽器緩存,減少對網(wǎng)站的訪問。
本地緩存:在客戶端本地的物理內(nèi)存中劃出一部分空間,來緩存客戶端回寫到服務(wù)器的數(shù)據(jù)。當本地回寫緩存達到緩存閾值時,將數(shù)據(jù)寫入到服務(wù)器中。
二、分析一下本地緩存的優(yōu)勢
數(shù)據(jù)緩存帶來了諸多優(yōu)勢,其中兩個核心優(yōu)點是:
- 降低數(shù)據(jù)庫壓力:通過將常用的數(shù)據(jù)存儲在快速訪問的內(nèi)存中,緩存有效地減輕了對后端數(shù)據(jù)庫的壓力。這意味著數(shù)據(jù)庫可以更專注地處理復(fù)雜的查詢和更新操作,而不必頻繁地處理重復(fù)的讀取請求。
- 提高響應(yīng)速度:將數(shù)據(jù)存儲在緩存中,使得系統(tǒng)能夠更迅速地響應(yīng)用戶的請求。相比每次都從數(shù)據(jù)庫中獲取數(shù)據(jù),緩存可以在毫秒級別內(nèi)提供所需信息,從而極大地改善用戶體驗。
三、本地緩存解決方案?
上面介紹了ConcurrentHashMap,這里不再贅述。
1、基于Guava Cache實現(xiàn)本地緩存
Guava是Google團隊開源的一款 Java 核心增強庫,包含集合、并發(fā)、緩存、IO、反射等工具箱性能和穩(wěn)定性上都有保障應(yīng)用十分廣泛。
Guava Cache支持很多特性:
- 支持最大容量限制。
- 支持兩種過期刪除策略插入時間和訪問時間。
- 支持簡單的統(tǒng)計功能。
- 基于LRU算法實現(xiàn)。
2、基于Caffeine實現(xiàn)本地緩存
Caffeine是基于java8實現(xiàn)的新一代緩存工具,緩存性能接近理論最優(yōu),可以看作是Guava Cache的增強版,功能上兩者類似。
不同的是Caffeine采用了一種結(jié)合LRU、LFU優(yōu)點的算法W-TinyLFU在性能上有明顯的優(yōu)越性。
3、基于Encache實現(xiàn)本地緩存
Encache是一個純Java的進程內(nèi)緩存框架具有快速、精干等特點。
同Caffeine和Guava Cache相比,Encache的功能更加豐富擴展性更強。
優(yōu)點:
- 支持多種緩存淘汰算法包括LRU、LFU和FIFO。
- 緩存支持堆內(nèi)存儲、堆外存儲、磁盤存儲支持持久化三種。
- 支持多種集群方案解決數(shù)據(jù)共享問題。
四、引入Redis
后來,因為一次事故,甲方被監(jiān)管平臺罰了100萬,本質(zhì)原因就是丟數(shù)據(jù)問題。
這可如何是好,我也是嚇了一身冷汗,連夜想整改方案,最終的解決方案是,“引入Redis”。
Redis作為一款高性能、內(nèi)存存儲的緩存數(shù)據(jù)庫,被廣泛應(yīng)用于緩存數(shù)據(jù)的場景。
- 用戶第一次訪問數(shù)據(jù)時,緩存中沒有數(shù)據(jù),要從數(shù)據(jù)庫中獲取數(shù)據(jù),因為是從磁盤中拿數(shù)據(jù)讀取數(shù)據(jù)的過程比較慢。
- 拿到數(shù)據(jù)后,將數(shù)據(jù)存儲在緩存中。
- 用戶第二次訪問數(shù)據(jù)時,可以從緩存中直接獲取,因為緩存是直接操作內(nèi)存的,訪問數(shù)據(jù)速度比較快。
下面將深入探討Redis的數(shù)據(jù)緩存策略,重點解析LRU(最近最少使用)、LFU(最不經(jīng)常使用)等算法,并分享如何通過性能優(yōu)化來提升緩存系統(tǒng)的效率。
五、Redis數(shù)據(jù)緩存策略
1、為什么需要數(shù)據(jù)緩存策略
在現(xiàn)代應(yīng)用中,數(shù)據(jù)緩存發(fā)揮著至關(guān)重要的作用。
通過將頻繁訪問的數(shù)據(jù)存儲在內(nèi)存中,我們能夠避免不必要的數(shù)據(jù)庫查詢,從而顯著提升系統(tǒng)的響應(yīng)速度和吞吐量。
然而,隨著應(yīng)用規(guī)模和用戶訪問量的不斷增加,有效的數(shù)據(jù)緩存策略變得尤為重要。
我們需要在性能和資源利用之間找到最佳平衡,以應(yīng)對不同需求和挑戰(zhàn)。
這進一步引出了一個關(guān)鍵問題:如何選擇適合的數(shù)據(jù)緩存策略來滿足不同的應(yīng)用場景?
下圖詳細地說明了數(shù)據(jù)緩存的優(yōu)勢和選擇適合的數(shù)據(jù)緩存策略的過程:
通過上圖,我們深入探討了數(shù)據(jù)緩存的優(yōu)勢,并展示了在選擇合適的緩存策略時,我們?nèi)绾卧谔嵘阅芎唾Y源利用之間找到最佳平衡。
選擇適合的策略能夠有效地降低數(shù)據(jù)庫壓力,并通過提高響應(yīng)速度來提供更出色的用戶體驗。
2、Redis作為緩存的優(yōu)勢
Redis(Remote Dictionary Server)是一款強大的高性能開源內(nèi)存數(shù)據(jù)庫,不僅被廣泛應(yīng)用于緩存場景,還可用作隊列、發(fā)布訂閱系統(tǒng)等。作為緩存數(shù)據(jù)庫,Redis擁有一系列突出的優(yōu)勢:
(1)高性能特點
Redis的數(shù)據(jù)存儲在內(nèi)存中,因此具備出色的讀寫性能。其高效的數(shù)據(jù)結(jié)構(gòu)和優(yōu)化的算法使得絕大多數(shù)情況下,讀寫操作能夠在微秒級別內(nèi)完成,滿足了高并發(fā)應(yīng)用的需求。
(2)多樣性的緩存策略
Redis提供了多種數(shù)據(jù)緩存策略,使開發(fā)者可以根據(jù)業(yè)務(wù)特點選擇合適的策略。這種靈活性允許我們根據(jù)數(shù)據(jù)的訪問模式、使用頻率以及其他因素來決定數(shù)據(jù)何時被清理或保留。
下圖說明緩存策略的選擇過程:
通過分析數(shù)據(jù)訪問模式,根據(jù)數(shù)據(jù)的訪問頻率選擇合適的緩存策略。根據(jù)實際情況不斷地監(jiān)控數(shù)據(jù)的訪問情況,并優(yōu)化緩存策略,在不同的場景中靈活應(yīng)用這些策略。
六、LRU算法:最近最少使用
LRU(Least Recently Used)算法是一種經(jīng)典的緩存替換策略,它的核心思想是優(yōu)先淘汰最近最少使用的數(shù)據(jù),以便為新數(shù)據(jù)騰出空間。在數(shù)據(jù)緩存場景中,LRU算法能夠保留熱門數(shù)據(jù),從而提高緩存的命中率。
1、LRU算法原理解析
LRU算法的原理非常直觀:當緩存空間滿了,系統(tǒng)會優(yōu)先淘汰最久未被訪問的數(shù)據(jù)。這個策略的背后思想是,如果某個數(shù)據(jù)在最近一段時間內(nèi)沒有被訪問,那么它在未來也可能不會被訪問。這種替換策略有助于保持緩存中的數(shù)據(jù)是熱數(shù)據(jù)
,即最近被頻繁訪問的數(shù)據(jù)。
上圖說明了LRU算法如何根據(jù)訪問順序來保留緩存中的數(shù)據(jù)。最近訪問的數(shù)據(jù)會被保留在緩存中,而最早訪問的數(shù)據(jù)會被優(yōu)先替換。
示例代碼如下,展示了如何通過繼承LinkedHashMap來實現(xiàn)LRU緩存:
import java.util.LinkedHashMap;
import java.util.Map;
class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int MAX_CAPACITY;
public LRUCache(int capacity) {
super(capacity, 0.75f, true);
MAX_CAPACITY = capacity;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > MAX_CAPACITY;
}
}
在這個示例中,我們創(chuàng)建了一個LRUCache類,繼承自LinkedHashMap。通過重寫removeEldestEntry方法,我們指定了當緩存大小超過一定閾值時,自動刪除最久未被訪問的數(shù)據(jù)。
2、redis中應(yīng)用LRU算法
在Redis中,我們可以通過配置maxmemory-policy選項來啟用LRU算法的緩存策略。當Redis的內(nèi)存使用達到限制時,LRU算法將被用于淘汰部分數(shù)據(jù),以便騰出空間給新數(shù)據(jù)。
以下是如何在Redis中啟用LRU緩存策略的示例:
# 啟用LRU緩存策略
CONFIG SET maxmemory-policy allkeys-lru
3、LRU算法的優(yōu)點與限制
LRU(Least Recently Used)算法是一種常用的數(shù)據(jù)緩存策略,它在管理緩存數(shù)據(jù)時有一些明顯的優(yōu)點和一些限制。
優(yōu)點
優(yōu)點 | 描述 |
適用于熱數(shù)據(jù) | LRU算法保留了最近最常訪問的數(shù)據(jù),因此非常適用于具有明顯訪問熱點的場景。 |
簡單有效 | LRU算法的實現(xiàn)相對簡單,不需要復(fù)雜的計算和維護。 |
限制
限制 | 描述 |
周期性訪問 | LRU算法可能會因為數(shù)據(jù)的周期性訪問而導(dǎo)致不必要的數(shù)據(jù)替換,特別是在某些特殊業(yè)務(wù)場景中。 |
緩存污染 | LRU算法容易受到突發(fā)的大量訪問影響,可能導(dǎo)致緩存中的“熱·數(shù)據(jù)被淘汰,從而影響緩存效果。 |
七、LFU算法:最不經(jīng)常使用
LFU(Least Frequently Used)算法是一種與LRU相似的緩存替換策略,它的核心思想是優(yōu)先淘汰最不經(jīng)常使用的數(shù)據(jù),以便為新數(shù)據(jù)騰出空間。在某些特定場景下,LFU算法能夠更好地適應(yīng)數(shù)據(jù)訪問模式的變化。
1、LFU算法原理解析
LFU算法的原理與LRU算法類似,但不同之處在于LFU算法基于數(shù)據(jù)被訪問的頻率來做出替換決策,而不僅僅是訪問的時間順序。LFU算法維護了一個數(shù)據(jù)訪問頻率的記錄,當需要淘汰數(shù)據(jù)時,會優(yōu)先選擇訪問頻率最低的數(shù)據(jù)。
上圖說明了LFU算法如何根據(jù)數(shù)據(jù)的訪問頻率來保留緩存中的數(shù)據(jù)。頻繁訪問的數(shù)據(jù)會被保留,而不經(jīng)常訪問的數(shù)據(jù)會被優(yōu)先替換。
2、在Redis中應(yīng)用LFU算法
在Redis中,您可以通過配置maxmemory-policy選項來啟用LFU算法的緩存策略。當Redis的內(nèi)存使用達到限制時,LFU算法將用于淘汰部分數(shù)據(jù),以便為新數(shù)據(jù)騰出空間。
以下是如何在Redis中啟用LFU緩存策略的示例:
# 啟用LFU緩存策略
CONFIG SET maxmemory-policy allkeys-lfu
3、LFU算法的優(yōu)點與限制
LFU(Least Frequently Used)算法是一種另類的數(shù)據(jù)緩存策略,它在不同的場景下具有一些明顯的優(yōu)點和一些限制。
優(yōu)點
優(yōu)點 | 描述 |
適用于頻繁刷新 | LFU算法能夠優(yōu)先保留頻繁被刷新的數(shù)據(jù),適合某些周期性訪問的場景。 |
對數(shù)據(jù)熱度變化敏感 | 相比于LRU算法,LFU算法更能適應(yīng)數(shù)據(jù)訪問模式的變化,能夠更好地反映數(shù)據(jù)的熱度。 |
限制
限制 | 描述 |
計算復(fù)雜性 | LFU算法需要維護數(shù)據(jù)的訪問頻率記錄,這可能導(dǎo)致一定的計算復(fù)雜性,特別是在大規(guī)模數(shù)據(jù)場景下。 |
冷啟動問題 | 對于剛開始訪問的數(shù)據(jù),由于沒有足夠的訪問頻率信息,LFU算法可能難以做出合適的替換決策。 |
八、其他數(shù)據(jù)緩存策略
1、Least Recently Used with Sampling(LRUS)
除了傳統(tǒng)的LRU算法,還存在一種改進的版本,即LRUS(Least Recently Used with Sampling)算法。LRUS算法通過周期性的采樣來記錄數(shù)據(jù)的訪問情況,從而更好地估計最近使用的數(shù)據(jù),減少了LRU算法中的“冷啟動·問題。
LRUS算法原理
LRUS算法引入了采樣機制,通過周期性地記錄一部分數(shù)據(jù)的訪問情況,從而更準確地判斷哪些數(shù)據(jù)是熱數(shù)據(jù),哪些是冷數(shù)據(jù)。與傳統(tǒng)的LRU算法不同,LRUS算法能夠更好地適應(yīng)數(shù)據(jù)訪問模式的變化,提高數(shù)據(jù)緩存的命中率。
上圖LRUS算法通過周期性采樣記錄數(shù)據(jù)的訪問情況,從而更精確地判斷哪些數(shù)據(jù)應(yīng)該被保留,哪些應(yīng)該被替換。
2、Random Replacement(隨機替換)
隨機替換是一種簡單但有效的緩存策略。與LRU和LFU不同,隨機替換策略不考慮數(shù)據(jù)的訪問時間或頻率,而是隨機選擇要替換的數(shù)據(jù)。盡管這聽起來不太智能,但在某些場景下,隨機替換策略表現(xiàn)出意外的優(yōu)勢。
隨機替換的原理
隨機替換的核心思想是,每次需要替換數(shù)據(jù)時,從緩存中隨機選擇一條數(shù)據(jù)進行替換。雖然這種策略沒有考慮數(shù)據(jù)的熱度或頻率,但在一些特殊情況下,隨機替換能夠避免特定數(shù)據(jù)被頻繁淘汰,從而維持一定的數(shù)據(jù)多樣性。
上圖中,隨機替換算法隨機選擇要替換的數(shù)據(jù),從而在一些情況下維持了數(shù)據(jù)多樣性。
九、性能優(yōu)化與實際應(yīng)用
1、數(shù)據(jù)緩存策略的性能考量
在選擇和配置數(shù)據(jù)緩存策略時,性能是一個關(guān)鍵因素。不同的緩存策略適用于不同的業(yè)務(wù)場景,因此在做出決策時需要綜合考慮多個因素。
(1)緩存大小與命中率的平衡
在配置緩存大小時,需要權(quán)衡緩存的總大小和實際存儲的數(shù)據(jù)量。一個過小的緩存可能導(dǎo)致命中率降低,無法有效減輕數(shù)據(jù)庫負載,而一個過大的緩存可能浪費內(nèi)存資源。通??梢酝ㄟ^監(jiān)控命中率和緩存利用率來優(yōu)化緩存大小。
(2)數(shù)據(jù)訪問模式的分析
分析業(yè)務(wù)的數(shù)據(jù)訪問模式對于選擇合適的緩存策略至關(guān)重要。例如,如果某些數(shù)據(jù)被頻繁地訪問,而另一些數(shù)據(jù)則很少被訪問,那么選擇適當?shù)牟呗钥梢蕴岣呔彺娴男Чτ陬l繁訪問的熱數(shù)據(jù),可以選擇LRU或者LFU策略,而對于較少訪問的冷數(shù)據(jù),可以考慮隨機替換策略。
2、實際應(yīng)用案例:電子商務(wù)網(wǎng)站
讓我們通過一個實際的應(yīng)用案例,來展示如何根據(jù)業(yè)務(wù)需求選擇合適的緩存策略??紤]一個電子商務(wù)網(wǎng)站,用戶經(jīng)常訪問商品列表、商品詳情以及購物車等頁面。針對這個場景,可以選擇不同的緩存策略來優(yōu)化性能。
(1)電子商務(wù)網(wǎng)站的緩存策略選擇
商品列表頁:由于商品列表頁中的商品信息經(jīng)常變動,可以選擇LRU或者隨機替換策略。這樣可以保留最近的商品數(shù)據(jù),提高頁面加載速度。
// 使用LRU算法實現(xiàn)商品列表頁緩存
LRUCache<String, List<Product>> productListCache = new LRUCache<>(1000); // 緩存容量1000
List<Product> cachedProductList = productListCache.get("productList");
if (cachedProductList == null) {
// 從數(shù)據(jù)庫獲取商品列表數(shù)據(jù)
List<Product> productList = database.getProductList();
productListCache.put("productList", productList);
cachedProductList = productList;
}
商品詳情頁:商品詳情頁的數(shù)據(jù)相對穩(wěn)定,適合選擇LFU策略。這樣可以保留頻繁訪問的商品詳情數(shù)據(jù),提高頁面響應(yīng)速度。
// 使用LFU算法實現(xiàn)商品詳情頁緩存
LFUCache<String, ProductDetails> productDetailsCache = new LFUCache<>(500); // 緩存容量500
ProductDetails cachedProductDetails = productDetailsCache.get("product123");
if (cachedProductDetails == null) {
// 從數(shù)據(jù)庫獲取商品詳情數(shù)據(jù)
ProductDetails productDetails = database.getProductDetails("product123");
productDetailsCache.put("product123", productDetails);
cachedProductDetails = productDetails;
}
購物車頁:購物車頁的數(shù)據(jù)與用戶關(guān)聯(lián)緊密,可以選擇LRU或者LRUS策略。這樣可以保留最近被訪問的購物車數(shù)據(jù),提供更好的用戶體驗。
// 使用LRUS算法實現(xiàn)購物車頁緩存
LRUSCache<String, ShoppingCart> shoppingCartCache = new LRUSCache<>(200); // 緩存容量200
ShoppingCart cachedShoppingCart = shoppingCartCache.get("user123");
if (cachedShoppingCart == null) {
// 從數(shù)據(jù)庫獲取購物車數(shù)據(jù)
ShoppingCart shoppingCart = database.getShoppingCart("user123");
shoppingCartCache.put("user123", shoppingCart);
cachedShoppingCart = shoppingCart;
}
(2)性能優(yōu)化與實際應(yīng)用改進
在實際應(yīng)用中,通過合理配置緩存策略以及優(yōu)化緩存大小,電子商務(wù)網(wǎng)站可以顯著提升頁面加載速度和用戶體驗。同時,通過監(jiān)控數(shù)據(jù)訪問模式的變化,還可以動態(tài)調(diào)整緩存策略,進一步優(yōu)化性能。
十、總結(jié)與實踐指導(dǎo)
1、Redis數(shù)據(jù)緩存策略的重要性
數(shù)據(jù)緩存不僅可以提升系統(tǒng)性能,還能降低后端數(shù)據(jù)庫的壓力,從而實現(xiàn)更快的響應(yīng)時間和更好的用戶體驗。在現(xiàn)代高并發(fā)應(yīng)用中,優(yōu)化數(shù)據(jù)緩存策略已經(jīng)成為系統(tǒng)設(shè)計中不可或缺的一環(huán)。
2、如何選擇合適的緩存策略
在實際應(yīng)用中,選擇合適的緩存策略是至關(guān)重要的。根據(jù)不同的業(yè)務(wù)場景和數(shù)據(jù)訪問模式,我們可以靈活地選擇LRU、LFU、LRUS、隨機替換等緩存策略。同時,還可以根據(jù)實際需要動態(tài)地調(diào)整緩存大小,以達到最佳的性能與資源利用率的平衡。
實踐指導(dǎo):
- 分析數(shù)據(jù)訪問模式:在選擇緩存策略之前,首先需要詳細分析數(shù)據(jù)的訪問模式。哪些數(shù)據(jù)被頻繁訪問?哪些數(shù)據(jù)變化較少?根據(jù)這些信息,選擇適合的緩存策略。
- 選擇合適的算法:根據(jù)業(yè)務(wù)需求,選擇合適的緩存算法。LRU適用于保留最近訪問的數(shù)據(jù),LFU適用于保留最頻繁訪問的數(shù)據(jù),而LRUS則更好地應(yīng)對訪問模式的變化。
- 監(jiān)控與優(yōu)化:緩存策略不是一成不變的,需要不斷監(jiān)控數(shù)據(jù)訪問情況,優(yōu)化緩存大小和策略。通過監(jiān)控緩存的命中率和利用率,可以動態(tài)地做出調(diào)整。
- 靈活應(yīng)用:不同的業(yè)務(wù)模塊可能需要不同的緩存策略。根據(jù)實際情況,可以在系統(tǒng)中采用多種緩存策略,以最大程度地提升性能。