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时间不好控制
先更数据库,后删缓存
其他线程再次读取时,就会发生缓存缺失,进而从数据库中读取最新值。所以,这种情况对业务的影响较小,不需要解决。
优点:不存在缓存缺失的问题,推荐!!
缓存更新替代删除
写+写并发时,必然会有数据不一致的情况。因此需要配合分布式锁使用。
写+读并发时,先更数据库可能会有短时不一致。
缓存异常
缓存雪崩
大量的应用请求无法在 Redis 缓存中进行处理,应用将大量请求发送到数据库层,导致数据库层的压力激增。
原因
- 缓存中有大量数据同时过期
- Redis 缓存实例发生故障宕机了,无法处理请求
解决办法
- 原因1:避免给大量的数据设置相同的过期时间,数据的过期时间增加一个较小的随机数
- 原因1:服务降级:非核心数据(例如电商商品属性)时,暂时停止从缓存中查询这些数据,而是直接返回预定义信息、空值或是错误信息;核心数据(例如电商商品库存)时,仍然允许查询缓存,如果缓存缺失,也可以继续通过数据库读取
- 原因2:业务系统中实现服务熔断或请求限流机制。暂停业务应用对缓存系统的接口访问。业务系统的请求入口前端控制每秒进入系统的请求数,避免过多的请求被发送到数据库。
- 事前预防。建立Redis 缓存高可靠主从集群。
缓存击穿
某个访问非常频繁的热点数据,无法在缓存中进行处理,访问该数据的大量请求,一下子都发送到了后端数据库,导致了数据库压力激增,会影响数据库处理其他请求。
解决办法
1、访问特别频繁的热点数据,不设置过期时间
2、使用分布式锁,只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完成后,才可以从缓存获取数据。
缓存穿透
要访问的数据既不在 Redis 缓存中,也不在数据库中,导致请求在访问缓存时,发生缓存缺失,再去访问数据库时,发现数据库中也没有要访问的数据。缓存也就成了“摆设”。
原因
业务层误操作:缓存中的数据和数据库中的数据被误删除了
恶意攻击:专门访问数据库中没有的数据。
解决办法
- 针对穿透查询数据,缓存空值或缺省值。
- 使用布隆过滤器快速判断数据是否存在,避免从数据库中查询数据是否存在,减轻数据库压力。大量请求只会查询 Redis 和布隆过滤器,而不会积压到数据库,也就不会影响数据库的正常运行。
- 前端进行请求检测,恶意的请求(例如请求参数不合理、请求参数是非法值、请求字段不存在)直接过滤掉,不让它们访问后端缓存和数据库