新聞中心
深入剖析Redis主鍵失效原理及實現(xiàn)機制

創(chuàng)新互聯(lián)是專業(yè)的懷柔網(wǎng)站建設(shè)公司,懷柔接單;提供成都網(wǎng)站設(shè)計、成都網(wǎng)站建設(shè)、外貿(mào)網(wǎng)站建設(shè),網(wǎng)頁設(shè)計,網(wǎng)站設(shè)計,建網(wǎng)站,PHP網(wǎng)站建設(shè)等專業(yè)做網(wǎng)站服務(wù);采用PHP框架,可快速的進行懷柔網(wǎng)站開發(fā)網(wǎng)頁制作和功能擴展;專業(yè)做搜索引擎喜愛的網(wǎng)站,專業(yè)的做網(wǎng)站團隊,希望更多企業(yè)前來合作!
Redis作為一款高性能的鍵值對存儲系統(tǒng),被廣泛應(yīng)用于緩存、消息隊列、分布式鎖等場景,在使用Redis的過程中,我們經(jīng)常會遇到鍵值對失效的情況,那么Redis中主鍵失效的原理是什么?又是如何實現(xiàn)的呢?本文將深入剖析Redis主鍵失效的原理及實現(xiàn)機制。
Redis主鍵失效原理
在Redis中,主鍵失效主要分為兩種情況:自然失效和手動失效。
1、自然失效
自然失效是指鍵值對在Redis中存儲的時間超過了設(shè)定的過期時間,Redis會自動刪除這些鍵值對,自然失效的實現(xiàn)依賴于Redis的過期鍵清理策略。
Redis的過期鍵清理策略主要有以下幾種:
(1)惰性刪除:在客戶端訪問鍵時,檢查鍵是否已經(jīng)過期,如果已經(jīng)過期,則刪除該鍵,并返回空,這種策略的優(yōu)點是操作簡單,缺點是內(nèi)存使用效率不高,可能會出現(xiàn)大量過期鍵占用內(nèi)存的情況。
(2)定時刪除:Redis內(nèi)部維護一個定時任務(wù),按照一定的頻率掃描數(shù)據(jù)庫中的鍵,刪除過期的鍵,這種策略可以有效地清理過期鍵,但會增加CPU的負擔(dān)。
(3)定期刪除:定期刪除是定時刪除的優(yōu)化版本,它將定時掃描調(diào)整為周期性掃描,每次掃描只處理部分鍵,從而降低CPU的負擔(dān)。
2、手動失效
手動失效是指通過DEL命令或其他相關(guān)命令手動刪除鍵值對,這種情況下,鍵值對會立即失效。
Redis主鍵失效實現(xiàn)機制
下面我們將從源碼角度分析Redis主鍵失效的實現(xiàn)機制。
1、自然失效實現(xiàn)機制
(1)惰性刪除實現(xiàn)
在Redis中,惰性刪除主要在db.c文件中的lookupKey函數(shù)中實現(xiàn):
robj *lookupKey(redisDb *db, robj *key, int flags) {
dictEntry *de = dictFind(db->dict, key->ptr);
if (de) {
robj *val = dictGetVal(de);
if (expireIfNeeded(db, key) == 0) {
/* If we are in the context of a Lua script, we return the
* value without checking if we need to propagate the expired
* key to AOF / slaves. */
if (server.lua_caller) return val;
if (flags & LOOKUP_NOTOUCH) {
/* This is a read-only lookup, don't touch the key */
} else {
/* Update the access time for the ageing algorithm.
* Don't do it if we have a saving child, as this will trigger
* a copy on write madness. */
if (!hasActiveChildProcess())
updateKeyAccessTime(key);
}
return val;
} else {
/* Key expired. If we are in the context of a script, it is up to
* the script to handle the key expiration. Otherwise, we return
* NULL to the caller, who should handle the key expiration
* properly. */
if (server.lua_caller) return NULL;
}
} else {
/* No key */
}
return NULL;
}
在這個函數(shù)中,如果找到了鍵,會調(diào)用expireIfNeeded函數(shù)檢查鍵是否過期,如果過期,刪除鍵并返回0。
(2)定時刪除和定期刪除實現(xiàn)
定時刪除和定期刪除的實現(xiàn)主要在redis.c文件中的activeExpireCycle函數(shù)中:
void activeExpireCycle(int type) {
/* This function has some global state in order to continue the work
* incrementally across calls. */
static unsigned int current_db = 0; /* Last DB tested. */
static int timelimit_exit = 0; /* Time limit hit in previous call? */
static long long last_fast_cycle = 0; /* When last fast cycle ran. */
int j, iteration = 0;
int dbs_per_call = CRON_DBS_PER_CALL;
long long start = ustime(), timelimit;
if (type == ACTIVE_EXPIRE_CYCLE_FAST) {
if (start < last_fast_cycle + ACTIVE_EXPIRE_CYCLE_FAST_DURATION)
return;
last_fast_cycle = start;
}
/* We usually should test CRON_DBS_PER_CALL per iteration, with
* two exceptions:
*
* 1) Don't test more DBs than we have.
* 2) If last time we hit the time limit, we want to scan all DBs
* in this iteration, as there is work to do in some DB and we don't want
* expired keys to use memory for too much time. */
if (dbs_per_call > server.dbnum || timelimit_exit)
dbs_per_call = server.dbnum;
/* We can use at max ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC percentage of CPU time
* per iteration. Since this function gets called with a frequency of
* server.hz times per second, the following is the max amount of time
* we can spend in this function. */
timelimit = 1000000 * ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC / server.hz / 100;
/* Iterate over a few databases at a time. */
for (j = 0; j < dbs_per_call; j++) {
int expired;
redisDb *db = server.db+(current_db % server.dbnum);
/* Increment the DB now so we are sure if we run out of time
* in the current iteration we'll restart from the next DB. */
current_db++;
/* Continue to expire if at the end of the cycle more than 25%
* of the keys were expired. */
do {
unsigned long num, slots;
long long now, ttl_sum;
int ttl_samples;
/* If there is nothing to expire try next DB. */
if ((num = dictSize(db->dict)) == 0) {
db->avg_ttl = 0;
break;
}
/* When we have a lot of keys to expire, we get a time sample
* to check later if the CPU time is too high. */
if (num > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP)
start = ustime();
/* Here we count the number of keys that are going to be
* expired in this loop, and the number of keys we actually
* look at to see if they should be expired. */
expired = 0;
ttl_sum = 0;
ttl_samples = 0;
if (type == ACTIVE_EXPIRE_CYCLE_FAST) {
slots = dictSlots(db->dict);
/* Fast mode: Sample keys from the dictionary. */
while (slots--) {
dictEntry *de = dictGetRandomKey(db->dict);
long long ttl;
if (dictGetVal(de) == NULL) {
/*_expired++; /* Key is already logically expired. */
continue;
}
ttl = dictGetSignedIntegerVal(de) - now;
if (activeExpireCycleTryExpire(db, de, now)) {
expired++;
} else {
/* If the key is non expired, add its TTL to the sum. */
if (ttl > 0) {
ttl_sum += ttl;
ttl_samples++;
}
}
}
} else {
/* Slow mode: Check every key. */
dictEntry *de = dictGetSafeIterator(db->dict);
while ((de = dictNext(de)) != NULL) {
long long ttl;
if (dictGetVal(de) == NULL) {
expired++;
continue;
}
ttl = dictGetSignedIntegerVal(de) - now;
if (activeExpireCycleTryExpire(db, de, now)) {
expired++;
} else {
/* If the key is non expired, add its TTL to the sum. */
if (ttl > 0) {
ttl_sum += ttl;
ttl_samples++;
}
}
}
dictReleaseIterator(de);
}
/* Update the average TTL stats for this DB. */
if (ttl_samples) {
long long avg_ttl = ttl_sum / ttl_samples;
/* Do a simple running average with a few samples.
* We just use the current estimate if the previous one
* was zero, otherwise we combine the two. */
if (db->avg_ttl == 0) {
db->avg_ttl = avg_ttl;
} else {
db->avg_ttl = (db->avg_ttl/2) + (avg_ttl/2);
}
}
/* We can't block forever here even if there are many keys to
* expire. So after a given amount of milliseconds return to the
* caller waiting for the other active expire cycle. */
if (ustime() - start > timelimit) {
timelimit_exit = 1;
break;
}
} while (expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP/4);
}
/* Update our estimate of keys existing but yet to be expired. */
updateKeyspaceEvents();
/* We don't repeat the cycle if there are less than 25% of keys to
* expire in the DB we just handled, however if we exited because of the
* time limit, we'll try again later
網(wǎng)站題目:Redis中主鍵失效的原理及實現(xiàn)機制剖析
當(dāng)前路徑:http://m.5511xx.com/article/coegpgd.html


咨詢
建站咨詢
