学习材料:https://xiaolincoding.com/redis/cluster/cache_problem.html
缓存雪崩
什么是缓存雪崩
在面对业务量较大的查询场景时,会把数据库中的数据缓存至redis中,避免大量的读写请求同时访问mysql客户端导致系统崩溃。这种情况下,客户端进来的请求会先经过redis缓存查询,如果redis缓存过期则进而去查询mysql,而后再将返回的结果返回给客户端并再redis中进行缓存。于是就出现了一个问题,如果此时redis中大量的缓存数据过期或者redis宕机,那么客户端发起的请求还是会直接访问到数据库中,会使得数据库的压力剧增,导致数据库奔溃进而导致系统崩溃,这就是缓存雪崩问题。
示意图如下:
如何解决缓存雪崩问题
针对不同的诱因,有不同的应对策略;
1、大量数据同时过期
- 均匀设置过期时间;应该尽量避免将大量数据设置同一个过期时间,比如可以过期时间加上一个随机数,这样就可以保证数据不会在同一个时间过期。
- 互斥锁;当业务线程处理用户请求时发现请求的数据不在redis中,则加入一个互斥锁,保证这个时间内只有一个请求来进行缓存构建。拿不到锁的请求要么就返回空值或者默认值,要么就等构建完成之后再来获取缓存。(需要设置超时时间,避免拿到锁的请求崩溃后导致系统阻塞崩溃)
- 后台更新缓存;把更新缓存的工作交由后台线程定时更新。
2、Redis宕机
- 服务熔断;服务熔断就是一旦redis发生宕机,那么所有对缓存服务的访问全部返回错误,这样的话业务线会全部无法正常工作,但是系统不会崩溃。
- 请求限流机制;限流机制就是一旦redis发生宕机,只允许少量的请求访问数据库,再多的请求则在入口直接拒绝访问,这种可以减少对业务的影响。
- 构建redis缓存高可用集群;通过主从节点的方式构建redis缓存的高可用集群。
缓存击穿
什么是缓存击穿?
一般在秒杀活动这种大量访问某几个数据的情况下会出现此问题。也就是某几个热点数据同时过期,也会导致大量的请求击垮数据库,这种情况就叫做缓存击穿。
如何解决缓存击穿?
缓存击穿其实可以认为是缓存雪崩中缓存过期的一种情况,所以可以采用互斥锁或者后台更新缓存的方式来解决。
缓存穿透
什么是缓存穿透
在缓存和数据库中都没有相关的业务数据,此时就没办法构建缓存来服务后续的请求。当有大量这样的请求进入时,业务缓存中查询不到数据且无法构建缓存,就会不停的查询数据库导致数据库压力过大甚至系统崩溃,这就叫缓存穿透。
导致缓存穿透的原因有哪些
- 业务误操作将查询的数据进行了删除。
- 黑客攻击,故意大量访问某些读取不存在数据的业务
如何解决缓存穿透
- 限制非法请求;在API入口处进行判断请求是否合理,不合理的直接拦截避免进一步访问缓存或者数据库。
- 缓存空值或默认值;如果发现线上业务出现缓存穿透的现象,可以给这些值设置一个空值或者默认值返回,避免访问数据库。
- 使用布隆过滤器快速判断数据是否存在,避免通过查询数据库来判断数据是否存在;
缓存雪崩、击穿、穿透的产生原因及其应对方案简述如下
补充问题
在面对并发请求时如何通过加互斥锁来避免缓存雪崩或者缓存击穿的问题?
在客户端请求进来之后,如果缓存中没有查询到数据,则使用setNX的方式来设置一个状态位,表示一种锁定状态。如果返回结果是0说明锁已经被占领,此时进行等待状态。如果返回结果是1则去请求数据库,将数据库中的数据读取到了之后再将此查询的key设置缓存共其他请求进行查询。这样就可以保证在并发请求中只会有一个请求查询数据库,其他请求只能等待重新发起查询,从而解决缓存并发问题。
解决缓存热点问题
那么缓存策略的总体思路:就是通过判断数据最新访问时间来做排名,并过滤掉不常访问的数据,只留下经常访问的数据,具体细节如下。
- 先通过缓存系统做一个排序队列(比如存放 1000 个商品),系统会根据商品的访问时间,更新队列信息,越是最近访问的商品排名越靠前。
- 同时系统会定期过滤掉队列中排名最后的 200 个商品,然后再从数据库中随机读取出 200 个商品加入队列中。
- 这样当请求每次到达的时候,会先从队列中获取商品 ID,如果命中,就根据 ID 再从另一个缓存数据结构中读取实际的商品信息,并返回。
- 在 Redis 中可以用 zadd 方法和 zrange 方法来完成排序队列和获取 200 个商品的操作。
如何保证缓存一致性
- 等待过期:等待redis中的key过期之后再重新去数据库读取数据进行更新。
- 优点
- 开发成本低、易实现
- 管理成本低,出现问题的概率小
- 缺点
- 完全依赖过期时间,时间太短容易导致缓存失效频繁,太长如果导致数据不一致。
- 优点
- 尝试删除:在更新数据库数据时尝试删除redis中的key,如果删除成功下次查询该缓存时就会查询到数据库层然后再去更新缓存
- 优点
- 延迟更小,数据一致性比方案一更高
- 实现成本低
- 缺点
- 如果数据库更新成功,但是redis删除失败,就会出现方案一的问题
- 在高并发场景容易出现连接数过多的问题
- 优点
- 主动更新:另外搭建一个消费服务订阅消息队列,再数据库数据更新时异步更新redis中的数据
- 优点
- 比较可靠,能保证更新操作至少在redis中执行了一次
- 解耦度高
- 缺点
- 实现成本高,需要另外增加消费服务
- 有时序性问题,如果因为网络延迟导致同一条数据的两次更新推送没有按照更新的顺序来,也会出现数据不一致问题
- 依然有客户端连接数过多问题
- 优点
- 订阅日志:搭建一个消费服务去订阅数据库的binlog日志,解析日志内的内容再去更新redis,实现与业务完全解耦
- 优点
- 在服务器压力不大的情况下,延迟最低
- 与业务解耦
- 解决了时序性问题,可靠性强
- 缺点
- 需要单独搭建一个同步服务,成本高
- 如果同步服务崩溃,会导致数据长时间不更新
- 优点
方案选型
如果数据变化多,且对数据的一致性要求高,那么直接不用缓存;
通常来说使用方案一就够了,如果要增加更新的即时性,就用方案二,如果对于延时要求高就用方案三或四,四能解决时序性问题。具体需要结合业务场景调整。
拓展:缓存如何保证高可用?
主从复制模式、哨兵模式、Redis Cluster模式
使用主从复制搭建的Redis集群可以实现读写分离的效果,一般使用一主多从,主写从读。但是这种模式有个问题就是一旦主从机有宕机的情况,就需要人工介入进行IP切换。
哨兵模式则是在主从复制的基础上进行了升级,加入了哨兵,也就是当主机宕机时会通过哨兵的检测判定是否下线,判定下线后会在其他的从机中选出一台主机然后调整其他从机的主机IP。
而Redis Cluster模式不仅有以上两种模式的优点,且其会将数据分节点存储,充分利用内存。Redis Cluster划分了16384个槽,每个key通过CRC16校验后对16384进行取模来决定放置哪个槽,集群的每个节点负责一部分的hash槽。这样就避免了不同节点中的内存数据冗余。