intprocessCommand(redisClient *c) { //略.... /* 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. */ // 如果设置了最大内存,那么检查内存是否超过限制,并做相应的操作 if (server.maxmemory) { // 如果内存已超过限制,那么尝试通过删除过期键来释放内存 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; } } //略.... }
intfreeMemoryIfNeeded(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) { listIter li; listNode *ln;
/* Check if we are over the memory limit. */ // 如果目前使用的内存大小比设置的 maxmemory 要小,那么无须执行进一步操作 if (mem_used <= server.maxmemory) return REDIS_OK;
// 如果占用内存比 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. */ // 计算需要释放多少字节的内存 mem_tofree = mem_used - server.maxmemory;
// 初始化已释放内存的字节数为 0 mem_freed = 0;
// 根据 maxmemory 策略, // 遍历字典,释放内存并记录被释放内存的字节数 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;
while(bestkey == NULL) { 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; de = dictFind(dict,pool[k].key);
/* 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. */ if (de) { bestkey = dictGetKey(de); break; } else { /* Ghost... */ continue; } } } }
/* volatile-ttl */ // 策略为 volatile-ttl ,从一集 sample 键中选出过期时间距离当前时间最接近的键 elseif (server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_TTL) { for (k = 0; k < server.maxmemory_samples; k++) { sds thiskey; long thisval;
de = dictGetRandomKey(dict); thiskey = dictGetKey(de); thisval = (long) dictGetVal(de);
/* Finally remove the selected key. */ // 删除被选中的键 if (bestkey) { longlong 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 = (longlong) zmalloc_used_memory(); dbDelete(db,keyobj); delta -= (longlong) zmalloc_used_memory(); mem_freed += delta; // 对淘汰键的计数器增一 server.stat_evictedkeys++;
/* When the memory to free starts to be big enough, we may * start spending so much time here that is impossible to * deliver data to the slaves fast enough, so we force the * transmission here inside the loop. */ if (slaves) flushSlavesOutputBuffers(); } }
if (!keys_freed) return REDIS_ERR; /* nothing to free... */ }
return REDIS_OK; }
这里还是贴一下redisObject的数据结构吧,主要用了lru字段来做内存淘汰机制:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
typedefstructredisObject {
// 类型 unsigned type:4;
// 编码 unsigned encoding:4;
// 对象最后一次被访问的时间 unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
/* 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. */ typedefstructredisDb {
// 数据库键空间,保存着数据库中的所有键值对 dict *dict; /* The keyspace for this DB */
// 键的过期时间,字典的键为键,字典的值为过期时间 UNIX 时间戳 dict *expires; /* Timeout of keys with a timeout set */
// 正处于阻塞状态的键 dict *blocking_keys; /* Keys with clients waiting for data (BLPOP) */
// 可以解除阻塞的键 dict *ready_keys; /* Blocked keys that received a PUSH */
// 正在被 WATCH 命令监视的键 dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */
structevictionPoolEntry *eviction_pool;/* Eviction pool of keys */
// 数据库号码 int id; /* Database ID */
// 数据库的键的平均 TTL ,统计信息 longlong avg_ttl; /* Average TTL, just for stats */
// 取出键的过期时间 mstime_t when = getExpire(db,key); mstime_t now;
// 没有过期时间 if (when < 0) return0; /* No expire for this key */
/* Don't expire anything while loading. It will be done later. */ // 如果服务器正在进行载入,那么不进行任何过期检查 if (server.loading) return0;
/* If we are in the context of a Lua script, we claim that time is * blocked to when the Lua script started. This way a key can expire * only the first time it is accessed and not in the middle of the * script execution, making propagation to slaves / AOF consistent. * See issue #1525 on Github for more information. */ now = server.lua_caller ? server.lua_time_start : mstime();
/* If we are running in the context of a slave, return ASAP: * the slave key expiration is controlled by the master that will * send us synthesized DEL operations for expired keys. * * Still we try to return the right information to the caller, * that is, 0 if we think the key should be still valid, 1 if * we think the key is expired at this time. */ // 当服务器运行在 replication 模式时 // 附属节点并不主动删除 key // 它只返回一个逻辑上正确的返回值 // 真正的删除操作要等待主节点发来删除命令时才执行 // 从而保证数据的同步 if (server.masterhost != NULL) return now > when;
// 运行到这里,表示键带有过期时间,并且服务器为主节点
/* Return when this key has not expired */ // 如果未过期,返回 0 if (now <= when) return0;
/* Return the expire time of the specified key, or -1 if no expire * is associated with this key (i.e. the key is non volatile) * * 返回给定 key 的过期时间。 * * 如果键没有设置过期时间,那么返回 -1 。 */ longlonggetExpire(redisDb *db, robj *key) { dictEntry *de;
/* No expire? return ASAP */ // 获取键的过期时间 // 如果过期时间不存在,那么直接返回 if (dictSize(db->expires) == 0 || (de = dictFind(db->expires,key->ptr)) == NULL) return-1;
/* The entry was found in the expire dict, this means it should also * be present in the main dict (safety check). */ redisAssertWithInfo(NULL,key,dictFind(db->dict,key->ptr) != NULL);