2026/3/6 0:09:29
网站建设
项目流程
建站seo怎么赚钱,中国菲律宾时差,开发者软件下载,湘潭网站建设 干净磐石网络缓存能加快数据的访问速度#xff0c;几乎每个软件都会使用这一技术。自1968 年 在 360/85 系统上引入高速缓存#xff08;cache#xff09;一词以来#xff0c;缓存技术经历了多次迭代更新#xff0c; 还出现了许多种缓存框架和工具#xff0c;以降低其使用门槛和风险。…缓存能加快数据的访问速度几乎每个软件都会使用这一技术。自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补偿任务使用主库在执行缓存补偿任务时一定要使用主数据库主库。如果系统设计中包含了主库和读库从库那么使用读库进行补偿可能会导致数据同步延迟出现数据不一致的时间窗口。使用主库可以确保补偿任务获 取的是最新的、已经提交的数据从而提高数据的一致性。03缓存是把“双刃剑”缓存无疑是一项伟大的发明它极大地提高了数据访问的速度。然而正如 所有强大的工具一样缓存也有其固有的弱点。在使用缓存时我们必须注意以下几点。强时效性要求的场景不适合使用缓存。由于数据库和缓存之间必然存在时间不一致的窗口对于对数据时效性要求极高的场景使用缓存可能会引入不可接受的数据延迟。只有那些读多写少且能够容忍一定程度数据不一致性的场景 才适合使用缓存。数据不可丢失的场景不应使用缓存。缓存之所以能够提供快速的数据访问 是因为它将数据存储在内存中。然而内存存储的一个固有风险是数据可能会丢失。尽管可以采取各种补救措施但只要使用缓存数据丢失的可能性就无法完全消除。因此只有当我们接受这种潜在风险时才能安全地使用缓存。像用户余额这样的关键数据不应该被存储在缓存中以避免数据丢失的风险。缓存不能替代数据库。缓存和数据库之间存在显著差异。除了缓存数据可能丢失数据库还提供了事务处理和 ACID 特性这是缓存所不具备的。一个典型的例子是幂等性控制数据库事务的原子性可以确保一组操作要么全部成功要么全部失败。而缓存无法提供这种保证尤其是在缓存与数据库结合使用时更难以保证操作的原子性。因此试图仅通过缓存来实现幂等性控制是错误的。总结来说缓存是一个强大的工具但我们必须谨慎使用确保它适用于当前的场景并且不会引入无法接受的风险。