如果有遗漏,评论区告诉我进行补充
面试官: 谈---谈缓存穿透、缓存击穿和缓存雪崩,以及解决办法?
我回答:
在分布式系统和高并发场景中,缓存是提高系统性能和响应速度的重要手段。然而,如果缓存使用不当,可能会遇到一些问题,如缓存穿透、缓存击穿和缓存雪崩。下面我将详细解释这些问题以及相应的解决办法。
一、缓存穿透
定义:
缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,进而给数据库带来压力。如果有恶意攻击者不断请求系统中不存在的数据,会导致短时间大量请求落在数据库上,造成数据库压力过大,甚至导致数据库承受不住而崩溃。
解决办法:
- 接口校验:在接口层增加校验,如用户鉴权校验,对id做基础校验,id小于等于0的直接拦截。也可以在缓存层和数据库层都添加一些校验措施,例如检查请求的IP地址、User-Agent等信息,如果发现异常,则可以拒绝请求,防止缓存穿透。
- 使用布隆过滤器:布隆过滤器是一种概率型数据结构,可以用来判断一个元素是否在一个集合中。它类似于一个哈希算法,能够减少查询的次数,降低数据库的压力。
- 设置空值缓存:对于不存在的数据,可以设置一个默认值,如当查询不存在的数据时,返回这个默认值,而不是直接穿透缓存。也可以将key-value对写为key-null,并设置一个较短的过期时间(如30秒)。这样可以防止攻击用户反复用同一个id暴力攻击。
- 增加缓存更新频率:对于一些不常用的数据,可以增加其缓存更新频率,使得这些数据更快地过期,减少缓存穿透的可能性。
- IP限流:对频繁访问不存在数据的IP进行限流,防止恶意攻击。
二、缓存击穿
定义:
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,导致数据库压力瞬间增大。
解决办法:
- 设置热点数据不过期:对于经常被访问的热点数据,可以设置其缓存永不过期,从而避免缓存击穿的问题。
- 使用互斥锁:当缓存中不存在数据时,可以使用锁机制(如对key加锁),一次只允许一个线程去访问数据库,获取到数据后,马上更新缓存。这样可以避免大量并发请求同时冲击数据库。
- 设置逻辑过期时间:给缓存数据设置一个逻辑过期时间,而不是完全依赖于实际的缓存过期策略。当数据被访问时,先检查数据是否已经逻辑过期,如果是,则异步或在当前线程中更新缓存,同时返回旧数据给客户端。
- 延时更新:在缓存数据过期后,设置一个较短的过期时间,并在这个时间段内不断刷新缓存,直到数据从数据库中重新加载到缓存中。
三、缓存雪崩
定义:
缓存雪崩是指缓存中大批量数据同时过期,而查询量又大,导致这些请求都落在数据库上,使得数据库层压力巨大,甚至宕机。
解决办法:
- 分散过期时间:尽量让缓存中的数据过期时间分散一些,避免大量数据同时过期。
- 使用互斥锁或队列:在缓存失效时,可以设置一个短暂的锁定时间或队列,只允许一个请求查询数据库并刷新缓存,其他请求等待锁释放或队列处理后再读取缓存。
- 搭建缓存集群:为了防止单个缓存节点宕机导致雪崩,可以搭建缓存集群,提高缓存的容灾性。如 Redis Sentinel 或者 Redis Cluster,当某个缓存节点宕机时,其他节点可以继续提供服务,从而避免因为单个缓存服务器宕机导致的缓存雪崩问题。
- 设置热点数据不过期:类似于缓存击穿的处理方法,对于热点数据可以设置其永不过期。
- 实现本地缓存:在应用服务器本地维护一个小容量、高速缓存,作为远程缓存的补充,减少对外部缓存和数据库的依赖。
- 多级缓存:使用多级缓存策略,例如本地缓存 + 分布式缓存。即使分布式缓存失效,本地缓存也可以起到缓冲作用。如在本地缓存(如 Guava Cache 等)和分布式缓存(如 Redis)之间建立多级缓存。
- 限流与降级:通过限流技术控制到达数据库的请求数量,或者对某些非核心服务进行降级处理,优先保障系统的稳定性。
- 服务熔断:当检测到数据库负载过高时,触发服务熔断机制,暂时停止某些非核心服务的请求,以保护核心服务的正常运行。
- 异步更新:对于大规模更新操作,可以采用异步方式进行,避免短时间内大量数据失效。
总结
- 缓存穿透:使用布隆过滤器或缓存空对象来减少无效请求对数据库的影响。
- 缓存击穿:通过互斥锁、设置随机过期时间或永不过期来防止热点数据的集中访问。
- 缓存雪崩:通过设置随机过期时间、多级缓存、限流与降级和服务熔断来分散失效时间和减轻数据库压力。
这些策略可以帮助你在高并发场景下更好地管理缓存,确保系统的稳定性和性能。