10.Redis 缓存

Redis 缓存是 Redis 最常用的场景之一,通过将热点数据存储在内存中,显著提升应用程序的读写性能,减轻数据库负担。

1.什么是缓存

缓存(cache)核心思路就是把一些常用的数据结构放到触手可及(访问速度更快)的地方,方便随时获取。

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

2.使用Redis 作为缓存

在一个网站中,我们经常会使用关系型数据库(MySQL)来存储数据。
关系型数据库虽然功能强⼤, 但是有⼀个很⼤的缺陷, 就是性能不⾼. (换⽽⾔之, 进⾏⼀次查询操作消耗的系统资源较多).

为啥关系型数据库效率不高?

硬件:

  1. 数据库把数据存储在硬盘上, 硬盘的 IO 速度并不快. 尤其是随机访问.
  2. 如果查询不能命中索引, 就需要进⾏表的遍历, 这就会⼤⼤增加硬盘 IO 次数.
    软件:
  3. 关系型数据库对于 SQL 的执⾏会做⼀系列的解析, 校验, 优化⼯作.
  4. 如果是⼀些复杂查询, ⽐如联合查询, 需要进⾏笛卡尔积操作, 效率更是降低很多.
    因此, 如果访问数据库的并发量⽐较⾼, 对于数据库的压⼒是很⼤的, 很容易就会使数据库服务器宕机.

为什么并发量高了就容易挂机?

服务器每次处理⼀个请求, 都是需要消耗⼀定的硬件资源的. 所谓的硬件资源包括不限于 CPU,
内存, 硬盘, ⽹络带宽......
⼀个服务器的硬件资源本⾝是有限的. ⼀个请求消耗⼀份资源, 请求多了, ⾃然把资源就耗尽
了. 后续的请求没有资源可⽤, ⾃然就⽆法正确处理. 更严重的还会导致服务器程序的代码出现
崩溃.

如何让数据库能够承担更⼤的并发量呢?

核⼼思路主要是两个:
1.开源:引入更多的机器,部署更多的数据库实例,构造数据库集群.(主从复制,分库分表等..)
2.节流:引入缓存,使用其他方法的方式保存经常访问的热点数据,从而降低直接访问的数据库的请求数量。(实际开发中,这两种方案往往是会搭配使用的)
Redis 就像一个护盾一样,把MySQL给罩住了

1.客户端访问业务服务器,发起查询请求。

2.业务服务器先查询 Redis,看想要的数据是否在Redis 中是否存在

*如果已经存在,就直接返回,不用访问MySQL了

*若不存在,再查询MySQL

3.缓存的更新策略

还有个重要的问题,到底哪些数据才是"热点数据"呢?

1.定期生成

每隔⼀定的周期(⽐如⼀天/⼀周/⼀个⽉), 对于访问的数据频次进⾏统计. 挑选出访问频次最⾼的前 N% 的数据.(会把访问的数据,给以日志形式存下来

复制代码
以搜索引擎为例.
⽤⼾在搜索引擎中会输⼊⼀个 "查询词", 有些词是属于⾼频的, ⼤家都爱搜(鲜花, 蛋糕, 同城交
友, 不孕不育...). 有些词就属于低频的, ⼤家很少搜.
搜索引擎的服务器会把哪个⽤⼾什么时间搜了啥词, 都通过⽇志的⽅式记录的明明⽩⽩. 然后
每隔⼀段时间对这期间的搜索结果进⾏统计 (⽇志的数量可能⾮常巨⼤, 这个统计的过程可能
需要使⽤ hadoop 或者 spark 等⽅式完成). 从⽽就可以得到 "⾼频词表" .

优点:实现比较简单;过程更可控(缓存中有啥是比较固定的),方便排查问题。
缺点:这种做法实时性较低. 对于⼀些突然情况应对的并不好.
⽐如春节期间, "春晚" 这样的词就会成为⾮常⾼频的词. ⽽平时则很少会有⼈搜索 "春晚".

2.实时生成

先给缓存设定容量上限(可以通过 Redis 配置⽂件的 maxmemory 参数设定).
接下来把⽤⼾每次查询:

  1. 如果在 Redis 查到了, 就直接返回.
  2. 如果 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, 并设置永不过期.
• 进⾏必要的服务降级. 例如访问数据库的时候使⽤分布式锁, 限制同时请求数据库的并发数

相关推荐
从零开始的-CodeNinja之路2 小时前
【Redis】Redis 缓存应用、淘汰机制—(四)
java·redis·缓存
星辰徐哥2 小时前
CDN工作原理:节点缓存、智能调度,减少跨网传输延迟
服务器·缓存·php
星辰徐哥2 小时前
ARP缓存表:作用、查看方法与刷新技巧
开发语言·缓存·php
雨墨✘2 小时前
CSS如何提高团队协作效率_推广BEM规范减少样式沟通成本
jvm·数据库·python
hef2882 小时前
如何实现SQL字段值的计算输出:算术运算符与别名结合
jvm·数据库·python
那个失眠的夜2 小时前
Spring 的纯注解配置
xml·java·数据库·后端·spring·junit
XDHCOM2 小时前
ORA-06722: TLI Driver连接失败,Oracle报错修复对比远程处理,选择最佳解决方案
数据库·oracle
小红的布丁2 小时前
Redis 集群详解:主从哨兵和切片集群有什么区别
前端·数据库·redis
杰克尼2 小时前
redis(day08-Redis原理篇)
数据库·redis·php