登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  数据库 >  Redis

Redis 热 Key 治理实战:发现访问倾斜、拆分缓存和本地兜底

来源:17golang原创

时间:2026-06-13 03:10:27 111浏览 收藏

接口压测时最容易被忽略的问题之一,是缓存并没有慢,但某一个 Key 被打得太热:大部分请求都挤到同一个 Redis 节点,应用线程在等待缓存返回,数据库又担心缓存失效后的瞬时回源。热 Key 治理不是简单加机器,而是先找到倾斜,再让请求有层次地被削峰。

适合人群:正在维护 Redis 缓存、商品详情、配置中心、活动页、排行榜等高频读取接口的后端同学。读完后你可以把热 Key 排查步骤和保护链路直接套到自己的业务里。

目录

  • 热 Key 的典型现象
  • 如何定位访问倾斜
  • 保护链路:本地短缓存 + singleflight + 过期时间抖动
  • 拆分缓存与降级策略
  • 常见坑和上线检查

一、热 Key 的典型现象

热 Key 指某个或少数几个缓存 Key 承担了远高于平均值的访问量。它常见于秒杀商品详情、首页配置、热门文章、城市列表、直播间状态等场景。问题出现时,Redis 整体 QPS 可能看起来还好,但某个分片的网络、CPU 或连接队列已经明显偏高。

可以先用三类信号判断是否有访问倾斜:

  • 应用侧:某个接口 P95/P99 抖动,慢日志集中在读取同一类缓存。
  • Redis 侧:某个节点的输入输出流量明显高于其他节点。
  • 业务侧:某个商品、活动、配置在短时间内被大量读取。

二、如何定位访问倾斜

定位热 Key 时,先从低侵入的指标和采样开始。线上不要直接全量扫描大库,优先用 Redis 自带统计、代理层访问日志、应用埋点组合判断。

# 观察命令调用量和耗时趋势
redis-cli info commandstats | grep -E 'cmdstat_get|cmdstat_mget|cmdstat_hget'

# 在支持 LFU 的实例上观察热点采样
redis-cli --hotkeys

# 观察延迟变化,确认是否和业务高峰同步
redis-cli --latency-history -i 1

如果应用侧可以加一层轻量采样,建议记录“缓存 Key 模板 + 业务 id + 命中状态 + 耗时”。不要只记录完整 Key,否则日志量会上去;也不要只记录接口名,否则无法定位到具体对象。

Redis 热 Key 从业务流量到采样指标再到热点定位的流程图

三、保护链路:本地短缓存 + singleflight + 过期时间抖动

发现热 Key 后,最实用的一条保护链路是:应用先查本地短缓存,本地没有再查 Redis;如果 Redis 也没有,用 singleflight 合并同一 Key 的加载请求;写回缓存时给 TTL 加一点随机抖动,避免一批热点同时失效。

type HotCache struct {
    redis RedisClient
    local LocalCache
    group singleflight.Group
}

func (h *HotCache) GetProduct(ctx context.Context, id int64) ([]byte, error) {
    key := fmt.Sprintf("product:%d", id)

    if value, ok := h.local.Get(key); ok {
        return value, nil
    }

    value, err, _ := h.group.Do(key, func() (any, error) {
        if cached, ok := h.local.Get(key); ok {
            return cached, nil
        }

        data, err := h.redis.Get(ctx, key)
        if err == nil && len(data) > 0 {
            h.local.Set(key, data, 3*time.Second)
            return data, nil
        }

        fresh, err := loadProductFromDB(ctx, id)
        if err != nil {
            return nil, err
        }

        ttl := 5*time.Minute + time.Duration(rand.Intn(60))*time.Second
        _ = h.redis.Set(ctx, key, fresh, ttl)
        h.local.Set(key, fresh, 3*time.Second)
        return fresh, nil
    })
    if err != nil {
        return nil, err
    }
    return value.([]byte), nil
}

这段逻辑的重点不是把本地缓存时间设得很长,而是用 2 到 5 秒挡住瞬时尖峰。singleflight 负责把同一 Key 的并发加载合成一次,TTL 抖动则减少同一批缓存同时过期的概率。

Redis 热 Key 保护链路中本地短缓存、合并加载、Redis 回写和数据库兜底的流程图

四、拆分缓存与降级策略

如果一个 Key 长期保持极高访问量,单纯靠本地缓存还不够,可以考虑按业务读法拆分:

  • 把大对象拆成基础信息、库存状态、活动状态等多个 Key,减少每次读取的数据量。
  • 对只读配置类数据增加本地定时刷新,让接口请求少走远程缓存。
  • 对排行榜、热门列表这类读多写少数据,准备只读副本或静态快照。
  • 对非核心字段设置降级默认值,缓存异常时优先保住主流程。

拆分时要注意一致性边界。比如商品标题和封面可以慢一点刷新,价格和库存不能随意放宽。不同字段的刷新频率不同,缓存模型也应该不同。

五、常见坑和上线检查

1. 本地缓存不是越久越好

本地缓存时间过长会带来数据不一致,尤其是价格、权限、状态类字段。热 Key 保护通常只需要几秒,让峰值请求在应用内被吸收即可。

2. 只加随机 TTL 不等于解决热 Key

TTL 抖动主要解决“同时失效”,但访问倾斜仍然存在。真正的治理要配合本地短缓存、合并加载、拆分对象和限流降级。

3. 上线前要看三组指标

  • 接口 P95/P99 是否下降,错误率是否稳定。
  • Redis 热点节点的流量是否回落,连接数是否平稳。
  • 数据库回源次数是否可控,缓存失效时是否出现突刺。

总结

Redis 热 Key 治理的顺序可以记成四步:先用指标和采样发现倾斜,再用本地短缓存削峰,用 singleflight 合并加载,用 TTL 抖动和缓存拆分降低集中失效风险。这样做的好处是改动小、回滚简单,也能把压力从 Redis 单点分散到应用层的短时缓冲里。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>