Skip to content

Redis 用作缓存的注意事项

背景

在一些高并发的场景中,为了避免流量过大打崩数据库,通常会在用户请求和数据库使用 Redis 作为缓存,将频繁查询的数据写入到缓存中,利用 Reids 的高性能来应对大部分流量,保证服务的可用性。但是,一旦引入了缓存,就会带来使用缓存带来的一系列问题。

缓存异常问题

缓存异常的问题包括缓存穿透缓存击穿缓存雪崩

缓存穿透

缓存穿透指要访问的数据既不在 Redis 缓存中,也不在数据库中(数据库的数据被误删了),导致请求在访问缓存时,发生缓存缺失,再去访问数据库时,发现数据库中也没有要访问的数据。(缓存变成了“摆设”)

解决方案

  1. 查询前做好参数校验(重要)
  2. 缓存空值或默认值。针对查询的数据,在 Redis 中缓存一个空值或者默认值(比较鸡肋)
  3. 使用布隆过滤器,在写入缓存时,先在布隆过滤器做个标记,然后在用户请求到来时,业务线程确认缓存失效后,可以通过布隆过滤器快速判断数据是否存在,不存在就不同查库

缓存击穿

缓存击穿是指,针对某个访问非常频繁的热点数据的请求,无法在缓存中进行处理,即热点数据过期,紧接着,访问该数据的大量请求,一下子都发送到了后端数据库,导致了数据库压力激增,会影响数据库处理其他请求。

可以将缓存击穿看作雪崩的一个子集

解决方案

  • 使用互斥锁。在访问数据库前加个锁,确保只有一个线程能够访问数据库,查询数据库之后将数据写回缓存,后面的请求就能走缓存
  • 不设置过期时间,由后台异步更新缓存,或者在过期之前通知后台线程更新缓存

缓存雪崩

缓存雪崩是指大量的应用请求无法在 Redis 缓存中进行处理,直接发送到数据库层,导致数据库层的压力激增

主要发生在两种场景:大量缓存数据在同一时间过期Redis 故障宕机

解决方案

针对大量 key 同一时间过期的情况:

  • 均匀设置过期时间,避免同一时间过期
  • 使用互斥锁。保证同一时间只有一个线程来构建缓存
  • 不设置过期时间,由后台异步更新缓存

针对 Redis 不可用的情况: 紧急处理

  • 服务熔断,避免影响其他服务
  • 请求限流

事先预防

  • 事先构建高可用的集群,如 Redis Cluster 集群

缓存的类型

缓存一般有两种使用方法,分别是只读缓存读写缓存

只读缓存

顾名思义,只会从缓存中读数据,也就是常说的旁路缓存。

应用要读取数据的话,先在 Redis 中寻找数据是否存在。如果发生了缓存缺失,就从数据库中读出来并且写到数据库中。

所有的数据写请求,直接在数据库中增删改。对于删改的数据来说,如果 Redis 已经缓存了相应的数据,需要将这些缓存删除。

读写缓存

服务端把 cache 当作主要的数据存储,从中读取数据并将数据写入。

除了读请求会发送到缓存进行处理,所有的写请求也会发送到缓存,在缓存中直接对数据进行增删改操作。这种方式能够快速返回给业务应用,提升响应速度。但是如果 Redis 发生了掉电,数据可能会丢失,给业务带来风险。

针对业务对数据可靠性和缓存性能的要求,有同步直写异步写回两种方案。

同步直写

适合写多的场景,通常需要结合分布式锁

优先考虑了数据的可靠性。写请求在发给缓存的同时,也会发给数据库进行处理,等到缓存和数据库都完成后才会返回给客户端。

将数据写回 DB 是同步的,能够保证数据的强一致,但是会阻塞 Redis,降低访问性能

异步写回

优先考虑了响应延迟,将所有写请求都先在缓存中处理。等到 cache 写满时,才异步地将数据同步到 DB,cache 和 DB 的数据会出现短暂的不一致。

这种模式写性能非常高,适合数据经常变化但一致性要求不高的场景,如统计点赞量,浏览量。

缓存和 DB 的一致性问题

在业务系统中,一旦使用了缓存,就需要注意保证缓存和 DB 的一致性,否则容易出现问题。

  • 对于读写缓存,想要保证数据的一致性,需要使用同步直写的策略,并且要保证缓存和 DB 的更新具有原子性。
  • 对于只读缓存,如果有新数据,直接写入 DB;当有数据需要修改时,将缓存中对应的数据标记为无效(删除),需要保证更新 DB 和删除缓存的原子性。

TIP

为了保证一致性,一般采用先更新 DB,后删除缓存的策略,就能够满足一般的业务场景了,如果需要强保证一致性,就需要引入额外的重试机制来保障,同时成本也会提高,需要结合业务衡量是否值得

需要注意的是,【先删除缓存,再更新数据库】策略不是银弹,在【读+写】并发请求是也有可能导致不一致。 此时可以考虑使用延迟双删。先删除缓存,然后更新 DB,然后等待一段时间后再次删除缓存,但是这个等待时间很难把握。

上次更新于: