文章導航
Redis原始碼系列的初衷,是幫助我們更好地理解Redis,更懂Redis,而怎麼才能懂,光看是不夠的,建議跟著下面的這一篇,把環境搭建起來,後續可以自己閱讀原始碼,或者跟著我這邊一起閱讀。由於我用c也是好幾年以前了,些許錯誤在所難免,希望讀者能不吝指出。
曹工說Redis原始碼(1)-- redis debug環境搭建,使用clion,達到和除錯java一樣的效果
曹工說Redis原始碼(2)-- redis server 啟動過程解析及簡單c語言基礎知識補充
曹工說Redis原始碼(3)-- redis server 啟動過程完整解析(中)
曹工說Redis原始碼(4)-- 通過redis server原始碼來理解 listen 函式中的 backlog 引數
曹工說Redis原始碼(5)-- redis server 啟動過程解析,以及EventLoop每次處理事件前的前置工作解析(下)
曹工說Redis原始碼(6)-- redis server 主迴圈大體流程解析
曹工說Redis原始碼(7)-- redis server 的週期執行任務,到底要做些啥
什麼是記憶體淘汰
記憶體淘汰,和平時我們設定redis key的過期時間,不是一回事;記憶體淘汰是說,假設我們限定redis只能使用8g記憶體,現在已經使用了這麼多了(包括設定了過期時間的key和沒設過期時間的key),那,後續的set操作,還怎麼辦呢?
是不是隻能報錯了?
那不行啊,不科學吧,因為有的key,可能已經很久沒人用了,可能以後也不會再用到了,那我們是不是可以把這類key給幹掉呢?
幹掉key的過程,就是記憶體淘汰。
記憶體淘汰什麼時候啟用
當我們在配置檔案裡設定瞭如下屬性時:
# maxmemory <bytes>
預設,該屬性是被註釋掉的。
其實,這個配置項的註釋,相當有價值,我們來看看:
# Don't use more memory than the specified amount of bytes.
# When the memory limit is reached Redis will try to remove keys
# according to the eviction policy selected (see maxmemory-policy).
#
# If Redis can't remove keys according to the policy, or if the policy is
# set to 'noeviction', Redis will start to reply with errors to commands
# that would use more memory, like SET, LPUSH, and so on, and will continue
# to reply to read-only commands like GET.
#
# This option is usually useful when using Redis as an LRU cache, or to set
# a hard memory limit for an instance (using the 'noeviction' policy).
#
# WARNING: If you have slaves attached to an instance with maxmemory on,
# the size of the output buffers needed to feed the slaves are subtracted
# from the used memory count, so that network problems / resyncs will
# not trigger a loop where keys are evicted, and in turn the output
# buffer of slaves is full with DELs of keys evicted triggering the deletion
# of more keys, and so forth until the database is completely emptied.
#
# In short... if you have slaves attached it is suggested that you set a lower
# limit for maxmemory so that there is some free RAM on the system for slave
# output buffers (but this is not needed if the policy is 'noeviction').
#
# maxmemory <bytes>
渣翻譯如下:
不能使用超過指定數量bytes的記憶體。當該記憶體限制被達到時,redis會根據過期策略(eviction policy,通過引數 maxmemory-policy來指定)來驅逐key。
如果redis根據指定的策略,或者策略被設定為“noeviction”,redis會開始針對如下這種命令,回覆錯誤。什麼命令呢?會使用更多記憶體的那類命令,比如set、lpush;只讀命令還是不受影響,可以正常響應。
該選項通常在redis使用LRU快取時有用,或者在使用noeviction策略時,設定一個程式級別的記憶體limit。
記憶體淘汰策略
所謂策略,意思是,當我們要刪除部分key的時候,刪哪些,不刪哪些?是不是需要一個策略?比如是隨機刪,就像滅霸一樣?還是按照lru時間來刪,lru的策略意思就是,最近最少使用的key,將被優先刪除。
總之,我們需要定一個規則。
redis預設支援以下策略:
# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
# is reached. You can select among five behaviors:
#
# volatile-lru -> remove the key with an expire set using an LRU algorithm
# allkeys-lru -> remove any key accordingly to the LRU algorithm
# volatile-random -> remove a random key with an expire set
# allkeys-random -> remove a random key, any key
# volatile-ttl -> remove the key with the nearest expire time (minor TTL)
# noeviction -> don't expire at all, just return an error on write operations
#
# Note: with any of the above policies, Redis will return an error on write
# operations, when there are not suitable keys for eviction.
#
# At the date of writing this commands are: set setnx setex append
# incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
# sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
# zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
# getset mset msetnx exec sort
#
# The default is:
#
# maxmemory-policy noeviction
maxmemory-policy allkeys-lru
針對設定了過期時間的,使用lru演算法
# volatile-lru -> remove the key with an expire set using an LRU algorithm
針對全部key,使用lru演算法
# allkeys-lru -> remove any key accordingly to the LRU algorithm
針對設定了過期時間的,隨機刪
# volatile-random -> remove a random key with an expire set
針對全部key,隨機刪
# allkeys-random -> remove a random key, any key
針對設定了過期時間的,馬上要過期的,刪掉
# volatile-ttl -> remove the key with the nearest expire time (minor TTL)
不過期,不能寫了,就報錯
# noeviction -> don't expire at all, just return an error on write operations
一般呢,我們會設定為:
allkeys-lru
,即,針對全部key,進行lru。
原始碼實現
配置讀取
在如下結構體中,定義瞭如下欄位:
struct redisServer {
...
unsigned long long maxmemory; /* Max number of memory bytes to use */
int maxmemory_policy; /* Policy for key eviction */
int maxmemory_samples; /* Pricision of random sampling */
...
}
當我們在配置檔案中,進入如下配置時,該結構體中幾個欄位的值如下:
maxmemory 3mb
maxmemory-policy allkeys-lru
# maxmemory-samples 5 這個取了預設值
maxmemory_policy為3,是因為列舉值為3:
#define REDIS_MAXMEMORY_VOLATILE_LRU 0
#define REDIS_MAXMEMORY_VOLATILE_TTL 1
#define REDIS_MAXMEMORY_VOLATILE_RANDOM 2
#define REDIS_MAXMEMORY_ALLKEYS_LRU 3
#define REDIS_MAXMEMORY_ALLKEYS_RANDOM 4
#define REDIS_MAXMEMORY_NO_EVICTION 5
#define REDIS_DEFAULT_MAXMEMORY_POLICY REDIS_MAXMEMORY_NO_EVICTION
處理命令時,判斷是否進行記憶體淘汰
在處理命令的時候,會呼叫中的
redis.c processCommand
int processCommand(redisClient *c) {
/* The QUIT command is handled separately. Normal command procs will
* go through checking for replication and QUIT will cause trouble
* when FORCE_REPLICATION is enabled and would be implemented in
* a regular command proc. */
// 特別處理 quit 命令
void *commandName = c->argv[0]->ptr;
redisLog(REDIS_NOTICE, "The server is now processing %s", commandName);
if (!strcasecmp(c->argv[0]->ptr, "quit")) {
addReply(c, shared.ok);
c->flags |= REDIS_CLOSE_AFTER_REPLY;
return REDIS_ERR;
}
/* Now lookup the command and check ASAP about trivial error conditions
* such as wrong arity, bad command name and so forth. */
// 1 查詢命令,並進行命令合法性檢查,以及命令引數個數檢查
c->cmd = c->lastcmd = lookupCommand(c->argv[0]->ptr);
if (!c->cmd) {
// 沒找到指定的命令
flagTransaction(c);
addReplyErrorFormat(c, "unknown command '%s'",
(char *) c->argv[0]->ptr);
return REDIS_OK;
}
/* Check if the user is authenticated */
//2 檢查認證資訊
if (server.requirepass && !c->authenticated && c->cmd->proc != authCommand) {
flagTransaction(c);
addReply(c, shared.noautherr);
return REDIS_OK;
}
/* If cluster is enabled perform the cluster redirection here.
*
* 3 如果開啟了叢集模式,那麼在這裡進行轉向操作。
*
* However we don't perform the redirection if:
*
* 不過,如果有以下情況出現,那麼節點不進行轉向:
*
* 1) The sender of this command is our master.
* 命令的傳送者是本節點的主節點
*
* 2) The command has no key arguments.
* 命令沒有 key 引數
*/
if (server.cluster_enabled &&
!(c->flags & REDIS_MASTER) &&
!(c->cmd->getkeys_proc == NULL && c->cmd->firstkey == 0)) {
int hashslot;
// 叢集已下線
if (server.cluster->state != REDIS_CLUSTER_OK) {
flagTransaction(c);
addReplySds(c, sdsnew("-CLUSTERDOWN The cluster is down. Use CLUSTER INFO for more information\r\n"));
return REDIS_OK;
// 叢集運作正常
} else {
int error_code;
clusterNode *n = getNodeByQuery(c, c->cmd, c->argv, c->argc, &hashslot, &error_code);
// 不能執行多鍵處理命令
if (n == NULL) {
flagTransaction(c);
if (error_code == REDIS_CLUSTER_REDIR_CROSS_SLOT) {
addReplySds(c, sdsnew("-CROSSSLOT Keys in request don't hash to the same slot\r\n"));
} else if (error_code == REDIS_CLUSTER_REDIR_UNSTABLE) {
/* The request spawns mutliple keys in the same slot,
* but the slot is not "stable" currently as there is
* a migration or import in progress. */
addReplySds(c, sdsnew("-TRYAGAIN Multiple keys request during rehashing of slot\r\n"));
} else {
redisPanic("getNodeByQuery() unknown error.");
}
return REDIS_OK;
//3.1 命令針對的槽和鍵不是本節點處理的,進行轉向
} else if (n != server.cluster->myself) {
flagTransaction(c);
// -<ASK or MOVED> <slot> <ip>:<port>
// 例如 -ASK 10086 127.0.0.1:12345
addReplySds(c, sdscatprintf(sdsempty(),
"-%s %d %s:%d\r\n",
(error_code == REDIS_CLUSTER_REDIR_ASK) ? "ASK" : "MOVED",
hashslot, n->ip, n->port));
return REDIS_OK;
}
// 如果執行到這裡,說明鍵 key 所在的槽由本節點處理
// 或者客戶端執行的是無引數命令
}
}
/* Handle the maxmemory directive.
*
* First we try to free some memory if possible (if there are volatile
* keys in the dataset). If there are not the only thing we can do
* is returning an error. */
//4 如果設定了最大記憶體,那麼檢查記憶體是否超過限制,並做相應的操作
if (server.maxmemory) {
//4.1 如果記憶體已超過限制,那麼嘗試通過刪除過期鍵來釋放記憶體
int retval = freeMemoryIfNeeded();
// 如果即將要執行的命令可能佔用大量記憶體(REDIS_CMD_DENYOOM)
// 並且前面的記憶體釋放失敗的話
// 那麼向客戶端返回記憶體錯誤
if ((c->cmd->flags & REDIS_CMD_DENYOOM) && retval == REDIS_ERR) {
flagTransaction(c);
addReply(c, shared.oomerr);
return REDIS_OK;
}
}
....
- 1處,查詢命令,對應的函式指標(類似於java裡的策略模式,根據命令,找對應的策略)
- 2處,檢查,是否密碼正確
- 3處,叢集相關操作;
- 3.1處,不是本節點處理,直接返回ask,指示客戶端轉向
- 4處,判斷是否設定了maxMemory,這裡就是本文重點:設定了maxMemory時,記憶體淘汰策略
- 4.1處,呼叫了下方的 freeMemoryIfNeeded
接下來,深入4.1處:
int freeMemoryIfNeeded(void) {
size_t mem_used, mem_tofree, mem_freed;
int slaves = listLength(server.slaves);
/* Remove the size of slaves output buffers and AOF buffer from the
* count of used memory. */
// 計算出 Redis 目前佔用的記憶體總數,但有兩個方面的記憶體不會計算在內:
// 1)從伺服器的輸出緩衝區的記憶體
// 2)AOF 緩衝區的記憶體
mem_used = zmalloc_used_memory();
if (slaves) {
...
}
if (server.aof_state != REDIS_AOF_OFF) {
mem_used -= sdslen(server.aof_buf);
mem_used -= aofRewriteBufferSize();
}
/* Check if we are over the memory limit. */
//1 如果目前使用的記憶體大小比設定的 maxmemory 要小,那麼無須執行進一步操作
if (mem_used <= server.maxmemory) return REDIS_OK;
//2 如果佔用記憶體比 maxmemory 要大,但是 maxmemory 策略為不淘汰,那麼直接返回
if (server.maxmemory_policy == REDIS_MAXMEMORY_NO_EVICTION)
return REDIS_ERR; /* We need to free memory, but policy forbids. */
/* Compute how much memory we need to free. */
// 3 計算需要釋放多少位元組的記憶體
mem_tofree = mem_used - server.maxmemory;
// 初始化已釋放記憶體的位元組數為 0
mem_freed = 0;
// 根據 maxmemory 策略,
//4 遍歷字典,釋放記憶體並記錄被釋放記憶體的位元組數
while (mem_freed < mem_tofree) {
int j, k, keys_freed = 0;
// 遍歷所有字典
for (j = 0; j < server.dbnum; j++) {
long bestval = 0; /* just to prevent warning */
sds bestkey = NULL;
dictEntry *de;
redisDb *db = server.db + j;
dict *dict;
if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_LRU ||
server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_RANDOM) {
// 如果策略是 allkeys-lru 或者 allkeys-random
//5 那麼淘汰的目標為所有資料庫鍵
dict = server.db[j].dict;
} else {
// 如果策略是 volatile-lru 、 volatile-random 或者 volatile-ttl
//6 那麼淘汰的目標為帶過期時間的資料庫鍵
dict = server.db[j].expires;
}
/* volatile-random and allkeys-random policy */
// 如果使用的是隨機策略,那麼從目標字典中隨機選出鍵
if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_RANDOM ||
server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_RANDOM) {
de = dictGetRandomKey(dict);
bestkey = dictGetKey(de);
}
/* volatile-lru and allkeys-lru policy */
//7
else if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_LRU ||
server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_LRU) {
struct evictionPoolEntry *pool = db->eviction_pool;
while (bestkey == NULL) {
// 8
evictionPoolPopulate(dict, db->dict, db->eviction_pool);
/* Go backward from best to worst element to evict. */
for (k = REDIS_EVICTION_POOL_SIZE - 1; k >= 0; k--) {
if (pool[k].key == NULL) continue;
// 8.1
de = dictFind(dict, pool[k].key);
/* 8.2 Remove the entry from the pool. */
sdsfree(pool[k].key);
/* Shift all elements on its right to left. */
memmove(pool + k, pool + k + 1,
sizeof(pool[0]) * (REDIS_EVICTION_POOL_SIZE - k - 1));
/* Clear the element on the right which is empty
* since we shifted one position to the left. */
pool[REDIS_EVICTION_POOL_SIZE - 1].key = NULL;
pool[REDIS_EVICTION_POOL_SIZE - 1].idle = 0;
/* If the key exists, is our pick. Otherwise it is
* a ghost and we need to try the next element. */
// 8.3
if (de) {
bestkey = dictGetKey(de);
break;
} else {
/* Ghost... */
continue;
}
}
}
}
/* volatile-ttl */
// 策略為 volatile-ttl ,從一集 sample 鍵中選出過期時間距離當前時間最接近的鍵
else if (server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_TTL) {
...
}
/* Finally remove the selected key. */
// 8.4 刪除被選中的鍵
if (bestkey) {
long long delta;
robj *keyobj = createStringObject(bestkey, sdslen(bestkey));
propagateExpire(db, keyobj);
/* We compute the amount of memory freed by dbDelete() alone.
* It is possible that actually the memory needed to propagate
* the DEL in AOF and replication link is greater than the one
* we are freeing removing the key, but we can't account for
* that otherwise we would never exit the loop.
*
* AOF and Output buffer memory will be freed eventually so
* we only care about memory used by the key space. */
// 計算刪除鍵所釋放的記憶體數量
delta = (long long) zmalloc_used_memory();
dbDelete(db, keyobj);
delta -= (long long) zmalloc_used_memory();
mem_freed += delta;
// 對淘汰鍵的計數器增一
server.stat_evictedkeys++;
notifyKeyspaceEvent(REDIS_NOTIFY_EVICTED, "evicted",
keyobj, db->id);
decrRefCount(keyobj);
keys_freed++;
...
}
}
if (!keys_freed) return REDIS_ERR; /* nothing to free... */
}
return REDIS_OK;
}
-
1處,如果目前使用的記憶體大小比設定的 maxmemory 要小,那麼無須執行進一步操作
-
2處,如果佔用記憶體比 maxmemory 要大,但是 maxmemory 策略為不淘汰,那麼直接返回
-
3處,計算需要釋放多少位元組的記憶體
-
4處,遍歷字典,釋放記憶體並記錄被釋放記憶體的位元組數
-
5處,如果策略是 allkeys-lru 或者 allkeys-random 那麼淘汰的目標為所有資料庫鍵
-
6處,如果策略是 volatile-lru 、 volatile-random 或者 volatile-ttl ,那麼淘汰的目標為帶過期時間的資料庫鍵
-
7處,如果使用的是 LRU 策略, 那麼從 sample 鍵中選出 IDLE 時間最長的那個鍵
-
8處,呼叫evictionPoolPopulate,該函式在下面講解,該函式的功能是,傳入一個連結串列,即這裡的db->eviction_pool,然後在函式內部,隨機找出n個key,放入傳入的連結串列中,並按照空閒時間排序,空閒最久的,放到最後。
當該函式,返回後,db->eviction_pool這個連結串列裡就存放了我們要淘汰的key。
-
8.1處,找到這個key,這個key,在後邊會被刪除
-
8.2處,下面這一段,從db->eviction_pool將這個已經處理了的key刪掉
-
8.3處,如果這個key,是存在的,則跳出迴圈,在後面8.4處,會被刪除
-
8.4處,刪除這個key
選擇哪些key作為被淘汰的key
前面我們看到,在7處,如果為lru策略,則會進入8處的函式:
evictionPoolPopulate。
該函式的名稱為:填充(populate)驅逐(eviction)物件池(pool)。驅逐的意思,就是現在達到了maxmemory,沒辦法,只能開始刪除掉一部分元素,來騰空間了,不然新的put型別的命令,根本沒辦法執行。
該方法的大概思路是,使用lru的時候,隨機找n個key,類似於抽樣,然後放到一個連結串列,根據空閒時間排序。
具體看看該方法的實現:
void evictionPoolPopulate(dict *sampledict, dict *keydict, struct evictionPoolEntry *pool) {
其中,傳入的第三個引數,是要被填充的物件,在c語言中,習慣傳入一個入參,然後在函式內部填充或者修改入參物件的屬性。
該屬性,就是前面說的那個連結串列,用來存放收集的隨機的元素,該連結串列中節點的結構如下:
struct evictionPoolEntry {
unsigned long long idle; /* Object idle time. */
sds key; /* Key name. */
};
該結構共2個欄位,一個儲存key,一個儲存空閒時間。
該連結串列中,共maxmemory-samples個元素,會按照idle時間長短排序,idle時間長的在連結串列尾部,(假設頭在左,尾在右)。
void evictionPoolPopulate(dict *sampledict, dict *keydict, struct evictionPoolEntry *pool) {
int j, k, count;
dictEntry *_samples[EVICTION_SAMPLES_ARRAY_SIZE];
dictEntry **samples;
/* Try to use a static buffer: this function is a big hit...
* Note: it was actually measured that this helps. */
if (server.maxmemory_samples <= EVICTION_SAMPLES_ARRAY_SIZE) {
samples = _samples;
} else {
samples = zmalloc(sizeof(samples[0]) * server.maxmemory_samples);
}
/* 1 Use bulk get by default. */
count = dictGetRandomKeys(sampledict, samples, server.maxmemory_samples);
// 2
for (j = 0; j < count; j++) {
unsigned long long idle;
sds key;
robj *o;
dictEntry *de;
de = samples[j];
key = dictGetKey(de);
/* If the dictionary we are sampling from is not the main
* dictionary (but the expires one) we need to lookup the key
* again in the key dictionary to obtain the value object. */
if (sampledict != keydict) de = dictFind(keydict, key);
// 3
o = dictGetVal(de);
// 4
idle = estimateObjectIdleTime(o);
/* 5 Insert the element inside the pool.
* First, find the first empty bucket or the first populated
* bucket that has an idle time smaller than our idle time. */
k = 0;
while (k < REDIS_EVICTION_POOL_SIZE &&
pool[k].key &&
pool[k].idle < idle)
k++;
...
// 6
pool[k].key = sdsdup(key);
pool[k].idle = idle;
}
if (samples != _samples) zfree(samples);
}
-
1處,獲取
server.maxmemory_samples
個key,這裡是隨機獲取的,(dictGetRandomKeys),這個值,預設值為5,放到samples中 -
2處,遍歷返回來的samples
-
3處,呼叫如下巨集,獲取val
he的型別為dictEntry:
/* * 雜湊表節點 */ typedef struct dictEntry { // 鍵 void *key; // 值 union { // 1 void *val; uint64_t u64; int64_t s64; } v; // 指向下個雜湊表節點,形成連結串列 struct dictEntry *next; } dictEntry;
所以,這裡去
robj *o; o = dictGetVal(de);
實際就是獲取其v屬性中的val,(1處):
#define dictGetVal(he) ((he)->v.val)
-
4處,準備計算該val的空閒時間
我們上面3處,看到,獲取的o的型別為robj。我們現在看看怎麼計算物件的空閒時長:
/* Given an object returns the min number of milliseconds the object was never * requested, using an approximated LRU algorithm. */ unsigned long long estimateObjectIdleTime(robj *o) { //4.1 獲取系統的當前時間 unsigned long long lruclock = LRU_CLOCK(); // 4.2 if (lruclock >= o->lru) { // 4.3 return (lruclock - o->lru) * REDIS_LRU_CLOCK_RESOLUTION; } else { return (lruclock + (REDIS_LRU_CLOCK_MAX - o->lru)) * REDIS_LRU_CLOCK_RESOLUTION; } }
這裡,4.1處,獲取系統的當前時間;
4.2處,如果系統時間,大於物件的lru時間
4.3處,則用系統時間減去物件的lru時間,再乘以單位,換算為毫秒,最終返回的單位,為毫秒(可以看註釋。)
#define REDIS_LRU_CLOCK_RESOLUTION 1000 /* LRU clock resolution in ms */
-
5處,這裡拿當前元素,和pool中已經放進去的元素,從第0個開始比較,如果當前元素的idle時長,大於pool中指標0指向的元素,則和pool中索引1的元素比較;直到條件不滿足為止。
這句話意思就是,類似於冒泡,把當前元素一直往後冒,直到idle時長小於被比較的元素為止。
-
6處,把當前元素放進pool中。
經過上面的處理後,連結串列中存放了全部的抽樣元素,且ide時間最長的,在最右邊。
物件還有欄位儲存空閒時間?
前面4處,說到,用系統的當前時間,減去物件的lru時間。
大家看看物件的結構體
typedef struct redisObject {
// 型別
unsigned type:4;
// 編碼
unsigned encoding:4;
//1 物件最後一次被訪問的時間
unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
// 引用計數
int refcount;
// 指向實際值的指標
void *ptr;
} robj;
上面1處,lru屬性,就是用來儲存這個。
建立物件時,直接使用當前系統時間建立
robj *createObject(int type, void *ptr) {
robj *o = zmalloc(sizeof(*o));
o->type = type;
o->encoding = REDIS_ENCODING_RAW;
o->ptr = ptr;
o->refcount = 1;
/*1 Set the LRU to the current lruclock (minutes resolution). */
o->lru = LRU_CLOCK();
return o;
}
1處即是。
robj *createEmbeddedStringObject(char *ptr, size_t len) {
robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr)+len+1);
struct sdshdr *sh = (void*)(o+1);
o->type = REDIS_STRING;
o->encoding = REDIS_ENCODING_EMBSTR;
o->ptr = sh+1;
o->refcount = 1;
// 1
o->lru = LRU_CLOCK();
sh->len = len;
sh->free = 0;
if (ptr) {
memcpy(sh->buf,ptr,len);
sh->buf[len] = '\0';
} else {
memset(sh->buf,0,len+1);
}
return o;
}
1處即是。
每次查詢該key時,重新整理時間
robj *lookupKey(redisDb *db, robj *key) {
// 查詢鍵空間
dictEntry *de = dictFind(db->dict,key->ptr);
// 節點存在
if (de) {
// 取出值
robj *val = dictGetVal(de);
/* 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. */
// 更新時間資訊(只在不存在子程式時執行,防止破壞 copy-on-write 機制)
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1)
// 1
val->lru = LRU_CLOCK();
// 返回值
return val;
} else {
// 節點不存在
return NULL;
}
}
1處即是,包括get、set等各種操作,都會重新整理該時間。
仔細看下面的堆疊,set的,get同理:
總結
大家有沒有更清楚一些呢?
總的來說,就是,設定了max-memory後,達到該記憶體限制後,會在處理命令時,檢查是否要進行記憶體淘汰;如果要淘汰,則根據maxmemory-policy的策略來。
隨機選擇maxmemory-sample個元素,按照空閒時間排序,拉鍊表;挨個挨個清除。