缓存的使用和优化

在实际的业务场景中,Redis 一般和其他数据库搭配使用,用来减轻后端数据库的压力,比如和关系型数据库 MySQL 配合使用。

Redis 会把 MySQL 中经常被查询的数据缓存起来,比如热点数据,这样当用户来访问的时候,就不需要到 MySQL 中去查询了,而是直接获取 Redis 中的缓存数据,从而降低了后端数据库的读取压力。如果说用户查询的数据 Redis 没有,此时用户的查询请求就会转到 MySQL 数据库,当 MySQL 将数据返回给客户端时,同时会将数据缓存到 Redis 中,这样用户再次读取时,就可以直接从 Redis 中获取数据。流程图如下所示:

1 缓存的收益与成本

1.1 收益

1 加速读写

2 降低后端负载:后端服务器通过前端缓存降低负载,业务端使用redis降低后端mysql负载

1.2 成本

1 数据不一致:缓存层和数据层有时间窗口不一致,和更新策略有关

2 代码维护成本:多了一层缓存逻辑

3 运维成本:比如使用了Redis Cluster

1.3 使用场景

1 降低后端负载:对高消耗的sql,join结果集/分组统计的结果做缓存

2 加速请求响应:利用redis优化io响应时间

3 大量写合并为批量写:如计数器先redis累加再批量写入db,比如用redis统计网站浏览量,每天晚上低峰时期落盘(写入数据库)

 

2 缓存更新策略

2.1 LRU/LFU/FIFO算法剔除:

例如maxmemory-policy(到了最大内存,对应的应对策略),超过最大内存,新的放不进去了,涉及到剔除一些东西,淘汰策略

​LRU -Least Recently Used   没有被使用时间最长的,保证热点数据

LFU -Least Frequenty User 一定时间段内使用次数最少的

FIFO -First In First Out       先进先出

​LIRS (Low Inter-reference Recency Set)是一个页替换算法,相比于LRU(Least Recently Used)和很多其他的替换算法,LIRS具有较高的性能。这是通过使用两次访问同一页之间的距离(本距离指中间被访问了多少非重复块)作为一种尺度去动态地将访问页排序,从而去做一个替换的选择。

LRU配置:

# 常用配置,对有过期时间的key采用LRU淘汰算法
> maxmemory-policy:volatile-lru

# 其他配置
>(1)noeviction:      如果内存使用达到了maxmemory,client还要继续写入数据,那么就直接报错给客户端
>(2)allkeys-lru:     就是我们常说的LRU算法,移除掉最近最少使用的那些keys对应的数据,ps最长用的策略
>(3)volatile-lru:    也是采取LRU算法,但是仅仅针对那些设置了指定存活时间(TTL)的key才会清理掉
>(4)allkeys-random:  随机选择一些key来删除掉
>(5)volatile-random: 随机选择一些设置了TTL的key来删除掉
>(6)volatile-ttl:    移除掉部分keys,选择那些TTL时间比较短的keys

LFU配置:

maxmemory-policy:volatile-lfu

># LFU配置 Redis4.0之后为maxmemory_policy淘汰策略添加了两个LFU模式:
>volatile-lfu:对有过期时间的key采用LFU淘汰算法
>allkeys-lfu: 对全部key采用LFU淘汰算法

># 还有2个配置可以调整LFU算法:
>lfu-log-factor 10  可以调整计数器counter的增长速度,lfu-log-factor越大,counter增长的越慢。
>lfu-decay-time 1   一个以分钟为单位的数值,可以调整counter的减少速度。
# 如:1分钟使用10次,不在这个范围就把它剔除

2.2 超时剔除:例如expire,设置过期时间

2.3 主动更新:开发控制生命周期

策略 数据双写一致性 维护成本
算法剔除 最差
超时剔除 较差
主动更新

1 低一致性:最大内存和淘汰策略

2 高一致性:超时剔除和主动更新结合,最大内存和淘汰策略兜底

 

3 缓存粒度控制

