每日八股——Redis(4)

缓存雪崩、击穿、穿透

缓存穿透------数据根本不存在

现象

  • 查询一个根本不存在的数据,比如id=-1
  • Redis查不到,请求穿透到数据库
  • 数据库也查不到,所以无法回写缓存

**后果:**每次请求都会打到数据库,如果有人利用大量不存在的ID发起攻击,数据库瞬间就挂掉

解决方法:

  • 缓存空值
    • 数据库查不到,我就在Redis里存一个null,并设置一个较短的过期时间
    • 缺点:浪费内存;如果这时数据真加进去了,会有一个短暂的不一致
  • 布隆过滤器
    • 请求到达Redis之前,先过一层布隆过滤器
    • 认为不存在时就一定不存在,直接拦截,不查Redis和DB
    • 认为存在时不一定存在,需要放过去查一下Redis
    • 优点:内存占用小,效率高

缓存击穿------单个热点Key过期

现象:

  • 一个超级热点Key扛着每秒几万的并发
  • 在他过期的那一瞬间,有几万个请求同时发现Redis没有数据

**后果:**几万个数据同时冲向数据库,数据库瞬间被压垮

解决方法

  • 互斥锁
    • 发现缓存没有,不是所有人都去查库,只有抢到锁的哪一个线程去查库、写缓存
    • 其他线程等待并重试读缓存
    • 优点:强一致性
    • 缺点:性能下降,查库的线程突然挂了会导致死锁
  • 逻辑过期:
    • 不设置Redis的TTL(永不过期)
    • 但在Value的内容里包含一个expire_time
    • 查询时发现逻辑时间过期了,后台开一个线程去更新数据,当前请求直接返回旧数据
    • 优点:高可用,性能好
    • 缺点:会有短暂的数据不一致

缓存雪崩------大量Key同时过期/Redis挂了

现象:

  • 场景A:你设置缓存时,给一大批数据(比如首页推荐商品),设置了相同的过期时间,时间到了后他们同时过期
  • 场景B:Redis宕机了

后果:海量请求全部涌向数据库,数据库立刻崩了

