Redis 的 LFU(Least Frequently Used)淘汰策略通过引入时间衰减机制和概率递增算法,巧妙地平衡了访问频率与访问时效性,有效避免了“频率老化”问题。
下表清晰地展示了解决时效性问题的两个核心机制:
| 机制名称 | 解决的核心问题 | 实现方式 | 配置参数 |
|---|---|---|---|
| 时间衰减 (Time Decay) | 过去频繁访问但现在不再活跃的键(频率老化) | 根据时间间隔自动降低计数器值 | lfu-decay-time |
| 概率递增 (Probabilistic Increment) | 新键因初始频率低容易被淘汰(冷启动),以及公平提升频率 | 计数器值越高,再次增加的概率越低,访问次数呈对数增长 | lfu-log-factor |
⏳ 时间衰减机制
LFU 策略的核心挑战在于,如果一个键在过去被频繁访问,但后来不再使用,它的高访问计数会一直保留,导致无法被淘汰,也就是“频率老化”问题。
Redis 的解决方案是 记录每个键的最后一次计数器衰减时间。在 Redis 的对象结构体 (redisObject) 中,原本的 24 位 lru字段被拆分为两部分:
- 高 16 位:记录最近一次计数器降低的时间(Last Decrement Time),单位是分钟。
- 低 8 位:作为访问频率计数器(Logistic Counter),范围是 0-255。
当一个键被访问时,系统会先执行衰减逻辑:
- 计算当前时间与键中记录的最后衰减时间的差值。
- 这个时间差值会除以配置参数
lfu-decay-time(默认为1),得到需要衰减的周期数num_periods。 - 将计数器的值减去
num_periods(但不能减到小于0)。
举个例子:如果 lfu-decay-time设置为 1,一个键在 5 分钟前更新过计数器,那么当它再次被访问时,计数器会先直接减去 5,然后再进行增加操作。这就确保了长时间未被访问的键,其频率值会逐渐降低,为更活跃的新键让出空间。
📈 概率递增与对数计数
为了解决新键容易淘汰(冷启动)和公平提升频率的问题,LFU 策略在增加计数器时采用了概率递增算法,而非每次访问简单加1。
计数器增加的逻辑如下:
- 生成一个 0 到 1 之间的随机数
r。 - 根据当前计数器值
counter和配置参数lfu-log-factor,计算一个概率p,公式为p = 1.0 / (baseval * lfu_log_factor + 1)(其中baseval是当前计数器值减去初始值5)。 - 只有当随机数
r小于概率p时,计数器才会加1。
这意味着:
- 计数器值越低,增长越快:新键或低频键被访问时,计数器增加的概率较大,能较快地提升排名。
- 计数器值越高,增长越慢:高频键需要被访问更多次,计数器才可能增加一次,这避免了早期热点数据永久占据缓存,也为其他键的上升提供了机会。
这种设计使得访问次数与计数器值之间呈现对数增长关系。例如,在 lfu-log-factor为 10 的情况下,需要约 10 次访问才能从 1 增长到 10,但需要约 1000 万次访问才能从 200 多增长到上限 255。这很好地区分了不同热度的数据。
⚙️ 相关配置参数
你可以通过以下两个参数精细调整 LFU 策略的行为,以适应你的业务场景:
lfu-log-factor:控制计数器增长速度的因子。值越大(如100),计数器增长越慢,适合访问频率差异巨大的场景;值越小(如10),计数器增长越快,适合区分度要求不高的场景。lfu-decay-time:计数器衰减时间,单位是分钟。值越大(如60),衰减越慢,历史访问记录影响更持久;值越小(如1),衰减越快,策略对近期的访问行为更敏感。
💎 总结
Redis 的 LFU 淘汰策略通过时间衰减机制防止了历史高频键长期霸占缓存,又通过概率递增和对数计数机制兼顾了新生键的生存机会与频率计数的公平性。这种组合拳有效地解决了数据的时效性问题,使其特别适合有明显热点数据且需要根据长期访问模式来优化缓存的应用场景。
希望这些解释能帮助你更好地理解 Redis LFU 策略的精妙之处。