redis缓存

redis缓存使用

  • 应用读取数据时,需要先读取 Redis;

  • 发生缓存缺失时,需要从数据库读取数据并更新缓存。

Redis为旁路缓存,因为读取缓存、读取数据库和更新缓存的操作都需要在应用程序中来完成。

缓存分类

  • 只读缓存:加速读请求。

  • 读写缓存:加速读写请求。读写缓存又有两种数据写回策略,可根据业务需求,在保证性能和保证数据可靠性之间进行选择。

只读缓存

  • 读取数据先调用 Redis GET 接口;若不存在,应用从数据库中读取,并写到缓存中。
  • 写请求,直接发往后端的数据库;删改数据时,应用需要把这些缓存的数据删除。

优点

数据库和缓存可以保证完全一致,并且缓存中永远保留的是经常访问的热点数据。

缺点

每次修改操作都会把缓存中的数据删除,之后访问时都会先触发一次缓存缺失,然后从后端数据库加载数据到缓存中,这个过程访问延迟会变大。

读写缓存

读写请求都会发送到缓存,在缓存中直接操作数据。最新数据在redis,考虑掉电风险。

同步直写

  • 写请求发给缓存的同时,也会发给后端数据库,等到缓存和数据库都写完数据,才给客户端返回
  • 需要在业务应用中使用事务实现

缺点:降低缓存的访问性能

优点:被修改后的数据永远在缓存中存在,下次访问时,能够直接命中缓存

异步直写

  • 所有写请求都先在缓存中处理。

Redis 本身不提供机制将淘汰数据写回数据库

Read/Write Throught策略

应用层读写只需要操作缓存,缓存层会自动从数据库中加载或写回到数据库中

优点

对于应用层的使用非常友好,只需要操作缓存即可

缺点

需要缓存层支持和后端数据库的联动。

Write Back策略

写操作只写缓存。而读操作如果命中缓存则直接返回,否则需要从数据库中加载到缓存中,如果缓存已满,则先把需要淘汰的缓存数据写回到后端数据库。

优点

写操作飞快(只写缓存)

缺点

如果数据还未来得及写入后端数据库,系统发生异常会导致缓存和数据库的不一致。

缓存淘汰

“八二原理”:80% 的请求实际只访问了 20% 的数据。

缓存大小设置:结合应用数据实际访问特征和成本开销综合考虑,建议把缓存容量设置为总数据量的 15% 到 30%,兼顾访问性能和内存空间开销。

  • 在设置了过期时间的数据中进行淘汰,包括 volatile-random、volatile-ttl、volatile-lru、volatile-lfu(Redis 4.0 后新增)。
  • 在所有数据范围内进行淘汰,包括 allkeys-lru、allkeys-random、allkeys-lfu(Redis 4.0 后新增)。

淘汰策略

  • volatile-lru 尝试淘汰设置了过期时间的 key,最少使用的 key 优先被淘汰。
  • volatile-ttl key 的剩余寿命 ttl 的值越小越优先被淘汰。
  • volatile-random 设置了过期时间的 key集合中随机的 key。
  • allkeys-lru全体的 key 集合中最近最少使用的。
  • allkeys-random 全体的 key 集合中随机的 key

LRU

Redis 中,LRU 算法被做了简化。

  • Redis 在决定淘汰的数据时,第一次会随机选出 N 个数据,把它们作为候选集合。
  • Redis 会比较这 N 个数据的 lru 字段,把 lru 字段值最小的数据从缓存中淘汰出去。
  • 再次淘汰数据时,Redis 需要挑选数据进入第一次淘汰时创建的候选集合。能进入候选集合的数据的 lru 字段值必须小于候选集合中最小的 lru 值

LFU

从两个维度来筛选并淘汰数据:

  • 数据的被访问次数

  • 数据访问的时效性,访问时间离当前时间的远近

计数规则:每当数据被访问一次时,首先,用计数器当前的值乘以配置项 lfu_log_factor 再加 1,再取其倒数,得到一个 p 值;然后,把这个 p 值和一个取值范围在(0,1)间的随机数 r 值比大小,只有 p 值大于 r 值时,计数器才加 1。

counter 值的衰减机制

LFU 策略会计算当前时间和数据最近一次访问时间的差值,并把这个差值换算成以分钟为单位。然后,LFU 策略再把这个差值除以 lfu_decay_time 值,所得的结果就是数据 counter 要衰减的值。

缓存一致性

原因1:更新操作失败

