1. 前言
Redis 相對(duì)于傳統(tǒng)的關(guān)系型數(shù)據(jù)庫(kù)(例如 MySQL )而言,還具有設(shè)置過(guò)期時(shí)間的特性,在項(xiàng)目實(shí)戰(zhàn)中,我們經(jīng)常關(guān)心的三元組是 {key,value,expire_time}
。這里的過(guò)期時(shí)間(expire_time)是的具體執(zhí)行方式,涉及到 Redis 的緩存過(guò)期策略。
2. 緩存過(guò)期策略
面試官提問(wèn): Redis 里的 Key 超過(guò)失效時(shí)間,是如何處理的呢?
題目解析:
首先,我們要明白緩存過(guò)期的目的是為了在 Key 超過(guò) expire_time 后,從內(nèi)存中刪除,減少內(nèi)存空間的占用。其次,要分析不同策略的定義、優(yōu)點(diǎn)和缺點(diǎn)。Redis 的過(guò)期策略主要有三種實(shí)現(xiàn)方式:
(1)定時(shí)刪除:
- 定義:對(duì)于每一個(gè)有過(guò)期時(shí)間的 Key,創(chuàng)建一個(gè)定時(shí)器,到過(guò)期時(shí)間立即刪除;
- 優(yōu)點(diǎn):保存內(nèi)存可以盡快釋放,減少過(guò)期 Key 對(duì)內(nèi)存空間的占用;
- 缺點(diǎn):占用大量的 CPU 資源去處理定時(shí)器,影響緩存的吞吐量,這和 Redis 設(shè)計(jì)追求性能的設(shè)定不符,所以一般不會(huì)采用這種策略。
(2)惰性刪除:
- 定義:過(guò)期的 Key 不做處理,只有當(dāng)訪問(wèn)這個(gè) Key 時(shí)才會(huì)判斷是否過(guò)期,過(guò)期則清除;
- 優(yōu)點(diǎn):刪除操作只有在訪存的時(shí)候才可能執(zhí)行,對(duì) CPU 的占用做到了最小。
- 缺點(diǎn):如果內(nèi)存中大量的 Key 均過(guò)期,且一段時(shí)間內(nèi)沒(méi)有被訪問(wèn),會(huì)占用大量?jī)?nèi)存。
(3)定期刪除:
- 定義:每隔一段時(shí)間全量掃描設(shè)置了過(guò)期時(shí)間的 Key,如果失效則從內(nèi)存中刪除,否則不做處理;
- 優(yōu)點(diǎn):通過(guò)限制定時(shí)的頻率,來(lái)減少對(duì) CPU 的占用和對(duì)內(nèi)存的占用;
- 缺點(diǎn):作為一種折中的方案,在內(nèi)存友好方面,不如定時(shí)刪除策略;在 CPU 友好方面,不如惰性刪除。使用時(shí)的表現(xiàn)非常依賴(lài)配置的定期頻率。
3. 內(nèi)存淘汰機(jī)制
面試官提問(wèn): Redis 的內(nèi)存淘汰機(jī)制有哪些,能枚舉說(shuō)明嗎?
題目解析:
我們需要注意,緩存過(guò)期策略和內(nèi)存淘汰機(jī)制是容易混淆的兩個(gè)概念,兩者的目的不同。
- 緩存過(guò)期策略:針對(duì)過(guò)期 Key ,從內(nèi)存中移除的方式。
- 內(nèi)存淘汰機(jī)制:針對(duì) Redis 內(nèi)存不足時(shí),業(yè)務(wù)還在繼續(xù)往 Redis 追加內(nèi)容,如何處理已有的內(nèi)容。
在 Redis 的 redis.conf
文件中,我們能找到八種可配置的內(nèi)存淘汰機(jī)制:
- volatile-lru:當(dāng)內(nèi)存不足以容納新寫(xiě)入數(shù)據(jù)時(shí),在設(shè)置了過(guò)期時(shí)間的鍵空間中,移除最近最少使用的 key;
- allkeys-lru:當(dāng)內(nèi)存不足以容納新寫(xiě)入數(shù)據(jù)時(shí),在鍵空間中,移除最近最少使用的 key;
- volatile-lfu:當(dāng)內(nèi)存不足以容納新寫(xiě)入數(shù)據(jù)時(shí),在過(guò)期密集的鍵中,使用 LFU 算法進(jìn)行刪除 key;
- allkeys-lfu:當(dāng)內(nèi)存不足以容納新寫(xiě)入數(shù)據(jù)時(shí),對(duì)所有的 key 執(zhí)行 LFU 算法篩選過(guò)期;
- volatile-random:當(dāng)內(nèi)存不足以容納新寫(xiě)入數(shù)據(jù)時(shí),在設(shè)置了過(guò)期的鍵中,隨機(jī)刪除一個(gè) key;
- allkeys-random:當(dāng)內(nèi)存不足以容納新寫(xiě)入數(shù)據(jù)時(shí),隨機(jī)刪除一個(gè)或者多個(gè) key;
- volatile-ttl:當(dāng)內(nèi)存不足以容納新寫(xiě)入數(shù)據(jù)時(shí),在設(shè)置了過(guò)期時(shí)間的鍵空間中,有更早過(guò)期時(shí)間的 key 優(yōu)先移除;
- noeviction:當(dāng)內(nèi)存不足以容納新寫(xiě)入數(shù)據(jù)時(shí),新寫(xiě)入操作直接報(bào)錯(cuò),無(wú)法寫(xiě)入。
上述算法按照特性可以分為幾類(lèi):LRU/LFU 算法、隨機(jī)刪除算法、優(yōu)先淘汰歷史數(shù)據(jù)算法、報(bào)錯(cuò)處理算法。
在項(xiàng)目中推薦兩種 LRU 算法,即如果 Redis 用作持久化數(shù)據(jù)庫(kù),不配置緩存過(guò)期時(shí)間,采用 allkeys-lru ;如果 Redis 作為緩存數(shù)據(jù)庫(kù),配置了 Key 過(guò)期時(shí)間,采用 volatile-lru 算法。
4. LRU 算法
面試官提問(wèn): 緩存過(guò)期的 LRU 算法原理是什么?
題目解析:
既然在 Redis 中談到了 LRU ,就不可避免解釋其原理。LRU(Least Recently Used,最近最少使用算法)是最常見(jiàn)的緩存淘汰算法,核心思想是 "如果數(shù)據(jù)最近被訪問(wèn)過(guò),那么將來(lái)被訪問(wèn)的可能性也更高"。
?
LRU 算法的核心步驟在于:
- 新數(shù)據(jù)直接插入到鏈表頭部;
- 每當(dāng)緩存命中(即緩存數(shù)據(jù)被訪問(wèn)),則將被命中的數(shù)據(jù)移到鏈表頭部;
- 當(dāng)鏈表滿的時(shí)候,將鏈表尾部的數(shù)據(jù)丟棄。
從性質(zhì)上總結(jié),越靠近鏈表頭部的數(shù)據(jù)越是不容易被淘汰,越靠近鏈表尾部的數(shù)據(jù)越是容易淘汰。
5. 小結(jié)
本章介紹了 Redis 緩存過(guò)期策略和內(nèi)存淘汰策略,需要大家區(qū)分兩種策略解決的問(wèn)題,同時(shí)需要針對(duì) LRU 算法原理進(jìn)行說(shuō)明,必要性能夠做到手寫(xiě) LRU 算法的偽代碼。