用Redis時(shí),有用到EXPIRE、PEXPIRE、SETEX之類命令去設(shè)置key的過期時(shí)間。從2.8版開始還可以去做簡(jiǎn)單的定時(shí)器服務(wù)。
原先也沒太具體去了解Redis的過期實(shí)現(xiàn)方式,但心中總覺得有塊石頭沒放下,于是乎開3.0的源碼去翻看了。
過期策略有3種:立即過期、定時(shí)過期、訪問過期(惰性過期)。但看了源碼后,發(fā)現(xiàn)Redis并沒采用立即過期的策略,而是采用 定時(shí)過期 和 訪問過期 混合方式。使用立即過期的話,Linux環(huán)境下在Redis進(jìn)程里會(huì)有很多timerfd,在幾十萬個(gè)Key這種數(shù)量級(jí)開始,對(duì)cpu是很大的負(fù)擔(dān)。
redis.h
在redis.h文件里有redisDb結(jié)構(gòu)體
typedef struct redisDb {
dict *dict;
dict *expires;
......
}
expires這屬性是存放當(dāng)前db有過期時(shí)間的鍵,使用hash數(shù)據(jù)結(jié)構(gòu)。
定期刪除策略
代碼在redis.c的activeExpireCycle函數(shù),需要傳個(gè)type參數(shù),用以區(qū)分是用“快速模式”還是“正常模式”。
在“正常模式”下,會(huì)遍歷每一個(gè)編號(hào)下的庫(kù),然后最多隨機(jī)獲取20個(gè)帶過期時(shí)間的key(20是宏ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP的默認(rèn)值),倘若key太多則直接return了。接著調(diào)用redis.c/activeExpireCycleTryExpire函數(shù)嘗試去刪除它,能刪除成功則再發(fā)送expired的pub通知給訂閱者即可。定期刪除的定時(shí)時(shí)長(zhǎng)是100ms。
惰性過期
代碼分布在所有讀寫命令里,如SET、GET、TTL、SADD、HGET...等。每次調(diào)用這些命令的實(shí)際執(zhí)行前,都會(huì)調(diào)用db.c/expireIfNeeded函數(shù)來刪除過期鍵,在刪除之前,也會(huì)發(fā)送expired的pub通知。
EXPIRE/PEXPIRE/SETEX的作用?
這幾個(gè)命令的真正作用是設(shè)置過期時(shí)間,如EXPIRE命令的處理在db.c/expireCommand開始,然后將過期時(shí)間單位從秒轉(zhuǎn)換成毫秒,接著判斷是否存在從節(jié)點(diǎn),最后傳播刪除了的expired key(pub del)。在主節(jié)點(diǎn)則設(shè)置過期時(shí)間,是在宏代碼里去設(shè)置值(聯(lián)合體結(jié)構(gòu)),最后在pub expire的通知。
后記
redis是單線程的,但個(gè)人覺得在pub方面可以用另一條線程去處理。