百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

麻烦不断的分布式缓存。。 分布式缓存操作异常怎么解决

suiw9 2024-12-17 16:13 30 浏览 0 评论

缓存能加快数据的访问速度,几乎每个软件都会使用这一技术。


自1968 年 在 360/85 系统上引入高速缓存(cache)一词以来,缓存技术经历了多次迭代更新, 还出现了许多种缓存框架和工具,以降低其使用门槛和风险。


在分布式技术中, 缓存尤为重要,相关使用方法和介绍文档也相当丰富。


然而,互联网技术历史中不乏因缓存异常导致的重大故障:


  • 2012 年,Facebook 的 Memcached 缓存更新异常, 导致用户看到了错误的信息;
  • 2013 年,Google 的 Spanner 数据库缓存更新异常, 使得数百万用户无法使用 Google 服务;
  • 2016 年,亚马逊 AWS 云服务由于 Elastic Load Balancer 缓存未能正确更新,造成大量网站和应用程序停机。

这些案例引发我们深思:我们是否真正会用缓存?是否所有应用场景都适合引入缓存?在哪些情况下,缓存可能会造成严重损害?

接下来,我们将基于大厂 的实践经验,通过具体案例分析缓存使用中可能遇到的可用性和一致性问题,并 探索解决这些问题的方法,以深化对缓存技术的理解,并确保其更好地服务于我 们的应用。

01

只要使用缓存,就会存在可用性风险

在系统链路上增加一个环节就会增加可用性风险。

尽管缓存的引入提升了数据访问速度,但缓存架构的复杂性也给系统引入了更多的可用性风险,令系统更加脆弱。

因此,在设计缓存系统时,必须充分考虑这些风险,并采取相应的措施来确保系统的稳定性和可靠性。

1. 缓存加载不当导致服务器宕机

缓存通常架设在数据库之前,用于缓存常用数据,以加快访问速度并减轻数据库的负担。为了保持缓存数据与数据库中的数据尽可能一致,需要对缓存数据进行刷新。然而,一旦缓存刷新策略不当,就可能会对数据库造成严重影响。

下面以会员系统缓存刷新为例进行分析。

会员系统存储着用户的基础信息,这类数据的写入和更新频率不高,但读取量大,非常适合放入缓存中。

图1展示了一种利用缓存 JAR 包的方法。服务 提供方将缓存功能封装在一个 JAR 包中,供服务调用方系统集成。这样,服务调 用方可以像访问本地数据一样轻松、迅速地获取远程数据。

图1 缓存 JAR 包的利用过程

在现实中,由于项目时间紧迫或开发者经验不足,缓存刷新方法可能非常简单,例如设定固定的过期时间,一旦缓存数据失效,就立即刷新缓存数据。

这种方法在缓存数据量较小的情况下通常不会出现问题。然而,它存在一个致命的缺点:可能导致大部分数据在同一时刻失效,进而导致所有缓存 JAR 包在同一时刻发起查询请求,将数据更新到缓存中。

一旦大量查询请求集中在同一时间点到达会员系统,就可能使会员系统的数据库过载,导致宕机,从而使整个会员服务不可用。

为了解决这个问题,可以通过调整缓存刷新的频率来减轻数据库的压力。例如,在缓存失效时间上增加随机数,以错开缓存刷新的高峰期,避免集中刷新对服务器造成过大的压力。这种方法可以有效地规避因集中刷新而导致的系统崩溃。

2. 缓存刷新不当导致服务宕机

除了注意缓存刷新的时机,缓存刷新的小细节也同样重要。

如图2所示,这种做法在大多数情况下可能没有问题,但如果远程调用服务 userService.queryAllUsers 时出现网络抖动,缓存就可能会变成空值。

在这种情况下,由于无法从缓存中找到数据,所以系统可能再次触发缓存刷新逻辑,导致远程调用,而远程调用由于网络抖动无法快速返回结果,从而引发服务雪崩, 导致服务调用方和服务提供方全部宕机。

图2 错误的缓存刷新的代码

一个相对更严谨的做法是在远程调用获取数据结果后,再将新的数据结果赋给原缓存变量。这样即使远程调用出现异常,缓存内容也不会为空。

