Day 12 的結論是過期的 key 不一定馬上被刪掉。那如果刪不夠快,記憶體真的滿了會怎樣?
很多人以為 Redis 會自己清掉舊的然後撐下去。但預設不會,滿了就直接讓寫入報錯。
0 是不設限)evicted_keys(累計被淘汰掉幾個 key)先看預設值:

0 是不設限,Redis 會一路吃到被作業系統殺掉。noeviction 是不淘汰,寫不下就報錯。
給一個 50MB 的上限,再灌 10 萬筆 1KB 的資料進去。以下 redis-cli 都在 db1 操作:
CONFIG SET maxmemory 50mb
灌資料這兩行在主機的終端機下,不是在 redis-cli 裡面(V 是 1000 個 x,讓每一筆大約 1KB,內容不重要):
V=$(printf 'x%.0s' $(seq 1 1000))
for i in $(seq 1 100000); do echo "SET cache:$i $V"; done | docker exec -i redis30days redis-cli -n 1 --pipe

10 萬筆只寫進去 46,660 筆,剩下的 53,340 筆整排被拒絕:

INFO stats 的 evicted_keys 是 0,一個 key 都沒被清掉。Redis 寧可不收新的,也不會動已經存進去的東西。
不用背,這邊就是「從誰裡面挑」乘上「怎麼挑」:
| 所有 key | 只挑有設 TTL 的 | |
|---|---|---|
| 最久沒用(LRU) | allkeys-lru |
volatile-lru |
| 最少被用(LFU) | allkeys-lfu |
volatile-lfu |
| 隨機挑 | allkeys-random |
volatile-random |
| 剩下 TTL 最短 | — | volatile-ttl |
七種加上預設不淘汰的 noeviction,就是常聽到的那八種。
資料不動、Redis 不重開,只改一個設定:
CONFIG SET maxmemory-policy allkeys-lru
再灌一次一樣的 10 萬筆:


這次一筆都沒被拒絕,evicted_keys 從 0 變成 53,431。
同樣的資料、同樣裝不下,差別只在 Redis 是拒收新的還是偷偷丟掉舊的。兩邊裝得下的量幾乎一樣(46,660 對 46,637),上限擋的是總容量,不是筆數。
兩個各自看一件事,而且看得到。先在 LRU 底下建兩個 key,隔二十幾秒只讀 hot
SET cold "iamcold"
SET hot "iamhot"
# 等二十幾秒,中間只讀 hot
GET hot
OBJECT IDLETIME hot # 3
OBJECT IDLETIME cold # 24

OBJECT IDLETIME 就是 LRU 的判斷依據:多久沒被碰過。換成 LFU 之後同樣這兩個 key,讀 hot 十次:
CONFIG SET maxmemory-policy allkeys-lfu
OBJECT FREQ hot # 6
OBJECT FREQ cold # 0

讀十次不是變 10,OBJECT FREQ 取的是對數,不是真的次數,而且久沒被讀會慢慢掉回來。
兩個各有各的盲點:
OBJECT FREQ 從 5 起跳而不是 0,就是在補這個洞。選了 volatile-*,但沒有任何 key 設過 TTL,行為等同 noeviction。 沒有候選人可以淘汰,就只能報錯。
清空重來,同樣 50MB、同樣灌 10 萬筆,這次用 volatile-lru:
FLUSHDB
CONFIG RESETSTAT # evicted_keys 歸零
CONFIG SET maxmemory-policy volatile-lru
結果是 errors: 53151、evicted_keys: 0,跟第一個實驗一模一樣。

原因在這裡:

46,849 個 key,設了 TTL 的是 0 個。這個 expires 就是 Day 12 定期刪除抽樣的那個池子,volatile-* 挑要丟誰也是從這裡挑,一樣是空的。
把灌資料那行改成 SET cache:$i $V EX 600,設定一個字都不用改,errors 就會變 0。
Day 4 說每個快取 key 都要設 TTL,當時的理由是不設就永遠佔著記憶體。現在多一條:不設的話,選好的淘汰策略根本不會生效。
allkeys-* 的話所有 key 都是候選人,跟快取擠在同一台的設定檔、白名單這種本來就該一直在的資料也會被丟掉。測完記得還原,不然後面幾天會莫名其妙寫不進去:
CONFIG SET maxmemory 0
CONFIG SET maxmemory-policy noeviction
FLUSHALL
上面這些都還在記憶體裡,容器一重開就全沒了。明天來看 RDB 怎麼把整包資料拍成快照存到硬碟上,還有存檔的那一瞬間為什麼會卡住所有連線![]()