Redis過期時(shí)間的設(shè)計(jì)與實(shí)現(xiàn)代碼
1. 設(shè)置過期時(shí)間
Redis 提供了多個(gè)命令來設(shè)置鍵的過期時(shí)間,如 EXPIRE、PEXPIRE、EXPIREAT 和 PEXPIREAT。這些命令可以以秒或毫秒為單位設(shè)置鍵的過期時(shí)間,也可以設(shè)置具體的過期時(shí)間點(diǎn)。
EXPIRE key secondsPEXPIRE key millisecondsEXPIREAT key timestampPEXPIREAT key milliseconds-timestamp
示例:
void expireCommand(client *c) {
long long seconds;
if (getLongLongFromObjectOrReply(c, c->argv[2], &seconds, NULL) != C_OK)
return;
setExpire(c, c->db, c->argv[1], mstime() + seconds*1000);
addReply(c, shared.cone);
}
2. 過期鍵的存儲(chǔ)結(jié)構(gòu)
每個(gè) Redis 數(shù)據(jù)庫實(shí)例(redisDb)中都有一個(gè)名為 expires 的字典,用于存儲(chǔ)鍵的過期時(shí)間。這個(gè)字典將鍵指針映射到以毫秒為單位的到期時(shí)間點(diǎn)。
typedef struct redisDb {
dict *dict; // 主字典,存儲(chǔ)所有鍵值對
dict *expires; // 過期字典,存儲(chǔ)鍵的過期時(shí)間
...
} redisDb;
3. 設(shè)置過期時(shí)間
通過 setExpire 函數(shù)設(shè)置鍵的過期時(shí)間。如果鍵已經(jīng)存在于 expires 字典中,則更新其過期時(shí)間;否則,將其添加到 expires 字典中。
void setExpire(client *c, redisDb *db, robj *key, long long when) {
dictEntry *de = dictFind(db->dict, key->ptr);
if (de == NULL) return;
/* Set the new expire time */
if (dictAdd(db->expires, dictGetKey(de), (void*)when) == DICT_ERR) {
dictReplace(db->expires, dictGetKey(de), (void*)when);
}
}
4. 刪除過期鍵的策略
Redis 采用了以下三種策略來刪除過期鍵:
- 惰性刪除(Lazy Deletion) :每次訪問鍵時(shí)檢查其是否過期,如果已過期則刪除。這樣只在訪問鍵時(shí)才進(jìn)行過期檢查,節(jié)省了資源。
robj *lookupKeyRead(redisDb *db, robj *key) {
robj *val;
expireIfNeeded(db,key); // 檢查并刪除過期鍵
val = lookupKey(db,key,LOOKUP_NONE);
return val ? val : NULL;
}
- 定期刪除(Periodic Deletion) :Redis 會(huì)周期性地隨機(jī)抽取一定數(shù)量的鍵進(jìn)行過期檢查,并刪除其中已過期的鍵。這一過程由后臺(tái)任務(wù)定期執(zhí)行,確保盡可能多的過期鍵被及時(shí)刪除。
int activeExpireCycle(int type) {
unsigned int current_db = server.dbnum;
long long start = ustime();
long long timelimit = 1000000; // 1秒
int dbs_per_call = CRON_DBS_PER_CALL;
current_db = server.current_db;
while(dbs_per_call--) {
redisDb *db = server.db + (current_db % server.dbnum);
activeExpireCycleTryExpire(db, cycle_tickets);
current_db++;
}
long long elapsed = ustime()-start;
return elapsed > timelimit;
}
- 主動(dòng)刪除(Active Expiration) :在內(nèi)存使用接近最大限制時(shí),會(huì)觸發(fā)主動(dòng)刪除策略,通過掃描所有庫的鍵刪除過期數(shù)據(jù),以確保內(nèi)存使用量保持在設(shè)定范圍內(nèi)。
void evictExpiredKeys() {
for (int j = 0; j < server.dbnum; j++) {
redisDb *db = server.db+j;
scanDatabaseForExpiredKeys(db);
}
}
Redis 默認(rèn)采用以下兩種刪除過期鍵策略:
惰性刪除(Lazy Deletion) :每次訪問某個(gè)鍵時(shí)檢查其是否過期,如果過期則刪除。
定期刪除(Periodic Deletion) :后臺(tái)任務(wù)定期掃描數(shù)據(jù)庫中的鍵,隨機(jī)抽取部分鍵進(jìn)行過期檢查并刪除其中已過期的鍵。
5. 檢查并刪除過期鍵
expireIfNeeded 函數(shù)用于檢查某個(gè)鍵是否過期,如果過期則刪除該鍵。
int expireIfNeeded(redisDb *db, robj *key) {
mstime_t when = getExpire(db, key);
if (when < 0) return 0;
if (mstime() > when) {
server.stat_expiredkeys++;
propagateExpire(db,key);
dbDelete(db,key);
return 1;
} else {
return 0;
}
}
getExpire:從expires字典中獲取鍵的過期時(shí)間。mstime:返回當(dāng)前的毫秒時(shí)間戳。- 如果鍵已過期,則調(diào)用
dbDelete刪除該鍵,并增加統(tǒng)計(jì)計(jì)數(shù)器stat_expiredkeys。
6. 獲取過期時(shí)間
getExpire 函數(shù)用于獲取鍵的過期時(shí)間,如果鍵沒有設(shè)置過期時(shí)間則返回 -1。
mstime_t getExpire(redisDb *db, robj *key) {
dictEntry *de;
if (dictSize(db->expires) == 0 ||
(de = dictFind(db->expires, key->ptr)) == NULL) return -1;
return (mstime_t)dictGetSignedIntegerVal(de);
}
總結(jié)
Redis 的過期時(shí)間設(shè)計(jì)與實(shí)現(xiàn)包括以下幾個(gè)關(guān)鍵點(diǎn):
設(shè)置過期時(shí)間:通過 EXPIRE、PEXPIRE 等命令設(shè)置鍵的過期時(shí)間,并將過期時(shí)間存儲(chǔ)在
expires字典中。過期字典:每個(gè)數(shù)據(jù)庫實(shí)例都有一個(gè)
expires字典,用于存儲(chǔ)鍵的過期時(shí)間。刪除策略:
- 惰性刪除:每次訪問鍵時(shí)檢查其是否過期,如果已過期則刪除。
- 定期刪除:通過后臺(tái)任務(wù)周期性地檢測并刪除過期鍵。
- 主動(dòng)刪除:在內(nèi)存使用接近最大限制時(shí)觸發(fā),掃描所有鍵并刪除過期鍵。
定期刪除activeExpireCycle函數(shù)詳細(xì)解析
void activeExpireCycle(int type) {
static unsigned int current_db = 0; // 記錄上一次處理的數(shù)據(jù)庫索引
static int timelimit_exit = 0; // 用于指示是否超出時(shí)間限制
unsigned int j;
// 每次要處理的數(shù)據(jù)庫數(shù)量
unsigned int dbs_per_call = CRON_DBS_PER_CALL;
long long start = ustime(); // 開始時(shí)間
long long timelimit; // 時(shí)間限制
if (type == ACTIVE_EXPIRE_CYCLE_FAST) {
/* Fast cycle: 1 ms */
timelimit = 1000;
} else {
/* Slow cycle: 25% CPU time p. DB / Configurable percentage. */
timelimit = server.hz < 100 ? 1000 : 10;
if (server.active_expire_effort != 1)
timelimit *= server.active_expire_effort-1;
timelimit /= server.dbnum;
timelimit_exit = 0;
}
for (j = 0; j < dbs_per_call; j++) {
redisDb *db = server.db + (current_db % server.dbnum);
current_db++;
int expired, sampled;
do {
long now = mstime();
expireEntry *de;
dictEntry *d;
/* Sample a few keys in the database */
expired = 0;
sampled = 0;
while ((de = dictGetRandomKey(db->expires)) != NULL &&
mstime() - now < timelimit) {
long long ttl = dictGetSignedIntegerVal(de) - mstime();
if (ttl < 0) {
d = dictFind(db->dict, dictGetKey(de));
dbDelete(db, dictGetKey(d));
server.stat_expiredkeys++;
expired++;
}
sampled++;
}
} while (expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP / 2);
elapsed = ustime() - start;
if (elapsed > timelimit) {
timelimit_exit = 1;
break;
}
}
}
關(guān)鍵步驟解析
1. 初始化變量
static unsigned int current_db = 0; static int timelimit_exit = 0; unsigned int j; unsigned int dbs_per_call = CRON_DBS_PER_CALL; long long start = ustime(); long long timelimit;
current_db:靜態(tài)變量,用于記錄上一次處理的數(shù)據(jù)庫索引。timelimit_exit:用于指示是否耗盡了時(shí)間配額,防止無限循環(huán)。dbs_per_call:每次掃描的數(shù)據(jù)庫數(shù)量,通常由配置決定。start:記錄開始執(zhí)行此函數(shù)的時(shí)間戳。timelimit:本次調(diào)用允許消耗的最大時(shí)間(以微秒為單位)。
2. 確定時(shí)間限制
if (type == ACTIVE_EXPIRE_CYCLE_FAST) {
timelimit = 1000; // 快速模式:1 毫秒
} else {
timelimit = server.hz < 100 ? 1000 : 10;
if (server.active_expire_effort != 1)
timelimit *= server.active_expire_effort - 1;
timelimit /= server.dbnum;
timelimit_exit = 0;
}
- 如果是快速模式,時(shí)間限制為 1 毫秒。
- 如果是慢速模式,時(shí)間限制根據(jù) Redis 配置和當(dāng)前服務(wù)器負(fù)載情況計(jì)算。
server.active_expire_effort參數(shù)可以調(diào)整過期鍵清理的力度。
3. 遍歷數(shù)據(jù)庫
每次調(diào)用 activeExpireCycle 時(shí),會(huì)遍歷一定數(shù)量的數(shù)據(jù)庫,并在每個(gè)數(shù)據(jù)庫中隨機(jī)抽取鍵進(jìn)行過期檢查和刪除。
for (j = 0; j < dbs_per_call; j++) {
redisDb *db = server.db + (current_db % server.dbnum);
current_db++;
int expired, sampled;
do {
long now = mstime();
expireEntry *de;
dictEntry *d;
expired = 0;
sampled = 0;
while ((de = dictGetRandomKey(db->expires)) != NULL &&
mstime() - now < timelimit) {
long long ttl = dictGetSignedIntegerVal(de) - mstime();
if (ttl < 0) {
d = dictFind(db->dict, dictGetKey(de));
dbDelete(db, dictGetKey(d));
server.stat_expiredkeys++;
expired++;
}
sampled++;
}
} while (expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP / 2);
elapsed = ustime() - start;
if (elapsed > timelimit) {
timelimit_exit = 1;
break;
}
}
關(guān)鍵點(diǎn)解析:
選擇數(shù)據(jù)庫:
redisDb *db = server.db + (current_db % server.dbnum); current_db++;
使用循環(huán)方式選擇下一個(gè)要檢查的數(shù)據(jù)庫實(shí)例。current_db 記錄上一次處理的數(shù)據(jù)庫索引,通過取模操作確保索引在有效范圍內(nèi)。
初始化變量:
int expired, sampled; expired = 0; sampled = 0;
過期檢查循環(huán):
while ((de = dictGetRandomKey(db->expires)) != NULL && mstime() - now < timelimit) {
long long ttl = dictGetSignedIntegerVal(de) - mstime();
if (ttl < 0) {
d = dictFind(db->dict, dictGetKey(de));
dbDelete(db, dictGetKey(d));
server.stat_expiredkeys++;
expired++;
}
sampled++;
}
- 從
expires字典中隨機(jī)獲取一個(gè)鍵de。 - 檢查當(dāng)前時(shí)間是否超過了本次周期的時(shí)間限制
timelimit。 - 如果鍵已經(jīng)過期(
ttl < 0),則刪除該鍵,并增加已過期鍵的計(jì)數(shù)器expired。 - 增加已檢查鍵的計(jì)數(shù)器
sampled。
- 從
多輪過期檢查:
do {
...
} while (expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP / 2);
如果在一輪檢查中刪除的過期鍵數(shù)量超過預(yù)設(shè)值的一半,則繼續(xù)下一輪檢查。
4. 時(shí)間限制檢查
在每次處理完一個(gè)數(shù)據(jù)庫后,檢查是否超出時(shí)間限制:
elapsed = ustime() - start;
if (elapsed > timelimit) {
timelimit_exit = 1;
break;
}
- 計(jì)算已耗費(fèi)的時(shí)間
elapsed。 - 如果已耗費(fèi)時(shí)間超過
timelimit,設(shè)置timelimit_exit為 1 并跳出循環(huán)。
總結(jié)
Redis 的 activeExpireCycle 函數(shù)通過以下步驟實(shí)現(xiàn)定期刪除過期鍵:
- 初始化變量并確定時(shí)間限制:根據(jù)當(dāng)前的模式(快速或慢速)和配置參數(shù),計(jì)算本次函數(shù)調(diào)用的時(shí)間限制。
- 遍歷數(shù)據(jù)庫:循環(huán)遍歷一定數(shù)量的數(shù)據(jù)庫。
- 過期檢查與刪除:從每個(gè)數(shù)據(jù)庫中隨機(jī)抽取鍵,檢查其是否過期并進(jìn)行刪除,直到達(dá)到時(shí)間限制或刪除了一定數(shù)量的過期鍵。
- 時(shí)間限制檢查:確保函數(shù)不會(huì)超出規(guī)定的時(shí)間配額,以避免影響 Redis 的其他操作。
AOF、RDB和復(fù)制功能對過期鍵的處理
在 Redis 中,AOF(Append Only File)、RDB(Redis DataBase)和復(fù)制(Replication)功能對過期鍵的處理方式有所不同。下面詳細(xì)介紹這些機(jī)制如何處理過期鍵:
AOF 持久化
記錄過期時(shí)間:
- 在 AOF 文件中,除了寫入每個(gè)鍵值的設(shè)置操作外,還會(huì)寫入
EXPIRE或PEXPIRE命令來記錄鍵的過期時(shí)間。
- 在 AOF 文件中,除了寫入每個(gè)鍵值的設(shè)置操作外,還會(huì)寫入
重寫(Rewrite)過程:
- 當(dāng) AOF 文件需要重寫時(shí),Redis 會(huì)檢查每個(gè)鍵,如果鍵已過期,則不會(huì)將其寫入新的 AOF 文件。
加載 AOF 文件:
- 當(dāng) Redis 重啟并加載 AOF 文件時(shí),會(huì)執(zhí)行文件中的所有命令,包括設(shè)置鍵值和設(shè)置過期時(shí)間的命令。如果某些鍵已經(jīng)過期,這些鍵會(huì)立即被刪除。
RDB 持久化
保存快照:
- 當(dāng) Redis 創(chuàng)建 RDB 快照時(shí),它會(huì)將所有鍵及其剩余的過期時(shí)間一起保存到快照文件中。
加載快照:
- 當(dāng) Redis 從 RDB 文件恢復(fù)數(shù)據(jù)時(shí),會(huì)載入所有鍵值對,同時(shí)載入它們的過期時(shí)間。如果某個(gè)鍵在載入時(shí)已經(jīng)過期,Redis 會(huì)立即將其刪除。
復(fù)制(Replication)
主從同步:
- 在主從復(fù)制架構(gòu)中,主節(jié)點(diǎn)會(huì)將過期鍵的刪除操作傳播給從節(jié)點(diǎn)。
- 如果一個(gè)鍵在主節(jié)點(diǎn)上過期并被刪除,主節(jié)點(diǎn)會(huì)向從節(jié)點(diǎn)發(fā)送
DEL操作,從而在從節(jié)點(diǎn)上也刪除該鍵。
延遲過期:
- 從節(jié)點(diǎn)可能因?yàn)榫W(wǎng)絡(luò)延遲等原因,對過期鍵的處理會(huì)稍有滯后,但最終主從節(jié)點(diǎn)的數(shù)據(jù)將保持一致。
總結(jié)
AOF:
- 通過記錄
EXPIRE/PEXPIRE命令來處理過期時(shí)間。 - 在重寫過程中跳過已過期的鍵。
- 加載時(shí)刪除已過期的鍵。
- 通過記錄
RDB:
- 將過期時(shí)間與鍵值一起保存。
- 加載時(shí)立即刪除已過期的鍵。
復(fù)制:
- 主節(jié)點(diǎn)將刪除過期鍵的操作同步到從節(jié)點(diǎn)。
- 確保主從節(jié)點(diǎn)數(shù)據(jù)的一致性。
以上就是Redis過期時(shí)間的設(shè)計(jì)與實(shí)現(xiàn)代碼的詳細(xì)內(nèi)容,更多關(guān)于Redis過期時(shí)間的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Redis分布式鎖如何設(shè)置超時(shí)時(shí)間
這篇文章主要介紹了Redis分布式鎖如何設(shè)置超時(shí)時(shí)間,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11
Redis?中使用?list,streams,pub/sub?幾種方式實(shí)現(xiàn)消息隊(duì)列的問題
這篇文章主要介紹了Redis?中使用?list,streams,pub/sub?幾種方式實(shí)現(xiàn)消息隊(duì)列,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-03-03
Redis的Python客戶端redis-py安裝使用說明文檔
這篇文章主要介紹了Redis的Python客戶端redis-py安裝使用說明文檔,本文講解了安裝方法、入門使用實(shí)例、API參考和詳細(xì)說明,需要的朋友可以參考下2015-06-06
Spring boot+redis實(shí)現(xiàn)消息發(fā)布與訂閱的代碼
這篇文章主要介紹了Spring boot+redis實(shí)現(xiàn)消息發(fā)布與訂閱,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值需要的朋友可以參考下2020-04-04
redis?sentinel監(jiān)控高可用集群實(shí)現(xiàn)的配置步驟
這篇文章主要介紹了redis?sentinel監(jiān)控高可用集群實(shí)現(xiàn)的配置步驟,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04
redis.clients.jedis.exceptions.JedisDataException異常的錯(cuò)誤解決
本文主要介紹了redis.clients.jedis.exceptions.JedisDataException異常的錯(cuò)誤解決,這個(gè)異常通常發(fā)生在嘗試連接到一個(gè)?Redis?服務(wù)器時(shí),客戶端發(fā)送了一個(gè)?AUTH?命令來驗(yàn)證密碼,但是沒有配置密碼驗(yàn)證,下來就來解決一下2024-05-05
Spring?Boot?3.0x的Redis?分布式鎖的概念和原理
Redis?分布式鎖是一種基于?Redis?的分布式鎖解決方案,它的原理是利用?Redis?的原子性操作實(shí)現(xiàn)鎖的獲取和釋放,從而保證共享資源的獨(dú)占性,這篇文章主要介紹了適合?Spring?Boot?3.0x的Redis?分布式鎖,需要的朋友可以參考下2024-08-08