然而,这种全量刷新缓存数据的方法可能会对系统资源造成较大压力。

一个更好的做法是,当服务端数据变化时,通过推送的方式对缓存进行增量刷新。

这样可以更有效地更新缓存,减少对系统资源的消耗。如图 3所示, 代码稍作调整,采用推送方式进行缓存增量刷新。

图3 调整后的缓存刷新的代码

3. 本地缓存不当导致服务宕机

缓存 JAR 包对服务调用方友好,因为它提供了一种便捷的方法来获取缓存数 据。然而,由于缓存 JAR 包寄宿在服务调用方系统中,需要注意以下一些潜在的风险。

缓存刷新的任务量过大:当缓存刷新的任务量过大时,可能会导致服务调用方的负载急剧增加,甚至引发宿主系统崩溃。这是因为缓存刷新通常涉及大量数 据的读取和写入操作,如果这些操作过于频繁或数据量过大,可能就会超出服务 器的处理能力。

缓存 JAR 包中缓存的数据量过大:如果缓存 JAR 包中缓存的数据量过大, 就可能会直接影响宿主系统的稳定性。例如,过大的缓存数据量可能会导致频繁 的垃圾回收(Full GC),这会严重影响系统的响应时间和吞吐量。

4. 分布式缓存穿透击垮数据库

若换成分布式缓存,是不是能够一劳永逸地解决问题呢?会员系统将数据都存储在分布式缓存中的具体情况如图4所示。

图4 分布式缓存示例

当查询的数据已存在于分布式缓存中时,直接返回结果可以提高查询效率。

然而,如果部分数据本来就不存在,直接查询数据库并在返回数据库结果的同时将结果写入缓存中,就可能导致问题。

如果服务调用方在缓存中找不到数据,它就会继续查询数据库;如果数据库也找不到,就可能导致服务调用方不断重试查 询,最终可能引起雪崩效应,击垮数据库。

对于分布式缓存中的数据也需要提前预热,对于不存在的数据需要在缓存中构建特殊空对象以防止缓存被穿透。


02

只要使用缓存,就会存在数据不一致问题

从原理上来说,同一份数据既放到缓存中又存储在数据库中,就一定会带来数据一致性的挑战。

尽管可以通过各种策略和技术手段来减少数据不一致的时间窗口, 例如设置合理的缓存过期时间、使用缓存预读取和后写入机制、实施分布式锁等, 但这些措施并不能从根本上杜绝数据不一致的问题。接下来将分场景论述数据不一致的根源。

1. 数据不一致的本质分析

(1)纯写场景。在正常的业务处理逻辑完成后,可以在本地事务结束之后, 通过回调方法 afterCompletion 将模型写入缓存,如图5所示。

图5 纯写场景

写入缓存的请求可能会失败,导致数据库中有数据而缓存中却没有相应的数据。为了处理这种情况,需要实施一个补偿方案。

具体来说,当缓存中缺少数据时, 系统应该查询数据库,并将查询结果重新写入缓存。在纯写场景中,由于数据库已经包含了最新的数据,因此不会出现数据一致性问题。

在这种情况下,主要关注的是分布式缓存的命中率,即缓存中的数据与数据库中的数据保持一致的频率。如果缓存命中率较低,则意味着系统需要频繁查询数据库来获取缺失的数据,这会增加数据库的负载,从而降低系统的整体性能。

(2)纯删场景。这个场景也是比较简单的,先将缓存中的数据删除,再删 除数据库中的数据,如图6所示。

图6 纯删场景

先删除缓存中的数据再删除数据库中数据的风险在于,最终数据库事务提交可能会失败,这可能导致数据不一致。

为了降低数据不一致的概率,可将删除缓存数据的操作放在最后一步,即在所有业务逻辑处理完毕后再调用删除缓存数据 的方法。

即使缓存数据被删除,但数据库中的数据依然存在,最终读取到的数据库数据不会是脏读。

因此,在纯删场景下,实际上并不存在数据不一致的问题。

