
寫在前面:要想深入了解redis,《redis設(shè)計(jì)與實(shí)現(xiàn)(第二版)》這本書(shū)是很不錯(cuò)的選擇;關(guān)于redis的底層存儲(chǔ)結(jié)構(gòu)會(huì)在下一篇文章中寫,本章切掉這部分內(nèi)容了。
一、redis 持久化方案(RDB、AOF)
1、RDB(默認(rèn)持久化方案)
服務(wù)運(yùn)行時(shí)將內(nèi)存文件保存到.rdb 文件中,重啟時(shí):讀取數(shù)據(jù)存儲(chǔ)文件.rdb,加載到內(nèi)存中,還原DB。
- 保存文件時(shí),rdb文件保存會(huì)阻塞主線程;解決方法:rdbbackgroud 方法后臺(tái)運(yùn)行
int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) {
pid_t childpid;
long long start;
if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) return C_ERR;
if ((childpid = fork()) == 0) {
int retval;
/* Child */
closeListeningSockets(0);
redisSetProcTitle("redis-rdb-bgsave");
retval = rdbSave(filename,rsi);
.............省略一部分
/* Parent */
server.stat_fork_time = ustime()-start;
server.stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / server.stat_fork_time / (1024*1024*1024); /* GB per second. */
latencyAddSampleIfNeeded("fork",server.stat_fork_time/1000);
if (childpid == -1) {
closeChildInfoPipe();
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 %d",childpid);
server.rdb_save_time_start = time(NULL);
server.rdb_child_pid = childpid;
server.rdb_child_type = RDB_CHILD_TYPE_DISK;
updateDictResizePolicy();
return C_OK;
}
return C_OK; /* unreached */
}
- 啟動(dòng)時(shí)會(huì)讀取rdb文件到內(nèi)存中,載入過(guò)程中,不支持其他請(qǐng)求,下面的這段代碼值得好好看一下
/* Load an RDB file from the rio stream 'rdb'. On success C_OK is returned,
* otherwise C_ERR is returned and 'errno' is set accordingly. */
int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi) {
rdb->update_cksum = rdbLoadProgressCallback;
rdb->max_processing_chunk = server.loading_process_events_interval_bytes;
while(1) {
。。。 省略一部分代碼
/* Read type. */
if ((type = rdbLoadType(rdb)) == -1) goto eoferr;
/* Handle special types. */
if (type == RDB_OPCODE_EXPIRETIME) {
/* EXPIRETIME: load an expire associated with the next key
* to load. Note that after loading an expire we need to
* load the actual type, and continue. */
if ((expiretime = rdbLoadTime(rdb)) == -1) goto eoferr;
/* We read the time so we need to read the object type again. */
if ((type = rdbLoadType(rdb)) == -1) goto eoferr;
/* the EXPIRETIME opcode specifies time in seconds, so convert
* into milliseconds. */
expiretime *= 1000;
} else if (type == RDB_OPCODE_EXPIRETIME_MS) {
/* EXPIRETIME_MS: milliseconds precision expire times introduced
* with RDB v3. Like EXPIRETIME but no with more precision. */
if ((expiretime = rdbLoadMillisecondTime(rdb)) == -1) goto eoferr;
/* We read the time so we need to read the object type again. */
if ((type = rdbLoadType(rdb)) == -1) goto eoferr;
} else if (type == RDB_OPCODE_EOF) {
/* EOF: End of file, exit the main loop. */
break;
}
decrRefCount(auxkey);
decrRefCount(auxval);
continue; /* Read type again. */
}
。。。 省略一部分代碼
}
rdb 文件保存時(shí)間點(diǎn),在redis.conf中進(jìn)行配置 '
save <seconds> <changes>'
################################ SNAPSHOTTING ################################
#
# Save the DB on disk:
#
# save <seconds> <changes>
#
# Will save the DB if both the given number of seconds and the given
# number of write operations against the DB occurred.
#
# In the example below the behaviour will be to save:
# after 900 sec (15 min) if at least 1 key changed
# after 300 sec (5 min) if at least 10 keys changed
# after 60 sec if at least 10000 keys changed
#
# Note: you can disable saving completely by commenting out all "save" lines.
#
# It is also possible to remove all the previously configured save
# points by adding a save directive with a single empty string argument
# like in the following example:
#
# save ""
save 900 1 //900秒保存一個(gè)key
save 300 10 //300秒保存10個(gè)key
save 60 10000 //60秒保存10000個(gè)key
2、AOF
aof是一個(gè)協(xié)議文本方式的存儲(chǔ)方案,對(duì)數(shù)據(jù)庫(kù)的操作命令記錄到aof文件中,達(dá)到記錄記錄數(shù)據(jù)的目的(有點(diǎn)類似MySQL的bin log)
寫入流程:
- 1、將請(qǐng)求命令轉(zhuǎn)換為網(wǎng)絡(luò)協(xié)議文本方式
- 2、將協(xié)議文本內(nèi)容寫到server.aof_buf
- 3、達(dá)到某種條件的時(shí)候,講aof_buf寫入到磁盤中
- AOF_FSYNC_NO 不保存只會(huì)追加到cof_buf 文件中,在緩存寫滿的情況下,會(huì)保存;正常關(guān)閉redis,會(huì)保存;AOF功能關(guān)閉,會(huì)保存。
- AOF_FSYNC_ALWAYS 每次執(zhí)行一條命令都會(huì)進(jìn)行一次保存操作
- AOF_FSYNC_EVERYSEC 每秒保存一次,默認(rèn)配置后臺(tái)執(zhí)行,不會(huì)阻塞住進(jìn)程。
上面的常量,也是在redis.conf 中配置aof的參數(shù)。 默認(rèn)appendfsync no
aof的出現(xiàn)是為了解決什么問(wèn)題?官方說(shuō)明:rds 這種方案本來(lái)已經(jīng)是一種很好的方案了,能夠適應(yīng)絕大多數(shù)的情況,但是在redis進(jìn)程或電源發(fā)生故障的情況下,可能會(huì)造成小部份的數(shù)據(jù)丟失,這取決于配置的保存時(shí)間點(diǎn);
Appendonly是一種能夠提供非常好的持久化的模式,例如使用默認(rèn)的Fsync方案,redis能在發(fā)生服務(wù)器電源故障或操作系統(tǒng)仍然正常運(yùn)行但redis進(jìn)程莫名掛掉的情況下,只丟失1秒的數(shù)據(jù)。 aof與rdb模式可以同時(shí)啟用,這并不沖突。如果aof是可用的,那redis啟動(dòng)時(shí)將自動(dòng)加載aof,這個(gè)文件能夠提供更好的持久性保障。
原文:
############################## APPEND ONLY MODE ###############################
# By default Redis asynchronously dumps the dataset on disk. This mode is
# good enough in many applications, but an issue with the Redis process or
# a power outage may result into a few minutes of writes lost (depending on
# the configured save points).
#
# The Append Only File is an alternative persistence mode that provides
# much better durability. For instance using the default data fsync policy
# (see later in the config file) Redis can lose just one second of writes in a
# dramatic event like a server power outage, or a single write if something
# wrong with the Redis process itself happens, but the operating system is
# still running correctly.
#
# AOF and RDB persistence can be enabled at the same time without problems.
# If the AOF is enabled on startup Redis will load the AOF, that is the file
# with the better durability guarantees.
#
# Please check http://redis.io/topics/persistence for more information.
- AOF保存數(shù)據(jù)的還原:講協(xié)議文本轉(zhuǎn)換為命令執(zhí)行,從而還原數(shù)據(jù)。
AOF VS RDB
- AOF 文件通常會(huì)大于RDB文件
- AOF 的效率取決于配置策略
- RDB 設(shè)置過(guò)大,操作耗時(shí)過(guò)多,可能會(huì)出現(xiàn)短時(shí)間無(wú)法響應(yīng)的情況;設(shè)置過(guò)小,也會(huì)有IO瓶頸
二、redis 常用數(shù)據(jù)結(jié)構(gòu)
- string: key value 服務(wù);應(yīng)用:一般用于緩存
- set: 集合去重,集合的交、并、差操作;應(yīng)用: 可以用作共同、二度好友的計(jì)算
- sortset: 帶有排序的set集合; 應(yīng)用:取Top10、消息隊(duì)列、設(shè)置權(quán)重
- hash: 結(jié)構(gòu)化數(shù)據(jù)的存儲(chǔ)、通常用于對(duì)象屬性的更新;(結(jié)構(gòu)化數(shù)據(jù)存儲(chǔ)建議使用hash,效率更高)
- list: 鏈表 ,pop push,也可以用作隊(duì)列
- pub/sub: 消息系統(tǒng),key的發(fā)布和訂閱(也可以用作定時(shí)任務(wù))
三、redis的啟動(dòng)流程分析
任何一個(gè)組件,包括redis,MySQL或者說(shuō)servlet的啟動(dòng)過(guò)程都是有著很多的相似之處,對(duì)比學(xué)習(xí)是一個(gè)很好的學(xué)習(xí)方法;
redis 啟動(dòng)過(guò)程包括兩個(gè)主要操作:初始化服務(wù)器配置、初始化功能模塊
初始化服務(wù)器配置,主要做以下事情
- 初始化server變量、設(shè)置redis默認(rèn)值
- 讀取配置文件,接受參數(shù),替換服務(wù)器默認(rèn)配置
- 初始化功能模塊
- 1 注冊(cè)信號(hào)事件
- 2 初始化客戶端連接
- 3 初始共享對(duì)象
- 4 監(jiān)測(cè)最大連接數(shù)
- 5 初始化DB
- 6 初始化network
- 7 初始化服務(wù)器實(shí)時(shí)統(tǒng)計(jì)
- 8 初始化后臺(tái)后臺(tái)計(jì)劃任務(wù)
- 9 初始化lua腳本
- 10 初始化慢查詢?nèi)罩?/li>
- 11 初始化后臺(tái)線程任務(wù)系統(tǒng)
- 從RDB或者AOF重載數(shù)據(jù)(磁盤數(shù)據(jù)加載內(nèi)存)
- 網(wǎng)絡(luò)監(jiān)聽(tīng)服務(wù)啟動(dòng)前的準(zhǔn)備操作
- 開(kāi)啟事件監(jiān)聽(tīng)、開(kāi)始接受客戶端請(qǐng)求
下面是轉(zhuǎn)的一個(gè)流程圖,整個(gè)過(guò)程表達(dá)的很清晰
