1. 删除策略
Redis将键的过期时间对时间戳都保存在过期字典中,在删除时通过对比系统当前时间戳和存储的过期时间戳计算得到是否过期。那么如果一个键过期了,Redis是怎么将其删除的呢?
我们可以推测出有这样几种删除策略:
- 定时删除:
在设置过期时间时,创建一个定时器,当定时器到键的过期时间时,进行删除操作。 - 惰性删除:
当键达到过期时间时,放任其不管,但是当每次访问键时,都检查键是否过期,过期则将其删除。 - 定期删除:
每隔一段时间,通过某个选择算法,对键的过期时间进行检查,删除过期键。
1.1. 定时删除策略
定时删除是保证键及时删除最有效的方式。但是定时器是对于CPU不友好的,当定时器与键一一对应时,大量的定时器会占用相当一部分的CPU时间。在业务繁忙时,将CPU时间用在删除与当前任务无关的键上,无疑会对服务器的响应时间和吞吐量造成影响。
当然在一些场景下,通过算法优化,去只使用一个定时器触发所有键的过期事件,也是一条具备可行性的思路。
1.2. 惰性删除策略
惰性删除是对于CPU最友好的策略。程序仅需要消耗几乎可以忽略不计的CPU时间的操作就能触发键的过期事件,程序的关注点一直聚焦在相关的键上。
惰性删除的缺点就和懒惰的不喜欢打扫的人一样,内存空间里面会有大量的“垃圾”。已经过期的键长期存在,只要这个键不被删除,其占用内存就不会释放。
在极端情况下,当过期的键不再被访问时,那么它将永远不会被删除。这样的对于内存的浪费是极大的,我们可以理解这样的情况是一种内存泄露。
1.3. 定期删除策略
定期删除就像是定时删除和惰性删除的折中方案。通过定期触发,减少对CPU时间的浪费;通过不再绑定键过期检查和键等其他操作,避免内存泄露,减少过期键对内存的浪费。
定期策略也有两个难以权衡的变量:检查的广度、检查的频率:如果广度和频率过大,这样会浪费大量的CPU时间在遍历检查上;如果广度和频率过小,这样会使得检查并不全面,导致依旧有大量内存浪费。
2. Redis的删除策略
Redis的删除策略融合了惰性删除和定期删除,一方面Redis执行惰性删除,放任键过期;另一方面,为了权衡CPU和内存的浪费,Redis通过随机抽样选择部分键来进行过期检查及删除,当样本中的过期样本占比超过阈值时,再进行一次随机抽样。
这样的算法很巧妙地运用狭义概率算法使得数据库中整体的内存浪费基本上在可控范围。然后这个策略也使得系统中的CPU和内存的浪费具备更强的随机性。
我们可以推测出Redis的删除策略,一定有这样几个参数:
- 定期抽样的时间间隔
- 抽样的样本数量
- 过期样本占比阈值
时间间隔是通过配置参数hz
设置的,默认为10
,也就是一秒10
次抽样,换算的时间间隔为100ms
。
通过阅读Redis源码:
在6.0.0
版本之前,抽样的样本数量和过期样本占比阈值都是固定的,分别为20
和25%
。
在6.0.0
版本及之后,样本数量和阈值都取决于配置参数active_exprie_effort
。
n
样本最大数量
=
20
+
5
×
(
a
c
t
i
v
e
_
e
x
p
r
i
e
_
e
f
f
o
r
t
)
n_{样本最大数量} = 20 + 5\times (active\_exprie\_effort)
n样本最大数量=20+5×(active_exprie_effort)
p
过期占比阈值
=
11
−
a
c
t
i
v
e
_
e
x
p
r
i
e
_
e
f
f
o
r
t
p_{过期占比阈值}=11-active\_exprie\_effort
p过期占比阈值=11−active_exprie_effort
当
(
n
样本数量
=
0
)
∨
(
n
过期样本
n
样本数量
×
100
>
p
过期占比阈值
)
(n_{样本数量}=0)\vee(\frac{n_{过期样本}}{n_{样本数量}}\times 100>p_{过期占比阈值})
(n样本数量=0)∨(n样本数量n过期样本×100>p过期占比阈值)时,再进行一次采样。
active_exprie_effort
取值于
[
1
,
10
]
[1,10]
[1,10],这个参数越大,则清理越彻底,占用的CPU时间越多。