(3)纯读并写场景。为了提高缓存命中率并确保数据的最终一致性,常见的做法是首先尝试从缓存中读数据。如果缓存中没有数据(即缓存未命中),则回退到数据库中读数据。一旦从数据库中获取数据,不论是空数据还是有实际内 容的数据,都应该将其更新回缓存中,以便后续的请求能够直接从缓存中获取数 据,减少数据库的访问压力。

这种策略如图7所示。

图7 纯读并写场景

在系统中仅涉及数据读取操作,而不包含数据更新、删除或写入的场景下, 不存在数据不一致的问题。

(4)纯更新场景。在这个场景中,由于数据库和缓存的操作不是原子性的, 无论是先更新数据库还是先更新缓存,都存在数据不一致的风险。

如图8所示, 无论先更新数据库,而缓存更新失败,还是先更新缓存,而数据库更新失败,都会导致数据不一致。这是因为这两个操作不能保证同时成功,所以无法实现强一致性,只能追求最终一致性。

图8 纯更新场景

清晰地认识问题的本质是我们选择解决方案的基础。为了减轻数据库的压力并确保其高可用性,缓存仅是一种手段。

为了维护数据的最终一致性,我们必须优先确保数据库数据的正确性,然后尽最大努力去修正缓存中的数据。

在图8所示的纯更新场景中,应该首先确保数据库更新成功。

然后,可以持久化一个缓存补偿任务,这个任务会在数据库事务提交后执行,用于更新缓存。

最后,通过这个缓存补充任务来检查数据库与缓存的数据一致性。如果发现不一致,应该以数据库的数据为准来修正缓存中的数据。

因此,数据库与缓存之间的数据不一致窗口期取决于缓存写入的成功率,以及定时补偿任务的执行频率。

这种方式,可以最大限度地减少数据不一致的可能 性,并确保系统最终达到一致性状态。

(5)综合场景。以上论述的场景是在仅考虑单一场景的理想情况下进行的 推演(实际上一个系统中不太可能只有数据写入而没有数据更新)。

然而,在现实中,系统通常涉及多种操作,包括数据的读取、写入、更新和删除。

假设需要删除数据,即使缓存和数据库的删除操作都成功执行,仍然存在一种情况:在删除操作之后,并发的读请求可能会将旧数据重新写入缓存, 如图9所示。

这是因为,在多线程或分布式系统中,可能会有多个请求同时进行,其中一些请求可能在删除操作之后但缓存补偿任务执行之前到达。这种情况下,数据的一致性可能会受到影响,因为缓存中可能会短暂地存储过时的数据。

图9 综合场景

2. 减少不一致窗口的方案

虽然数据不一致性在某种程度上是不可避免的,但这并不意味着我们无法对其进行优化。

当优化的效果达到投入与产出比的最佳平衡时,实际上问题也就得到了有效解决。

整个优化思路如图10所示。

图10 减少不一致窗口的方案

具体步骤如下。

(1)本地事务中更新业务数据和持久化缓存补偿任务:在本地事务中,首 先更新数据库的业务数据。同时,在事务中持久化一个缓存补偿任务,这个任务 包含了更新缓存所需的信息。

(2)事务提交后更新分布式缓存:当数据库事务成功提交后,执行之前持 久化的缓存补偿任务。将最新的数据模型存放到分布式缓存中,确保缓存与数据 库的数据一致。

(3)数据版本控制:存入缓存的数据应该包含版本信息,以便检测数据的 新旧。可以选择数据的最新修改时间作为版本号,这样在读取数据时可以比较版 本号,确保使用的是最新数据。

(4)查询请求中的缓存补偿:当查询请求在缓存中找不到数据时,触发缓 存补偿机制。从数据库的主库中捞取最新的数据进行补偿,确保缓存中数据的准确性。

在处理修改和删除场景时,需要特别注意几个容易出错的地方,以确保数据 的一致性和准确性。

(1)使用排他锁:在修改或删除数据时,应该对数据记录加上排他锁 (Exclusive Lock),以防止并发操作导致缓存中出现脏数据。

排他锁可以确保在锁释放之前,其他事务无法读取或修改相同的数据,从而避免了并发问题。

