為什么需要持久化?
由于Redis是一種內(nèi)存型數(shù)據(jù)庫(kù),即服務(wù)器在運(yùn)行時(shí),系統(tǒng)為其分配了一部分內(nèi)存存儲(chǔ)數(shù)據(jù),一旦服務(wù)器掛了,或者突然宕機(jī)了,那么數(shù)據(jù)庫(kù)里面的數(shù)據(jù)將會(huì)丟失,為了使服務(wù)器即使突然關(guān)機(jī)也能保存數(shù)據(jù),必須通過(guò)持久化的方式將數(shù)據(jù)從內(nèi)存保存到磁盤(pán)中。
對(duì)于進(jìn)行持久化的程序來(lái)說(shuō),數(shù)據(jù)從程序?qū)懙接?jì)算機(jī)的磁盤(pán)的流程如下:
1、客戶端發(fā)送一個(gè)寫(xiě)指令給數(shù)據(jù)庫(kù)(此時(shí)數(shù)據(jù)在客戶端的內(nèi)存)
2、數(shù)據(jù)庫(kù)接收到寫(xiě)的指令以及數(shù)據(jù)(數(shù)據(jù)此時(shí)在服務(wù)端的內(nèi)存)
3、數(shù)據(jù)庫(kù)發(fā)起一個(gè)系統(tǒng)調(diào)用,把數(shù)據(jù)寫(xiě)到磁盤(pán)(此時(shí)數(shù)據(jù)在內(nèi)核的內(nèi)存)
4、操作系統(tǒng)把數(shù)據(jù)傳輸?shù)酱疟P(pán)控制器(數(shù)據(jù)此時(shí)在磁盤(pán)緩存中)
5、磁盤(pán)控制器執(zhí)行真正寫(xiě)入數(shù)據(jù)到物理媒介的操作(如磁盤(pán))
如果只是考慮數(shù)據(jù)庫(kù)層面,數(shù)據(jù)在第三階段之后就安全了,在這個(gè)時(shí)候,系統(tǒng)調(diào)用已經(jīng)發(fā)起了,即使數(shù)據(jù)庫(kù)進(jìn)程奔潰了,系統(tǒng)調(diào)用會(huì)繼續(xù)進(jìn)行,也能順利將數(shù)據(jù)寫(xiě)入到磁盤(pán)中。 在這一步之后,在第4步內(nèi)核會(huì)將數(shù)據(jù)從內(nèi)核緩存保存到磁盤(pán)緩存中,但為了系統(tǒng)的效率問(wèn)題,默認(rèn)情況下不會(huì)太頻繁地執(zhí)行這個(gè)動(dòng)作,大概會(huì)在30s執(zhí)行一次,這就意味著如果這一步失敗了或者就在進(jìn)行這一步的時(shí)候服務(wù)器突然關(guān)機(jī)了,那么就可能會(huì)有30s的數(shù)據(jù)丟失了,這種比較普通的災(zāi)難性問(wèn)題也是需要考慮的。
POSIX API也提供了一個(gè)系統(tǒng)調(diào)用讓內(nèi)核強(qiáng)制將緩存數(shù)據(jù)寫(xiě)入到磁盤(pán)中,比較常見(jiàn)的就是fsync系統(tǒng)調(diào)用。
int fsync(int fd);
fsync函數(shù)只對(duì)由文件描述符fd指定的一個(gè)文件起作用,并且等待寫(xiě)磁盤(pán)操作結(jié)束后才返回。每次調(diào)用fsync時(shí),會(huì)初始化一個(gè)寫(xiě)操作,然后把緩沖區(qū)的數(shù)據(jù)寫(xiě)入到磁盤(pán)中。fsync()函數(shù)在完成寫(xiě)操作的時(shí)候會(huì)阻塞進(jìn)程,如果其他線程也在寫(xiě)同一個(gè)文件,它也會(huì)阻塞其他線程,直到完成寫(xiě)操作。
持久化
持久化是將程序數(shù)據(jù)在持久狀態(tài)和瞬時(shí)狀態(tài)間轉(zhuǎn)換的機(jī)制。對(duì)于程序來(lái)說(shuō),程序運(yùn)行中數(shù)據(jù)是在內(nèi)存的,如果沒(méi)有及時(shí)同步寫(xiě)入到磁盤(pán),那么一旦斷電或者程序突然奔潰,數(shù)據(jù)就會(huì)丟失了,只有把數(shù)據(jù)及時(shí)同步到磁盤(pán),數(shù)據(jù)才能永久保存,不會(huì)因?yàn)殄礄C(jī)影像數(shù)據(jù)的有效性。而持久化就是將數(shù)據(jù)從程序同步到磁盤(pán)的一個(gè)動(dòng)作過(guò)程。
Redis的持久化
redis有RDB和AOF兩種持久化方式。RDB是快照文件的方式,redis通過(guò)執(zhí)行SAVE/BGSAVE命令,執(zhí)行數(shù)據(jù)的備份,將redis當(dāng)前的數(shù)據(jù)保存到*.rdb文件中,文件保存了所有的數(shù)據(jù)集合。AOF是服務(wù)器通過(guò)讀取配置,在指定的時(shí)間里,追加redis寫(xiě)操作的命令到*.aof文件中,是一種增量的持久化方式。
RDB
RDB文件通過(guò)SAVE或BGSAVE命令實(shí)現(xiàn)。 SAVE命令會(huì)阻塞Redis服務(wù)進(jìn)程,直到RDB文件創(chuàng)建完成為止。 BGSAVE命令通過(guò)fork子進(jìn)程,有子進(jìn)程來(lái)進(jìn)行創(chuàng)建RDB文件,父進(jìn)程和子進(jìn)程共享數(shù)據(jù)段,父進(jìn)程繼續(xù)提供讀寫(xiě)服務(wù),子進(jìn)程實(shí)現(xiàn)備份功能。BGSAVE階段只有在需要修改共享數(shù)據(jù)段的時(shí)候才進(jìn)行拷貝,也就是COW(Copy On Write)。SAVE創(chuàng)建RDB文件可以通過(guò)設(shè)置多個(gè)保存條件,只要其中一個(gè)條件滿足,就可以在后臺(tái)執(zhí)行SAVE操作。
SAVE和BGSAVE命令的實(shí)現(xiàn)代碼如下:
void saveCommand(client *c) {
// BGSAVE執(zhí)行時(shí)不能執(zhí)行SAVE
if (server.rdb_child_pid != -1) {
addReplyError(c,"Background save already in progress");
return;
}
rdbSaveInfo rsi, *rsiptr;
rsiptr = rdbPopulateSaveInfo(rsi);
// 調(diào)用rdbSave函數(shù)執(zhí)行備份(阻塞當(dāng)前客戶端)
if (rdbSave(server.rdb_filename,rsiptr) == C_OK) {
addReply(c,shared.ok);
} else {
addReply(c,shared.err);
}
}
/*
* BGSAVE 命令實(shí)現(xiàn) [可選參數(shù)"schedule"]
*/
void bgsaveCommand(client *c) {
int schedule = 0;
/* 當(dāng)AOF正在執(zhí)行時(shí),SCHEDULE參數(shù)修改BGSAVE的效果
* BGSAVE會(huì)在之后執(zhí)行,而不是報(bào)錯(cuò)
* 可以理解為:BGSAVE被提上日程
*/
if (c->argc > 1) {
// 參數(shù)只能是"schedule"
if (c->argc == 2 !strcasecmp(c->argv[1]->ptr,"schedule")) {
schedule = 1;
} else {
addReply(c,shared.syntaxerr);
return;
}
}
// BGSAVE正在執(zhí)行,不操作
if (server.rdb_child_pid != -1) {
addReplyError(c,"Background save already in progress");
} else if (server.aof_child_pid != -1) {
// aof正在執(zhí)行,如果schedule==1,BGSAVE被提上日程
if (schedule) {
server.rdb_bgsave_scheduled = 1;
addReplyStatus(c,"Background saving scheduled");
} else {
addReplyError(c,
"An AOF log rewriting in progress: can't BGSAVE right now. "
"Use BGSAVE SCHEDULE in order to schedule a BGSAVE whenever "
"possible.");
}
} else if (rdbSaveBackground(server.rdb_filename,NULL) == C_OK) {// 否則調(diào)用rdbSaveBackground執(zhí)行備份操作
addReplyStatus(c,"Background saving started");
} else {
addReply(c,shared.err);
}
}
有了RDB文件之后,如果服務(wù)器關(guān)機(jī)了,或者需要新增一個(gè)服務(wù)器,重新啟動(dòng)數(shù)據(jù)庫(kù)服務(wù)器之后,就可以通過(guò)載入RDB文件恢復(fù)之前備份的數(shù)據(jù)。 但是bgsave會(huì)耗費(fèi)較長(zhǎng)時(shí)間,不夠?qū)崟r(shí),會(huì)導(dǎo)致在停機(jī)的時(shí)候丟失大量數(shù)據(jù)。
AOF(Append Only File)
RDB文件保存的是數(shù)據(jù)庫(kù)的鍵值對(duì)數(shù)據(jù),AOF保存的是數(shù)據(jù)庫(kù)執(zhí)行的寫(xiě)命令。
AOF的實(shí)現(xiàn)流程有三步:
append->write->fsync
append追加命令到AOF緩沖區(qū),write將緩沖區(qū)的內(nèi)容寫(xiě)入到程序緩沖區(qū),fsync將程序緩沖區(qū)的內(nèi)容寫(xiě)入到文件。 當(dāng)AOF持久化功能處于開(kāi)啟狀態(tài)時(shí),服務(wù)器每執(zhí)行完一個(gè)命令,就會(huì)將命令以協(xié)議格式追加寫(xiě)入到redisServer結(jié)構(gòu)體的aof_buf緩沖區(qū),具體的協(xié)議這里不展開(kāi)闡述。
AOF的持久化發(fā)生時(shí)期有個(gè)配置選項(xiàng):appendfsync。該選項(xiàng)有三個(gè)值: always:所有內(nèi)容寫(xiě)入并同步到aof文件 everysec:將aof_buf緩沖區(qū)的內(nèi)容寫(xiě)入到AOF文件,如果距離上次同步AOF文件的 no:將aof_buf緩沖區(qū)中的所有內(nèi)容寫(xiě)入到AOF文件,但并不對(duì)AOF文件進(jìn)行同步,由操作系統(tǒng)決定何時(shí)進(jìn)行同步,一般是默認(rèn)情況下的30s。
AOF持久化模式每個(gè)寫(xiě)命令都會(huì)追加到AOF文件,隨著服務(wù)器不斷運(yùn)行,AOF文件會(huì)越來(lái)越大,為了避免AOF產(chǎn)生的文件太大,服務(wù)器會(huì)對(duì)AOF文件進(jìn)行重寫(xiě),將操作相同key的相同命令合并,從而減少文件的大小。
舉個(gè)例子,要保存一個(gè)員工的名字、性別等信息:
> hset employee_12345 name "hoohack"
> hset employee_12345 good_at "php"
> hset employee_12345 gender "male"
只是錄入這個(gè)哈希鍵的狀態(tài),AOF文件就需要保存三條命令,如果還有其他,比如刪除,或者更新值的操作,那命令將會(huì)更多,文件會(huì)更大,有了重寫(xiě)后,就可以適當(dāng)?shù)販p少文件的大小。
AOF重寫(xiě)的實(shí)現(xiàn)原理是先服務(wù)器中的數(shù)據(jù)庫(kù),然后遍歷數(shù)據(jù)庫(kù),找出每個(gè)數(shù)據(jù)庫(kù)中的所有鍵對(duì)象,獲取鍵值對(duì)的鍵和值,根據(jù)鍵的類型對(duì)鍵值對(duì)進(jìn)行重寫(xiě)。比如上面的例子,可以合并為下面的一條命令:
> hset employee_12345 name "hoohack" good_at "php" gender "male"。
AOF的重寫(xiě)會(huì)執(zhí)行大量的寫(xiě)入操作,Redis是單線程的,所以如果有服務(wù)器直接調(diào)用重寫(xiě),服務(wù)器就不能處理其他命令了,因此Redis服務(wù)器新起了單獨(dú)一個(gè)進(jìn)程來(lái)執(zhí)行AOF重寫(xiě)。
Redis執(zhí)行重寫(xiě)的流程:
在子進(jìn)程執(zhí)行AOF重寫(xiě)時(shí),服務(wù)端接收到客戶端的命令之后,先執(zhí)行客戶端發(fā)來(lái)的命令,然后將執(zhí)行后的寫(xiě)命令追加到AOF緩沖區(qū)中,同時(shí)將執(zhí)行后的寫(xiě)命令追加到AOF重寫(xiě)緩沖區(qū)中。 等到子進(jìn)程完成了重寫(xiě)工作后,會(huì)發(fā)一個(gè)完成的信號(hào)給服務(wù)器,服務(wù)器就將AOF重寫(xiě)緩沖區(qū)中的所有內(nèi)容追加到AOF文件中,然后原子性地覆蓋現(xiàn)有的AOF文件。
RDB和AOF的優(yōu)缺點(diǎn)
RDB持久化方式可以只通過(guò)服務(wù)器讀取數(shù)據(jù)就能加載備份中的文件到程序中,而AOF方式必須創(chuàng)建一個(gè)偽客戶端才能執(zhí)行。
RDB的文件較小,保存了某個(gè)時(shí)間點(diǎn)之前的數(shù)據(jù),適合做災(zāi)備和主從同步。
RDB備份耗時(shí)較長(zhǎng),如果數(shù)據(jù)量大,在遇到宕機(jī)的情況下,可能會(huì)丟失部分?jǐn)?shù)據(jù)。另外,RDB是通過(guò)配置使達(dá)到某種條件的時(shí)候才執(zhí)行,如果在這段時(shí)間內(nèi)宕機(jī),那么這部分?jǐn)?shù)據(jù)也會(huì)丟失。
AOF方式,在相同數(shù)據(jù)集的情況下,文件大小會(huì)比RDB方式的大。
AOF的持久化方式也是通過(guò)配置的不同,默認(rèn)配置的是每秒同步,最快的模式是同步每一個(gè)命令,最壞的方式是等待系統(tǒng)執(zhí)行fsync將緩沖同步到磁盤(pán)文件中,大部分操作系統(tǒng)是30s。通常情況下會(huì)配置為每秒同步一次,所以最多會(huì)有1s的數(shù)據(jù)丟失。
怎樣的同步方式更好?
RDB和AOF方式結(jié)合。起一個(gè)定時(shí)任務(wù),每小時(shí)備份一份服務(wù)器當(dāng)前狀態(tài)的數(shù)據(jù),以日期和小時(shí)命名,另外起一個(gè)定時(shí)任務(wù),定時(shí)刪除無(wú)效的備份文件(比如48小時(shí)之前)。AOF配置為1s一次。這樣一來(lái),最多會(huì)丟失1s的數(shù)據(jù),同時(shí)如果redis發(fā)生雪崩,也能迅速恢復(fù)為前一天的狀態(tài),不至于停止服務(wù)。
總結(jié)
Redis的持久化方案也不是一成不變的,紙上的理論還需要結(jié)合實(shí)踐成果來(lái)證明其可行性。
您可能感興趣的文章:- redis學(xué)習(xí)之RDB、AOF與復(fù)制時(shí)對(duì)過(guò)期鍵的處理教程
- Redis兩種持久化方案RDB和AOF詳解
- redis的2種持久化方案深入講解
- Linux下redis的持久化、主從同步與哨兵詳解
- redis持久化的介紹
- 通過(guò)Nginx+Tomcat+Redis實(shí)現(xiàn)持久會(huì)話
- Redis持久化RDB和AOF區(qū)別詳解