只读缓存:无法保证删改数据库和删除缓存的原子性。

  • 先删缓存,后更数据库(失败):缓存缺失,数据库读取到旧值。
  • 先更数据库,后删缓存(失败):先在缓存中查询,但此时,就会读到旧值了

解决办法:重试机制

  • 把要删除的缓存值或者是要更新的数据库值暂存到消息队列中(例如使用 Kafka 消息队列)。当应用没有能够成功地删除缓存值或者是更新数据库值时,可以从消息队列中重新读取这些值,然后再次进行删除或更新。
  • 如果重试超过的一定次数,还是没有成功,我们就需要向业务层发送报错信息了。

解决办法:binlog监听的消息队列

  • 一般大公司本身都会有监听binlog消息的消息队列存在,主要是为了做一些核对的工作。
  • 可以借助监听binlog的消息队列来做删除缓存的操作。这样做的好处是,不用你自己引入,侵入到你的业务代码中,中间件帮你做了解耦,同时,中间件的这个东西本身就保证了高可用。

解决办法:设置缓存过期时间

  • 每次放入缓存的时候,设置一个过期时间,比如5分钟,以后的操作只修改数据库,不操作缓存,等待缓存超时后从数据库重新读取。

如果对于一致性要求不是很高的情况,可以采用这种方案。

原因2:大量并发请求

先删缓存,后更数据库

并发缓存不一致

解决办法:延迟双删

在线程 A 更新完数据库值以后,我们可以让它先 sleep 一小段时间(保证“偷菜”完成),再进行一次缓存删除操作。

难点:sleep时间不好控制

先更数据库,后删缓存

并发缓存不一致2

其他线程再次读取时,就会发生缓存缺失,进而从数据库中读取最新值。所以,这种情况对业务的影响较小,不需要解决。

优点:不存在缓存缺失的问题,推荐!!

缓存更新替代删除

写+写并发时,必然会有数据不一致的情况。因此需要配合分布式锁使用。

写+读并发时,先更数据库可能会有短时不一致。

缓存异常

缓存雪崩

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

原因

  • 缓存中有大量数据同时过期
  • Redis 缓存实例发生故障宕机了,无法处理请求

解决办法

  • 原因1:避免给大量的数据设置相同的过期时间,数据的过期时间增加一个较小的随机数
  • 原因1:服务降级:非核心数据(例如电商商品属性)时,暂时停止从缓存中查询这些数据,而是直接返回预定义信息、空值或是错误信息;核心数据(例如电商商品库存)时,仍然允许查询缓存,如果缓存缺失,也可以继续通过数据库读取
  • 原因2:业务系统中实现服务熔断或请求限流机制。暂停业务应用对缓存系统的接口访问。业务系统的请求入口前端控制每秒进入系统的请求数,避免过多的请求被发送到数据库。
  • 事前预防。建立Redis 缓存高可靠主从集群。

缓存击穿

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

解决办法

1、访问特别频繁的热点数据,不设置过期时间

2、使用分布式锁,只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完成后,才可以从缓存获取数据。

缓存穿透

要访问的数据既不在 Redis 缓存中,也不在数据库中,导致请求在访问缓存时,发生缓存缺失,再去访问数据库时,发现数据库中也没有要访问的数据。缓存也就成了“摆设”。

原因

  • 业务层误操作:缓存中的数据和数据库中的数据被误删除了

  • 恶意攻击:专门访问数据库中没有的数据。

解决办法

  • 针对穿透查询数据,缓存空值或缺省值。
  • 使用布隆过滤器快速判断数据是否存在,避免从数据库中查询数据是否存在,减轻数据库压力。大量请求只会查询 Redis 和布隆过滤器,而不会积压到数据库,也就不会影响数据库的正常运行。
  • 前端进行请求检测,恶意的请求(例如请求参数不合理、请求参数是非法值、请求字段不存在)直接过滤掉,不让它们访问后端缓存和数据库

   转载规则


《redis缓存》 wangyixin-tom 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
redis分布式锁 redis分布式锁
为了保证并发访问的正确性,Redis 提供了两种方法,分别是加锁和原子操作。 原子操作 单命令操作(INCR/DECR); 把多个操作写到一个 Lua 脚本中,以原子性方式执行单个 Lua 脚本 分布式锁 分布式锁的加锁和释放锁的过程,涉
2020-10-29
下一篇 
rabbitmq消息可靠性 rabbitmq消息可靠性
消息丢失场景消息从生产者写入到消息队列的过程问题原因:网络抖动 解决办法: 事务在生产者发送消息之前,通过channel.txSelect开启一个事务,接着发送消息, 如果消息投递失败,进行事务回滚channel.txRollback,然
2020-10-26
  目录