跳至主要內容

Redis进阶 - 缓存问题

友人大约 6 分钟

Redis进阶 - 缓存问题

缓存

缓存就是数据交换的缓冲区(Cache),是存储数据的临时地方,一般读写性能比较高。

  • 例如
  1. 内存是硬盘的缓存
  2. cache 是内存的缓存
  • 作用
  1. 降低后端负载
  2. 提高读写效率,降低响应时间
  • 成本
  1. 数据一致性成本
  2. 代码维护成本

缓存穿透

缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库。如请求不存在的数据,则 Redis 缓存中不存在,数据库中也不存在,频繁请求,造成资源的浪费。

解决方案

  • 缓存空对象
    • 优点:实现简单,维护方便
    • 缺点:1. 额外的内存消耗。 2. 可能造成短期的不一致
  • 布隆过滤
    • 优点:内存占用小,没有多余的key
    • 缺点:1. 实现复杂 2. 存在误判可能
  • 增强ID的复杂度,避免被猜测ID规律
  • 做好数据的基础格式校验

缓存雪崩

缓存雪崩是指在同一时间段内,大量的缓存 Key 同时失效或者 Redis 服务宕机,导致瞬间大量请求到达数据库,带来巨大压力。

解决方案

  • 给不同的 Key 的TTL添加随机值
  • 利用 Redis 集群提高服务的可用性
  • 给缓存业务添加降级限流操作
  • 给业务添加多级缓存

缓存击穿

缓存击穿问题也叫热点 key 问题,就是一个被高并发访问并且缓存重建业务较复杂的 key 突然失效,无数的请求访问会在瞬间给数据库带来巨大的冲击。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库,导致请求数据库的总量比较大。

解决方案

  • 互斥锁
    1. 查询缓存,未命中
    2. 获取锁,再更新缓存
    3. 更新完以后,释放锁
  • 逻辑过期
解决方案优点缺点
互斥锁1. 没有额外的内存消耗
2. 保证一致性
3. 实现简单
1. 线程需要等待,性能受影响
2. 可能有死锁风险
逻辑过期线程无需等待,性能较好1. 不保证一致性
2. 有额外内存消耗
3. 实现复杂

缓存污染

缓存污染问题指的是缓存中一些只会被访问一次或者几次的数据,被访问完以后,再也不会被访问到,但这部分数据依然留在缓存中,占用缓存空间。

缓存污染会随着数据的持续增加而逐渐显露,随着服务的不断运行,缓存中会存在大量的永远不会被访问的数据。然而缓存空间是有限的,如果缓存写满了,再往缓存中存储数据时就会有额外的性能开销,影响 Redis 的性能。这部分开销主要是指往缓存中写数据时判断缓存淘汰策略,根据淘汰策略去选择要淘汰的数据,然后进行删除操作。

  • 最大缓存设置多少

系统的设计选择是一个权衡的过程:大容量缓存是能带来性能加速的收益,但是成本也会更高,而小容量缓存不一定就起不到加速访问的效果。一般来说,我会建议把缓存容量设置为总数据量的 15% 到 30%,兼顾访问性能和内存空间开销。对于 Redis 来说,一旦确定了缓存最大容量,比如 4GB,你就可以使用下面这个命令来设定缓存的大小了:

CONFIG SET maxmemory 4gb

不过,缓存被写满是不可避免的, 所以需要数据淘汰策略。

  • 缓存淘汰策略

具体的放到下一个小节来说

缓存更新策略

内存淘汰超时剔除主动更新
说明不用自己维护,利用 Redis 的内存淘汰机制,当内存不足时自动淘汰部分数据。下次查询时更新缓存给缓存数据添加TTL时间,到期后自动删除缓存。下次查询时更新缓存。编写业务逻辑,在修改数据库的同时,更新缓存。
一致性一般
维护成本

业务场景:

  • 低一致性需求:数据的变化频率低,使用内存淘汰机制。例如:店铺类型的查询缓存。
  • 高一致性需求:数据经常发生改变,主动更新,并以超时剔除作为兜底方案。例如:店铺详情查询的缓存。

主动更新策略

  1. Cache Aside Pattern:由缓存的调用者,在更新数据库的同时更新缓存。

  2. Read/Write Through Pattern:缓存和数据库整合为一个服务,由服务来维护一致性。调用者调用该服务,无需关心缓存一致性问题。

  3. Write Behind Caching Pattern:调用者只操作缓存,由其他线程异步的将缓存数据持久化到数据库,保证最终一致。

一般情况下都使用第一种方法,可控性更高。

在操作缓存时,数据库发生修改则直接删除对应的缓存,待查询时再创建缓存。

  • 单体系统,将缓存和数据库操作放在同一个事务内。
  • 分布式系统,使用TCC等分布式事务方案。

先操作数据库,在删除缓存。

缓存淘汰策略

Redis 一共支持 8 种淘汰策略

  • noeviction:当内存使用超过配置的时候会返回错误,不会驱逐任何键。

  • allkeys-lru:加入键的时候,如果过限,首先通过LRU算法驱逐最久没有使用的键。

  • volatile-lru:加入键的时候如果过限,首先从设置了过期时间的键集合中驱逐最久没有使用的键。

  • allkeys-random:加入键的时候如果过限,从所有key随机删除。

  • volatile-random:加入键的时候如果过限,从过期键的集合中随机驱逐。

  • volatile-ttl:从配置了过期时间的键中驱逐马上就要过期的键。

  • volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键。

  • allkeys-lfu:从所有键中驱逐使用频率最少的键。