在Redis中,了解某个Key被访问的频率对于缓存优化、内存淘汰策略调优以及热点数据识别至关重要。OBJECT FREQ命令是Redis 4.0版本引入的一个子命令,专门用于查看Key当前的访问频率计数器。这个计数器与Redis的LFU(Least Frequently Used)淘汰机制紧密相关,但许多开发者在使用时容易将其与简单的访问次数混淆。实际上,该计数器的计算方式包含了对数增长和时间衰减,理解其原理才能正确解读输出结果。

下面将从命令的基本用法入手,逐步解析其输出含义、与LFU策略的关联,并给出实际场景中的排查思路。本文所有示例均基于Redis 6.x或更高版本,旧版本可能不支持OBJECT FREQ。
OBJECT FREQ命令的基本用法与输出解读
OBJECT FREQ命令的语法非常简单:OBJECT FREQ key。它会返回一个整数值,表示该Key当前被记录的访问频率。需要注意的是,这个值并不是简单的“访问了多少次”,而是经过LFU算法处理后的一个近似频率值。我们先来看一个最基础的示例:
127.0.0.1:6379> SET user:1001 "Alice" OK 127.0.0.1:6379> OBJECT FREQ user:1001 (integer) 5 127.0.0.1:6379> GET user:1001 "Alice" 127.0.0.1:6379> OBJECT FREQ user:1001 (integer) 5
从上面的输出可以看到,新建一个Key并执行SET后,OBJECT FREQ返回了5。这里需要明确:默认情况下,一个新创建的Key的频率计数器初始值并不是0,而是由Redis内部的LFU初始化逻辑决定的。在Redis 4.0之后的版本中,每个新Key会默认获得一个初始的LFU值,这个值通常为5。这个初始值并不是随机的,它可以在源码的LFU_INIT_VAL宏中找到,默认值就是5。这意味着即使一个Key从未被访问过,它的频率计数器也是5,这会在后续的衰减运算中逐渐降低。
接着执行了一次GET操作,理论上访问频率应该增加,但再次执行OBJECT FREQ后返回的值仍然是5。这是因为LFU计数器并不是每次访问都线性加1,而是遵循一个对数概率递增的规则。具体来说,每次访问时Redis会根据当前计数器值和lfu-log-factor配置计算一个增量概率,计数器越大,增加的概率越低。此外,访问的累积也需要时间衰减配合,所以短时间内的单次访问几乎不会改变输出。这也解释了为什么OBJECT FREQ的结果不是实时精确的访问次数,而是一个经过平滑处理的频率指标。
OBJECT FREQ与LFU淘汰策略的紧密关系
要真正理解OBJECT FREQ的意义,必须先了解Redis的两种内存淘汰策略:LRU(Least Recently Used)和LFU(Least Frequently Used)。LRU关注的是“最近一次访问时间”,而LFU关注的是“访问频率”。当Redis的内存使用达到maxmemory上限时,如果配置了allkeys-lfu或volatile-lfu策略,Redis就会根据每个Key的频率计数器来决定淘汰哪些Key。OBJECT FREQ命令正是为观察这个内部计数器而提供的调试手段。
LFU算法在Redis中并不是一个简单计数器,它由两个部分组成:一个是8位对数计数器,另一个是衰减时间。计数器在每次访问时根据对数概率增加,同时会根据Key的空闲时间进行衰减。具体来说,Redis在内部维护了一个16位的LFU字段,高8位表示访问频率计数器,低8位表示衰减时间戳。当执行OBJECT FREQ时,Redis会读取该Key的LFU字段,根据当前时间与衰减时间戳的差值进行衰减修正,然后返回高8位的值。这就是为什么在长时间未访问后,OBJECT FREQ可能返回一个较小的值,尽管该Key之前被频繁访问过。
下面通过一个直观的实验来展示衰减效果。首先创建一个Key并访问多次,使其频率值升高,然后等待一段时间后再查看:
127.0.0.1:6379> SET hotkey "value" OK 127.0.0.1:6379> OBJECT FREQ hotkey (integer) 5 127.0.0.1:6379> GET hotkey "value" 127.0.0.1:6379> GET hotkey "value" 127.0.0.1:6379> GET hotkey "value" 127.0.0.1:6379> OBJECT FREQ hotkey (integer) 6 # 等待60秒后再次查看 127.0.0.1:6379> OBJECT FREQ hotkey (integer) 6
在上面的例子中,连续三次GET后频率值只从5变成了6,这是因为对数递增的效果。而等待60秒后再次查询,由于默认lfu-decay-time为1分钟,所以频率值可能保持不变或轻微下降。如果等待更长时间,频率值会明显降低。这里需要说明:OBJECT FREQ返回的是经过衰减后的当前频率,衰减公式为new_counter = counter - (idle_time / lfu_decay_time),其中counter是原始高8位计数器值,idle_time是Key的空闲时间(秒),lfu_decay_time是衰减周期,默认值为1(分钟)。所以如果Key空闲了10分钟,并且原始计数器为255,那么衰减后的值会大幅下降。
实战:定位热点Key并优化内存策略
在实际运维中,OBJECT FREQ最常用的场景是诊断热点Key和验证LFU参数是否合理。例如,当线上Redis出现某些Key被频繁访问导致内存压力时,我们可以通过scan配合OBJECT FREQ来找出频率最高的Key。下面给出一个使用Redis命令行批量查询的示例,借助redis-cli的管道和awk命令筛选出频率较高的Key:
redis-cli --scan --pattern 'user:*' | while read key; do
freq=$(redis-cli OBJECT FREQ "$key" | tr -d '\r')
if [ "$freq" -gt 20 ]; then
echo "$key $freq"
fi
done
这段脚本会扫描所有以user:开头的Key,对每个Key执行OBJECT FREQ,并打印频率大于20的Key及其当前频率。需要注意的是,OBJECT FREQ命令是一个O(1)操作,因为它直接读取Key对象内部字段,不会遍历额外数据结构,所以对大批量Key扫描的性能影响较小。不过如果Key数量极大,建议在从节点或低峰期执行,避免阻塞主线程。
另一个常见的调优场景是发现某些Key被意外淘汰,但业务上认为它们应该是热点。此时可以通过OBJECT FREQ查看这些Key在被淘汰前的频率值,如果发现频率值很低但实际访问量很大,就说明lfu-log-factor设置得过大,导致计数器增长缓慢;或者lfu-decay-time设置得太短,导致频率衰减过快。例如,默认的lfu-log-factor为10,这个值越大,计数器增长越慢,适合大型Key场景;而lfu-decay-time默认为1分钟,表示每分钟衰减1个计数单位。如果业务访问间隔很短,但衰减很快,就可能导致热点Key被错误淘汰。
调整这些参数需要修改Redis配置文件或使用CONFIG SET命令动态修改。下面演示如何通过命令行临时调整lfu-log-factor和lfu-decay-time,然后创建新Key观察频率增长:
127.0.0.1:6379> CONFIG SET lfu-log-factor 5 OK 127.0.0.1:6379> CONFIG SET lfu-decay-time 2 OK 127.0.0.1:6379> SET testkey "abc" OK 127.0.0.1:6379> OBJECT FREQ testkey (integer) 5 127.0.0.1:6379> GET testkey "abc" 127.0.0.1:6379> GET testkey "abc" 127.0.0.1:6379> OBJECT FREQ testkey (integer) 6
将lfu-log-factor从默认10调低到5,会使计数器增长更快,连续两次GET后频率从5升到了6。如果将lfu-log-factor调高,比如设为100,那么即使访问几十次,频率值也可能仍在初始值附近。因此,OBJECT FREQ的输出必须结合这两个参数来解读,否则很容易产生误判。
最后需要提醒的是,OBJECT FREQ只对开启了LFU淘汰策略的Redis实例有意义。如果maxmemory-policy设置为noeviction或者LRU相关策略,该命令依然可以返回频率值,但这个值不会被用于淘汰决策,仅作为调试参考。可以通过CONFIG GET maxmemory-policy确认当前策略:
127.0.0.1:6379> CONFIG GET maxmemory-policy 1) "maxmemory-policy" 2) "allkeys-lfu"
如果返回的是allkeys-lfu或volatile-lfu,则说明LFU机制正在生效,此时OBJECT FREQ的输出直接关系到哪些Key会被优先淘汰。对于线上系统,建议定期抽样检查高频Key的频率分布,结合监控告警,提前发现潜在的内存压力问题。

