redis 数据删除策略和逐出算法的问题小结

2020-06-12 15:00:04王旭

执行过期动作expireIfNeeded其实内部做了三件事情,分别是:

查看key判断是否过期 向slave节点传播执行过期key的动作并发送事件通知 删除过期key
/*
 * 检查 key 是否已经过期,如果是的话,将它从数据库中删除。
 *
 * 返回 0 表示键没有过期时间,或者键未过期。
 *
 * 返回 1 表示键已经因为过期而被删除了。
 */
int expireIfNeeded(redisDb *db, robj *key) {

 // 取出键的过期时间
 mstime_t when = getExpire(db,key);
 mstime_t now;

 // 没有过期时间
 if (when < 0) return 0; /* No expire for this key */

 /* Don't expire anything while loading. It will be done later. */
 // 如果服务器正在进行载入,那么不进行任何过期检查
 if (server.loading) return 0;

 // 当服务器运行在 replication 模式时
 // 附属节点并不主动删除 key
 // 它只返回一个逻辑上正确的返回值
 // 真正的删除操作要等待主节点发来删除命令时才执行
 // 从而保证数据的同步
 if (server.masterhost != NULL) return now > when;

 // 运行到这里,表示键带有过期时间,并且服务器为主节点

 /* Return when this key has not expired */
 // 如果未过期,返回 0
 if (now <= when) return 0;

 /* Delete the key */
 server.stat_expiredkeys++;

 // 向 AOF 文件和附属节点传播过期信息
 propagateExpire(db,key);

 // 发送事件通知
 notifyKeyspaceEvent(REDIS_NOTIFY_EXPIRED,
 "expired",key,db->id);

 // 将过期键从数据库中删除
 return dbDelete(db,key);
}

判断key是否过期的数据结构是db->expires,也就是通过expires的数据结构判断数据是否过期。
内部获取过期时间并返回。

/*
 * 返回字典中包含键 key 的节点
 *
 * 找到返回节点,找不到返回 NULL
 *
 * T = O(1)
 */
dictEntry *dictFind(dict *d, const void *key)
{
 dictEntry *he;
 unsigned int h, idx, table;

 // 字典(的哈希表)为空
 if (d->ht[0].size == 0) return NULL; /* We don't have a table at all */

 // 如果条件允许的话,进行单步 rehash
 if (dictIsRehashing(d)) _dictRehashStep(d);

 // 计算键的哈希值
 h = dictHashKey(d, key);
 // 在字典的哈希表中查找这个键
 // T = O(1)
 for (table = 0; table <= 1; table++) {

 // 计算索引值
 idx = h & d->ht[table].sizemask;

 // 遍历给定索引上的链表的所有节点,查找 key
 he = d->ht[table].table[idx];
 // T = O(1)
 while(he) {

 if (dictCompareKeys(d, key, he->key))
 return he;

 he = he->next;
 }

 // 如果程序遍历完 0 号哈希表,仍然没找到指定的键的节点
 // 那么程序会检查字典是否在进行 rehash ,
 // 然后才决定是直接返回 NULL ,还是继续查找 1 号哈希表
 if (!dictIsRehashing(d)) return NULL;
 }

 // 进行到这里时,说明两个哈希表都没找到
 return NULL;
}

优点

节约CPU性能,发现必须删除的时候才删除。

缺点