目录
缓存穿透
缓存穿透解决办法
缓存击穿
击穿解决办法?
缓存穿透和缓存击穿的区别?
缓存雪崩
雪崩解决办法?
如何确保缓存和数据库数据的一致性?
常见的缓存更新策略?
缓存穿透
定义:缓存穿透说简单点就是大量请求的 key 是不合理的,根本不存在于缓存中,也不存在于数据库中 。这就导致这些请求直接到了数据库上,根本没有经过缓存这一层,对数据库造成了巨大的压力,可能直接就被这么多请求弄宕机了。
缓存穿透解决办法
1.最基本的就是首先做好参数校验,一些不合法的参数请求直接抛出异常信息返回给客户端。比如查询的数据库 id不能小于 0、传入的邮箱格式不对的时候直接返回错误消息给客户端等等。
2.缓存空值或者默认值
可以针对查询的数据,在缓存中设置一个空值或者默认值,这样后续请求就可以从缓存中读取到空值或者默认值,返回给应用,而不会继续查询数据库。
3.采用布隆过滤器
可以在写入数据库数据时,使用布隆过滤器做个标记,然后在用户请求到来时,业务线程确认缓存失效后,可以通过查询布隆过滤器快速判断数据是否存在,如果不存在,就不用通过查询数据库来判断数据是否存在。即使发生了缓存穿透,大量请求只会查询 Redis 和布隆过滤器,而不会查询数据库,保证了数据库能正常运行,Redis 自身也是支持布隆过滤器的。
布隆过滤器的工作过程:
布隆过滤器由「初始值都为 0 的位图数组」和「 N 个哈希函数」两部分组成。当我们在写入数据库数据时,在布隆过滤器里做个标记,这样下次查询数据是否在数据库时,只需要查询布隆过滤器,如果查询到数据没有被标记,说明不在数据库中。
布隆过滤器会通过 3 个操作完成标记:
-
第一步,使用 N 个哈希函数分别对数据做哈希计算,得到 N 个哈希值;
-
第二步,将第一步得到的 N 个哈希值对位图数组的长度取模,得到每个哈希值在位图数组的对应位置。
-
第三步,将每个哈希值在位图数组的对应位置的值设置为 1;
布隆过滤器由于是基于哈希函数实现查找的,高效查找的同时存在哈希冲突的可能性,所以,查询布隆过滤器说数据存在,并不一定证明数据库中存在这个数据,但是查询到数据不存在,数据库中一定就不存在这个数据。
缓存击穿
缓存击穿中,请求的 key 对应的是 热点数据 ,该数据 存在于数据库中,但不存在于缓存中(通常是因为缓存中的那份数据已经过期) 。这就可能会导致瞬时大量的请求直接打到了数据库上,对数据库造成了巨大的压力,可能直接就被这么多请求弄宕机了。
击穿解决办法?
-
设置热点数据永不过期或者过期时间比较长。
-
针对热点数据提前预热,将其存入缓存中并设置合理的过期时间比如秒杀场景下的数据在秒杀结束之前不过期。
-
互斥锁方案,保证同一时间只有一个业务线程更新缓存,未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。
缓存穿透和缓存击穿的区别?
缓存穿透中,请求的 key 既不存在于缓存中,也不存在于数据库中。
缓存击穿中,请求的 key 对应的是 热点数据 ,该数据 存在于数据库中,但不存在于缓存中(通常是因为缓存中的那份数据已经过期) 。
缓存雪崩
实际上,缓存雪崩描述的就是这样一个简单的场景:缓存在同一时间大面积的失效,导致大量的请求都直接落到了数据库上,对数据库造成了巨大的压力。 这就好比雪崩一样,摧枯拉朽之势,数据库的压力可想而知,可能直接就被这么多请求弄宕机了。
雪崩解决办法?
针对 Redis 服务不可用的情况:
-
采用 Redis 集群,避免单机出现问题整个缓存服务都没办法使用。
-
限流,避免同时处理大量的请求。
针对热点缓存失效的情况:
-
将缓存失效时间随机打散: 我们可以在原有的失效时间基础上增加一个随机值(比如 1 到 10 分钟)这样每个缓存的过期时间都不重复了,也就降低了缓存集体失效的概率。
-
缓存永不失效(不太推荐,实用性太差)。
-
设置二级缓存。
如何确保缓存和数据库数据的一致性?
-
缓存延时双删:
1 先删除缓存 2 再更新数据库 3 休眠一会(比如1秒),再次删除缓存。
-
删除缓存重试机制:
-
写请求更新数据库
-
缓存因为某些原因,删除失败
-
把删除失败的key放到消息队列
-
消费消息队列的消息,获取要删除的key
-
重试删除缓存操作
-
更新时删除缓存:在更新数据库中的数据时,先更新数据库,然后再删除 Redis 缓存中对应的数据。当下一个请求访问缓存时,会发现缓存失效,从数据库中读取最新的数据,并将其写入 Redis 缓存。这种方式可以保证在数据库更新后,下一次读取会获取到最新的数据。
-
使用事务:Redis 支持事务操作,可以将数据库更新和 Redis 缓存更新放在一个事务中执行。这样可以确保数据库和缓存的更新操作要么同时成功,要么同时失败,保持数据一致性。
-
发布/订阅模式:使用发布/订阅模式(Pub/Sub)或消息队列(Message Queue)来实现缓存和数据库之间的数据同步。当数据库中的数据发生变化时,发布一个消息通知缓存更新对应的数据。缓存接收到消息后,更新对应的数据,从而保持一致性。
-
双写策略:在某些情况下,可以选择将数据同时写入数据库和缓存,而不是更新策略中的先后顺序。这样可以保证数据的一致性,但也增加了写入的开销和复杂性。
常见的缓存更新策略?
常见的缓存更新策略共有3种:
-
Cache Aside(旁路缓存)策略;
-
Read/Write Through(读穿 / 写穿)策略;
-
Write Back(写回)策略;
实际开发中,Redis 和 MySQL 的更新策略用的是 Cache Aside,另外两种策略应用不了。
旁路缓存策略:
Cache Aside(旁路缓存)策略是最常用的,应用程序直接与「数据库、缓存」交互,并负责对缓存的维护,该策略又可以细分为「读策略」和「写策略」。
写策略的步骤:
-
先更新数据库中的数据,再删除缓存中的数据。
读策略的步骤:
-
如果读取的数据命中了缓存,则直接返回数据;
-
如果读取的数据没有命中缓存,则从数据库中读取数据,然后将数据写入到缓存,并且返回给用户。
注意,写策略的步骤的顺序不能倒过来,即不能先删除缓存再更新数据库,原因是在「读+写」并发的时候,会出现缓存和数据库的数据不一致性的问题。