解决方法:

  • 随机TTL
    • 给过期时间加一个随机值(比如一小时+random(1-5分钟)
    • 让过期时间分散开,别凑到一起
  • 高可用架构
    • Redis哨兵或集群,保证Redis挂了能自动切换
  • 限流降级
    • 在网关层或者应用层做限流
    • Redis挂了,直接返回默认值或空值保护数据库

热Key问题

某个Key接收到的访问请求远高于其他Key,导致请求压力集中在单点上,引发Redis、网络或下游系统性能瓶颈,就叫做热Key问题。

如何识别热Key

业务感知

  • 针对业务的提前预判,知道哪些ID会是热Key

Redis4.0自带工具

  • redis-cli --hotkeys:Redis会扫描所有Key并统计访问次数
  • 缺点:要开启LFU淘汰策略才准确,且可能增加Redis负担

客户端收集(SDK)

  • 在连接Redis的客户端代码里添加计数器
  • 缺点:虽然准确,但是对客户端代码有入侵

抓包分析

  • 监听Redis端口流量,通过 tcpdump 抓取一段时间内的流量并上报,然后由一个外部的程序进行解析、聚合和计算。
  • 缺点:运维成本高,且热 key 节点的网络流量和系统负载已经比较高了,抓包可能会情况进一步恶化。

如何解决热Key

方案一:多级缓存(Local Cache)

**核心思想:**既然Redis单机扛不住,那就在Redis 前增加其他缓存层(如CDN、本地缓存),以分担 Redis 的访问压力,先在应用服务器的内存里读,没有再去查Redis。

做法:

  • 当发现某个Key是热Key时,把它缓存到Web服务器的本地内存中
  • 下次请求来时,先查内存:
    • 没有------>查Redis,回写服务器内存
    • 有------>直接返回

**优点:**性能强,理论上可以无限并发(取决于Web服务器数量)

**缺点:**数据一致性变差,如果Redis数据改了,各个Web服务器本地内存里可能还是旧数据(需要设置极短的过期时间)

方案二:热Key拆分

**核心思想:**既然一个Redis节点扛不住,那就把这个Key复制多分,分散到不同节点上去

做法:

  • 假设Key是hot_product
  • 我们在写入时会把它复制N份,改名为hot_product_1,hot_product_2...hot_product_N
  • 这N个key会被哈希算法分配到Redis集群不同分片上
  • 查询时,客户端随机生成一个后缀1---N,比如随机到3,就去查hot_product_3

**优点:**利用了整个集群的计算能力,不再有单点瓶颈

缺点:

  • 浪费内存:存了N份
  • 数据更新麻烦:更新时需要把这N个备份都更新一遍,容易出现短暂不一致

方案三:读写分离

做法:增加Redis的从节点Slave,让主节点负责写,多个从节点负责读。

缺点:成本高,且有主从同步延迟


大Key问题

大Key问题指的是单个Redis Key对应的value体积非常大,或包含的元素非常多(两个大:体积大,数量大),本质为处理时间过长,阻塞了后面的请求。

大Key的危害

阻塞主线程:

  • Redis时单线程处理命令的
  • 如果读取一个5MB的String或者删除一个100万元素的List,这个操作可能耗时几百毫秒
  • 这几百毫秒内,Redis无法处理其他请求,导致线上服务卡顿

网络阻塞:

  • 虽然Redis吞吐量高,但网卡宽带是有限的
  • 高并发下读取大Key,会瞬间打满网卡宽带,导致其他Key读不出来

内存问题:

  • 一次淘汰一个大Key,会让内存大跳水,命中率骤降

主从复制与持久化问题:

  • 复制大Key时时间就,同步慢,会造成不一致
  • AOF与RDB的恢复时间长

如何发现大Key

工具扫描redis-cli --bigkeys

  • 缺点:只能给出每种类型最大的那个 Key,不够全。

RDB 分析 (推荐) :使用 rdb-tools 等第三方工具离线分析 RDB 文件。

  • 优点:不影响线上服务,分析最彻底。

如何解决大Key问题

拆分

针对集合类(Hash/List/Set),如果元素太多,就把它拆成多个小 Key。

  • 场景 :一个 Hash 存了 100 万个用户对象 user_list
  • 做法 :利用 hash(field) % 1000,拆分成 1000 个小 Hash:user_list_0, user_list_1 ... user_list_999

压缩

针对 String 类型的大 JSON,可以通过压缩算法减小体积。

  • Go 生态 :不要只知道 gzip。在追求速度的场景下,Snappy (Google 开源) 或 LZ4 是更好的选择。
  • 做法
    • Go 结构体 -> JSON -> Snappy 压缩 -> Redis。
    • 读取时:Redis -> Snappy 解压 -> JSON -> Go 结构体。

安全删除

千万别直接用 DEL****删除大 Key!

  • 问题 :执行 DEL huge_key(比如一个百万级的 List),Redis 会在主线程 中一个个回收内存,直接导致 Redis 阻塞几秒钟。
  • 解决
    • Redis 4.0+ :使用 UNLINK 命令代替 DEL
      • UNLINK 只是把 Key 从元数据中解绑(逻辑删除),真正的内存回收交给后台线程 异步执行,不阻塞主线程
      • Redis 4.0 以下 :必须自己在 Go 代码里写脚本,分批次删除(比如用 SCAN 每次扫 100 个元素删掉,循环几万次删完)。
相关推荐
液态不合群15 分钟前
[特殊字符] MySQL 覆盖索引详解
数据库·mysql
计算机毕设VX:Fegn089544 分钟前
计算机毕业设计|基于springboot + vue蛋糕店管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
瀚高PG实验室1 小时前
PostgreSQL到HighgoDB数据迁移
数据库·postgresql·瀚高数据库
宝宝单机sop1 小时前
Ai 算法资源合集
经验分享
计算机小手1 小时前
一个带Web UI管理的轻量级高性能OpenAI模型代理网关,支持Docker快速部署
经验分享·docker·语言模型·开源软件
打码人的日常分享2 小时前
智能制造数字化工厂解决方案
数据库·安全·web安全·云计算·制造
没差c2 小时前
springboot集成flyway
java·spring boot·后端
三水不滴2 小时前
Redis 过期删除与内存淘汰机制
数据库·经验分享·redis·笔记·后端·缓存
其古寺2 小时前
Spring事务嵌套异常处理深度解析
经验分享
笨蛋不要掉眼泪2 小时前
Spring Boot集成LangChain4j:与大模型对话的极速入门
java·人工智能·后端·spring·langchain