Redis中鍵的過(guò)期刪除策略深入講解
如果一個(gè)鍵過(guò)期了,那么它什么時(shí)候會(huì)被刪除呢?
這個(gè)問(wèn)題有三種可能的答案,它們分別代表了三種不同的刪除策略:
- 定時(shí)刪除:在設(shè)置鍵的過(guò)期時(shí)間的同時(shí),創(chuàng)建一個(gè)定時(shí)器( timer ). 讓定時(shí)器在鍵的過(guò)期時(shí)間來(lái)臨時(shí),立即執(zhí)行對(duì)鍵的刪除操作。
- 惰性刪除:放任鍵過(guò)期不管,但是每次從鍵空間中獲取鍵時(shí),都檢查取得的鍵是否過(guò)期,如果過(guò)期的話,就刪除該鍵;如果沒(méi)有過(guò)期,就返回該鍵。
- 定期刪除: 每隔一段時(shí)間,程序就對(duì)數(shù)據(jù)庫(kù)進(jìn)行一次檢查,刪除里面的過(guò)期鍵。至于要?jiǎng)h除多少過(guò)期鍵,以及要檢查多少個(gè)數(shù)據(jù)庫(kù), 則由算法決定。
在這三種策略中,第一種和第三種為主動(dòng)刪除策略, 而第二種則為被動(dòng)刪除策略。
前言
使用Redis時(shí)我們可以使用EXPIRE或EXPIREAT命令給key設(shè)置過(guò)期刪除時(shí)間,結(jié)構(gòu)體redisDb中的expires字典保存了所有key的過(guò)期時(shí)間,這個(gè)字典(dict)的key是一個(gè)指針,指向redis中的某個(gè)key對(duì)象,過(guò)期字典的value是一個(gè)保存過(guò)期時(shí)間的整數(shù)。
/* Redis database representation. There are multiple databases identified * by integers from 0 (the default database) up to the max configured * database. The database number is the 'id' field in the structure. */ typedef struct redisDb { dict *dict; /* The keyspace for this DB */ dict *expires; /* 過(guò)期字典*/ dict *blocking_keys; /* Keys with clients waiting for data (BLPOP) */ dict *ready_keys; /* Blocked keys that received a PUSH */ dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */ struct evictionPoolEntry *eviction_pool; /* Eviction pool of keys */ int id; /* Database ID */ long long avg_ttl; /* Average TTL, just for stats */ } redisDb;
設(shè)置過(guò)期時(shí)間
不論是EXPIRE,EXPIREAT,還是PEXPIRE,PEXPIREAT,底層的具體實(shí)現(xiàn)是一樣的。在Redis的key空間中找到要設(shè)置過(guò)期時(shí)間的這個(gè)key,然后將這個(gè)entry(key的指針,過(guò)期時(shí)間)加入到過(guò)期字典中。
void setExpire(redisDb *db, robj *key, long long when) { dictEntry *kde, *de; /* Reuse the sds from the main dict in the expire dict */ kde = dictFind(db->dict,key->ptr); redisAssertWithInfo(NULL,key,kde != NULL); de = dictReplaceRaw(db->expires,dictGetKey(kde)); dictSetSignedIntegerVal(de,when); }
過(guò)期刪除策略
如果一個(gè)key過(guò)期了,何時(shí)會(huì)被刪除呢?在Redis中有兩種過(guò)期刪除策略:(1)惰性過(guò)期刪除;(2)定期刪除。接下來(lái)具體看看。
惰性過(guò)期刪除
Redis在執(zhí)行任何讀寫(xiě)命令時(shí)都會(huì)先找到這個(gè)key,惰性刪除就作為一個(gè)切入點(diǎn)放在查找key之前,如果key過(guò)期了就刪除這個(gè)key。
robj *lookupKeyRead(redisDb *db, robj *key) { robj *val; expireIfNeeded(db,key); // 切入點(diǎn) val = lookupKey(db,key); if (val == NULL) server.stat_keyspace_misses++; else server.stat_keyspace_hits++; return val; }
定期刪除
key的定期刪除會(huì)在Redis的周期性執(zhí)行任務(wù)(serverCron,默認(rèn)每100ms執(zhí)行一次)中進(jìn)行,而且是發(fā)生Redis的master節(jié)點(diǎn),因?yàn)閟lave節(jié)點(diǎn)會(huì)通過(guò)主節(jié)點(diǎn)的DEL命令同步過(guò)來(lái)達(dá)到刪除key的目的。
依次遍歷每個(gè)db(默認(rèn)配置數(shù)是16),針對(duì)每個(gè)db,每次循環(huán)隨機(jī)選擇20個(gè)(ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP)key判斷是否過(guò)期,如果一輪所選的key少于25%過(guò)期,則終止迭次,此外在迭代過(guò)程中如果超過(guò)了一定的時(shí)間限制則終止過(guò)期刪除這一過(guò)程。
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 DB we'll restart from the next. This allows to * distribute the time evenly across DBs. */ 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; /* 如果該db沒(méi)有設(shè)置過(guò)期key,則繼續(xù)看下個(gè)db*/ if ((num = dictSize(db->expires)) == 0) { db->avg_ttl = 0; break; } slots = dictSlots(db->expires); now = mstime(); /* When there are less than 1% filled slots getting random * keys is expensive, so stop here waiting for better times... * The dictionary will be resized asap. */ if (num && slots > DICT_HT_INITIAL_SIZE && (num*100/slots < 1)) break; /* The main collection cycle. Sample random keys among keys * with an expire set, checking for expired ones. */ expired = 0; ttl_sum = 0; ttl_samples = 0; if (num > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP) num = ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP;// 20 while (num--) { dictEntry *de; long long ttl; if ((de = dictGetRandomKey(db->expires)) == NULL) break; ttl = dictGetSignedIntegerVal(de)-now; if (activeExpireCycleTryExpire(db,de,now)) expired++; if (ttl > 0) { /* We want the average TTL of keys yet not expired. */ ttl_sum += ttl; ttl_samples++; } } /* Update the average TTL stats for this database. */ 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 with a weight of 2% * and the previous estimate with a weight of 98%. */ if (db->avg_ttl == 0) db->avg_ttl = avg_ttl; db->avg_ttl = (db->avg_ttl/50)*49 + (avg_ttl/50); } /* 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. */ iteration++; if ((iteration & 0xf) == 0) { /* 每迭代16次檢查一次 */ long long elapsed = ustime()-start; latencyAddSampleIfNeeded("expire-cycle",elapsed/1000); if (elapsed > timelimit) timelimit_exit = 1; } // 超過(guò)時(shí)間限制則退出 if (timelimit_exit) return; /* 在當(dāng)前db中,如果少于25%的key過(guò)期,則停止繼續(xù)刪除過(guò)期key */ } while (expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP/4); }
總結(jié)
惰性刪除:讀寫(xiě)之前判斷key是否過(guò)期
定期刪除:定期抽樣key,判斷是否過(guò)期
好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
Redis實(shí)戰(zhàn)之Lettuce的使用技巧詳解
Lettuce?是?Redis?的一款高級(jí)?Java?客戶端,與?Jedis?并列成為最熱門(mén)的客戶端之一,目前已成為?SpringBoot?2.0?版本默認(rèn)的?redis?客戶端。本文主要和大家講講Lettuce的使用技巧,感興趣的可以了解一下2022-12-12談?wù)凴edis分布式鎖的正確實(shí)現(xiàn)方法
這篇文章主要給大家介紹了關(guān)于Redis分布式鎖的正確實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Redis具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08Redis和springboot 整合redisUtil類的示例代碼
這篇文章主要介紹了Redis和springboot 整合redisUtil類的示例代碼,本文通過(guò)實(shí)例圖文相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12詳解Redis中Lua腳本的應(yīng)用和實(shí)踐
這篇文章主要介紹了詳解Redis中Lua腳本的應(yīng)用和實(shí)踐,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-01-01