Day 14 最後那 5 筆沒掉,是 AOF 救回來的。
RDB 是隔一段時間拍一張照,AOF 比較像記帳:每一筆寫入指令照順序記下來,重開的時候從頭再跑一遍。
開一個全新的 Redis 試試看(在主機的終端機下,跑完會自己刪掉):
docker run --rm redis:7-alpine sh -c 'redis-server --daemonize yes > /dev/null && sleep 1 && redis-cli CONFIG GET appendonly'

我們的是 yes,因為 Day 2 的 docker-compose 有加這一行:
command: redis-server --appendonly yes # AOF 持久化
所以 Day 14 其實一直是 RDB 和 AOF 兩套一起在跑。
先清乾淨(BGREWRITEAOF 等等會講,先照打,在主機的終端機下):
docker exec redis30days redis-cli FLUSHALL
docker exec redis30days redis-cli BGREWRITEAOF
docker exec redis30days ls -la /data/appendonlydir

Redis 7 的 AOF 是一個資料夾、三個檔案:
| 檔案 | 放什麼 |
|---|---|
.base.rdb |
某個時間點的完整資料 |
.incr.aof |
那之後的每一筆寫入 |
.manifest |
目錄,記現在該讀哪兩個檔 |
寫一筆,看 incr 多了什麼(在主機的終端機下):
docker exec redis30days redis-cli -n 1 SET hello world
docker exec redis30days sh -c 'cat /data/appendonlydir/*.incr.aof'

跳過 * 跟 $ 開頭的行(Redis 自己的記號),剩下的就是 SELECT 1(切到 db1)跟剛剛打的 SET hello world。AOF 記的就是指令本身。
記帳的問題是帳本只會越來越厚。計數器加 10 萬次(在主機的終端機下):
for i in $(seq 1 100000); do echo "INCR counter"; done | docker exec -i redis30days redis-cli -n 1 --pipe
docker exec redis30days ls -la /data/appendonlydir

counter 現在是 100000,檔案裡卻記了 10 萬行 INCR(2.6MB)。其實一行 SET counter 100000 就夠了。
BGREWRITEAOF 就是在做這件事:照現在的資料重新產一份,不管之前是怎麼改過來的(在主機的終端機下):
docker exec redis30days redis-cli BGREWRITEAOF
docker exec redis30days ls -la /data/appendonlydir

base 從 89 變 121 bytes(裝進了 hello 跟 counter = 100000),incr 從 2.6MB 清成 0,整個 AOF 從 2.6MB 瘦到 121 bytes。檔名的編號也跳了一號,舊的那組被刪掉了。
平常不用自己下,預設會自動重寫(在主機的終端機下):
docker exec redis30days redis-cli CONFIG GET 'auto-aof-rewrite-*'

檔案超過 64MB 而且比上次重寫完大一倍(.rdb + .aof),就自動重寫一次。重寫也是 fork 一個副本去做,跟 Day 14 的 BGSAVE 一樣。
base 的副檔名是 .rdb,看一下開頭(在主機的終端機下):
docker exec redis30days sh -c 'head -c 9 /data/appendonlydir/*.base.rdb; echo; head -c 9 /data/dump.rdb; echo'

跟 Day 14 的 dump.rdb 一模一樣,因為 base 本身就是一份 RDB(aof-use-rdb-preamble 預設 yes)。
再寫一筆,然後重開看 log(在主機的終端機下):
docker exec redis30days redis-cli -n 1 INCR counter # 這筆只在 incr 裡
docker restart redis30days
docker logs --tail 9 redis30days

先讀 RDB,把 hello 跟 counter 兩個 key 載回來,再跑 incr 補上剛剛那筆 INCR,counter 就是 100001。RDB 載入快,AOF 掉得少,Redis 7 預設就是兩個一起用。
「寫進檔案」其實有兩步:先交給作業系統的緩衝區,作業系統再找時間寫到硬碟。fsync 是叫它現在馬上寫。還在緩衝區的時候斷電,一樣會掉。
appendfsync 決定多久 fsync 一次:
| 設定 | 什麼時候寫進硬碟 | 最壞掉多少 |
|---|---|---|
always |
每一次寫入,寫完才回 OK | 不掉 |
everysec(預設) |
每秒一次 | 一秒左右 |
no |
交給作業系統 | Linux 通常是 30 秒 |
always 一筆都不掉,那為什麼不開?拿 Day 3 的壓測比一下(在主機的終端機下):
docker exec redis30days redis-cli CONFIG SET appendfsync always
docker exec redis30days redis-benchmark -t set -n 100000 -q
docker exec redis30days redis-cli CONFIG SET appendfsync everysec
docker exec redis30days redis-benchmark -t set -n 100000 -q


每秒吞吐量從 19 萬掉到 3.7 萬,慢了 5 倍多。每一筆都要等硬碟寫完才能回,Day 3 說的「在記憶體裡所以快」等於沒了。這就是為什麼預設是 everysec。
dump.rdb,但這時候 AOF 裡什麼都沒有。正確順序是先下 CONFIG SET appendonly yes,讓 Redis 把現有的資料寫進 AOF,再去改設定檔。BGSAVE 一樣要 fork。 資料越多,fork 那一下主行程卡越久;重寫期間寫入越多,多吃的記憶體越多。而且自動重寫不會先問你。持久化解決的是「重開之後資料還在」,沒解決「這台機器整個掛了」。明天來看主從複製,多開一台 Redis 當備份![]()