当前位置:首页 > 文章列表 > 数据库 > Redis > 热点 Key 不扩容也能缓解吗:拆分、复制与本地缓存

热点 Key 不扩容也能缓解吗:拆分、复制与本地缓存

来源:17golang原创 2026-10-08 10:10:00 0浏览 收藏

可以缓解,但前提是先弄清热点来自读还是写。Redis Cluster 中一个 Key 只属于一个哈希槽,稳定状态下这个槽由一个主节点负责,所以单纯把集群空闲资源留在旁边,并不会自动分担这个 Key 的压力。我的经验是:写热点优先拆分,读多写少优先复制或本地缓存,读写都很频繁则先改数据模型和访问路径。

不扩容不是“零成本”。拆分会增加聚合开销,复制会增加一致性复杂度,本地缓存会引入失效与内存管理。真正目标是把单点压力换成业务可接受的代价。

官方文档:https://redis.io/docs/latest/

先确认问题真的是热点 Key

热点 Key 的典型现象不是所有分片一起忙,而是某个分片 CPU、命令量或延迟明显高于其他分片。Redis 官方监控文档把热点 Key 定义为被极高频访问的 Key,并指出一个 Key 只属于一个分片,因此它可能把负载集中到单个分片。

排查时先看分片级 CPU、延迟、网络流量和命令分布,再使用热点采样。官方文档提示,redis-cli --hotkeys 依赖 LFU 统计;MONITOR 可能产生较高影响,只适合谨慎、短时、二次确认,不能当成常驻观测手段。

# 先确认实例当前的内存淘汰策略,避免误判 --hotkeys 的可用性
redis-cli CONFIG GET maxmemory-policy

# 在具备 LFU 统计的前提下采样热点 Key
redis-cli --hotkeys

# 检查拆分后的 Key 是否落到不同哈希槽
redis-cli CLUSTER KEYSLOT 'article:42:part:0'
redis-cli CLUSTER KEYSLOT 'article:42:part:1'

这里最重要的检查点是:热点是否持续、是否集中在同一个业务对象、读写比例怎样、值多大、更新频率多高。如果只是一次流量尖峰,限流和请求合并可能比改数据模型更合适。

三条路线解决的是不同压力

方案更适合主要收益主要代价
拆分 Key计数、集合、可分区聚合的写热点把写入分散到多个槽和分片读取需要聚合,跨槽操作受限
复制 Key读多写少、允许短暂最终一致把读取路由到多个物理 Key更新扇出、版本切换和过期管理更复杂
本地缓存高频读取、变化不频繁的小对象直接减少网络请求与 Redis 回源失效通知、断线清空和进程内存成本

我不建议先拍脑袋选择“最强”的方案。把热点类型与代价对上,通常比一次性叠加三层缓存更容易上线,也更容易回滚。

热点请求、拆分 Key、副本 Key、本地缓存与 Redis 分片之间的静态关系图
图1:三种缓解方案与请求入口、Redis Key 和分片之间的静态关系说明图,不是监控截图或运行结果。

写热点先拆成可合并的分片 Key

如果热点是全局计数器、点赞数、曝光数或可分区集合,可以把一个逻辑对象映射成多个物理 Key。请求依据用户 ID、请求 ID 或业务分区选择一个桶,例如 article:42:part:0 到 article:42:part:63。读取总数时再聚合这些桶,或者异步把桶汇总到一个只读快照。

Redis Cluster 有 16384 个哈希槽,Key 默认按完整键名计算槽位。拆分时不要给所有桶使用相同的哈希标签,否则花括号中的相同内容会强制它们回到同一个槽,等于把热点重新集中起来。跨槽的多 Key 命令、事务和 Lua 脚本也有约束,所以聚合通常要放在应用层,或由后台任务生成快照。

package hotkey

import (
    "fmt"
    "hash/fnv"
)

// BucketKey 根据稳定标识选择写入桶,避免同一请求重复路由到不同桶。
func BucketKey(base, stableID string, buckets uint32) string {
    h := fnv.New32a()
    _, _ = h.Write([]byte(stableID))

    // 键名不使用相同哈希标签,让不同桶有机会分布到不同槽。
    bucket := h.Sum32() % buckets
    return fmt.Sprintf("%s:part:%02d", base, bucket)
}

拆分的验收不是“生成了 64 个 Key”,而是这些 Key 的槽位和请求量确实分散,同时聚合延迟仍在预算内。桶太少无法摊平,桶太多则会放大读取、扫描和过期成本。

读多写少可以复制,但要接受一致性成本

对热门配置、商品基础信息或公共字典,可以维护多个内容相同的物理 Key,例如 product:42:copy:0 到 product:42:copy:7,请求按稳定散列读取其中一个。与写拆分类似,这些副本不能被相同哈希标签重新绑到同一个槽。

