解析Redis淘汰策略源码分析(redis淘汰策略源码)
Redis是一款非常流行的内存数据库,但是它也会面临内存不足的问题。为了防止内存溢出,Redis提供了五种不同的淘汰策略。在本文中,我们将解析Redis淘汰策略的源码,深入了解每种淘汰策略的工作原理。
1. noeviction
noeviction是Redis的默认淘汰策略,它不会删除任何键值对。当Redis内存空间使用完后,会导致新插入的数据无法存储,返回错误信息“OOM command not allowed when used memory > ‘maxmemory’”。下面是相关代码:
“`c
if (server.maxmemory_policy != MAXMEMORY_NO_EVICTION &&
free_memory
(stress_test || (mstime()-server.stat_starttime) > 5000))
{
int j, k, i, keys_freed = 0;
while(best effort to free memory);
}
2. volatile-lru
该策略会首先在所有设置过期时长的键值对中,按最近使用时间从旧到新进行排序,并删除最近最少使用的键值对。下面是相关代码:
```cstatic int compare_chunk(const void *a, const void *b) {
serverAssert((uintptr_t)a % LRU_BITS_PER_CHUNK == 0); serverAssert((uintptr_t)b % LRU_BITS_PER_CHUNK == 0);
return memcmp(a,b,LRU_BITS_PER_CHUNK);}
static int keylm_compare(void *privdata, const void *a, const void *b) { robj_roptr_wrapper *wa = (robj_roptr_wrapper*)a, *wb = (robj_roptr_wrapper*)b;
return compare_chunk(LRU_BITS(wa->o),LRU_BITS(wb->o));}
static int evictKeyFromPool(dict *pool, int samples, dict **evicted) { robj *keys[samples];
*evicted = dictCreate(&keylmDictType,NULL); dictAdd(*evicted,keylm_sentinel,NULL);
dictEnableResize(*evicted); dictSetHashFunctionSeed(*evicted,server.hash_seed);
int k = generatePool(pool,keys,samples); qsort(keys,k,sizeof(robj_roptr_wrapper),keylm_compare);
for (int i = 0; i if (dictSize(*evicted) == 1 || dictSize(pool)
if (dictGet(pool,keys[i]) == NULL) continue; if (dictGetExpire(pool,keys[i]) == -1) continue;
/* Remove the key from the original pool and insert it into the * "to be evicted" pool. */
dictAdd(*evicted,keys[i],NULL); dictGet(pool,keys[i]);
dictDelete(pool,keys[i]); activeDefragCycle();
} return k;
}
3. volatile-ttl
该策略会首先在所有设置过期时长的键值对中,按过期时间从早到晚进行排序,并删除最早过期的键值对。下面是相关代码:
“`c
void volatileTTLCommand(client *c) {
long long ttl_sum = 0, keys = 0;
/* We use a blacklist approach where we start with a blacklist of just
* the ‘*’ character which means all keys are selected. We then remove
* keys from this preferred list if certn patterns are found in the
* key names. When we have no other patterns left, we are left with
* the preferred list of keys that we want to operate on. */
sds pattern = c->argc == 2 ? c->argv[1]->ptr : “*”;
sds prefix = NULL;
if (stringmatch(pattern,”*:*”,0))
prefix = sdscatlen(sdsempty(),pattern+2,sdslen(pattern)-2);
else if (stringmatch(pattern,”*”,0))
prefix = sdscatlen(sdsempty(),””,0);
else {
addReplyError(c,”Invalid pattern argument.”);
return;
}
/* Scan the keyspace to build the preferred list of keys that match
* the pattern. */
void incrRefCountIfNeeded(robj *o);
void *replylen = addDeferredMultiBulkLength(c);
scanGenericCommand(c,0,NULL,NULL,SCAN_SELECTCOUNT);
unsigned long numkeys = 0;
if (c->flags & CLIENT_MULTI_BLOCK) {
numkeys = addReplyDeferredMultiBulkLength(c,replylen);
while(c->flags & CLIENT_MULTI_BLOCK) {
sds *response = setDeferredMultiBulkClientReply(c,numkeys);
for (unsigned long j = 0; j reply->elements; j++) {
robj *key = c->reply->element[j];
long long ttl = -1;
if (dictFind(c->db->dict,key->ptr) != NULL &&
getExpire(c->db,key) != -1) {
ttl = getExpire(c->db,key)-mstime();
if (ttl
}
if (ttl >= 0) {
ttl_sum += ttl;
keys++;
/* Add the key to the preferred list for this operation. */
response[j] = key->ptr;
incrRefCountIfNeeded(key);
}
}
numkeys -= c->reply->elements;
addReplyMultiBulkLen(c,c->reply->elements);
setDeferredAggregateMultiBulkLength(c,replylen,c->reply->elements);
decrRefCount(c->reply);
if (numkeys) {
c->flags &= ~CLIENT_MULTI_BLOCK;
scanGenericCommand(c,numkeys,NULL,NULL,SCAN_SELECTCOUNT);
}
}
}
setDeferredLength(c,replylen,numkeys*2);
/* Operate on the preferred list of keys. */
if (keys) {
sds *keynames = zmalloc(sizeof(sds)*keys);
int n = 0;
/* Sort the keys array by TTL, from the nearest to the furthest
* deadline. */
for (unsigned long j = 0; j
sds key = c->reply[j]->ptr;
long long ttl = -1;
if (dictFind(c->db->dict,key) != NULL &&
getExpire(c->db,c->reply[j]) != -1) {
ttl = getExpire(c->db,c->reply[j])-mstime();
if (ttl
}
if (ttl != -1)
keynames[n++] = key;
}
qsort(keynames,n,sizeof(char*),(void*)sortCompareStringPtr);
/* Delete keys by the sorted TTL order. */
for (int j = 0; j
delKey(c->db,keynames[j]);
zfree(keynames);
}
/* Finally send the reply to the client. */
addReplyLongLong(c,keys ? ttl_sum/keys : -1);
setDeferredAggregateLength(c,replylen,3);
}
4. volatile-lfu
该策略会首先在所有设置过期时长的键值对中,按访问次数从低到高进行排序,并删除访问次数最少的键值对。下面是相关代码:
```cvoid volatileLFUCommand(client *c) {
long long bestpop = 0, keys = 0;
/* We use a blacklist approach where we start with a blacklist of just * the '*' character which means all keys are selected. We then remove
* keys from this preferred list if certn patterns are found in the * key names. When we have no other patterns left, we are left with
* the preferred list of keys that we want to operate on. */ sds pattern = c->argc == 2 ? c->argv[1]->ptr : "*";
sds prefix = NULL; if (stringmatch(pattern,"*:*",0))
prefix = sdscatlen(sdsempty(),pattern+2,sdslen(pattern)-2); else if (stringmatch(pattern,"*",0))
prefix = sdscatlen(sdsempty(),"",0); else {
addReplyError(c,"Invalid pattern argument."); return;
}
/* Scan the keyspace to build the preferred list of keys that match * the pattern. */
void incrRefCountIfNeeded(robj *o); void *replylen = addDeferredMultiBulkLength(c);
scanGenericCommand(c,0,NULL,NULL,SCAN_SELECTCOUNT); unsigned long numkeys = 0;
if (c->flags & CLIENT_MULTI_BLOCK) { numkeys = addReplyDeferredMultiBulkLength(c,replylen);
while(c->flags & CLIENT_MULTI_BLOCK) { sds *response = setDeferredMultiBulkClientReply(c,numkeys);
for (unsigned long j = 0; j reply->elements; j++) {