删除一个 1GB 的大 key,DEL 命令会做什么?同步释放全部内存——在 Redis 单线程模型下,删除耗时与 value 大小成正比,大 key 的 DEL 会阻塞全库数百毫秒乃至数秒。**懒惰删除(lazy free,Redis 4.0+)**把内存回收移交给后台 BIO 线程,主线程只做"标记删除",O(1) 完成。本章解析其机制、配置与架构代价。
为什么删除慢:
> unlink big_key # 立即返回,后台回收
(integer) 1
> del big_key # 同步删除(对比)
(integer) 1UNLINK 将 key 从字典中摘除(O(1)),返回后主线程继续服务;| 命令/配置 | 行为 |
|---|---|
UNLINK key | 异步删除单个 key |
FLUSHDB ASYNC / FLUSHALL ASYNC | 异步清库(清空 10GB 数据库不阻塞) |
lazyfree-lazy-eviction yes | 内存淘汰改为异步释放 |
lazyfree-lazy-expire yes | 过期键删除改为异步释放 |
lazyfree-lazy-server-del yes | 服务端内部删除(如 rename 覆盖)异步化 |
lazyfree-lazy-user-del yes | DEL 自动转为异步(客户端无需改代码) |
推荐配置(生产,完整默认值见 5.1 节):lazyfree-lazy-* 四项全部设为 yes。
懒惰删除并非免费的午餐。Redis 之所以能在单线程下高效运行,根基是所有数据结构无锁共享。异步回收打破了这个前提:
| 代价 | 说明 |
|---|---|
| 引用计数管理 | 每个对象多一次计数维护 |
| 内存延迟释放 | 后台释放慢于同步,瞬时内存峰值可能升高 |
| 复杂度 | 代码路径显著复杂化(这是 Redis 演进中少见的"大手术") |
| 碎片化 | 异步释放与分配时机错位,内存碎片率可能上升 |
权衡结论:大 key 场景收益巨大(DEL 从秒级降到微秒级),代价在常规场景可忽略——生产环境全量开启。
> info memory
used_memory: 1073741824
used_memory_rss: 2147483648 # RSS 是 used 的 2 倍 → 碎片率 2.0
mem_fragmentation_ratio: 2.0| 手段 | 说明 |
|---|---|
activedefrag yes | 4.0+ 内置活跃碎片整理(7.0 起在专用后台线程移动对象) |
MEMORY PURGE | 手动触发 jemalloc 归还脏页(开销很小,可低峰期例行执行) |
| 重启 | 大版本/大对象场景下最有效的碎片清理 |
| 合理 TTL | 避免大量 key 同时过期引发碎片高峰 |
redis-cli --bigkeys 扫描大 key 清单;UNLINK;| 维度 | DEL | UNLINK |
|---|---|---|
| 删除方式 | 同步回收内存 | 主线程只断链,BIO 线程回收 |
| 阻塞 | 大 key 时阻塞主线程 | 几乎不阻塞(O(1) 返回) |
| 返回值 | 1(删除成功) | 1(删除成功) |
| 适用 | 小 key、需要立即确认 | 大 key、批量清理 |
相关配置:
# redis.conf
lazyfree-lazy-expire no # 过期键删除异步化(默认 no)
lazyfree-lazy-eviction no # 淘汰删除异步化(默认 no)
lazyfree-lazy-server-del no # 内部删除命令异步化(默认 no)
lazyfree-lazy-user-del no # 是否让 DEL 等价于 UNLINK(6.0+,默认 no)
lazyfree-lazy-user-del(6.0 起)可让DEL直接走异步路径,兼顾客户端习惯与主线程安全;7.0 起另有lazyfree-lazy-user-flush让FLUSHALL/FLUSHDB默认异步。
UNLINK/FLUSHALL ASYNC 是生产标配;lazyfree-lazy-* 全开,让淘汰、过期、DEL 全面异步化;mem_fragmentation_ratio 并适时 activedefrag;下一章进入集群化方案:先讲 Codis 代理架构,再深入官方 Redis Cluster。