Redis 的键过期和内存淘汰机制中,LRU(Least Recently Used)和 LFU(Least Frequently Used)是两种常用的策略。当内存达到 maxmemory 上限时,Redis 会根据这些策略挑选键进行删除。然而,并不是所有读取操作都应该被视为一次有效访问。例如,缓存预热完成后执行一遍完整性校验,或者监控工具定期遍历所有键采集指标,这些读取如果被计入 LRU/LFU 统计,就会污染真实的访问热度,导致本该淘汰的冷数据继续占着内存,而真正的热数据反而被提前清理。从 Redis 6.0 开始,CLIENT NO-TOUCH 选项可以精准解决这一矛盾。

CLIENT NO-TOUCH 是连接级别的设置,当开启后,当前客户端连接发出的读命令将不再触碰键的 LRU/LFU 元数据。这听起来只是一个很小的开关,但在高强度缓存场景中,它能够显著避免批量只读任务对淘汰决策的干扰。下面先梳理 Redis 的访问记录机制,再深入说明该选项的用法。
Redis LRU/LFU 的访问记录机制
Redis 内部为每个键维护了 LRU 时钟或 LFU 计数器,这些信息存储在对象结构中。对于 LRU 策略,键的访问时间戳会被更新为当前近似毫秒时间;对于 LFU 策略,键的访问频率计数会递增,同时结合时间衰减因子进行调整。无论是 GET、HGET、LRANGE 还是 SMEMBERS 等只读命令,在默认情况下都会触发这种元数据更新,因为 Redis 无法区分这次读取是业务访问还是维护扫描。
以 allkeys-lru 策略为例,Redis 在内存不够时,会随机抽取一定数量的键,比较它们的空闲时间,优先淘汰空闲时间最长的键。假设一个键已经很久没有被访问,属于冷数据,但在某个瞬间被扫描脚本读取了一下,它的空闲时间就会清零,从淘汰候选列表中“逃脱”。当内存紧张时,这个键仍然会以“热数据”的身份占着位置,而另一个真正频繁访问的键可能被错误淘汰。这种情况在批量任务中尤其明显,因为一次全量扫描会把成百上千个冷键集体标记为最近使用。
LFU 策略下问题同样存在。扫描操作会人为提升键的访问频率计数,即使业务上这些键并没有被频繁使用。LFU 计数器一旦被拉高,需要经过一段时间的衰减才能回落到真实水平,期间会造成明显的热度虚高。因此,需要一种机制让运维或辅助类读取不产生访问记录,CLIENT NO-TOUCH 正是为此而生。
CLIENT NO-TOUCH 命令详解与效果验证
CLIENT NO-TOUCH 的语法非常简单,直接在 redis-cli 中执行即可切换状态:
# 开启 NO-TOUCH redis-cli CLIENT NO-TOUCH ON # 关闭 NO-TOUCH(恢复默认行为) redis-cli CLIENT NO-TOUCH OFF
开启后,当前连接后续执行的所有只读命令都不会更新键的 LRU 或 LFU 信息。注意这里强调的是只读命令。写命令如 SET、INCR、LPUSH 等,由于它们本身就改变了键的内容或生命周期,Redis 仍然会更新相关元数据,NO-TOUCH 不会也无法阻止写命令对键状态的影响。这一点需要牢记,否则可能误以为 NO-TOUCH 能让键完全脱离淘汰视线。
可以通过 OBJECT IDLETIME 命令来观察 NO-TOUCH 的效果。在普通连接中,执行一次 GET 后,OBJECT IDLETIME 返回的空闲秒数通常会变小,因为该键被重新触达。而在开启了 NO-TOUCH 的连接中,GET 之后 OBJECT IDLETIME 的返回值不会被此次 GET 影响,依旧保留上一次真实访问以来的空闲时间。示例流程如下:
# 普通连接行为 127.0.0.1:6379> SET mykey value OK 127.0.0.1:6379> OBJECT IDLETIME mykey (integer) 5 127.0.0.1:6379> GET mykey "value" 127.0.0.1:6379> OBJECT IDLETIME mykey (integer) 0 # 开启 NO-TOUCH 后的行为 127.0.0.1:6379> CLIENT NO-TOUCH ON OK 127.0.0.1:6379> GET mykey "value" 127.0.0.1:6379> OBJECT IDLETIME mykey (integer) 7
从结果可以看出,普通连接下 GET 将空闲时间重置为 0,而 NO-TOUCH 连接下 GET 后空闲时间仍然延续原来的计数。虽然 OBJECT IDLETIME 本身不带有淘汰决策权重,但它直观地反映了 LRU 时钟是否被触碰。对于 LFU 模式,可以借助 OBJECT FREQ 命令查看频率计数,这里不再单独演示。
CLIENT NO-TOUCH 是连接作用域的,不会对其他客户端连接产生影响。如果同一个服务端连接被多个业务线程复用,开启后会影响该连接上所有后续读操作,因此需要谨慎评估。通常建议在独立维护连接或短生命周期脚本中使用,执行完扫描任务后及时关闭连接或显式执行 CLIENT NO-TOUCH OFF,避免误伤业务读取。
典型应用场景与操作建议
第一种典型场景是缓存预热后的完整性校验。很多团队在把数据库数据批量写入 Redis 后,会用一个只读任务逐键读取,确认缓存内容是否正确。这个校验过程如果使用普通连接,会把预热的热键和大量本该进入淘汰候选的冷键全部重新标记为近期使用,使得预热后的 LRU 状态失去参考价值。开启 CLIENT NO-TOUCH ON 后,校验读取不会干扰淘汰排序,预热完成后的内存分布更贴近真实业务访问。
第二种场景是监控与指标采集。监控系统通常会定期发送 SCAN 和 MGET 来收集键的存活状态、数据大小等信息。这些请求量可能很大,如果每次都触碰 LRU/LFU,等于持续给所有被扫描的键“续命”,严重情况下会让 maxmemory 淘汰策略形同虚设。通过为监控连接开启 NO-TOUCH,可以保持访问热度只来自真实用户请求,监控数据仍然准确获取,同时不干预内存回收。
第三种场景是数据迁移或备份。执行 DUMP、RESTORE 或第三方工具扫描时,连接发出的只读命令不应当被当作热访问。尤其在需要计算真实冷热分布、制定清理计划时,使用 NO-TOUCH 连接能够保证采集到的空闲时间和频率信息尽量接近业务侧的真实状态。操作上建议将维护任务放在独立脚本中,在建立连接后立即执行 CLIENT NO-TOUCH ON,避免默认行为造成误差。
注意事项、版本兼容与常见误区
CLIENT NO-TOUCH 需要 Redis 6.0 或更高版本,低版本实例无法使用该命令。对于使用 Redis Cluster 集群的场景,命令需要在目标节点的客户端连接上执行,因为配置是连接级别的,不会跨节点传播。如果需要同时扫描多个分片,需要在每个节点的连接中分别开启。
一个常见误区是将 CLIENT NO-TOUCH 与键过期时间 TTL 混为一谈。NO-TOUCH 只阻止 LRU/LFU 元数据更新,不会暂停或延长 TTL 倒计时。如果业务依赖 TTL 进行过期清理,该策略完全不受影响。另一个误区是认为 NO-TOUCH 可以阻止写命令对键的访问记录。实际上写命令必然改变键,其 LRU/LFU 信息会被更新,这是设计上的合理行为。不要试图在写密集任务中依赖 NO-TOUCH 来藏匿键。
此外,CLIENT NO-TOUCH 与 CLIENT CACHING 是两个不同的连接级选项。CLIENT CACHING 主要服务于服务端协助的客户端缓存,涉及键变更通知机制;而 NO-TOUCH 只关心是否触碰淘汰相关的访问标记。两者可以独立使用,也可以同时开启,但用途完全不同。理解它们的边界,可以避免在性能调优和缓存设计时走入误区。合理运用 CLIENT NO-TOUCH,能以极小的代价让批量只读任务不再扰乱 LRU/LFU 的淘汰决策,是 Redis 内存管理中一个值得掌握的实用技巧。
Redis CLIENT NO-TOUCHLRU淘汰策略键访问标记修改时间:2026-08-28 15:29:31