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 1646int 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()