(2)更新缓存前的再次读取:如果系统中没有排他锁的条件或者无法使用排 他锁,那么在更新缓存之前,应该从缓存中再次读取数据。

将这次读取到的数据与新更改的数据合并,然后再次放入缓存。这样做可以在一定程度上避免数据不一致,尽管可能会丢失本次修改的内容,但这是局部的 数据丢失,而不是数据错误。

(3)补偿任务使用主库:在执行缓存补偿任务时,一定要使用主数据库(主库)。

如果系统设计中包含了主库和读库(从库),那么使用读库进行补偿可能会导致数据同步延迟,出现数据不一致的时间窗口。使用主库可以确保补偿任务获 取的是最新的、已经提交的数据,从而提高数据的一致性。

相关推荐

看完这一篇数据仓库干货,终于搞懂什么是hive了

一、Hive定义Hive最早来源于FaceBook,因为FaceBook网站每天产生海量的结构化日志数据,为了对这些数据进行管理,并且因为机器学习的需求,产生了Hive这们技术,并继续发展成为一个成...

真正让你明白Hive参数调优系列1:控制map个数与性能调优参数

本系列几章系统地介绍了开发中Hive常见的用户配置属性(有时称为参数,变量或选项),并说明了哪些版本引入了哪些属性,常见有哪些属性的使用,哪些属性可以进行Hive调优,以及如何使用的问题。以及日常Hi...

HIVE SQL基础语法(hive sql是什么)

引言与关系型数据库的SQL略有不同,但支持了绝大多数的语句如DDL、DML以及常见的聚合函数、连接查询、条件查询。HIVE不适合用于联机事务处理,也不提供实时查询功能。它最适合应用在基于大量不可变数据...

[干货]Hive与Spark sql整合并测试效率

在目前的大数据架构中hive是用来做离线数据分析的,而在Spark1.4版本中spark加入了sparksql,我们知道spark的优势是速度快,那么到底sparksql会比hive...

Hive 常用的函数(hive 数学函数)

一、Hive函数概述及分类标准概述Hive内建了不少函数,用于满足用户不同使用需求,提高SQL编写效率:...

数仓/数开面试题真题总结(二)(数仓面试时应该讲些什么)

二.Hive...

Tomcat处理HTTP请求流程解析(tomcat 处理请求过程)

1、一个简单的HTTP服务器在Web应用中,浏览器请求一个URL,服务器就把生成的HTML网页发送给浏览器,而浏览器和服务器之间的传输协议是HTTP,那么接下来我们看下如何用Java来实现一个简单...

Python 高级编程之网络编程 Socket(六)

一、概述Python网络编程是指使用Python语言编写的网络应用程序。这种编程涉及到网络通信、套接字编程、协议解析等多种方面的知识。...

[904]ScalersTalk成长会Python小组第20周学习笔记

Scalers点评:在2015年,ScalersTalk成长会Python小组完成了《Python核心编程》第1轮的学习。到2016年,我们开始第二轮的学习,并且将重点放在章节的习题上。Python小...

「web开发」几款http请求测试工具

curl命令CURL(CommandLineUniformResourceLocator),是一个利用URL语法,在命令行终端下使用的网络请求工具,支持HTTP、HTTPS、FTP等协议...

x-cmd pkg | hurl - 强力的 HTTP 请求测试工具,让 API 测试更加简洁高效

简介...

Mac 基于HTTP方式访问下载共享文件,配置共享服务器

方法一:使用Python的SimpleHTTPServer进行局域网文件共享Mac自带Python,所以不需要安装其他软件,一条命令即可...

Python 基础教程十五之 Python 使用requests库发送http请求

前言...

使用curl进行http高并发访问(php curl 大量并发获得结果)

本文主要介绍curl异步接口的使用方式,以及获取高性能的一些思路和实践。同时假设读者已经熟悉并且使用过同步接口。1.curl接口基本介绍curl一共有三种接口:EasyInterface...

Django 中的 HttpResponse理解和用法-基础篇1

思路是方向,代码是时间,知识需积累,经验需摸索。希望对大家有用,有错误还望指出。...

取消回复欢迎 发表评论: