redis内存淘汰(如maxmemory触发的allkeys-lru)强制同步删除,不走unlink或异步逻辑,即使配置lazyfree-lazy-eviction yes也无效;淘汰由服务端内部调度,不经过命令解析器,无法被客户端unlink替代;缓解方法唯有提前干预大key。

淘汰键触发的DEL是强制同步的,根本绕不开主线程
Redis内存淘汰(比如 maxmemory 触发的 allkeys-lru)在真正删 key 时,底层调用的是 dbSyncDelete,不是 dbAsyncDelete。也就是说,哪怕你配置了 lazyfree-lazy-eviction yes,它也只对「主动触发的淘汰」生效——而标准淘汰流程本身不走 UNLINK 那套逻辑。
典型路径是:activeExpireCycle 扫到过期 key → 调用 expireIfNeeded → 内部强制走 dbSyncDelete;同理,evictIdleKeys 或 evictMemory 在驱逐时也直接同步删 value。
- 这个过程无法被
UNLINK替代,因为淘汰是 Redis 自动行为,不接受客户端命令介入 - 哪怕 key 很大(比如一个 500 万成员的
ZSet),也会在主线程里逐节点 free,期间所有新请求排队 -
INFO commandstats里看不到unlink的调用记录,但del的usec_per_call会突增到秒级
lazyfree-lazy-eviction 配置只影响“主动驱逐”,不覆盖基础淘汰链路
很多人以为开了 lazyfree-lazy-eviction yes 就万事大吉,其实它只在一种场景下起作用:当 Redis 主动执行 EVAL 脚本或 MEMORY PURGE 等显式命令触发驱逐时,才可能走异步路径。而日常最常发生的 maxmemory 溢出淘汰,压根不看这个配置。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 验证方式:
CONFIG GET lazyfree-lazy-eviction返回["lazyfree-lazy-eviction","yes"]并不能保证淘汰不卡 - 真正决定淘汰是否异步的,是淘汰函数里硬编码的删除方式——目前仍是同步
- 想确认是否真在异步删,得盯
INFO memory中的lazyfree_pending_objects:淘汰过程中它几乎不涨,说明没走异步队列
UNLINK 命令本身在淘汰流程中完全不可用
UNLINK 是客户端命令,只能由外部发起;淘汰是服务端内部调度行为,两者不在同一控制平面。Redis 淘汰器不会、也不能去调用 UNLINK —— 它连命令解析器都不经过。
- 你在 Lua 脚本里写
redis.call("UNLINK", key)会直接报错:ERR Error running script (call to f_...): @user_script:1: ERR unknown command `unlink` - 即使升级到 Redis 7.2,淘汰逻辑仍基于
dbSyncDelete,源码里找不到任何dbAsyncDelete调用点 - 所以“用 UNLINK 替代淘汰时的 DEL”这个想法,从机制上就不可行
真正能缓解淘汰阻塞的只有提前干预
既然淘汰链路本身无法异步化,唯一靠谱的做法,是在它发生前把大 key 拆掉、降级或主动清理。
- 监控
used_memory和mem_fragmentation_ratio,当接近maxmemory的 85% 时就该人工介入 - 对已知的大 key(比如用户画像
Hash),不要等淘汰,而是用UNLINK主动删,并确保lazyfree-lazy-user-del yes - 避免用单个 key 存聚合数据,改用分片 key(如
user:123:profile:0~:9),让淘汰时每次只删小块 - 如果必须保留大结构,考虑改用
Stream或外部存储,Redis 只存轻量索引
淘汰时的阻塞不是配置漏了,也不是版本低,而是设计如此——它优先保障淘汰决策的确定性,而非响应延迟。这点容易被忽略,但恰恰是线上延迟毛刺最顽固的来源之一。










