定时删除策略对内存是最友好的: 因为它保证过期键会在第一时间被删除, 过期键所消耗的内存会立即被释放。
这种策略的缺点是, 它对 CPU 时间是最不友好的: 因为删除操作可能会占用大量的 CPU 时间 —— 在内存不紧张、但是 CPU 时间非常紧张的时候 (比如说,进行交集计算或排序的时候), 将 CPU 时间花在删除那些和当前任务无关的过期键上, 这种做法毫无疑问会是低效的。
除此之外, 目前 Redis 事件处理器对时间事件的实现方式 —— 无序链表, 查找一个时间复杂度为 O(N)O(N) —— 并不适合用来处理大量时间事件。
惰性删除对 CPU 时间来说是最友好的: 它只会在取出键时进行检查, 这可以保证删除操作只会在非做不可的情况下进行 —— 并且删除的目标仅限于当前处理的键, 这个策略不会在删除其他无关的过期键上花费任何 CPU 时间。
惰性删除的缺点是, 它对内存是最不友好的: 如果一个键已经过期, 而这个键又仍然保留在数据库中, 那么 dict
字典和 expires
字典都需要继续保存这个键的信息, 只要这个过期键不被删除, 它占用的内存就不会被释放。
在使用惰性删除策略时, 如果数据库中有非常多的过期键, 但这些过期键又正好没有被访问的话, 那么它们就永远也不会被删除(除非用户手动执行), 这对于性能非常依赖于内存大小的 Redis 来说, 肯定不是一个好消息。
举个例子, 对于一些按时间点来更新的数据, 比如日志(log), 在某个时间点之后, 对它们的访问就会大大减少, 如果大量的这些过期数据积压在数据库里面, 用户以为它们已经过期了(已经被删除了), 但实际上这些键却没有真正的被删除(内存也没有被释放), 那结果肯定是非常糟糕。
从上面对定时删除和惰性删除的讨论来看, 这两种删除方式在单一使用时都有明显的缺陷: 定时删除占用太多 CPU 时间, 惰性删除浪费太多内存。
定期删除是这两种策略的一种折中:
Redis 使用的过期键删除策略是惰性删除加上定期删除, 这两个策略相互配合,可以很好地在合理利用 CPU 时间和节约内存空间之间取得平衡。
实现过期键惰性删除策略的核心是 db.c/expireIfNeeded
函数 —— 所有命令在读取或写入数据库之前,程序都会调用 expireIfNeeded
对输入键进行检查, 并将过期键删除:
比如说, GET
命令的执行流程可以用下图来表示:
expireIfNeeded
的作用是, 如果输入键已经过期的话, 那么将键、键的值、键保存在 expires
字典中的过期时间都删除掉。
用伪代码描述的 expireIfNeeded
定义如下:
def expireIfNeeded(key):
# 对过期键执行以下操作 。。。
if key.is_expired():
# 从键空间中删除键值对
db.dict.remove(key)
# 删除键的过期时间
db.expires.remove(key)
# 将删除命令传播到 AOF 文件和附属节点
propagateDelKeyToAofAndReplication(key)
对过期键的定期删除由 redis.c/activeExpireCycle
函执行: 每当 Redis 的例行处理程序 serverCron
执行时, activeExpireCycle
都会被调用 —— 这个函数在规定的时间限制内, 尽可能地遍历各个数据库的 expires
字典, 随机地检查一部分键的过期时间, 并删除其中的过期键。
整个过程可以用伪代码描述如下:
def activeExpireCycle():
# 遍历数据库(不一定能全部都遍历完,看时间是否足够)
for db in server.db:
# MAX_KEY_PER_DB 是一个 DB 最大能处理的 key 个数
# 它保证时间不会全部用在个别的 DB 上(避免饥饿)
i = 0
while (i < MAX_KEY_PER_DB):
# 数据库为空,跳出 while ,处理下个 DB
if db.is_empty(): break
# 随机取出一个带 TTL 的键
key_with_ttl = db.expires.get_random_key()
# 检查键是否过期,如果是的话,将它删除
if is_expired(key_with_ttl):
db.deleteExpiredKey(key_with_ttl)
# 当执行时间到达上限,函数就返回,不再继续
# 这确保删除操作不会占用太多的 CPU 时间
if reach_time_limit(): return
i += 1
dict
和 expires
两个字典构成,其中 dict
保存键值对,而 expires
则保存键的过期时间。expires
的某个键和 dict
的某个键共同指向同一个字符串对象,而 expires
键的值则是该键以毫秒计算的 UNIX 过期时间戳。DEL
命令到现有 AOF 文件末尾。DEL
命令到所有附属节点。DEL
命令,这样可以保证主节点和附属节点的数据总是一致的。dict
字典和 expires
字典的扩展策略和普通字典一样。它们的收缩策略是:当节点的填充百分比不足 10% 时,将可用节点数量减少至大于等于当前已用节点数量。
原文:https://www.cnblogs.com/albert32/p/13393022.html