Redis 典型应用之缓存

目录

[1. 缓存的基本概念](#1. 缓存的基本概念)

[2. 使用 Redis 作为缓存](#2. 使用 Redis 作为缓存)

[3. 缓存的更新策略](#3. 缓存的更新策略)

[3.1 定期生成](#3.1 定期生成)

[3.2 实时生成](#3.2 实时生成)

[3.2.1 内存淘汰策略](#3.2.1 内存淘汰策略)

[1. FIFO (First In First Out) 先进先出](#1. FIFO (First In First Out) 先进先出)

[2. LRU (Least Recently Used) 淘汰最久未使使用的](#2. LRU (Least Recently Used) 淘汰最久未使使用的)

[3. LFU (Least Frequently Used) 淘汰访问次数最少的](#3. LFU (Least Frequently Used) 淘汰访问次数最少的)

[4. Random 随机淘汰](#4. Random 随机淘汰)

[4. 缓存使用的注意事项](#4. 缓存使用的注意事项)

[1. 缓存预热 (Cache preheating)](#1. 缓存预热 (Cache preheating))

[2. 缓存穿透 (Cache penetration)](#2. 缓存穿透 (Cache penetration))

[3. 缓存雪崩 (Cache avalanche)](#3. 缓存雪崩 (Cache avalanche))

[4. 缓存击穿 (Cache breakdown)](#4. 缓存击穿 (Cache breakdown))


1. 缓存的基本概念

Redis 最主要的用途:

  1. 存储数据(内存数据库)

  2. 缓存 [redis 最常用的场景]

  3. 消息队列

缓存是计算机中的一个经典概念,在很多场景都会运用到。

核心思路就是将一些常用数据放到访问速度更快(跟原来相比)的地方,方便随时读取。

对于硬件的访问速度来说,通常是:CPU 寄存器 > 内存 > 硬盘 > 网络

速度快的可以作为速度慢的设备的缓存。

其中最常见的是 内存 作为 硬盘 的缓存。(redis 定位)

硬盘也可以作为网络的缓存(浏览器缓存),像体积大又不太改变的数据(图片,视频,音频,字体)就可以保存到浏览器本体(浏览器所在主机的硬盘上),后续再打开这个页面,就不用重新从网络上获取上述数据了。

缓存速度虽然快,但是空间小。一般在通常情况下,20% 热点数据,可以应对 80% 的请求。

2. 使用 Redis 作为缓存

通常是使用 redis 作为关系型数据库的缓存 (比如 mysql)。

因为在绝大部分商业项目中都会涉及到关系型数据库,虽然关系型数据库功能强大,但是性能不高,那么此时就可以使用 redis 作为缓存。

为什么说关系型数据库性能不高?

  1. 数据库把数据存储在硬盘上, 硬盘的 IO 速度并不快. 尤其是随机访问.
  2. 如果查询不能命中索引, 就需要进行表的遍历, 这就会大大增加硬盘 IO 次数.
  3. 关系型数据库对于 SQL 的执行会做一系列的解析, 校验, 优化工作.
  4. 如果是一些复杂查询, 比如联合查询, 需要进行笛卡尔积操作, 效率更是降低很多.

因为 mysql 等数据库,效率比较低,所以承担的并发量就有限。一旦请求数量多了,数据库的压力就会很大,甚至很容易就宕机了。

那么该如何提高 mysql 承担的并发量?

  1. 开源:引入更多的机器,构成数据库集群。

  2. 节流:引入缓存,就是典型的方案。把一些频繁读取的热点数据保存到缓存上,后续在查询数据的时候,如果缓存中已经存在,那就不再访问 mysql 了。

缓存是用来加快 "读操作" 的速度的. 如果是 "写操作", 还是要老老实实写数据库, 缓存并不能提高性能。

3. 缓存的更新策略

将热点数据存入 redis 中,那么怎么知道哪些数据才是 "热点数据" 呢?

有两种方法,第一种是定期生成,第二种是实时生成。

3.1 定期生成

把会访问的数据,通过日志的方式记录下来。

每隔一定的周期(比如一天/一周/一月),对于访问的数据的频次进行统计。挑选出访问频次最高的前 N% 的数据。

优点:上述过程,实际上实现起来是比较简单的,过程更可控。(缓存中有啥内容是固定的),方便排查问题。

缺点:实时性不够,如果出现一些突发性事件,有一些本来不是热词的内容,成了热词了,新的热词就可能给后面的数据库带来较大的压力。

3.2 实时生成

先给缓存设定容量上限(可以通过 Redis 配置⽂件的 maxmemory 参数设定)。

接下来用户的每次查询:

如果在 redis 查到了,就直接返回。

如果没在 redis 里查到,那就去数据库里查,同时把查询结果写入到 redis 中。

这样不停的写,就会使 redis 的内存占用越来越多,逐渐达到内存的上限。

此时如果再继续往里插入数据,就会出现问题,为了解决上述情况,redis 引入了 "内存淘汰策略"。

3.2.1 内存淘汰策略

通用的淘汰策略主要有以下几种:

1. FIFO (First In First Out) 先进先出

把缓存中存在时间最久的(也就是先来的数据) 淘汰掉。

2. LRU (Least Recently Used) 淘汰最久未使使用的

记录每个 key 的最近访问时间,把最近访问时间最老的 key 淘汰掉。

3. LFU (Least Frequently Used) 淘汰访问次数最少的

记录每个 key 最近一段时间的访问次数,把访问次数最少的淘汰掉。

4. Random 随机淘汰

从所有的 key 中抽取幸运儿被随机淘汰掉。

举个例子,假设你只有一部手机,手机里安装了一些游戏,你经常玩的游戏就那几个:雀魂,崩坏:星穹铁道,明日方舟,phigros,王者荣耀,闪耀暖暖

手机里所有的游戏,就相当于数据库中的全量数据,经常玩的游戏相当于热点数据,存放在缓存中。

某一天,你想玩一个新的游戏,但是你手机内存不够,此时就需要卸载一个游戏,那该卸载哪一个呢?

FIFO:王者荣耀是最先下载的,所以先删除王者荣耀。

LRU:统计最近游玩时间。雀魂(一月前),崩坏:星穹铁道(昨天),phigros(一周前),明日方舟(三天前),王者荣耀(两天前),闪耀暖暖(两周前)。卸载雀魂。

LFU:统计最近一个月的游玩次数。雀魂(3次),崩坏:星穹铁道(25次),phigros(10次),明日方舟(20次),王者荣耀(30次),闪耀暖暖(1次)。卸载闪耀暖暖。

Random:随机挑选一个游戏删除。

这里的淘汰策略, 我们可以自己实现. 当然 Redis 也提供了内置的淘汰策略, 也可以供我们直接使用。

Redis 内置的淘汰策略如下:

带有 volatile 的就是从设置了过期时间的 key 里删除(设置了过期时间就算,包括过期时间还没到的)

带有 allkeys 的就是从所有的 key 里删除。

volatile-lru

当内存不足以容纳新写入数据时,从设置了过期时间的 key 中使用 LRU(最近最少使用)算法进行淘汰。

allkeys-lru

当内存不足以容纳新写入数据时,从所有 key 中使用 LRU(最近最少使用)算法进行淘汰。

volatile-lfu

4.0 版本新增,当内存不足以容纳新写入数据时,在过期的 key 中,使用 LFU 算法进行删除 key。

allkeys-lfu

4.0 版本新增,当内存不足以容纳新写入数据时,从所有 key 中使用 LFU 算法进行淘汰。

volatile-random

当内存不足以容纳新写入数据时,从设置了过期时间的 key 中,随机淘汰数据。

allkeys-random

当内存不足以容纳新写入数据时,从所有 key 中随机淘汰数据。

volatile-ttl

在设置了过期时间的 key 中,根据过期时间进行淘汰,越早过期的优先被淘汰。(相当于 FIFO, 只不过是局限于过期的 key)

noeviction

默认策略,当内存不足以容纳新写入数据时,新写入操作会报错。

4. 缓存使用的注意事项

1. 缓存预热 (Cache preheating)

缓存中的数据:

  1. 定期生成 (这种情况,不涉及"预热")

  2. 实时生成

redis 服务器首次接入,服务器里是没有数据的,此时,所有的请求都会打给 mysql,随着时间的推移,redis 上的数据越积累越多,mysql 承担的压力就逐渐减小了。

缓存预热,就是用来解决上述问题的。把定期生成和实时生成结合一下。

先通过离线的方式,通过一些统计的途径,先把热点数据找到一批,导入到 redis 中,此时导入的这批热点数据,就能帮 mysql 承担很大的压力了。随着时间的推移,逐渐就使用新的热点数据淘汰掉旧的数据。

2. 缓存穿透 (Cache penetration)

如果访问的 key 在 redis 和 mysql 都不存在,那么这样的 key 不会被放到缓存上,后续仍然在访问该 key,就依然会访问到数据库。

像这样的数据存在很多,并且还反复查询,一样也会给 mysql 带来很大的压力。

产生的原因:

  1. 业务设计不合理,比如缺少必要的参数校验环节,导致非法的 key 也被进行查询了(典型)

  2. 开发/运维误操作,不小心把部分数据从数据库中误删了

  3. 黑客恶意攻击(比较少见)

如何解决?

通过改进业务/加强监控报警。

更靠谱的方案(降低问题严重性):

  1. 如果发现这个 key,在 redis 和 mysql 中都不存在,仍然写入 redis 中,value 设成一个非法值(比如 "")

  2. 还可以引入布隆过滤器,每次查询 redis / mysql 之前都先判定一下 key 是否在布隆过滤器上存在。(把所有 key 插入到布隆过滤器中)

3. 缓存雪崩 (Cache avalanche)

短时间内,redis 上大规模的 key 失效,导致缓存命中率陡然下降,并且 mysql 的压力迅速上升,甚至直接宕机。

产生原因:

  1. redis 直接挂了

  2. redis 好着呢,但是可能之前短时间内设置了很多 key 给 redis,并且设置的过期时间是相同的。

给 redis 里设置 key 作为缓存的时候,有的时候为了考虑缓存的时效性,就会设置过期时间。(和 redis 内存淘汰机制,配合使用)

解决方案:

  1. 加强监控报警,加强 redis 集群可用性的保证。

  2. 不给 key 设置过期时间,设置过期时间的时候添加随机的因子(避免同一时刻过期)

4. 缓存击穿 (Cache breakdown)

相当于缓存雪崩的一种特殊情况,针对热点 key, 突然过期了,导致大量的请求直接访问到数据库上,甚至引起数据库宕机。

解决方案:

  1. 基于统计的方式发现热点 key,并设置永不过期。

  2. 进行必要的服务降级,例如访问数据库的时候,使用分布式锁,限制同时请求数据库的并发数。

本身服务器的功能有十个,但是在特定情况下,适当的关闭一些不重要的功能,只保留核心功能(服务降级),类似于省电模式。

相关推荐
Arc星语1 小时前
Docker Redis集群3主3从模式
redis·docker
不惑_1 小时前
Redis与MySQL双写一致性的缓存模式
redis·mysql·缓存
是丝豆呀2 小时前
清理pip和conda缓存
缓存·conda·pip
走,我们去吹风4 小时前
redis实现分布式锁,go实现完整code
redis·分布式·golang
三日看尽长安花5 小时前
【Redis:原理、架构与应用】
数据库·redis·架构
孟章豪11 小时前
从零开始:在 .NET 中构建高性能的 Redis 消息队列
redis·c#
隔窗听雨眠11 小时前
深入理解Redis的四种模式
java·redis·mybatis
北笙··11 小时前
Redis慢查询分析优化
数据库·redis·缓存
p-knowledge11 小时前
redis的三种客户端
数据库·redis·缓存
说淑人11 小时前
Redis & 线程控制 & 问题
redis·线程控制