Redis 持久化設定:RDB 與 AOF#
為什麼需要 Redis 持久化?#
因為 Redis 是一個 in-memory 的資料庫 所以當 Redis 服務重啟時,所有資料都會消失
不過可以透過持久化設定 讓 Redis 在重啟時,能夠恢復狀態 !
Redis 持久化設定#
Redis 提供了兩種持久化設定 分別是 RDB 與 AOF
- RDB:Redis DatabaseRDB 會在指定的間隔時間內執行對資料庫的快照 (snapshot)。
- AOF:Append Only FileAOF 會將伺服器接收到的每個寫入操作記錄下來。這些操作可以在伺服器啟動時重新執行,以重建原始資料集。
- RDB+AOF:RDB 與 AOF可以同時啟用 RDB 與 AOF 持久化,以提供更強大的資料保護機制。
RDB: Redis Database#
簡而言之,RDB
需要 fork()
一個子 process 來建立資料庫的快照到磁碟上。
RDB
的優點#
RDB
其實只是一個非常緊湊的檔案,能夠表示你在某一時間點的 Redis 快照。
- 省空間:RDB 比 AOF 更節省空間
- 備份更簡單:RDB 更容易備份和恢復
因為它是一個單文件,可以把它複製到任何存儲,如 S3、Google Cloud Storage 等。
- 對於大 datasets 能夠更快恢復:與
AOF
相比,RDB
在擁有大數據集時能夠更快地恢復
RDB
的缺點#
- 數據丟失:如果你需要最小化數據丟失,
RDB
並不是一個好的選擇因為它只在指定的間隔時間保存數據集
- 不適合大數據集:
RDB
不適合大數據集因為它需要
fork()
一個子進程來將數據集保存到磁碟
如果你有一個大的數據集,fork()
會很慢
可能會停止服務客戶端數毫秒甚至數秒!
RDB
的詳細實作#
我們提到 RDB
需要 fork()
一個子進程來將數據集保存到磁碟。
讓我們來看看 Redis 源碼中 RDB
的g6yji4。
從
redis/src/rdb.c
:定義了bgsaveCommand
命令入口函數。3907 3908 3909 3910 3911 3912 3913 3914 3915 3916 3917 3918 3919 3920 3921 3922 3923 3924 3925 3926 3927 3928 3929 3930 3931 3932 3933 3934 3935 3936 3937 3938 3939 3940 3941 3942
/* BGSAVE [SCHEDULE] */ void bgsaveCommand(client *c) { int schedule = 0; /* The SCHEDULE option changes the behavior of BGSAVE when an AOF rewrite * is in progress. Instead of returning an error a BGSAVE gets scheduled. */ if (c->argc > 1) { if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"schedule")) { schedule = 1; } else { addReplyErrorObject(c,shared.syntaxerr); return; } } rdbSaveInfo rsi, *rsiptr; rsiptr = rdbPopulateSaveInfo(&rsi); if (server.child_type == CHILD_TYPE_RDB) { addReplyError(c,"Background save already in progress"); } else if (hasActiveChildProcess() || server.in_exec) { if (schedule || server.in_exec) { server.rdb_bgsave_scheduled = 1; addReplyStatus(c,"Background saving scheduled"); } else { addReplyError(c, "Another child process is active (AOF?): can't BGSAVE right now. " "Use BGSAVE SCHEDULE in order to schedule a BGSAVE whenever " "possible."); } } else if (rdbSaveBackground(SLAVE_REQ_NONE,server.rdb_filename,rsiptr,RDBFLAGS_NONE) == C_OK) { addReplyStatus(c,"Background saving started"); } else { addReplyErrorObject(c,shared.err); } }
而
bgsaveCommand
實際上會調用rdbSaveBackground
。rdbSaveBackground
會檢查是否已經有子 process 在運行,如果沒有,會調用redisFork
來 fork 一個子進程將數據集保存到磁碟。1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646
int rdbSaveBackground(int req, char *filename, rdbSaveInfo *rsi, int rdbflags) { pid_t childpid; if (hasActiveChildProcess()) return C_ERR; server.stat_rdb_saves++; server.dirty_before_bgsave = server.dirty; server.lastbgsave_try = time(NULL); if ((childpid = redisFork(CHILD_TYPE_RDB)) == 0) { int retval; /* Child */ redisSetProcTitle("redis-rdb-bgsave"); redisSetCpuAffinity(server.bgsave_cpulist); retval = rdbSave(req, filename,rsi,rdbflags); if (retval == C_OK) { sendChildCowInfo(CHILD_INFO_TYPE_RDB_COW_SIZE, "RDB"); } exitFromChild((retval == C_OK) ? 0 : 1); } else { /* Parent */ if (childpid == -1) { server.lastbgsave_status = C_ERR; serverLog(LL_WARNING,"Can't save in background: fork: %s", strerror(errno)); return C_ERR; } serverLog(LL_NOTICE,"Background saving started by pid %ld",(long) childpid); server.rdb_save_time_start = time(NULL); server.rdb_child_type = RDB_CHILD_TYPE_DISK; return C_OK; } return C_OK; /* unreached */ }
由於 fork()
是一個system call ,它會將 parent process 的整個 memory複製到子進程。這就是為什麼當你有一個大數據集時,fork()
會很慢!
Redis 有使用 CoW (Copy-on-Write) 來優化
fork()
此外,如果你有一個大數據集並且剩餘記憶體有限,這可能會導致 OOM (Out of Memory)。
AOF: Append Only File#
AOF
利用 fsync()
system call 將每次寫操作保存到磁碟。
就像關聯數據庫中的 WAL (Write-Ahead Logging)
fsync
在後台執行,以避免阻塞 Redis 主事件循環。
AOF
有三種 fsync
策略:
always
:每次寫操作後都進行fsync()
everysec
:每秒進行一次fsync()
(默認)no
:只有在明確調用fsync()
時才進行這是更快但不太安全的方法。
只需將數據交給操作系統處理。通常 Linux 在這種配置下會每 30 秒刷新數據,但這取決於內核的具體調整。
AOF
重寫:
- Redis 將在後台重寫
AOF
文件,避免文件變得過大。 serverCron
會檢查AOF
文件是否過大,如果是,會調用rewriteAppendOnlyFileBackground
在後台重寫AOF
文件。
類似於 RDB,
AOF
重寫 也需要fork()
一個子進程將數據集保存到磁碟。
當你有一個大數據集時,可能會遇到與RDB
相同的問題。
AOF
的優點#
- 丟失較少數據:如果你需要最小化數據丟失,
AOF
更好因為它將每次寫操作保存到磁碟
AOF
的缺點#
- 文件大小較大:
AOF
文件通常比相應的RDB
文件大 - 比
RDB
慢(取決於fsync()
策略):- 默認的
everysec
策略仍然非常高效! - 但如果你設置
always
策略,它會比RDB
慢,因為它需要在每次寫操作後進行fsync()
- 默認的
Reference#
- redis: RDB and AOF
- Redis source code explanation
- Linux :
fork()
andfsync()