复制不是 Redis 自动替你完成的数据一致性协议。更新时需要应用或发布任务写入全部副本,并用版本号、校验值或独立的发布状态判断是否完成。如果业务不能接受几秒钟的版本不一致,就不要把应用级副本当成默认方案;强一致读取应保留单一权威路径,或使用经过验证的读副本能力并明确陈旧读取是否可接受。

我更喜欢“新版本 Key + 旧版本延迟过期”的方式:先写一组带版本的新副本,确认数量和版本一致,再切换读取版本;出现异常时把读取版本切回去。它牺牲一些内存,却比原地逐个覆盖更容易回退。

本地缓存最省 Redis 请求,也最容易藏住陈旧数据

Redis 官方的 server-assisted client-side caching 使用 Tracking:服务器记录连接读过哪些 Key,当 Key 被修改、淘汰或过期时发送失效消息,客户端收到后移除本地副本。这样应用命中本地缓存时不再访问 Redis,网络和服务器负载都会下降。

它尤其适合“读得多、改得少”的小对象。对持续 INCR 的计数器、频繁变化的排行榜或大对象,本地缓存可能被失效消息反复击穿,管理成本反而高于收益。官方文档还要求连接丢失时清空本地缓存,避免应用在失去失效通道后继续返回旧数据。

  • 只缓存明确的热点前缀或白名单,不要默认把所有读取都塞进进程内存。
  • 设置本地容量上限、短 TTL 与随机抖动,避免大量进程同时回源。
  • 把缓存友好和高频更新的数据分开连接,减少无效的跟踪与失效消息。
  • 监控断线清空次数、失效消息量、本地命中率和 Redis 回源比例。

推荐的上线顺序与回滚开关

我通常先加一个只读的热点观察面板,再为候选 Key 做灰度开关。第一阶段只记录命中和路由结果;第二阶段让少量请求使用拆分、副本或本地缓存;第三阶段才扩大比例。每个方案都要保留直接访问原 Key 的回退路径。

上线后至少同时观察四组指标:热点分片 CPU 是否下降、P95/P99 延迟是否改善、新增聚合或失效开销有多大、错误和陈旧读取是否增加。只看平均延迟很容易掩盖单分片仍在抖动的事实。

读写信号、路由规则、版本控制、失效通知和回滚开关之间的静态关系图
图2:热点治理的信号、控制与验证边界说明图;这些节点用于组织上线检查,不代表实际执行时间线。

容易踩的四个坑

  1. 拆了 Key 却保留相同哈希标签。物理 Key 变多了,槽位仍然相同,单分片压力没有改变。
  2. 复制后没有版本字段。无法判断读到的是哪一批数据,也无法安全回滚。
  3. 本地缓存只设 TTL,不处理失效通道断线。连接异常后可能长时间返回旧值。
  4. 只盯 Redis 命中率。命中率高不代表尾延迟、分片倾斜和一致性错误都已改善。

快速选择清单

  • 高频累加、允许最终聚合:优先拆分 Key。
  • 内容稳定、读远多于写、允许短暂不一致:考虑多个副本 Key。
  • 对象小、读频繁、客户端支持可靠失效:优先本地缓存。
  • 写入频繁且要求强一致:不要先复制,优先重构数据模型、限流或合并请求。
  • 单分片仍有真实容量瓶颈:这些方法只能缓解,最终仍可能需要扩容或重新分片。

所以,热点 Key 在不扩容时确实有治理空间,但要把“分散压力”和“复制数据”看成不同工具。先用最小改动验证一个方向,再依据分片 CPU、尾延迟和一致性指标决定是否保留,通常比一次性堆满缓存层更稳。

常见问题

把一个大 Hash 拆成多个 Hash 一定有效吗?

不一定。只有拆分后的键落到不同槽并且请求也被分散,才可能降低单分片压力;如果仍使用相同哈希标签,效果很有限。

可以直接从 Redis Cluster 副本读吗?

集群规范允许在接受陈旧数据的场景使用副本扩展读取,但客户端、路由与一致性语义必须明确。它与应用自行复制多个 Key 不是同一方案。

本地缓存失效消息丢了怎么办?

连接或失效通道异常时应清空相关本地缓存并回源 Redis,恢复后再重新建立跟踪,不能继续信任旧副本。

热点消失后需要恢复单 Key 吗?

不必立刻恢复。先比较聚合成本和运维复杂度;如果热点已经消失且拆分长期增加维护成本,可以通过灰度开关回到单 Key。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
写文件成功但重启后内容丢失,原子更新还缺少什么步骤写文件成功但重启后内容丢失,原子更新还缺少什么步骤
上一篇
写文件成功但重启后内容丢失,原子更新还缺少什么步骤
以临时文件加原子重命名安全更新配置
下一篇
以临时文件加原子重命名安全更新配置
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    375次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    445次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    452次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    398次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    224次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码