Redis系列1:深刻理解高效能Redis的本質
Redis系列2:資料持久化提高可用性
Redis系列3:高可用之主從架構
Redis系列4:高可用之Sentinel(哨兵模式)
Redis系列5:深入分析Cluster 叢集模式
追求效能極致:Redis6.0的多執行緒模型
追求效能極致:使用者端快取帶來的革命
Redis系列8:Bitmap實現億萬級資料計算
Redis系列9:Geo 型別賦能億級地圖位置計算
Redis系列10:HyperLogLog實現海量資料基數統計
Redis系列11:記憶體淘汰策略
Redis系列12:Redis 的事務機制
Redis系列13:分散式鎖實現
Redis系列14:使用List實現訊息佇列
Redis系列15:使用Stream實現訊息佇列
Redis系列16:聊聊布隆過濾器(原理篇)
Redis系列17:聊聊布隆過濾器(實踐篇)
Redis系列18:過期資料的刪除策略
上一期我們介紹了 Redis系列18:過期資料的刪除策略 ,但是無論是惰性刪除還是定期刪除,都可能存在刪除不盡的情況,無法刪除完全,比如每次刪除完過期的 key 還是超過 25%,且這些 key 再也不會被使用者端存取。
這樣的話,定期刪除和墮性刪除可能都徹底的清理掉。如果這種情況長時間持續下去,可能會導致記憶體耗盡,所以Redis必須有一個完善的記憶體淘汰機制來保障。這就是我們這一篇的重點,Redis記憶體自動淘汰機制。
在 redis 中總共由8種淘汰策略,預設的淘汰策略是 noeviction。
noeviction不淘汰策略(預設) | |||
淘汰資料策略 | 設定過期時間的淘汰策略 | valatile-random | 隨機淘汰演演算法 |
volatile-ttl | 淘汰失效時間最短的key | ||
volatile-lru | 刪除最近最少使用的key | ||
volatile-lfu | 刪除存取次數最少的key | ||
所有資料的淘汰策略 | allkeys-lru | 刪除最近最少使用的key(全部) | |
allkeys-lfu | 刪除存取次數最少的key(全部) | ||
allkey-random | 隨機淘汰演演算法(全部) |
volatile-ttl、volatile-random、volatile-lru、volatile-lfu 這4種策略淘汰的資料範圍為設定了過期時間的資料。
allkeys-lru、allkeys-random、allkeys-lfu 這3種淘汰策略無論是否設定了過期時間,記憶體不足時都會進行淘汰。
也就是說無論它的過期時間到沒到,都有可能被刪除。
這邊以LRU演演算法為例子講解,它的全稱是 Least Rencently Used,即將最近最久未使用的演演算法進行資料淘汰。
我們這邊以圖例來講解,整個過程如下:
這邊注意,LRU 更新和新增資料都發生在連結串列首,刪除資料都發生在連結串列尾。
水果 Orange 跟 Pitaya 被存取,被移動到MRU端,新增的Mango也被插入到MRU端。而最末端的Olive則被刪除。
以下是使用Go語言實現Redis LRU淘汰過程的範例程式碼,程式碼註釋很清楚:
package main
import (
"container/list"
"fmt"
"time"
)
type Redis struct {
data map[string]*list.Element // 儲存快取項的鍵和值,以及它們在LRU連結串列中的位置
lru *list.List // LRU連結串列
}
type cacheItem struct {
key string
value string
// 記錄該快取項最後一次被存取的時間
lastAccess time.Time
}
func NewRedis() *Redis {
return &Redis{
data: make(map[string]*list.Element),
lru: list.New(),
}
}
func (r *Redis) Get(key string) (string, bool) {
// 從LRU連結串列中查詢快取項
if elem, ok := r.data[key]; ok {
// 將該快取項移動到連結串列頭部,表示最近被存取過
r.lru.MoveToFront(elem)
// 更新快取項的最後存取時間
item := elem.Value.(*cacheItem)
item.lastAccess = time.Now()
return item.value, true
}
return "", false
}
func (r *Redis) Set(key string, value string) {
// 從LRU連結串列中查詢快取項
if elem, ok := r.data[key]; ok {
// 如果快取項存在,更新其值和最後存取時間,並將其移動到連結串列頭部
item := elem.Value.(*cacheItem)
item.value = value
item.lastAccess = time.Now()
r.lru.MoveToFront(elem)
return
}
// 如果快取項不存在,建立新的快取項並將其新增到LRU連結串列頭部
item := &cacheItem{
key: key,
value: value,
lastAccess: time.Now(),
}
elem := r.lru.PushFront(item)
r.data[key] = elem
// 如果快取空間已滿,執行LRU淘汰操作
for r.lru.Len() > maxItems {
// 從連結串列尾部查詢最久未被存取的快取項
elem := r.lru.Back()
item := elem.Value.(*cacheItem)
// 如果該快取項的過期時間已到達,則從連結串列中刪除該快取項
if item.lastAccess.Add(expireTime).Before(time.Now()) {
r.lru.Remove(elem)
delete(r.data, item.key)
} else {
// 否則,只從連結串列中刪除該快取項
r.lru.Remove(elem)
}
}
}
在這個範例中,我們使用了一個map來儲存快取項的鍵和值,以及它們在LRU連結串列中的位置。我們使用了一個LRU連結串列來儲存快取項,並按照存取時間將它們排序。在Get方法中,我們從LRU連結串列中查詢快取項,並將其移動到連結串列頭部,表示最近被存取過。在Set方法中,如果快取項已存在,我們更新其值和最後存取時間,並將其移動到連結串列頭部;如果快取項不存在,我們建立新的快取項並將其新增到LRU連結串列頭部。如果快取空間已滿,我們執行LRU淘汰操作,從連結串列尾部查詢最久未被存取的快取項,並從連結串列中刪除它。注意,我們還檢查了快取項的過期時間,如果該快取項已過期,則也會從連結串列中刪除它。
第4小節基本來自baidu文心一言的組織,非常感謝。
這一篇我們介紹了Redis的幾種記憶體淘汰策略,並且詳細分析了LRU演演算法的實現原理。下一篇我們分析下 LFU 演演算法。