使用Redis快取時,您可以為快取資料設定過期時間,提高快取命中率,並根據過期策略刪除快取資料。
Redis 的快取過期刪除策略有兩種型別:延遲刪除和定期刪除。
在 Redis 中,設定了過期時間的金鑰將儲存在 Redis 的過期字典中。 當你對金鑰進行讀寫操作時,會先檢查過期的字典中是否存在金鑰,如果存在,則獲取金鑰的過期時間,並與系統當前時間進行對比
如果過期時間大於當前時間,則金鑰尚未過期。
如果過期時間小於或等於當前時間,則確定金鑰已過期。
過時的詞典
過期的字典儲存在 RedisDB 結構中,如下所示:
typedef struct redisdb redisdb;哪裡:
過期字典的鍵指向 Redis 資料庫中的鍵。
過期字典的值為long long型別的整數,用於儲存金鑰對應的過期時間(timestamp,單位:毫秒)。
如圖所示
延遲刪除處理邏輯,如圖所示
過程:對金鑰執行讀寫操作,以確定過期的字典中是否存在該金鑰
如果不存在,則正常執行讀寫操作。
如果是這樣,請將金鑰過期時間與系統的當前時間進行比較,以確定金鑰是否已過期
如果金鑰的過期時間小於或等於當前時間(過期),則刪除該金鑰。
如果金鑰過期時間長於當前時間(未過期),系統將執行讀/寫操作。
定期刪除處理邏輯,如圖所示
操作步驟:Redis以100ms的頻率從過期的字典中隨機抽取20個金鑰,檢查是否有過期的金鑰
如果金鑰的過期時間小於或等於當前時間,則表示金鑰已過期。
如果金鑰過期時間大於當前時間,則金鑰尚未過期。
如果存在過期金鑰,請確定過期金鑰數量是否超過提取數量的 25%,或者週期性刪除週期是否超過 25 毫秒
如果執行週期超過25%或執行時長超過25ms,則從過期字典中隨機抽取20個金鑰進行檢查,迴圈至過期金鑰數量不超過25%或執行時長不超過25ms。
如果執行時間不超過 25 毫秒,則刪除過期的金鑰。
如果沒有過期金鑰,請等待 100 毫秒,然後繼續從過期的字典中隨機選擇 20 個金鑰進行檢查。
注意:Redis在主線程上會定期刪除,如果大量金鑰同時過期,會影響Redis的讀寫效率。
redis2.在版本 8 之後,可以通過修改配置檔案來重新開發conf 中的 hz 引數(預設值為 10,即每秒 10 次),用於調整定時刪除的定時任務的執行頻率。
引數 Hz:Hz 的值範圍為 1 500,預設值為 10,不建議超過 100,增加 Hz 的值會占用更多的 CPU 資源,只有當請求延遲很低時,才能考慮將 Hz 的值增加到 100。
Redis 過期刪除策略採用延遲刪除 + 定期刪除作為 CPU 資源和記憶體消耗之間的權衡
如果使用延遲刪除,未訪問的金鑰將儲存在記憶體中,從而造成記憶體資源的浪費。
如果只使用週期性刪除,當大量金鑰同時過期時,會占用大量的CPU資源,影響讀寫操作的效能和系統吞吐量。
過期刪除策略是一種不精確的刪除策略,對於那些長時間未訪問且多次定期刪除的金鑰,它們將始終儲存在記憶體中,這可能會導致 Redis 的記憶體耗盡。
在配置檔案redis中conf,最大記憶體由引數 maxmemory 設定(預設為 unlimited,該引數的值通常設定為物理記憶體的 3 4)。
當Redis記憶體超過maxmemory引數設定的最大記憶體時,觸發記憶體消除策略。 記憶體消除策略是使用 maxmemory-policy 引數設定的,如下所示:
volatile-lru:設定了過期時間的金鑰,通過LRU演算法淘汰(lru:最近最少使用,表示使用時間最長)。
allkeys-lru:根據 LRU 演算法(環境中使用的常見策略)消除所有金鑰(包括有和沒有過期時間的金鑰)。
volatile-lfu:根據 LFU 演算法淘汰有過期時間的金鑰(lfu:最不常用)。
allkeys-LFU:基於LFU演算法消除所有金鑰。
volatile-random:設定了過期時間的金鑰,通過隨機消除方法進行淘汰。
allkeys-random:根據隨機消除方法消除所有鍵。
volatile-ttl:對於設定了過期時間的金鑰,丟棄即將過期的金鑰(次要 TTL)。
noeviction:不排除任何資料,當使用的記憶體空間超過maxmemory引數設定的值時,寫入資料到Redis時會返回錯誤(預設策略,一般不使用)。
可以看出,除了特殊的noeviction和volatile-TTL策略外,其他六種策略可以分為兩類:
從 volatile- 開始的策略是清除根據特定演算法設定了過期時間的金鑰。
從 allkeys- 開始的策略是根據特定演算法清除所有鍵。
redis.conf 中與記憶體消除策略相關的引數:
maxmemory:記憶體使用上限,預設值為0,表示記憶體使用上限不受限制。
maxmemory-policy:記憶體消除策略,預設策略為 noeviction。
maxmemory-samples:隨機樣本數,預設值為 5。
記憶體停用策略選擇:
如果資料是冪等的(即某些資料的訪問頻率更高,而其餘資料的訪問頻率較低),建議您選擇 allkeys-lru 或 allkeys-lfu 策略。
如果資料是均勻分布的(即所有資料的訪問概率大致相等),建議您選擇 allkeys-random 策略。
如果您需要設定不同的TTLS來確定資料過期的順序,建議您選擇Volatile-TTL策略。
如果有些資料需要長時間儲存,有些資料可以清除,建議選擇 volatile-lru 或 volatile-random 策略。
注意:由於設定過期時間會消耗額外的記憶體,因此您可以選擇 allkeys-lru 策略,以防止 Redis 因確定金鑰的過期時間而消耗額外的記憶體。
要檢視 Redis 使用的記憶體停用策略,請參閱 config get maxmemory-policy。
修改記憶體消除策略
config set maxmemory-policy Policy:立即生效,重啟並恢復為預設值。
修改配置檔案 Redisconf Policy中的引數maxmemory-policy:重啟生效。
redis - 大致的 LRU 計算執行過程:
隨機選擇n個key(n由引數maxmemory-samples設定,預設值為5),找到時間最長的未訪問金鑰(時間通過比較RedisObject中的LRU屬性與系統當前時間確定),並刪除該金鑰。
如果系統記憶體再次超過記憶體使用限制,並且仍然超過記憶體使用限制,則上述過程將繼續。
LRU 演算法中的缺陷
LRU演算法只關注資料的訪問時間或順序,忽略了訪問次數的值,在剔除資料的過程中可能會剔除熱點資料。
如圖所示
圖中時間線從左到右,資料a b c在同一時間段內被多次訪問。 資料C為最近訪問的資料,按照LRU演算法排列的資料熱度為C>B>A,資料實熱度為B>A>C。
為什麼 Redis 不只使用 LRU 演算法?
如果使用 LRU 演算法(基於 hashmap + 雙鏈表:稍後將共享乙個 LRU 演算法),則需要消耗額外的記憶體來儲存雙鏈表節點中的上乙個和下乙個指標,從而減少 Redis 的儲存空間。
為什麼maxmemory-samples引數的預設值為5?
當提取次數為 5 時,執行效率非常接近標準 LRU 演算法,雖然提取次數設定為 5 時可以更接近 LRU 演算法,但會消耗更多的 CPU 資源,因此提取次數的預設值是 LRU 演算法的執行效率和 CPU 資源消耗權衡的結果。
LFU(full name least used used)代表最近使用頻率最低的,與關鍵訪問次數有關,其核心思想是,如果乙個資料在不久的將來被頻繁訪問,那麼未來被重新訪問的概率會很高,訪問頻率較低的資料將來不會再次使用的概率會很高。
注意:訪問LFU演算法的頻率不能等同於訪問次數。 如圖所示
圖中,資料 A 被訪問了 5 次,資料 B 和 C 各被訪問了 4 次
如果資料熱度值由訪問次數決定,則該值為 a>b=c。
如果考慮到時效性,更接近當前時間的訪問更有價值,則資料熱度值為:c>b>a。
因此,LFU演算法一般具有時間衰減函式來參與熱值的計算,同時考慮接入時間的影響。
LFU演算法實現
LFU 演算法的實現不使用額外的資料結構,而是重用 RedisObject 資料結構的 LRU 字段。
redisobject 資料結構:
typedef struct redisobject robj;其中,LRU演算法和LFU演算法在LRU欄位的使用方式存在差異
在LRU演算法中,LRU欄位用於記錄金鑰的訪問時間戳,因此可以根據LRU欄位中記錄的值,將長時間未訪問的金鑰與當前時間進行比對。
在 LFU 演算法中,LRU 字段分為兩段,最高 16 位儲存 LDT(最後遞減時間)和最低 8 位儲存邏輯計數器 (LOGC)。
LFU演算法中的LRU欄位,如圖所示:
ldt:金鑰的訪問時間戳。
logc:用於記錄金鑰的訪問頻率(即流行度值),logc值越小,訪問頻率越低,被淘汰的可能性越大,新金鑰的logc初始值為5(目的是防止新金鑰立即被淘汰, 並且對新金鑰的訪問次數可能小於未訪問的舊金鑰的訪問次數)。
LFU接入頻率(邏輯計數器)演算法實現:
#define lfu_init_val 5/* logarithmically increment a counter. the greater is the current counter value * the less likely is that it gets really implemented. saturate it at 255. */uint8_t lfulogincr(uint8_t counter)哪裡:
如果計數器小於或等於 LFU init val,則一旦訪問並命中資料,計數器的概率接近 100%,並增加 1。
如果計數器大於 lfu init val,則需要先計算兩者之間的差值,然後作為分母的一部分參與計算遞增概率。
隨著計數器值的增加,增加的概率逐漸衰減,並且多次訪問可能不會將其值增加 1。
當計數器值達到 255 時,不再執行值遞增過程。
為了適應各種業務資料的特點,Redis 在 LFU 演算法的實現中引入了兩個可調引數:
lfu-decay-time:時間衰減因子,單位為分鐘,預設值為1,該引數值越高,衰減越慢。
lfu-log-factor:遞增衰減因子,預設值為10,該引數值越高,遞增概率越低。
閱讀推薦]更多精彩內容,如:
Redis 系列。
資料結構和演算法。
NACOS系列。
MySQL系列。
JVM 系列。
卡夫卡系列。
併發程式設計系列。
請移至【南秋】個人主頁參考。 內容不斷更新。
關於作者]熱愛科技、熱愛生活的老寶貝,專注J**A領域,關注【南秋同學】帶你一起學習、共同成長