常见概念
在使用缓存之前,了解缓存领域中的常用术语很重要:
- 1)缓存命中:表示可以从缓存获取数据,无需再次请求源数据;
- 2)缓存未命中:表示无法从缓存中获取数据,如果缓存有空间,将会将数据加入缓存;
- 3)存储成本:当缓存未命中时,需要从源数据中获取并存储到缓存中,这个过程所需的时间和空间称为存储成本;
- 4)缓存失效:当源数据发生变化时,缓存中的数据变得无效;
- 5)缓存污染:将不常访问的数据存放在缓存中,导致常访问的数据无法放入缓存;
- 6)替换策略:当缓存空间不足时,需要替换一些数据。选择替换哪些数据由替换策略决定。常见的替换策略包括:
缓存策略
当涉及到缓存策略时,以下是对三种常见的缓存策略机制进行分析:
Least-Recently-Used (LRU):最近最少使用
LRU是一种基于使用频率的缓存替换策略。它的基本思想是,如果一个数据项最近被访问过,那么它在未来也可能会被频繁访问。因此,当缓存空间不足时,LRU会选择最近最久未被使用的数据项进行替换。
实现LRU策略的一种方法是使用一个双向链表来维护所有缓存的顺序,其中最近使用过的缓存项会被移到链表的前面。当需要替换时,可以从链表的末尾删除最久未使用的缓存项。
Least-Frequently-Used (LFU):最少使用频率
LFU是一种基于使用频率的缓存替换策略。它的基本思想是,如果一个数据项在过去使用频率较低,那么在未来也可能很少被使用。因此,当缓存空间不足时,LFU会选择使用频率最低的数据项进行替换。
实现LFU策略需要维护每个缓存项被访问的频率信息。当某个缓存项被访问时,其对应的频率会增加。当需要替换时,可以选择频率最低的缓存项进行替换。
First in First Out (FIFO):先进先出
FIFO是一种简单的缓存替换策略。它的基本原则是,最先进入缓存的数据项最先被替换。当缓存空间不足时,FIFO会选择最早添加到缓存中的数据项进行替换。
实现FIFO策略可以使用一个队列来维护缓存项的顺序,新加入的项会被添加到队列的末尾。当需要替换时,可以从队列的头部删除最早添加的缓存项。
需要注意的是,每种缓存策略都有其优点和缺点,适用于不同的应用场景。选择合适的缓存策略取决于资源的特点以及访问模式的情况。例如,如果某个应用具有明显的访问热点,那么LRU可能是一个更好的选择;如果应用中的数据访问频率非常不均匀,LFU可能是一个更好的选择。
策略总结
由于存储空间有限,替代策略的核心问题是如何保留高频访问的缓存数据,以提高缓存命中率和整体缓存效率。难点在于需要通过对数据的历史访问情况进行分析,以预测未来访问情况,并找到合适的策略。
缓存访问场景分析
通常,使用缓存的操作如下:请求首先访问缓存数据,如果缓存中不存在,则将会从数据库获取数据,并将其写入缓存;如果缓存中存在数据,则直接返回。
缓存层作为数据访问的前置环节,在高并发情况下可以减轻数据库的负担,降低系统故障的风险。因此,在使用缓存时需谨慎分析。在访问缓存数据时。
缓存处理的三种问题场景
常见的有以下三种场景:缓存穿透、缓存击穿和缓存雪崩。
缓存穿透
每次请求都无法在缓存中找到相应的数据,导致请求直接回源到数据库中,给数据库带来巨大的访问压力,甚至可能导致数据库宕机的现象。
原因
请求的数据根本不存在于缓存中,也是说当前访问数据永远不会写入缓存中。但恶意请求或者异常情况下持续发送这样的请求,导致缓存一直未命中,就相当于缓存层形同虚设,从而对数据库造成了巨大的访问压力。
措施
为了解决缓存穿透问题,可以采取以下几种措施:
- 布隆过滤器(Bloom Filter):采用bloom filter保存缓存过的key,因此可以快速判断请求的数据是否在缓存中,如果不在则可以直接过滤掉,避免对数据库的访问。
- 缓存空值缓存:对于一些经常查询但是数据库中不存在的数据,可以将其在缓存中缓存为空值,这样当再次请求时可以直接命中缓存,避免了回源到数据库的操作,设置较短的失效时间。
- 异常数据屏蔽:对于恶意请求或者异常情况下的请求,可以针对性地进行限流或者屏蔽,避免对数据库的过度访问。此外还可以针对业务场景对请求的参数进行有效性校验,防止非法请求击垮db。
综上所述,解决缓存穿透问题需要采取合适的技术手段,从而降低对数据库的访问压力,提高系统的稳定性和性能。
缓存击穿
某个热点数据的缓存失效时,大量并发请求直接访问数据库,导致数据库压力剧增,甚至造成存储层的崩溃现象。
原因
为了保证缓存数据的时效性,通常会设置一个失效时间,如果是热点key,高并发时会有海量请求直接越过缓存层到数据库,这样就会给数据库造成的负担增大,设置宕机。
场景
- 缓存失效:当某个热点数据的缓存过期或者被意外删除时,新来的请求无法命中缓存,需要直接访问数据库获取数据,造成大量请求到db层,击垮存储层。
- 高并发请求:大量的并发请求同一时刻涌入,都请求缓存中已失效的热点数据,导致数据库负载骤增。
措施
- 互斥锁(Mutex Lock) :在缓存失效时,只允许一个线程(一般是第一个到达的线程)去查询数据库,其他线程等待查询结果并从缓存中获取数据。
- 热点数据预加载:针对可能会发生缓存击穿的热点数据,可以通过定时任务或者异步加载的方式,在缓存失效前提前将数据加载到缓存中,避免了缓存失效瞬间的高并发请求。
- 限流策略:对于大量并发的请求,可以通过限流策略来控制并发数量,避免所有请求同时涌入数据库。
- 使用备份机制:在缓存失效时,可以通过备份机制从数据库中获取数据,并将数据存储到缓存中,避免大量请求直接击穿到数据库。
- 缓存数据“永远不过期”:如果缓存数据不设置失效时间的话,就不会存在热点key过期造成了大量请求到数据库。但是,缓存数据就变成“静态数据”,因此当缓存数据快要过期时,采用异步线程的方式提前进行更新缓存数据。
通过以上措施的组合,可以有效地解决缓存击穿问题,降低数据库压力,提高系统的稳定性和性能。
缓存雪崩
在某一时刻,缓存中的多个key同时失效,导致大量的请求直接访问存储层(如数据库),从而造成数据库负担过重,甚至导致数据库宕机的情况。
原因
造成缓存雪崩的主要原因包括:
- 缓存失效时间设置不合理:如果缓存中的多个key的失效时间相同或者非常接近,那么它们很可能在同一时刻失效,导致大量的请求涌入存储层。
- 数据库宕机或网络故障:如果数据库出现宕机或者网络故障,此时缓存中的数据已经失效,无法及时从数据库中获取新的数据,从而导致大量请求直接访问数据库。
措施
解决缓存雪崩问题的核心原则是让多个key的失效时间分布更加均匀,避免集体失效的情况。可以采取以下措施:
- 设置合理的缓存失效时间:将缓存中的多个key的失效时间分散开,避免同时失效。可以使用随机值来设置失效时间,或者在失效时间上加入一些随机因素,使得失效时间分布更加均匀。
- 使用分布式缓存:将缓存数据分散存储在多个节点上,避免单点故障。当其中某个节点或者一部分节点出现问题时,其他节点可以继续提供服务。
- 数据预热:在高峰期到来之前,提前将热点数据加载到缓存中,避免在高峰期瞬时失效导致大量请求涌入存储层。
- 数据更新策略:在更新数据时,采取合适的策略,如异步更新、缓存和数据库同时更新等,避免在缓存失效时对数据库造成瞬时压力。
- 限流策略:对请求进行限流,控制并发访问数据库的数量,避免系统负荷过大。
综合以上措施,可以有效地减轻缓存雪崩带来的问题,提高系统的稳定性和可靠性。
总结
缓存穿透、缓存击穿和缓存雪崩是三个常见的缓存问题,容易混淆。下面是它们的简要总结:
- 缓存穿透:指的是请求访问不存在于缓存中的数据,而直接查询存储层(如数据库),这可能是攻击者利用业务漏洞攻击系统。解决方案的核心是过滤非法业务请求,与数据是否热点、缓存失效时间等因素无关。
- 缓存击穿:指的是热点数据的缓存失效,导致大量请求直接访问存储层(如数据库),造成数据库压力过大的问题。解决方案的核心是避免数据库的并发操作。
- 缓存雪崩:指的是多个缓存 key 同时失效,不一定与数据是否热点有关。解决方案的核心是让缓存 key 的失效时间分布更均匀,避免集体失效的情况。
要解决这些问题,可以采取相应的措施,例如进行合理缓存设置、使用互斥锁、热点数据预加载、限流策略等。通过综合应用这些解决方案,可以提高系统的性能、稳定性,避免缓存问题引发的系统崩溃或过载。