Redis 缓存是 Redis 最常用的场景之一,通过将热点数据存储在内存中,显著提升应用程序的读写性能,减轻数据库负担。
1.什么是缓存
缓存(cache)核心思路就是把一些常用的数据结构放到触手可及(访问速度更快)的地方,方便随时获取。

例如:浏览器通过 http / https 从服务器上(网络)获取到的数据(html,css,js,图片,视频音频......)并进行展示(像这样体积大,又不太爱改变的数据,就可以保存到浏览器本地(浏览器所在主机的硬盘上)后续再打开这个页面,就不必重新从网络获取上述数据了)
对于计算机硬件来说, 往往访问速度越快的设备, 成本越⾼, 存储空间越⼩.
缓存是更快, 但是空间上往往是不⾜的. 因此⼤部分的时候, 缓存只放⼀些 热点数据 (访问频繁的数据), 就⾮常有⽤了.

2.使用Redis 作为缓存
在一个网站中,我们经常会使用关系型数据库(MySQL)来存储数据。
关系型数据库虽然功能强⼤, 但是有⼀个很⼤的缺陷, 就是性能不⾼. (换⽽⾔之, 进⾏⼀次查询操作消耗的系统资源较多).
为啥关系型数据库效率不高?
硬件:
- 数据库把数据存储在硬盘上, 硬盘的 IO 速度并不快. 尤其是随机访问.
- 如果查询不能命中索引, 就需要进⾏表的遍历, 这就会⼤⼤增加硬盘 IO 次数.
软件: - 关系型数据库对于 SQL 的执⾏会做⼀系列的解析, 校验, 优化⼯作.
- 如果是⼀些复杂查询, ⽐如联合查询, 需要进⾏笛卡尔积操作, 效率更是降低很多.
因此, 如果访问数据库的并发量⽐较⾼, 对于数据库的压⼒是很⼤的, 很容易就会使数据库服务器宕机.
为什么并发量高了就容易挂机?
服务器每次处理⼀个请求, 都是需要消耗⼀定的硬件资源的. 所谓的硬件资源包括不限于 CPU,
内存, 硬盘, ⽹络带宽......
⼀个服务器的硬件资源本⾝是有限的. ⼀个请求消耗⼀份资源, 请求多了, ⾃然把资源就耗尽
了. 后续的请求没有资源可⽤, ⾃然就⽆法正确处理. 更严重的还会导致服务器程序的代码出现
崩溃.
如何让数据库能够承担更⼤的并发量呢?
核⼼思路主要是两个:
1.开源:引入更多的机器,部署更多的数据库实例,构造数据库集群.(主从复制,分库分表等..)
2.节流:引入缓存,使用其他方法的方式保存经常访问的热点数据,从而降低直接访问的数据库的请求数量。(实际开发中,这两种方案往往是会搭配使用的)
Redis 就像一个护盾一样,把MySQL给罩住了

1.客户端访问业务服务器,发起查询请求。
2.业务服务器先查询 Redis,看想要的数据是否在Redis 中是否存在
*如果已经存在,就直接返回,不用访问MySQL了
*若不存在,再查询MySQL

3.缓存的更新策略
还有个重要的问题,到底哪些数据才是"热点数据"呢?
1.定期生成
每隔⼀定的周期(⽐如⼀天/⼀周/⼀个⽉), 对于访问的数据频次进⾏统计. 挑选出访问频次最⾼的前 N% 的数据.(会把访问的数据,给以日志形式存下来)
以搜索引擎为例.
⽤⼾在搜索引擎中会输⼊⼀个 "查询词", 有些词是属于⾼频的, ⼤家都爱搜(鲜花, 蛋糕, 同城交
友, 不孕不育...). 有些词就属于低频的, ⼤家很少搜.
搜索引擎的服务器会把哪个⽤⼾什么时间搜了啥词, 都通过⽇志的⽅式记录的明明⽩⽩. 然后
每隔⼀段时间对这期间的搜索结果进⾏统计 (⽇志的数量可能⾮常巨⼤, 这个统计的过程可能
需要使⽤ hadoop 或者 spark 等⽅式完成). 从⽽就可以得到 "⾼频词表" .
优点:实现比较简单;过程更可控(缓存中有啥是比较固定的),方便排查问题。
缺点:这种做法实时性较低. 对于⼀些突然情况应对的并不好.
⽐如春节期间, "春晚" 这样的词就会成为⾮常⾼频的词. ⽽平时则很少会有⼈搜索 "春晚".
2.实时生成
先给缓存设定容量上限(可以通过 Redis 配置⽂件的 maxmemory 参数设定).
接下来把⽤⼾每次查询:
- 如果在 Redis 查到了, 就直接返回.
- 如果 Redis 中不存在, 就从数据库查, 把查到的结果同时也写⼊ Redis.
如果缓存已经满了(达到上限), 就触发缓存淘汰策略, 把⼀些 "相对不那么热⻔" 的数据淘汰掉.
按照上述过程, 持续⼀段时间之后 Redis 内部的数据⾃然就是 "热⻔数据" 了.
通用的淘汰策略主要有以下几种:
下列策略并⾮局限于 Redis, 其他缓存也可以按这些策略展开
1.FIFO(First In First Out)先进先出
把缓存中存在时间最久的 (也就是先来的数据) 淘汰掉.
2.LRU(Least Recently Used)淘汰最久未使用的
记录每个 key 的最近访问时间. 把最近访问时间最⽼的 key 淘汰掉.
3.LUF(Least Frequently Used)淘汰访问次数最少的
记录每个 key 最近⼀段时间的访问次数. 把访问次数最少的淘汰掉
4.Random()随机淘汰
从所有的 key 中抽取幸运⼉被随机淘汰掉.
这⾥的淘汰策略, 我们可以⾃⼰实现. 当然 Redis 也提供了内置的淘汰策略, 也可以供我们直接使⽤
Redis 内置的淘汰算法如下:
sql
1.volatile-lru 当内存不⾜以容纳新写⼊数据时,从设置了过期时间的key中使⽤LRU(最近最
少使⽤)算法进⾏淘汰
2.allkeys-lru 当内存不⾜以容纳新写⼊数据时,从所有key中使⽤LRU(最近最少使⽤)算法进
⾏淘汰.
3.volatile-lfu 4.0版本新增,当内存不⾜以容纳新写⼊数据时,在过期的key中,使⽤LFU算法
进⾏删除key
4.allkeys-lfu 4.0版本新增,当内存不⾜以容纳新写⼊数据时,从所有key中使⽤LFU算法进⾏
淘汰
5.volatile-random 当内存不⾜以容纳新写⼊数据时,从设置了过期时间的key中,随机淘汰数
据.
6.allkeys-random 当内存不⾜以容纳新写⼊数据时,从所有key中随机淘汰数据
7.volatile-ttl 在设置了过期时间的key中,根据过期时间进⾏淘汰,越早过期的优先被淘汰.
(相当于 FIFO, 只不过是局限于过期的 key)
8.noeviction 默认策略,当内存不⾜以容纳新写⼊数据时,新写⼊操作会报错.
Redis这里会针对 "过期key" 和 "全部key" 做分别处理。
4.缓存预热,缓存穿透,缓存雪崩 和 缓存击穿
1.缓存预热(Cache preheating)
使⽤ Redis 作为 MySQL 的缓存的时候, 当 Redis 刚刚启动, 或者 Redis ⼤批 key 失效之后, 此时由于Redis ⾃⾝相当于是空着的, 没啥缓存数据, 那么 MySQL 就可能直接被访问到, 从⽽造成较⼤的压⼒.
因此就需要提前把热点数据准备好, 直接写⼊到 Redis 中. 使 Redis 可以尽快为 MySQL 撑起保护伞.热点数据可以基于之前介绍的统计的⽅式⽣成即可. 这份热点数据不⼀定⾮得那么 "准确", 只要能帮助
MySQL 抵挡⼤部分请求即可. 随着程序运⾏的推移, 缓存的热点数据会逐渐⾃动调整, 来更适应当前情况
2.缓存穿透(Cache penetration)
访问的 key 在 Redis 和 数据库中都不存在. 此时这样的 key 不会被放到缓存上, 后续如果仍然在访问该 key, 依然会访问到数据库.
这就会导致数据库承担的请求太多, 压⼒很⼤.
这种情况称为 缓存穿透.
为何产生?
原因可能有⼏种:
1.业务设计不合理. ⽐如缺少必要的参数校验环节, 导致⾮法的 key 也被进⾏查询了.
2.开发/运维误操作. 不⼩⼼把部分数据从数据库上误删了.
3.⿊客恶意攻击
如何解决?
1.针对要查询的参数进⾏严格的合法性校验. ⽐如要查询的 key 是⽤⼾的⼿机号, 那么就需要校验当前key 是否满⾜⼀个合法的⼿机号的格式.
2.针对数据库上也不存在的 key , 也存储到 Redis 中, ⽐如 value 就随便设成⼀个 "". 避免后续频繁访问数据库.
3.使⽤布隆过滤器先判定 key 是否存在, 再真正查询
布隆过滤器回顾:
布隆过滤器(Bloom Filter)是一种空间效率极高的概率型数据结构,用于判断一个元素是否在集合中,在 Redis 中应用布隆过滤器,能够有效提升缓存系统等的性能,解决一些常见问题,以下是详细介绍:
布隆过滤器的基本原理
布隆过滤器本质上是一个位数组和一系列哈希函数的组合。
- 位数组:初始化时,位数组的所有位都设置为 0。
- 哈希函数:拥有多个相互独立的哈希函数,当向布隆过滤器中添加元素时,会使用这些哈希函数对元素进行计算,得到多个哈希值,然后将位数组中对应位置的位设置为 1 。
- 查询元素:判断一个元素是否在集合中时,同样使用这些哈希函数对元素进行计算,查看位数组中对应位置的位是否都为 1 。如果有任何一位为 0,那么可以确定该元素一定不在集合中;如果所有位都为 1,则该元素很有可能在集合中(存在误判,因为不同元素经过哈希计算后可能会将相同的位置设置为 1, 但不会漏判)。
3.缓存雪崩(Cache avalanche)
短时间内⼤量的 key 在缓存上失效, 导致数据库压骤增, 甚⾄直接宕机
本来 Redis 是 MySQL 的⼀个护盾, 帮 MySQL 抵挡了很多外部的压⼒. ⼀旦护盾突然失效了, MySQL ⾃⾝承担的压⼒骤增, 就可能直接崩溃.
为何产生?
⼤规模 key 失效, 可能性主要有两种:
• Redis 挂了.
• Redis 上的⼤量的 key 同时过期.
为啥会出现⼤量的 key 同时过期?
这种和可能是短时间内在 Redis 上缓存了⼤量的 key, 并且设定了相同的过期时间
如何解决?
• 部署⾼可⽤的 Redis 集群, 并且完善监控报警体系.
• 不给 key 设置过期时间 或者 设置过期时间的时候添加随机时间因⼦
4.缓存击穿(Cache breakdown)
针对热点 key , 突然过期了, 导致⼤量的请求直接访问到数据库上, 甚⾄引起数据库宕机.
如何解决?
• 基于统计的⽅式发现热点 key, 并设置永不过期.
• 进⾏必要的服务降级. 例如访问数据库的时候使⽤分布式锁, 限制同时请求数据库的并发数