Redis 应用实战(3):热 key 与大 key 治理 上一篇通过 TTL 抖动与请求合并压住集中回源但缓存内部仍可能严重倾斜。本篇把两个常被混称的问题拆开热 key 是访问频率异常大 key 是单个 value 或集合规模异常。前者消耗执行与网络吞吐后者放大传输、复制、持久化和释放成本只有分别测量治理动作才不会南辕北辙。一、痛点平均值会隐藏最危险的 key热 key 指访问频率显著高于其他 key不一定占很多内存大 key 指 value 或集合元素过大不一定经常访问。一个 20 字节的秒杀开关可能每秒读取十万次是热 key一个含百万成员但每天读取一次的 Set 是大 key。若只看实例平均 CPU、平均延迟和总内存就会错过局部倾斜。热 key 会集中在 Redis 单线程命令执行路径并在 Cluster 中集中到一个分片。副本分担读能缓解只读流量但带来复制延迟和一致性取舍。大 key 的GET虽是 O(1)返回几十 MB 仍会阻塞网络与客户端解析HGETALL、SMEMBERS、LRANGE 0 -1的成本与元素数相关。同步DEL巨型对象还可能在释放内存时卡住事件循环。二、发现从实例信号下钻到对象先看INFO commandstats、INFO stats、延迟和网络吞吐判断是命令计算、输出缓冲还是内存压力。SLOWLOG只记录服务器执行阶段不包含网络传输因此客户端很慢而慢日志为空并不矛盾。redis-cli --hotkeys依赖 LFU 相关计数适合辅助采样不应当作精确全量榜单应用侧按规范化 key 前缀统计请求频率通常更可靠。大 key 可用redis-cli --bigkeys做采样扫描它按类型报告“最大元素数”等信息并非所有类型都直接按字节比较。对候选 key 再用MEMORY USAGE key SAMPLES n、STRLEN、HLEN、LLEN、SCARD、ZCARD核实。扫描也会消耗资源应在副本或低峰进行设置节奏避免把诊断变成事故。下面程序按访问计数的中位数检测热点并按估算字节识别大对象。生产阈值应按实例容量和 SLO 校准示例强调两张榜单不能混为一谈。fromstatisticsimportmedian samples{config:flash-sale:{qps:12000,bytes:32},user:42:{qps:80,bytes:2048},catalog:all:{qps:20,bytes:8_500_000},user:43:{qps:70,bytes:1900},product:9:{qps:90,bytes:1200},}baselinemedian(item[qps]foriteminsamples.values())hot_thresholdbaseline*20large_threshold1_000_000hotsorted((keyforkey,valueinsamples.items()ifvalue[qps]hot_threshold),keylambdakey:samples[key][qps],reverseTrue,)largesorted((keyforkey,valueinsamples.items()ifvalue[bytes]large_threshold),keylambdakey:samples[key][bytes],reverseTrue,)print(fbaseline_qps{baseline})print(fhot_keys{hot})print(flarge_keys{large})print(foverlap{sorted(set(hot)set(large))})运行输出baseline_qps80 hot_keys[config:flash-sale] large_keys[catalog:all] overlap[]三、治理拆分、复制、限界和渐进删除热读数据可放应用进程本地缓存使用很短 TTL 或版本通知收敛这样请求不再全部穿过网络。允许轻微陈旧时可将同一值复制到多个带后缀的 key由客户端随机读取以分散 Cluster 槽位但更新必须写全副本并容忍短暂不一致。热写计数器可按时间或随机桶分片读取时汇总这把写压力换成读放大只适合可合并数据。大 String 应按业务边界拆分不要机械切字节导致每次读取仍需全量拼接。大 Hash/Set/Sorted Set 可按用户、月份或哈希桶拆 key并提供分页 API。集合遍历使用HSCAN、SSCAN、ZSCAN接受游标期间元素变化和可能重复SCAN 不是快照。列表或日志必须设置上限、归档周期和生产者背压。下面脚本创建一个受控测试 Hash用游标分批扫描然后以UNLINK异步释放。它不在共享命名空间运行也不会扫描整个数据库。#!/usr/bin/env bashset-euopipefailredis_url${REDIS_URL:-redis://127.0.0.1:6379/0}keydemo:bigkey:usersredis-cli-u$redis_urlDEL$key/dev/nullforstartin0100200300;doargs()for((istart;istart100;i));doargs(user:$iscore:$((i%17)))doneredis-cli-u$redis_urlHSET$key${args[]}/dev/nulldonecount$(redis-cli-u$redis_url--rawHLEN$key)cursor0seen0while:;domapfile-treply(redis-cli-u$redis_url--rawHSCAN$key$cursorCOUNT80)cursor${reply[0]}seen$((seen(${#reply[]}-1)/2))[[$cursor0]]breakdoneprintfhash_fields%s\n$countprintfscanned_fields%s\n$seenprintfunlinked%s\n$(redis-cli-u$redis_url--rawUNLINK$key)四、迁移治理动作本身也要限流拆 key 不能一次切换。先让写路径双写旧、新结构后台按游标回填历史数据读取路径优先新结构缺失时回退旧结构并补写核对数量、校验和与业务抽样后停止旧写最后等待回滚窗口再UNLINK。双写不是事务必须幂等并记录失败。Cluster 跨槽双写还不能依赖普通事务保证原子。随机过期旧 key或一次性删除大量 key都可能制造内存与回源尖峰。设置每秒迁移条数和字节预算观察used_memory、事件循环延迟、复制积压与副本 lag。若副本追不上继续迁移只会扩大故障域。生产者写入无界集合时治理重点不是定期清理而是把容量上限写进数据模型。热 key 的本地缓存也有代价进程越多总内存越大失效广播越复杂。极高价值且很小的配置适合这种方式用户权限等敏感数据必须用短 TTL、版本校验不能让撤权长期不生效。复制热点 key 时避免使用哈希标签把副本又固定到同一 Cluster 槽。五、验证建立按前缀的容量与热度预算每类 key 应有 owner、预计数量、单项大小、最大基数、TTL、读写 QPS 和删除方式。监控按业务前缀聚合而不是把完整用户 ID 作为指标标签造成高基数。报警后保留候选 key、命令、客户端和时间窗口才能复盘流量来源。压测要同时模拟请求分布和 value 大小。均匀随机流量无法复现热点只有小 value 的基准也看不到网络阻塞。观察 p99/p999、Redis CPU、网卡、客户端连接池等待和复制延迟。治理后若实例平均 QPS 不变但最热分片下降、尾延迟收敛才说明目标达成。当热点操作需要“只有一个客户端执行”时人们常顺手写SETNX却忽略租约、误删和故障恢复。下一篇将把这些风险收束成一把有明确安全边界的分布式锁。治理验收不要只比较一次MEMORY USAGE。至少跨一个业务高峰记录最热节点 CPU、每秒出站字节、命令 p99、复制 lag 和候选 key 的访问分布拆分后还要确认总 key 元数据没有反向吞掉节省的内存。若采用本地缓存额外测量配置变更传播的最长时间并在通知系统中断时验证 TTL 能自动收敛。对大集合分页接口设置最大页大小和游标有效期防止调用者绕过保护重新发起全量导出。参考来源Redis 官方文档诊断延迟问题Redis 官方文档MEMORY USAGERedis 官方文档UNLINK 觉得有用就点个赞 收藏方便回头查阅有疑问直接在评论区留言我看到都会回。 本文属于《Redis 应用实战》系列持续更新关注不迷路。 文章里的代码都能直接跑。想要可直接 clone 的完整工程 配套部署脚本 / 踩坑清单评论一声或发邮件到cj2664qq.com我免费发你。如果你正好在做类似系统、或有工程化难题想找人做也欢迎邮件聊一句——我按实际情况评估能落地的就接单或出方案。评论和邮件都能直接找到我不用跳别的平台。