文章目录
前言
前面说了:redis 可以用来作为内存数据库、消息队列和缓存,对于这三种使用场景,redis 作为缓存是最常见的,那么这篇文章将为大家介绍关于 redis 作为缓存的知识。
什么是缓存
缓存(Cache) 是计算机系统中用于存储临时数据的地方,以便后续更快地访问这些数据。缓存的存在是为了减少从原始数据源(如主存储器或数据库)获取数据的次数,从而提高数据访问的速度。
对于硬件的访问速度来说,通常情况下:cpu寄存器 > 内存 > 硬盘 > 网络,缓存是两个读取速度差异较大的硬件设备的中间状态。例如:硬盘内的读取速度相较于 cpu 寄存器来说差了几个量级,所以每次将数据从硬盘读到 cpu 寄存器的时候就需要花费比较多的时间,为了尽量减小两者读取数据的差异,就使用内存作为缓存来减少两者之间读取速度的差异。内存会将经常使用到的数据存储到内存中来作为缓存,当 cpu 寄存器要从硬盘上读取数据的时候会先去内存中读取数据,如果内存中存在需要找的数据,那么就会直接返回这个数据的值,否则就去硬盘上找。
但是我们都知道,读取速度越快也就意味着它的价格越高,所以通常内存的大小是比较小的,那么内存大小是否能够满足我们数据查询的请求呢?不知道大家有没有听过“二八定律”,就是百分之二十的数据能够应付我们百分之八十的需求,也就是内存中存储的数据一般是能够满足百分之八十的请求的,剩下的百分之二十则是去硬盘上查找。所以内存中存储经常使用到的数据是能够满足百分之八十的查询请求的。
使用redis作为数据库的缓存
关系型数据库虽然功能强大,但是有一个致命的问题,就是性能不高,那么为什么性能不高呢?
- 数据库把数据存储在硬盘上,硬盘的 IO 速度并不快。尤其是随机访问.
- 如果查询不能命中索引,就需要进行表的遍历,这就会大大增加硬盘 IO 次数
- 关系型数据库对于 SQL 的执行会做⼀系列的解析,校验,优化⼯作
- 如果是⼀些复杂查询,比如联合查询,需要进行笛卡尔积操作,效率更是降低很多
如果访问数据库的并发量比较高,那么对于数据库的压力是很大的,很容易就会导致数据库服务器宕机(数据库是非常脆弱的)。
那么如何才能让数据库能够承受更大的并发量呢?核心思路是两个:
- 开源:引入更多的机器,部署更多的数据库实例,构成数据库集群
- 节流:引入缓存,使用其他的方式保存经常访问的热点数据,从而降低直接访问数据库请求数量
那么 redis 作为数据库的缓存就是使用了节流的方式来减小数据库的请求压力。
redis 作为 MySQL 的缓存,虽然可以帮助 MySQL 解决百分之八十的请求,但是数据是一直在增加和变化的,所以 redis 中的数据也不是一成不变的,当内存中的数据的大小超过了内存大小的时候,该怎么办呢?这时是否就意味着内存中之前存储的数据就需要删除,为这个新插入的数据让位置,那么 redis 缓存中的数据是如何更新的呢?
redis缓存的更新策略
缓存更新又分为定期更新和实时更新。
定时生成
每隔一定的周期(一天/一周/一个月),对于访问的数据的频次进行统计,挑选出访问频次前 %N 的数据。这个数据访问频次的统计往往是用户在搜索的时候会生成一个日志,然后就根据这个日志来统计数据的访问频次。
对于定时更新的策略,虽然实现比较简单,过程更可控(在一个周期内,缓存中的数据都是固定的),方便排查问题,但是实时性比较低,如果出现了突发情况的话,就会出现问题。就比如:春晚这样的关键词,只有在春节期间才比较高频,在其他的时间访问的频率就比较低,所以这种情况使用定时更新的话,就不是很合适。
实时生成
实时生成会更快的根据用户的查询请求来更新缓存数据,当用户的某次查询的数据在缓存中存在的时候,就会直接返回这个数据的值,但是如果这个数据在缓存中不存在的时候,那么这次请求就会被数据库解决,并且会判断是否需要更新缓存,如果此时内存中数据的大小小于内存的大小的话就可以直接将这个数据放入缓存中,但是如果内存中的数据已经达到 redis 配置文件中 maxmemory 设置的值的话,就需要调整缓存中的数据,也就是需要删除缓存中的部分数据。那么具体该删除哪些数据呢?有以下几种淘汰策略:
FIFO(First In First Out)先进先出
把缓存中存在时间最久的(也就是先来的数据)淘汰掉
LRU (Least Recently Used)淘汰最久未使用的
记录每个 key 的最近一次的使用时间,将最近一次使用时间最长的 key 淘汰掉
LFU (Least Frequently Userd)淘汰访问次数最少的
记录每个 key 一段时间之内的访问次数,淘汰掉访问次数最少的 key
Random 随机淘汰
从所有的 key 中随机挑选出一个 key 进行删除
这些淘汰策略我们可以自己实现,当然 redis 也提供了内置的淘汰策略,我们可以直接使用。Redis 内置的淘汰策略如下:
volatile-lru:当内存不足以容纳新写入的数据的时候,从设置了过期时间的 key 中使用 LRU 算法进行淘汰
allkeys-lru:当内存不足以容纳新写入的数据的时候,从所有的 key 中使用 LRU 算法进行淘汰
volatile-lfu:当内存不足以容纳新写入的数据的时候,从设置了过期时间的 key 中使用 LFU 算法进行淘汰
allkeys-lfu:当内存不足以容纳新写入的数据的时候,从所有 key 中使用 LFU 算法进行淘汰
volatile-random:当内存不足以容纳新写入的数据的时候,从设置了过期时间的 key 中随机淘汰
allkeys-random:当内存不足以容纳新写入的数据的时候,从所有 key 中随机淘汰
volatile-ttl:在设置了过期时间的 key 中,根据过期时间来进行淘汰,越早过期的 key 优先被淘汰
noeviction:当内存不足以容纳新写入的数据的时候,新写入操作会报错
redis作为缓冲可能会出现的问题
1. 缓存预热(Cache preheating)
使用 redis 作为 MySQL 的缓存的时候,当 redis 刚刚启动的时候,或者 redis 大批 key 失效之后,此时由于 redis 自身相当于是空着的,没啥缓存数据,所以客户端访问数据的请求最终都会落到 MySQL 数据库服务器上,就会对 MySQL 服务器产生较大的压力。
为了解决这个问题,我们在新启动作为 MySQL 缓存的 redis 服务器之前,需要提前把热点数据准备好,直接写入到 redis 中,使得 redis 能够尽早的为 MySQL 撑起保护伞。这份热点数据不需要一定的“准确”,只要能够帮助 MySQL 抵挡大部分的请求就可以了,随着 redis 内存中数据的更新,缓存中的数据会逐渐自动调整,更加使用请求。
2. 缓存穿透(Cache penetration)
访问的 key 在 redis 和 MySQL 数据库中都不存在的时候,那么这个 key 的数据就不会存储到 redis 中,那么后面每次访问这个 key 的时候都需要去 MySQL 中查询,如果反复查询这个 key 的话,就会给 MySQL 服务器造成很大的压力,这种情况就叫做缓存穿透。
产生缓存穿透的原因有哪些呢?
- 业务设计不合理,比如缺少必要的参数校验环节,导致非法的 key 也被查询了
- 开发/运维误操作,不小心将部分数据从数据库中删除了
- 黑客攻击
那么如何解决这个问题呢?
- 针对要查询的 key 进行参数的合法性校验,避免非法的 key 的查询对 MySQL 数据库服务器造成较大的压力
- 对于 redis 和 MySQL 中不存在的 key,也将其存进 redis 内存中,并且设置 key 的值为空 “” 等不影响结果的值,这样每次查询该 key 的时候,都会命中缓存,并且不影响结果,避免了频繁访问数据库
- 使用布隆过滤器先判断 key 是否存在,再真正查询。将数据库中已存在的数据通过布隆过滤器给记录下来,每次查询 key 的时候,先在布隆过滤器中查看该 key 是否存在,不存在则可以直接返回,存在才继续插叙
3. 缓存雪崩(Cache avalanche)
段短时间内,redis 缓存中的大量的 key 都失效了,那么大部分的查询请求最终都会落到 MySQL 数据库服务器上,从而导致 MySQL 服务器压力骤增,甚至直接宕机。
什么情况下,redis 内的大部分数据会消失呢?一是,这些 key 的过期时间相同,当到了过期时间之后,这些大部分的 key 都会消失,二就是 redis 挂了,这些情况都会导致缓存雪崩。
如何避免缓存雪崩问题的出现呢?
- 部署高可用的 redis 集群,并且完善监控报警机制
- 不给 key 设置过期时间或者设置过期时间的时候添加随机因子,防止大部分的 key 的过期时间相同
4. 缓存击穿(Cache breakdown)
这里 breakdown 翻译为“瘫痪”或者“崩溃”或许更好一些,缓存崩溃相当于缓存雪崩的特殊情况,针对热点 key,突然过期了,并且这个时候大量的访问请求需要访问这个 key,那么大量的请求就会落到 MySQL 中,造成 MySQL 服务器压力过大甚至宕机。
如何解决:
- 基于统计发现的方式发现热点 key,并不设置过期时间
- 进行必要的服务降级,访问数据库的时候使用分布式锁,限制同时请求数据库的并发数
这里分布式锁是什么,下一篇文章为大家介绍。