缓存
缓存(cache) 是计算机中的一个经典概念
核心思路是将 热点数据 放到访问速度更快的地方, 方便随时读取
通常情况下, 访问数据的速度: CPU 寄存器 > 内存 > 硬盘 > 网络
速度快的设备可以作为速度慢的设备的缓存
对于计算机硬件来说, 访问速度越快, 成本越高, 存储空间越小
但是大部分情况下, 缓存只存储一些 热点数据 , 是完全够用的
通常将 redis 作为 关系型数据库 (MySQL, Oracle …) 的缓存
关系型数据库的功能强大, 但是性能不高 (一次操作消耗资源较多)
如果查询不能命中索引, 就需要进行表的遍历, 就会大大增加硬盘 IO 次数
- 数据存储在磁盘上, 磁盘的 IO 速度并不快
- 关系型数据库对于 SQL 的执行会做一系列解析, 校验, 优化操作
- 复杂查询会触发笛卡尔积操作, 效率成指数下降
如何让数据库承担更大的并发量?
- 开源: 引入更多机器, 构成数据库集群
- 节流: 引入缓存, 热点数据通过缓存处理, 可以大大降低数据库的请求数量
经过缓存的业务执行流程
客户端访问业务服务器, 发起查询请求
业务服务器会先查询 缓存
- 如果所需数据在 redis 中已存在, 则不必再访问 MySQL
- 如果 redis 中不存在, 则访问 MySQL
缓存的更新策略
- 定期生成
每隔一定周期, 对于访问数据进行统计, 根据访问频率挑选热点数据 (实时性差)- 实时生成
对于每次用户的查询
- 如果在 redis 中查到了, 就返回对应结果
- 如果在 redis 中查询不到, 就从数据库查, 并把查询到的数据也写入 redis
缓存淘汰机制
缓存 的容量是有限的, 对于热点数据的不断插入, 需要把一些 “不是那么热门” 的数据删除, 对此 缓存 拥有几种淘汰机制
FIFO (First In First Out) 先进先出 – 把缓存中存在最久的数据淘汰掉
LRU (Least Recently Used) 淘汰最久未使用的 – 记录每个 key 的最近访问时间, 每次淘汰访问时间最老的 key
LFU (Least Frequently Used) 淘汰访问次数最少的 – 记录每个 key 的访问次数, 每次淘汰访问次数最少的 key
Random 随机淘汰
缓存预热
当 Redis 作为 MySQL 缓存, 刚刚启动的时候, 会有大批 热点数据 生成到 Redis 中, 此时会造成 MySQL 的压力很大
缓存预热就是提前在 Redis 中准备部分热点数据, 让 MySQL 在刚刚启动时的压力没那么大
缓存穿透 (Cache Penetration)
访问的 key 在 Redis 和 数据库中都不存在, 这样的 key 并不会被用户获取到, 也不会被更新到Redis 中, 后续若是仍有此请求, 仍会访问数据库 (会导致数据库压力变大)
问题原因:
- 业务设计不合理
- 开发 / 运维误删数据库
- 黑客恶意攻击
解决:
- 当在 Redis 和 MySQL 中都查询不到时, 在 redis 中插入 value = “” 的键值对,防止后续频繁访问数据库
- 布隆过滤器对 key 进行校验
- 对查询数据做合法性校验 (针对业务上的优化)
缓存雪崩
问题:
- 短时间内 redis 上 key 值大量失效 (redis 挂了 / 多个 key 的过期时间相同), 导致 redis 命中率下降, MySQL 压力骤增
解决:
- 部署高可用的 Redis 集群, 完善报警机制 (哨兵)
- 不设置过期时间, 或者对过期时间添加随机因子 (别同时全部过期)
缓存击穿 (Cache Breakdown)
问题:
- 缓存雪崩的一种特殊情况, 热点 key 突然过期, 导致大量请求直接访问数据库
解决:
- 将热点 key 设置为永不过期
- 服务降级 (类似于手机省电模式, 适当关闭 MySQL 的非核心功能)