一、前言
在redis的使用中会遇到缓存失效、缓存穿透、缓存雪崩等问题。分布式缓存服务,主要是为了解决集群环境下,内存数据不共享的问题,比如 session 会话,以及一些字典缓存等等,在当前服务器的内存中存储,在另一台服务器中难以获取查询的问题,通过引入缓存服务,将缓存数据统一归一到一个服务器里面,以解决系统中内存数据不共享的问题,同时缓存性能也不会受到很大影响。
二、常见问题
1.为什么存入 redis 的数据,查询失效
Redis 的所有数据都是保存在内存中,然后不定期的通过异步方式保存到磁盘上;也可以把每一次数据变化都写入到一个aof
日志文件里面,当 redis 的服务器重启的时候,自动从日志文件里面恢复数据到内存中。
有哪些场景会发生缓存失效呢?总结起来有以下两种场景:
- 1.当 redis 服务器重启的时候,可能会发生缓存失效,此时可以将 redis 的持久化方式改成
AOF
模式,也就是全持久化模式,但是性能会消耗很大 - 2.存入redis 的数据,设置了自动过期时间,这种情况可以重新调整过期时间
2.缓存与数据库的数据不一致
通常情况下我们使用缓存,其中有一个很重要的目的就是降低数据库的访问压力,比如商品的信息查询,优先是从缓存中查询,如果没有,再从数据库里面查询。
对于既有数据库写入又有缓存操作的接口,一般分为两种情况执行。
- 1.先写入数据库,再操作缓存。这种情况下如果数据库操作成功,缓存操作失败就会导致缓存和数据库不一致
- 2.先操作缓存,再写入数据库。这种情况下如果缓存操作成功,数据库操作失败也会导致数据库和缓存不一致
大部分情况下,缓存理论上都是需要可以从数据库恢复出来的,所以基本上采取第一种顺序都是不会有问题的,但是无法保证数据库和缓存完全一致。
也就是说,使用缓存,就可能会出现缓存与数据库不一致的情况,只是说这种几几率的情况有多大。
针对那些必须保证数据库和缓存一致的情况,通常是不建议使用缓存的,直接从数据库查询。
3.什么是缓存穿透
缓存穿透,表示恶意用户频繁的模拟请求缓存中不存在的数据,此时如果有大量的接口请求,短时间内会直接落在了数据库上,缓存被击穿,导致数据库性能急剧下降,最终影响服务整体的性能。
这个在实际项目中很容易遇到,如抢购活动、秒杀活动、抢优惠券等接口 API 被大量的恶意用户刷,导致短时间内数据库宕机。对于缓存击穿的问题,有以下几种解决方案。
- 1.使用分布式锁排队。当从缓存中获取数据失败时,给当前接口加上锁,从数据库中加载完数据并写入后再释放锁。若其它线程获取锁失败,则等待一段时间后再重试。
- 2.使用布隆过滤器。将所有可能存在的数据哈希到一个足够大的
bitmap
中,一个一定不存在的数据会被这个bitmap
拦截掉,从而避免了对底层存储系统的查询压力 - 3.对空结果进行缓存。如果一个查询返回的数据为空,我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟,这样第二次到缓存中获取就有值了,而不会继续访问数据库,简单粗暴好使。
4.什么是缓存雪崩
缓存雪崩,简单的说就是在短时间内有大量缓存失效,如果这期间有大量的请求发生,同样也有可能会导致数据库发生宕机。在 Redis 机群的数据分布算法上如果使用的是传统的 hash 取模算法,在增加或者移除 Redis 节点的时候就会出现大量的缓存临时失效的情形。
对于缓存雪崩的问题,有以下几种解决方案。
- 1.像解决缓存穿透一样加锁排队
- 2.建立备份缓存。比如缓存 A 和缓存 B,A 设置超时时间,B 不设值超时时间,先从 A 读缓存,A 没有读 B,当缓存 A 发生变化的时候,同时更新缓存 B
- 3.计算数据缓存节点的时候采用一致性 hash 算法,这样在节点数量发生改变时不会存在大量的缓存数据需要迁移的情况发生
5.redis 缓存会不会出现并发问题
首先 Redis 是单线程执行命令的,在出现多个 Redis Client 并发操作数据时,秉承先发起先执行
的原则,其它的处于阻塞状态。
redis 缓存并发问题,其实主要指的还是读取数据库数据的并发操作问题。
当缓存过期后会从数据库查询数据然后再存入Redis
缓存,但是在高并发情况下,可能还没来得及将数据库中查出来的数据存入Redis
时,其它Client
又从数据库里查询数据再存入Redis
了。
这样一来会造成多个请求并发的从数据库获取数据,然后存入Redis
,可能在读取的时候,出现脏数据。
针对这种场景,有以下几种解决方案。
- 1.同步加锁处理。在写入数据库的时候,再操作缓存这个阶段,进行加锁处理,保证服务串行,可能会牺牲一点时间
- 2.异步队列串行执行。把写入数据库和操作缓存的操作,放在队列中使其串行化,让他们一个一个的执行,比如通过消息中间件异步执行。
- 3.使用类似SQL的乐观锁机制:在并发写入
Redis
缓存时,把要写入数据的版本号和时间戳与Redis
中的数据进行对比,如果写入的数据时间戳或者版本号 比Redis
高,则写入;否则就不写入