跟具体要查mysql哪些数据相关,是*查所有,还时只需要查name、id等关键属性,需要结合公司具体业务场景

1 从mysql获取用户信息:select * from user where id=100

2 设置用户信息缓存:set user:100 select * from user where id=100

3 缓存粒度:
​ 缓存全部属性,占内存
​ 缓存部分属性,不便于扩展
​ 缓存部分重要属性,推荐

 

4 缓存穿透,缓存击穿,缓存雪崩

4.1 缓存穿透

缓存穿透是指当用户查询某个数据时,Redis 中不存在该数据,也就是缓存没有命中,此时查询请求就会转向持久层数据库 MySQL,结果发现 MySQL 中也不存在该数据,MySQL 只能返回一个空对象,代表此次查询失败。

如发起请求id为“-1”的数据或id为特别大不存在的数据。如果这种类请求非常多,或者用户利用这种请求进行恶意攻击,就会给 MySQL 数据库造成很大压力,甚至于崩溃,这种现象就叫缓存穿透。

解决方案:

1) 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;

2) 缓存空对象

当 MySQL 返回空对象时, Redis 将该对象缓存起来(将key-value对写为key-null),同时为其设置一个过期时间(如设置30秒,太长会导致正常情况也没法使用)。当用户再次发起相同请求时,就会从缓存中拿到一个空对象,用户的请求被阻断在了缓存层,从而保护了后端数据库,但是这种做法也存在一些问题,虽然请求进不了 MSQL,但是这种策略会占用 Redis 的缓存空间。

3) 布隆过滤器

我们知道,布隆过滤器判定不存在的数据,那么该数据一定不存在,利用它的这一特点可以防止缓存穿透。

首先将用户可能会访问的热点数据,如MySQL存的所有用户id,存储在布隆过滤器中(也称缓存预热),当有一个用户请求到来时会先经过布隆过滤器,如果请求的数据,布隆过滤器中不存在,那么该请求将直接被拒绝,减少没有必要的数据库请求,否则将继续执行查询。相较于第2种方法,用布隆过滤器方法更为高效、实用。其流程示意图如下:

缓存预热:是指系统启动时,提前将相关的数据加载到 Redis 缓存系统中。这样避免了用户请求的时再去加载数据。

4.2 缓存击穿

缓存击穿是指用户查询的数据缓存中不存在,但是后端数据库却存在,这种现象出现原因一般是由缓存中 key 过期导致的。比如一个热点数据 key,它无时无刻都在接受大量的并发访问,如果某一时刻这个 key 突然失效了,就致使大量的并发请求进入后端数据库,导致其压力瞬间增大。这种现象被称为缓存击穿。

 解决方案:

1) 改变过期时间

设置热点数据永不过期。

2) 分布式锁

采用分布式锁的方法,重新设计缓存的使用方式,过程如下:

  • 上锁:当我们通过 key 去查询数据时,首先查询缓存,如果没有,就通过分布式锁进行加锁,第一个获取锁的进程进入后端数据库查询,并将查询结果缓到Redis 中。
  • 解锁:当其他进程发现锁被某个进程占用时,就进入等待状态,直至解锁后,其余进程再依次访问被缓存的 key。

4.3 缓存雪崩

缓存雪崩是指缓存中大批量的 key 同时过期,而此时数据访问量又非常大,从而导致后端数据库压力突然暴增,甚至会挂掉,这种现象被称为缓存雪崩。它和缓存击穿不同,缓存击穿是在并发量特别大时,某一个热点 key 突然过期,而缓存雪崩则是大量的 key 同时过期,因此它们根本不是一个量级。

解决方案:

缓存雪崩和缓存击穿有相似之处,所以也可以采用热点数据永不过期的方法,来减少大批量的 key 同时过期。再者就是为 key 设置随机过期时间,避免 key 集中过期。如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中。

posted @ 2022-11-01 11:38  不会钓鱼的猫  阅读(142)  评论(0编辑  收藏  举报