Redis 設計與實現(第九章) -- 持久化RBD
概述
Redis為內存數據庫,即所有的鍵值對信息保存在內存中,那麽一旦服務器出現問題重啟,內存中的數據就會沒有了。所以Redis需要實現持久化,將內存中的數據持久化到硬盤,在重新啟動後,又將硬盤中的數據加載到內存中。
RDB文件生成與載入
- 有兩個命令可用於生成RDB文件,save和bgsave:
Save:save命令會阻斷服務器進程,直到RDB文件生成,在阻塞期間,Redis服務器不會出現任何請求。
bgsave:bgsave命令會派生出一個子進程,然後由子進程來創建RDB文件,服務器進程繼續出現請求命令。
- Redis中RDB文件載入沒有專門的命令,只要在redis服務器重啟的過程中,檢測到rdb文件的存在就會自動加載。(這裏先忽略AOF的情況,因為在AOF開啟的情況下,服務器會優先加載AOF文件,因為AOF的更新頻率通常比較RDB的高)
- 命令執行的服務器狀態
- SAVE命令會阻塞服務器的請求,也就是保存期間,如果服務器有新的請求過來,不會去處理請求直到RDB文件生成;
- BGSAVE命令,會fork一個子進程出來,所以服務器能夠處理其他請求命令;
- 如果在執行BGSAVE命令時,客戶端再次執行SAVE,BGSAVE或BGREWRITEAOF呢?
- 不能同時執行,避免競爭條件發生。
- 在RDB文件載入時,所有請求均處於阻塞狀態。
我們不可能每次手動去調用命令來持久化,Redis自身肯定是有一套策略的,什麽時候進行持久化。
默認情況下,redis在下列條件滿足其一,BGSAVE命令就會執行(可通過配置修改):
- 服務器在900秒內對數據庫進行了至少一次修改;
- 服務器在300秒內對數據庫進行了至少10次修改;
- 服務器在60秒內對數據庫進行了至少10000次修改;
可以看一下數據結構:
在redisServer的數據結構中,有個屬性用於記錄保存條件的修改:
/* RDB persistence */ long long dirty; /* Changes to DB from the last save */ long long dirty_before_bgsave; /* Used to restore dirty on failed BGSAVE */ pid_t rdb_child_pid;/* PID of RDB saving child */ struct saveparam *saveparams; /* Save points array for RDB */
再看下saveparam的數據結構:
struct saveparam { time_t seconds; //時間 int changes; //修改數 };
此外,redisServer中還有兩個屬性dirty和lastsave:
/* RDB persistence */ long long dirty; /* Changes to DB from the last save,上次保存命令執行後,數據庫的修改次數 */ long long dirty_before_bgsave; /* Used to restore dirty on failed BGSAVE */ pid_t rdb_child_pid; /* PID of RDB saving child */ struct saveparam *saveparams; /* Save points array for RDB */ int saveparamslen; /* Number of saving points */ char *rdb_filename; /* Name of RDB file */ int rdb_compression; /* Use compression in RDB? */ int rdb_checksum; /* Use RDB checksum? */ time_t lastsave; /* Unix time of last successful save ,上次成功保存的時間*/
RedisServer會定期執行serverCron(100ms一次),每次執行時,會檢測之前默認的條件是否滿足,如果滿足條件則執行bgsave命令。程序會遍歷saveparam中的數組,判斷是否滿足條件。
/* If there is not a background saving/rewrite in progress check if(之前會判斷是否有AOF或BSAVE命令正在執行) * we have to save/rewrite now */ for (j = 0; j < server.saveparamslen; j++) { struct saveparam *sp = server.saveparams+j; /* Save if we reached the given amount of changes, * the given amount of seconds, and if the latest bgsave was * successful or if, in case of an error, at least * REDIS_BGSAVE_RETRY_DELAY seconds already elapsed. */ if (server.dirty >= sp->changes && server.unixtime-server.lastsave > sp->seconds && (server.unixtime-server.lastbgsave_try > REDIS_BGSAVE_RETRY_DELAY || server.lastbgsave_status == REDIS_OK)) { redisLog(REDIS_NOTICE,"%d changes in %d seconds. Saving...", sp->changes, (int)sp->seconds); rdbSaveBackground(server.rdb_filename); break; } }
查看odb文件,如下,set 一個msgkey的內容為aaa,rdb文件如下:
redis 127.0.0.1:6379> keys *
(empty list or set)
redis 127.0.0.1:6379> set msg aaa
redis 127.0.0.1:6379> BGSAVE
Background saving started
[[email protected] bin]# od -c dump.rdb
0000000 R E D I S 0 0 0 6 376 \0 \0 003 m s g
0000020 003 a a a 377 241 362 266 \f Z ` 030 265
0000035
0000000 R E D I S 0 0 0 6 :代表RDB文件標誌及版本
376 \0 :切換到數據庫0
\0 :類型,代表字符串類型
003 m s g:003代表key長度,msg為鍵
003 a a a:003代表內容長度,aaa代表值
377:代表EOF
後面的241.。。。。265:代表校驗和
可以再繼續看一個切換到db 1的list集合
redis 127.0.0.1:6379> select 1
OK
redis 127.0.0.1:6379[1]> rpush listkey "aa" "ccc"
(integer) 2
redis 127.0.0.1:6379[1]> BGSAVE
Background saving started
redis 127.0.0.1:6379[1]>
You have mail in /var/spool/mail/root
[[email protected] bin]# od -c dump.rdb
0000000 R E D I S 0 0 0 6 376 001 \n \a l i s
0000020 t k e y 024 024 \0 \0 \0 016 \0 \0 \0 002 \0 \0
0000040 002 a a 004 003 c c c 377 377 } y 232 302 X h
0000060 g &
0000062
Redis 設計與實現(第九章) -- 持久化RBD