目录
[(一)Cache Aside Pattern(旁路缓存模式)](#(一)Cache Aside Pattern(旁路缓存模式))
[Cache Aside Pattern模式不一致问题的发生](#Cache Aside Pattern模式不一致问题的发生)
[(二)Read/Write Through Pattern(读写穿透模式)](#(二)Read/Write Through Pattern(读写穿透模式))
[Read Through](#Read Through)
[Write Through](#Write Through)
[(三)Write Behind Caching Pattern(异步写缓存模式)](#(三)Write Behind Caching Pattern(异步写缓存模式))
[1. 主备缓存切换](#1. 主备缓存切换)
[2. 缓存异常处理](#2. 缓存异常处理)
[3. 异常情况处理](#3. 异常情况处理)
[1. 数据总线重试机制](#1. 数据总线重试机制)
[2. 双缓存更新策略](#2. 双缓存更新策略)
[3. 自动校对任务](#3. 自动校对任务)
干货分享,感谢您的阅读!在日常生活中,大家都会在家里储备一些粮食,比如米面油。但是,你不会在初期就囤积大量的食物,比如说一口气买半年甚至一年的粮食。
如果你住的地方附近有便利的超市,随时可以买到新鲜的食物,且你目前的家庭成员不多,消耗量不大,那么大规模囤粮不仅占用储物空间,还可能造成浪费(因为食品有保质期)。
只有在你预计家里即将迎来很多客人,或者附近的超市要关门维修几个月时,大规模囤粮才是明智之举。
说这个例子主要还是想提缓存的必要性:虽然缓存技术可以大幅提升系统性能,但在没有明确需要时,应该慎重考虑是否使用缓存。如果在系统初期或者短期内,直接使用数据库就能满足业务需求,那么最好先不要引入缓存。缓存的引入应该是在系统确实需要提升性能,或者有其他明确原因的时候。
历史基础问题回顾:
|-------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 具体内容基础 | 对应详细知识和解法链接 |
| 探析缓存穿透问题 | 高并发场景下的缓存穿透问题探析与应对策略 |
| 探析缓存雪崩 | 高并发场景下的缓存雪崩探析与应对策略-CSDN博客 |
| 探析缓存击穿 | 高并发场景下的缓存击穿问题探析与应对策略-CSDN博客 |
| 热key识别与实战解决 | 优化分布式系统性能:热key识别与实战解决方案_热key识别框架-CSDN博客 |
| 探析缓存热点key | 高并发场景下的热点key问题探析与应对策略_热点账户高并发解决方案-CSDN博客 |
| 探析大 Key 问题 | 高并发场景下的大 Key 问题及应对策略-CSDN博客 |
一、走进业务中的缓存
在现代高并发系统中,缓存作为一种关键技术,被广泛应用于各种场景中以提升性能和系统稳定性。缓存的存在,使得系统能够在面对大量请求时,依然保持高效的响应速度和较高的吞吐量。因此,缓存被誉为高并发系统的三大保护利器之一(缓存、限流、降级),能够显著提升系统访问速度和并发用户数。
在一次用户请求的路径中,数据通常会经过多个缓存节点。这些节点包括浏览器缓存、CDN节点缓存、网关代理缓存,以及在各业务系统内常用的本地缓存和分布式缓存等。每个缓存节点都承担着特定的作用,以减少数据源的访问压力,提高数据的访问速度。我们聚焦在服务端开发时,其主要包括本地缓存和分布式缓存两部分。
(一)本地缓存
对于单机应用或数据量较小的场景,使用本地缓存是一个高效且简单的解决方案。利用 HashMap
或 Guava Cache
等工具,可以轻松实现一个简单而快速的本地缓存。本地缓存的主要优点是速度快,因为它直接在应用进程内存中存储数据,避免了网络开销。
然而,本地缓存也有其局限性。由于数据存储在本地内存中,当应用部署在多台服务器上时,本地缓存的数据一致性和同步问题将会变得复杂。此外,本地缓存受限于单台服务器的内存容量,无法处理大规模数据。
(二)分布式缓存
对于需要处理大量数据和高并发请求的分布式系统,分布式缓存是必不可少的。常用的分布式缓存解决方案包括 Redis、Tair 等。分布式缓存通过将数据存储在独立的缓存服务器上,并提供高效的访问接口,极大地提升了数据的访问速度和系统的伸缩性。
二、缓存更新模式分析
通过将缓存部署在数据库前面,可以有效地抵挡大量的数据查询请求,直接减轻数据库的负载,提高系统的响应速度。然而,缓存的引入也带来了数据一致性和缓存更新的问题。为了应对这些挑战,开发中常用几种缓存更新模式:
- Cache Aside Pattern(旁路缓存模式)
- Read/Write Through Pattern(读写穿透模式)
- Write Behind Caching Pattern(异步写缓存模式)
(一)Cache Aside Pattern(旁路缓存模式)
Cache Aside Pattern(旁路缓存模式)是一种常见且实用的缓存管理模式,特别适用于需要在高并发系统中提升读取效率的场景。该模式没有单独的缓存维护组件,而是由应用程序负责管理缓存和数据库之间的数据一致性。
读操作流程
在 Cache Aside 模式中,对于读请求的处理流程如下:
- 请求读取数据:应用程序首先尝试从缓存中读取数据。
- 缓存命中:如果缓存中存在请求的数据,则直接返回缓存中的数据。
- 缓存未命中:如果缓存中不存在请求的数据,应用程序则从数据库中查询数据。
- 更新缓存:从数据库中读取到数据后,应用程序将数据写入缓存,以便下次相同的读请求可以直接从缓存中获取。
- 返回数据:最后,应用程序将从数据库中查询到的数据返回给请求方。
写操作流程
对于写请求的处理流程如下:
- 更新数据库:应用程序首先执行对数据库的写操作,确保数据的持久化和一致性。
- 失效缓存:成功更新数据库后,应用程序将对应的缓存条目标记为失效(或者直接删除),以便下次读取时强制从数据库重新加载最新数据。
流程问题思考
问题1:为什么不是先删缓存,再更新数据库?
这种做法会引发并发访问下的数据一致性问题,即脏数据的产生。具体来说,假设同时存在并发的读写请求:
- 写请求A删除缓存:写请求A首先删除了缓存中的数据,并且成功删除。
- 读请求B查询缓存未命中:此时读请求B发起了查询操作,发现缓存中不存在数据,因此从数据库中查询数据。
- 读请求B获取旧数据并写入缓存:读请求B从数据库中获取了旧数据,并将旧数据写入了缓存中。
- 写请求A更新数据库:写请求A继续执行数据库更新操作,将新数据写入数据库中。
在这种情况下,缓存中的数据变成了旧数据,而数据库中已经更新为新数据。因此,后续从缓存中读取的数据将是旧的,导致数据的不一致性问题。这种不一致可能会一直持续,直到缓存中的旧数据过期或被替换为新数据。
问题2:为什么更新操作是将cache失效,而不是更新?
这种情况会造成2方面的问题:同时2个并发的写请求时可能会导致脏数据+违背数据懒加载。
并发写请求可能导致脏数据:
-
写请求A先更新了数据库。
-
之后写请求B成功更新了数据库,并成功更新了缓存。
-
写请求A最后更新了缓存,此时写请求A的数据已经是脏数据,造成了不一致,并且会一致脏下去。
违背数据懒加载的原则:
- 某些缓存值可能需要经过复杂的计算才能得出,例如缓存的结果是从数据库中查询并经过计算得出的。
- 如果每次更新数据时都直接更新缓存,即使后续在一段时间内并没有读取该缓存数据,仍然进行了不必要的计算。
- 相反,采用失效缓存的策略,即先更新数据库后失效缓存,可以保证数据的一致性,并且在需要数据时再重新计算缓存值,避免了不必要的计算消耗,符合数据懒加载的原则。
综上所述,选择失效缓存而不是直接更新缓存,可以有效避免并发写操作导致的数据不一致性问题,并符合数据懒加载的设计原则,提高了系统的性能和可维护性。
Cache Aside Pattern模式不一致问题的发生
实际上先更新db,再失效cache这种模式理论上也可能出现问题,只是相对于以上的更新顺序,出现不一致的几率会更小。
-
读请求A首先读取缓存未命中,这个时候去读数据库成功查询到数据。
-
写请求B进来更新数据库成功,并删除缓存的数据成功。
-
最后请求A再将查询的数据写入到缓存中,而此时请求A写入的数据已经是脏数据,造成了数据不一致。
之所以建议用这种更新顺序,因为理论上造成不一致的几率会比较小,要达到不一致需要读请求要先与写请求查询,然后后与写请求返回,通常来说数据库的查询的耗时会小于数据库写入的耗时,所以这种问题出现概率会比较小。
(二)Read/Write Through Pattern(读写穿透模式)
Cache Aside Pattern模式中由应用方维护数据库和缓存的读写,导致应用方数据库和缓存的维护设计侵入代码,数据层的耦合增大,代码复杂性增加。
而Read/Write Through Pattern模式弥补了这一问题,调用方无需管理缓存和数据库调用,通过在设计中多抽象出一层缓存管理组件来负责和缓存和数据库读写维护,并且缓存和数据库的读写维护是同步的。调用方直接和缓存管理组件打交道,缓存和数据库对调用方是透明的视为一个整体。通过分离出缓存管理组件,解耦业务代码。
Read Through
应用向缓存管理组件发送查询请求,由缓存管理组件查询缓存,若缓存未命中,查询数据库,并将查询的数据写入缓存,并返回给应用。
Write Through
Write Through 套路和Read Through相仿,当更新数据的时候,将请求发送给缓存管理组件,由缓存管理组件同步更数据库和缓存数据。
(三)Write Behind Caching Pattern(异步写缓存模式)
Write Behind模式和Write Through模式整个架构是一样的,最核心的一点在于Write Through在缓存数据库中的更新是同步的,而Write Behind是异步的。
每次的请求写都是直接更新缓存然后就成功返回,并没有同步把数据更新到数据库。而把更新到数据库的过程称为flush,触发flush的条件可自定义,如定时或达到一定容量阈值时进行flush操作。并且可以实现批量写,合并写等策略,也有效减少了更新数据的频率,这种模式最大的好处就是读写响应非常快,吞吐量也会明显提升,因为都是跟cache交互。当然这种模式也有其他的问题。例如:数据不是强一致性的,因为选择了把最新的数据放在缓存里,如果缓存在flush到数据库之前宕机了就会丢失数据,另外实现也是最复杂的。
(四)几种模式对比
模式 | 优点 | 缺点 |
---|---|---|
Cache Aside | 简单直观,易于实现; 适用于读多写少的场景; | 可能出现数据一致性问题,因为读写操作分开处理,代码侵入大; 需要调用方维护缓存和db的更新逻辑 |
Read/Write Through | 引入缓存管理组件,缓存和数据库的维护对应用方式透明的 应用代码入侵小,逻辑更清晰 | 引入缓存管理组件,实现更复杂 |
Write Behind Caching | 读写直接和缓存打交道,异步批量更新数据库,性能最好 缓存和数据库对应用方透明 | 实现最复杂 数据丢失的风险 一致性最弱 |
三、缓存一致性分析
(一)一致性问题根因
在实际应用中,缓存引入后确实会带来一致性问题,特别是在分布式环境中使用分布式缓存时更为突出。这些问题通常涉及业务层面和系统层面:
业务层面引起的一致性问题
在业务层面,缓存和数据库的一致性问题主要由以下因素引起:
-
缓存更新策略:不同的缓存更新策略会影响到数据一致性。例如,在 Cache Aside Pattern 中,先更新数据库再失效缓存或者先删除缓存再更新数据库,都有可能在高并发情况下导致数据不一致的情况发生。选择合适的更新策略可以降低数据不一致的概率,但并不能完全消除问题。
-
并发访问控制:缺乏有效的并发访问控制机制可能导致并发写操作造成的数据竞争和不一致。例如,在多个请求同时更新同一条数据时,如果没有合适的并发控制,可能会导致最终数据的不确定性或者错误。
-
事务边界处理:在涉及到事务的复杂操作中,将缓存的更新与数据库操作放在同一个事务中,可能会增加事务的粒度和持有数据库连接的时间,从而降低系统的性能。因此,有时候必须将缓存更新与数据库更新分开处理,但这会引入数据一致性的挑战。
系统层面引起的一致性问题
在系统层面,主要的一致性问题源于分布式环境中单个节点或者整个系统的失败或异常情况:
-
缓存服务的故障:如果使用分布式缓存,其中一台缓存节点的故障可能会导致部分数据不可用或者不一致。尤其是在缓存更新时,如果更新请求发送到的缓存节点故障或者网络分区,更新可能无法正常完成,从而导致缓存与数据库数据的不一致。
-
网络分区和延迟:分布式系统中的网络分区和网络延迟可能导致缓存更新操作的延迟或失败,进而影响数据的一致性。例如,数据写入缓存成功,但是由于网络延迟或者分区问题,导致数据库的更新操作未能及时完成。
-
分布式事务的实现:如果应用程序需要在分布式环境中实现跨多个数据存储的事务,例如同时更新数据库和缓存,需要确保事务的原子性和一致性。分布式事务的复杂性和性能开销可能会限制其适用性。
(二)强一致性解决方案
强一致性在分布式系统中确实能够保证数据的实时一致性,但其带来的性能开销常常是系统设计中需要权衡的重要因素。一般采用采用强一致性协议或将并行请求转为串行化。
采用强一致性协议
强一致性协议确保在任何时间点,系统中所有副本的数据都保持一致。主要的强一致性协议如两阶段提交(2PC)、三阶段提交(3PC)等,它们通过同步协调多个节点或服务的状态来保证事务的原子性和一致性。
并行请求转为串行化
为了确保强一致性,有时系统会将并行执行的请求转换为串行化处理,即一次只处理一个请求,等待前一个请求处理完成后再处理下一个请求。这种方式可以确保事务的顺序执行和数据的强一致性,但也明显地增加了系统的响应时间和处理能力上限。
综合考虑
在实际应用中,选择是否采用强一致性协议或者将并行请求转为串行化,需要根据具体的业务需求、性能要求和系统架构来进行权衡和决策。通常情况下,强一致性适用于对数据实时性要求极高的场景,如金融交易系统;而对于需要更高吞吐量和较低延迟的系统,可能会选择牺牲一些一致性来换取性能和可用性的提升,采用最终一致性策略。
(三)最终一致性解决方案
最终一致性是指系统保证在一段时间内所有数据副本最终达到一致状态,即使在某些时间段内可能存在数据不一致的情况,但最终会达到一致状态。在互联网场景下,这种方式通常是可接受的,并且具有较好的可扩展性和性能。
重试机制
-
应用更新数据库,若这一步就失败,那么更新事务失败回退。
-
应用更新缓存失败,将失败的数据写入mq
-
消费mq得到失败的数据,重试删除缓存
整个过程考虑了数据库写入成功,缓存因系统故障等写入失败,导致数据库和缓存此时数据不一致,将失败的数据写入mq,监听mq重试删除缓存来到达最终一致性。缺点是整个重试写入的维护都在业务代码中,代码侵入性比较高。因此可以考虑一下方式引入databus,订阅数据更新binlog,解耦缓存更新过程。
重试+binlog
-
应用更新数据库,binlog日志同步databus。
-
缓存管理组件订阅binlog,并删除缓存,失败则将缓存key写入mq。
-
缓存管理组件订阅mq,重试删除缓存。
通过引入databus和缓存管理组件,将缓存更新的维护和业务代码解耦。
另一个原因是,现在的数据库通常是主从架构来提升整体的查询qps,因数据库主从同步的延迟,删除缓存后,如果此时从数据库还未同步完成,新来的请求发现缓存失效了,从从库里查询了已经过期的数据放到缓存中,也会造成数据的不一致。而通过订阅binlog的同步的延迟性,使删除缓存的时序延后,进一步降低不一致的几率。
综合考虑
缓存的引入在提升系统性能方面有着显著的效果,但也带来了数据一致性的问题。在实际设计中,需要根据具体的业务需求、数据特性和系统架构,综合考虑缓存策略和过期时间设置,以在高性能和一致性之间找到最佳的平衡点。通过灵活运用最终一致性策略、主动刷新、缓存预热等技术手段,可以在保证系统高性能的同时,尽量减少数据不一致的风险。
四、缓存热门问题分析和解决
在高并发场景下,缓存作为前置查询机制,显著减轻了数据库的压力,提高了系统性能。然而,这也带来了缓存失效、增加回溯率等风险。常见的问题包括缓存穿透、缓存雪崩、热Key和大Key等。这些问题如果不加以处理,会影响系统的稳定性和性能。因此,采用有效的缓存策略,如缓存空结果、布隆过滤器、缓存过期时间随机化、多级缓存等,对于保障系统在高并发情况下的可靠性至关重要。接下来,我们将详细探讨这些常见缓存问题及其应对策略。
(一)缓存穿透
问题描述
缓存的设计通常旨在提高系统的查询效率,通过减少对数据库的直接访问来缓解压力。然而,当大量非法请求查询数据库中不存在的数据时,既无法命中缓存,也无法从数据库中获取结果,这种情况被称为缓存穿透。缓存穿透使缓存形同虚设,缓存命中率降为零,每次请求都直接穿过缓存到达数据库。
在这种情况下,数据库承受了所有请求的压力,缓存未能发挥其应有的作用。如果有人恶意攻击系统,通过大量不存在的key请求接口,这些请求会直接穿透缓存并打到数据库上,可能导致数据库负载过重甚至宕机。
解决策略分析
详细解决方案可见:高并发场景下的缓存穿透问题探析与应对策略-CSDN博客
(二)缓存雪崩
问题描述
缓存层挡在db层前面,抗住了非常多的流量,在分布式系统中,"everything will fails",缓存作为一种资源,当cache crash后,流量集中涌入下层数据库,称之为缓存雪崩。
造成这种问题通常有2种原因:
- 业务层面:大量的缓存key同时失效,失效请求全部回源到数据库,造成数据库压力过大崩溃。
- 系统层面:缓存服务宕机。
解决策略分析
详细解决方案可见:高并发场景下的缓存雪崩探析与应对策略-CSDN博客
(三)缓存击穿
问题描述
在缓存系统中,有些数据可能会被频繁访问,这些数据被称为热点数据。为了保证缓存的有效性,缓存通常会设置一个过期时间。然而,当一个热点数据的缓存失效时,所有对该数据的请求会同时到达数据库。这种情况会导致以下问题:
-
数据库压力大:当大量请求同时涌向数据库时,数据库的负载会瞬间增加,可能导致数据库性能下降,甚至崩溃。
-
无效的重复查询:所有请求都试图从数据库中读取相同的数据并更新缓存,这种重复的查询是无效的,因为只需要一次查询结果就能满足所有请求。
缓存击穿是由于热点数据的缓存失效导致的数据库压力过大问题。为了解决这个问题,可以采用互斥锁、永不过期和提前预热缓存等方法。这些方法各有优缺点,可以根据具体业务场景选择合适的解决方案,以确保系统在高并发访问下的稳定性和高性能。
解决策略分析
详细解决方案可见:高并发场景下的缓存击穿问题探析与应对策略-CSDN博客
(四)热点key问题
问题描述
热点 key 问题是指某些数据的访问量非常高,超过了缓存服务器的处理能力。这种现象在电商促销、社交媒体热点等场景中特别常见。热点 key 问题主要有以下几个方面:
- 流量集中,达到物理网卡上限:当大量请求集中到某个热点 key 时,这些请求会被路由到相同的缓存服务器。随着流量增加,服务器的物理网卡可能达到带宽上限,无法再处理更多请求。
- 请求过多,缓存分片服务被打垮:缓存系统通常使用分片机制来分担负载。然而,热点 key 的访问量可能过高,单个分片无法处理,导致该分片服务被打垮。
- 缓存分片打垮,重建再次被打垮,引起业务雪崩:当某个缓存分片被打垮后,系统可能会尝试重建该分片。然而,重建过程中的负载再次集中到该分片上,导致分片再次被打垮,形成恶性循环,引起业务系统的雪崩。
对于其发现机制可见:优化分布式系统性能:热key识别与实战解决方案-CSDN博客
详细解决方案可见:高并发场景下的热点key问题探析与应对策略
(五)大key问题
问题描述
大 Key 是指在缓存系统中,某些 Key 对应的值(Value)存储的数据量非常大,大 Key 可能会导致一系列性能问题和系统不稳定性:
- 响应超时 :由于 Redis 是单线程的,如果某个 Key 的 Value 很大,在进行
GET
或SET
操作时会占用 Redis 的单线程,导致其他请求被阻塞,从而引发响应超时。另外集合类型(如 Set、List、Hash、ZSet)的元素较多时,删除或读取这些大集合的时间复杂度为 O(n),会严重阻塞 Redis 进程,导致应用服务的超时和崩溃。 - 数据倾斜:大 Key 会导致 Redis 集群中某些节点存储的数据量远大于其他节点,从而引起数据分布不均衡的问题。集群负载不均衡会导致某些节点的内存和计算资源紧张,降低整体性能。
大key的认定
缓存系统中,一般大 Key 的定义如下:
- String 类型 :
- Value 大于 10K 为"大" Key
- Value 大于 100K 为"超大" Key
- Set、List、Hash、ZSet 等集合类型 :
- 元素个数超过 1000 为"大" Key
- 元素个数超过 10000 为"超大" Key
解决策略分析
详细解决方案可见:高并发场景下的大 Key 问题及应对策略-CSDN博客
五、复杂工程应对:本地缓存+双缓存方案
(一)本地缓存+双缓存方案架构
当设计缓存方案时可采用了本地缓存和双缓存策略应对平台的高流量压力和对缓存组件高度依赖的情况。
本地缓存
本地缓存被用于存储那些数据量小、频繁访问且变更频率较低的数据,例如API信息和业务基础数据。这些数据通常不会经常变更,因此可以在本地缓存中实现快速访问和响应,减少对分布式缓存的请求次数,从而提升系统性能和响应速度。
双缓存方案
尽管分布式缓存系统Redis、Squirrel、Tair等已经实现了高可用性的建设,但考虑到实际使用过程中偶尔会因为单一缓存不稳定而导致的业务报警,所以可以引入了双缓存方案。在这种方案中,Tair和Redis互为主备,可以随时切换其角色。主缓存用于正常运行时的数据访问,备用缓存则作为故障恢复或负载均衡的备选。
优势与实施细节
-
提高系统可用性: 双缓存方案通过备用缓存的存在,确保即使主缓存出现故障或性能问题,系统仍能继续运行。
-
降低对缓存的单一依赖: 避免了单一缓存带来的风险,如性能瓶颈或故障导致的服务中断。
-
实时切换能力: 可以根据监控系统或自动化策略,在发生问题时快速切换到备用缓存,最大限度地减少业务影响和服务下线时间。
通过这种组合的本地缓存和双缓存方案,我们能够在保证高性能和高可用性的同时,有效管理和优化平台的整体缓存策略,以应对不断增长的流量和业务需求。
(二)策略层设计
在缓存架构中,策略层主要负责实现降级、双缓存熔断自动切换或人工切换,以及保证最终一致性的兜底策略和及时报警的监控策略。
降级策略
降级策略是为了在缓存出现问题或性能下降时保证系统的稳定性和可用性:
-
自动切换到备用缓存: 当主缓存出现熔断或故障时,系统会自动切换到备用缓存,以保证数据访问的连续性。
-
人工切换: 运维人员可以通过手动开关将请求切换到备用缓存,从而快速响应缓存故障或性能问题。
-
直接查询数据库: 当所有缓存失效或不可用时,降级业务可以直接向数据库发起查询,确保业务的正常运行。
兜底策略
兜底策略用于保证缓存与数据库数据的最终一致性:
-
数据订阅与同步: 引入数据总线(如Databus),订阅数据库表的数据变化。当数据库数据发生变更时,及时更新对应的缓存数据,确保缓存的数据与数据库保持一致性。
-
定期校对与刷新: 定时任务会周期性地检查缓存和数据库数据的一致性。如果发现缓存更新失败或数据总线组件异常,系统会通过刷新缓存来修复不一致的数据状态。
报警策略
报警策略用于监控和及时响应缓存运行时的异常情况:
-
实时监控运行状态: 系统会持续监控双缓存的运行状态,包括缓存命中率、响应时间、缓存失效率等关键指标。
-
异常报警通知: 一旦监控到缓存运行状态异常(如缓存熔断、性能下降、数据不一致等),系统会立即触发报警通知,通知相关的运维团队或负责人员进行及时处理。
通过精心设计和实施这些策略能够确保缓存系统在面对高流量和复杂业务场景时保持高可用性、高性能,并且能够及时响应和修复潜在的故障和数据一致性问题。
(三)缓存查询流程
采用灵活的查询策略以应对不同的运行情况和故障场景,保证系统的稳定性和高可用性。
1. 主备缓存切换
-
主缓存优先: 正常情况下,系统优先使用主缓存来响应数据请求,以保证快速的访问速度和低延迟。
-
熔断策略: 当主缓存频繁失败或命中率低于设定阈值时,系统会触发熔断机制。此时,自动切换到备用缓存来维护数据访问的连续性和稳定性。
-
手动切换: 运维团队可以通过操作开关手动将数据请求切换到备用缓存,以应对主缓存故障或性能下降的紧急情况。
2. 缓存异常处理
-
缓存异常处理流程: 当主备缓存均异常或缓存未命中时,系统会自动触发查询数据库的流程。
-
数据库异步加载: 查询数据库时,系统会异步加载数据到缓存中,不要求即时成功。这种方式保证了即使数据库访问不稳定或数据丢失,系统仍能继续提供服务,避免因缓存问题而导致的服务中断或性能下降。
3. 异常情况处理
- 数据加载成功性: 异步加载数据库数据到缓存中的过程中,系统不强制要求每次加载都成功。如果加载失败,系统会在后续的重试过程中尝试修复并补充缓存数据。
(四)缓存更新策略流程
采了多层次的更新策略来处理缓存更新失败的情况。
1. 数据总线重试机制
-
更新失败通知: 任何一个缓存(Tair或Squirrel)的更新失败时,都会通知数据总线(DataBus),标记该次更新为失败。
-
重试机制: DataBus会自动进行重试,确保最终的数据一致性。重试机制确保即使在初次更新失败的情况下,数据总线仍会不断尝试,直至更新成功。
2. 双缓存更新策略
-
允许部分更新成功: 在更新缓存时,允许Tair和Redis中的一个缓存更新成功。如果其中一个缓存更新失败,系统会标记该次更新为失败,并触发重试机制。
-
数据一致性保证: 当某个缓存不可用时,系统保证更新成功的缓存数据与数据库数据保持一致。通过这种策略,即使部分缓存服务出现故障,系统仍能保证数据的一致性和可用性。
3. 自动校对任务
-
定期自动校对: 系统会在每天的低峰期运行自动校对任务,检查并修正缓存与数据库之间的差异。此任务可以确保在长时间运行后,缓存数据仍能与数据库数据保持一致。
-
手动触发校对: 除了自动校对任务,运维团队还可以手动触发校对任务,以应对紧急情况或特殊需求。
-
通知机制: 每次校对任务完成后,系统会发送通知,告知任务的执行情况和结果,确保相关人员及时了解数据状态。
无论是缓存更新失败、部分缓存不可用,还是需要定期校对数据,我们都能通过有效的机制和流程来保证系统的稳定运行和数据的可靠性。
六、总结
缓存技术在现代分布式系统中至关重要,不仅提升了系统性能,还减轻了后端数据库的压力。然而,缓存系统也面临着诸多挑战,如缓存穿透、缓存雪崩、缓存击穿和热点key问题。通过多种策略的综合应用,包括本地缓存、双缓存方案、多级缓存、多副本、热点key拆分和动态分散等,可以有效应对这些问题。
本地缓存适用于频繁访问且少变更的数据,如API信息和业务基础信息,而双缓存方案则通过Tair和Squirrel的组合,实现主备缓存切换和高可用性。在缓存更新上,采用旁路缓存模式、读写穿透模式和异步写缓存模式等方法,确保数据一致性和系统稳定性。策略层设计中,降级策略、兜底策略和报警策略进一步保障了缓存系统的可靠性。
综合来看,通过合理设计和灵活应用缓存技术,可以显著提升分布式系统的性能和稳定性,为业务的高效运行提供强有力的支持。