redis 数据删除策略和逐出算法

数据存储和有效期

redis 工作流程中,过期的数据并不需要马上就要执行删除操作。因为这些删不删除只是一种状态表示,可以异步的去处理,在不忙的时候去把这些不紧急的删除操作做了,从而保证 redis 的高效

数据的存储

在redis中数据的存储不仅仅需要保存数据本身还要保存数据的生命周期,也就是过期时间。在redis 中 数据的存储结构如下图:

redis 数据删除策略和逐出算法

获取有效期

Redis是一种内存级数据库,所有数据均存放在内存中,内存中的数据可以通过TTL指令获取其状态

redis 数据删除策略和逐出算法

删除策略

在内存占用与CPU占用之间寻找一种平衡,顾此失彼都会造成整体redis性能的下降,甚至引发服务器宕机或内存泄漏。

定时删除

创建一个定时器,当key设置过期时间,且过期时间到达时,由定时器任务立即执行对键的删除操作

优点

节约内存,到时就删除,快速释放掉不必要的内存占用

缺点

CPU压力很大,无论CPU此时负载多高,均占用CPU,会影响redis服务器响应时间和指令吞吐量

总结

用处理器性能换取存储空间

惰性删除

数据到达过期时间,不做处理。等下次访问该数据,如果未过期,返回数据。发现已经过期,删除,返回不存在。这样每次读写数据都需要检测数据是否已经到达过期时间。也就是惰性删除总是在数据的读写时发生的。

expireIfNeeded函数

对所有的读写命令进行检查,检查操作的对象是否过期。过期就删除返回过期,不过期就什么也不做~。

执行数据写入过程中,首先通过expireIfNeeded函数对写入的key进行过期判断。

/* * 为执行写入操作而取出键 key 在数据库 db 中的值。 * * 和 lookupKeyRead 不同,这个函数不会更新服务器的命中/不命中信息。 * * 找到时返回值对象,没找到返回 NULL 。 */ robj *lookupKeyWrite(redisDb *db, robj *key) { // 删除过期键 expireIfNeeded(db,key); // 查找并返回 key 的值对象 return lookupKey(db,key); }

执行数据读取过程中,首先通过expireIfNeeded函数对写入的key进行过期判断。

/* * 为执行读取操作而取出键 key 在数据库 db 中的值。 * * 并根据是否成功找到值,更新服务器的命中/不命中信息。 * * 找到时返回值对象,没找到返回 NULL 。 */ robj *lookupKeyRead(redisDb *db, robj *key) { robj *val; // 检查 key 释放已经过期 expireIfNeeded(db,key); // 从数据库中取出键的值 val = lookupKey(db,key); // 更新命中/不命中信息 if (val == NULL) server.stat_keyspace_misses++; else server.stat_keyspace_hits++; // 返回值 return val; }

执行过期动作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性能,发现必须删除的时候才删除。

缺点

内存压力很大,出现长期占用内存的数据。

总结

用存储空间换取处理器性能

定期删除

周期性轮询redis库中时效性数据,采用随机抽取的策略,利用过期数据占比的方式删除频度。

优点

CPU性能占用设置有峰值,检测频度可自定义设置

内存压力不是很大,长期占用内存的冷数据会被持续清理

缺点

需要周期性抽查存储空间

定期删除详解

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpsdxz.html