首页 > 数据库 >深入分析与解决方案:缓存与数据库双写不一致问题

深入分析与解决方案:缓存与数据库双写不一致问题

时间:2024-08-20 09:05:08浏览次数:6  
标签:缓存 数据库 一致性 线程 key 深入分析 数据 双写

我们上次探讨了 Redis 的常见问题,本章将深入分析更细致的细节,例如如何从业务角度有效处理缓存与数据库之间的双写不一致问题。接下来,让我们深入研究这个话题。

key重建优化

开发人员通常使用“缓存+过期时间”的策略,以便既能加速数据读写,又能确保数据的定期更新。这种模式基本上能够满足绝大部分需求。然而,当以下两个问题同时出现时,可能会对应用系统造成严重的影响:

  1. 热点 key 的出现:当前的 key 是一个热点 key,例如一条热门的娱乐新闻,导致并发请求量非常大。这种情况会使得缓存的读取请求集中在这个热点 key 上,造成缓存的压力显著增加。
  2. 缓存重建的复杂性:当缓存失效后,重建缓存的过程不能在短时间内完成。重建缓存可能涉及复杂的计算任务,例如执行复杂的 SQL 查询、多次 I/O 操作、以及处理多个数据依赖等。这种复杂的重建过程可能会导致系统性能下降,进而影响用户体验。

在缓存失效的瞬间,如果大量线程同时启动缓存重建操作,会导致后端负载急剧增加,甚至可能使应用系统崩溃。这种情况会显著影响系统的稳定性和性能。为了解决这一问题,关键在于避免大量线程同时进行缓存重建。

一个有效的解决方案是使用互斥锁机制,该方法确保在任何给定时刻只有一个线程被允许执行缓存重建操作。其他线程则需要等待重建线程完成缓存重建后,才能从缓存中重新获取数据。这种策略不仅能减轻后端系统的压力,还能避免因并发重建引起的性能瓶颈,显著提升系统的稳定性和响应速度。

示例伪代码:

String get(String key) {

    // 从Redis中获取数据
    String value = redis.get(key);

    // 如果value为空,则开始重构缓存
    if (value == null) {

        // 生成唯一的mutex key,确保只有一个线程能重建缓存
        String mutexKey = "mutex:key:" + key;

        // 尝试设置mutex key,使用NX(仅在不存在时设置)和EX(设置过期时间)
        boolean isMutexSet = redis.set(mutexKey, "1", "ex 180", "nx");

        if (isMutexSet) {
            try {
                // 从数据源获取数据
                value = db.get(key);

                // 回写数据到Redis,设置过期时间
                redis.setex(key, timeout, value);

            } finally {
                // 删除mutex key,确保其他线程可以继续重建缓存
                redis.delete(mutexKey);
            }

        } else {
            // 其他线程等待50毫秒后重试
            Thread.sleep(50);
            value = get(key);
        }
    }

    return value;
}

缓存与数据库双写不一致

在高并发场景下,同时进行数据库与缓存的操作可能会引发数据不一致性的问题。具体来说,当多个线程或进程同时尝试更新缓存和数据库时,可能会导致缓存与数据库之间的数据不匹配。

双写不一致情况

当多个线程或进程同时进行缓存和数据库的更新时,可能出现以下问题:

  • 缓存与数据库的数据不一致:例如,两个线程同时更新数据库,但只一个线程更新了缓存,这会导致缓存中的数据和数据库中的数据不一致。
  • 延迟问题:即使在更新缓存和数据库时都执行了操作,也可能由于网络延迟或其他因素,导致缓存和数据库之间的状态不同步。

image

读写并发不一致

读写并发不一致是指在并发场景下,多个线程或进程对同一数据进行读写操作时,可能导致数据的不一致或错误。

image

以下是一些常见的读写并发不一致的解决方法:

  1. 针对并发几率较小的数据
    • 对于个人维度的订单数据、用户数据等,并发操作较少且对数据一致性的要求相对宽松。对于这类数据,可以通过设置缓存的过期时间来解决缓存与数据库之间的数据不一致问题。具体做法是,在缓存中设置合理的过期时间,缓存数据会在过期后自动失效。每当缓存失效时,系统将自动从数据库中读取最新的数据,并更新缓存。这种策略简单有效,可以大大减少缓存不一致的发生几率。
  2. 在并发较高的场景下的缓存数据一致性
    • 即使在业务场景下并发较高,但如果可以容忍短时间的缓存数据不一致(例如商品名称、商品分类菜单等),则仍然可以通过设置缓存的过期时间来满足大部分业务需求。通过合理设置过期时间,虽然缓存数据可能会在短时间内出现不一致,但这种不一致通常不会对业务造成严重影响。因此,缓存过期策略仍然是一种有效的解决方案。
  3. 对于不能容忍缓存数据不一致的场景
    • 如果业务对缓存数据的一致性有严格要求,可以使用分布式读写锁来保证并发读写操作的顺序性。具体做法是,在进行写操作时,通过分布式锁机制来确保只有一个操作能够执行,从而避免写写冲突。而对于读操作,通常可以在不加锁的情况下进行,以提高性能。分布式锁能够有效地控制并发写操作,确保数据的一致性,尽管可能会对系统性能产生一定影响。
  4. 引入中间件以维护数据一致性
    • 可以使用阿里开源的 Canal 工具,通过监听数据库的 binlog 日志来及时更新缓存。这种方法可以在数据发生变化时自动更新缓存,从而减少缓存和数据库之间的一致性问题。然而,引入 Canal 或类似的中间件会增加系统的复杂度,因此需要权衡其带来的额外复杂性和对系统一致性的增强。使用这种方案时,应考虑中间件的维护、配置和潜在的性能影响,以确保系统的稳定性和可靠性。

image

总结

上述解决方案主要针对的是读多写少的场景,通过引入缓存来提升性能。然而,对于写多读多且不能容忍缓存数据不一致的情况,我们需要重新考虑缓存的使用策略。以下是针对这种情况的优化建议:

  1. 避免使用缓存
    • 在写操作频繁且读操作也较多的场景中,如果业务对数据一致性的要求非常高,使用缓存可能并不是最佳选择。此时,直接操作数据库可以避免缓存数据与数据库数据之间的不一致问题,因为所有的数据操作都直接在数据库中进行,从而确保数据的一致性和准确性。
  2. 数据库作为主存储
    • 如果数据库面临着高负载的压力,但仍然需要处理大量的读写操作,可以考虑将缓存作为数据的主存储,而将数据库作为备份。具体做法是:所有的读写操作都先写入缓存,缓存会异步地将数据同步到数据库中。这样,缓存可以在高并发读写操作中提供快速的响应,而数据库则用于长期的数据存储和备份。这种策略可以提高系统的读写性能,同时保持数据库的数据完整性。
  3. 缓存适用的数据类型
    • 将缓存用于对实时性和一致性要求不是特别高的数据。例如,商品分类信息、系统配置等数据可以缓存,因为这些数据变化频率较低,对一致性要求不是很高。缓存能显著提升访问速度,但在数据不一致的情况下,对业务影响较小。避免将缓存用于对一致性要求极高的关键业务数据,以减少因缓存引发的复杂性和风险。
  4. 避免过度设计
    • 在设计缓存系统时,要避免为了保证绝对一致性而进行过度设计和复杂控制。这种过度设计不仅会增加系统的复杂性,还可能影响系统的性能。应当根据实际业务需求,合理选择缓存策略,平衡性能和一致性要求,避免不必要的复杂性和资源浪费。

总之,在选择是否使用缓存及其设计时,需要根据业务场景和数据一致性要求进行权衡。缓存应主要用于提升读操作性能,而对于写多读多且对一致性要求高的场景,可能需要依赖数据库本身的能力或采用其他策略来处理数据的一致性问题。


我是努力的小雨,一名 Java 服务端码农,潜心研究着 AI 技术的奥秘。我热爱技术交流与分享,对开源社区充满热情。同时也是一位掘金优秀作者、腾讯云创作之星、阿里云专家博主、华为云云享专家。

标签:缓存,数据库,一致性,线程,key,深入分析,数据,双写
From: https://www.cnblogs.com/guoxiaoyu/p/18363049

相关文章

  • oracle数据库缓存区高速缓存区
    文章目录Oracle数据库高速缓存一、数据库高速缓存的基本概念二、数据库高速缓存的工作原理三、数据库高速缓存的配置四、数据库高速缓存的块管理五、多种数据块大小的高速缓存配置Oracle数据库高速缓存一、数据库高速缓存的基本概念1、数据库高速缓存(DatabaseB......
  • 清除 Electron 中的缓存数据
    Electron将其缓存存储在以下文件夹中:window:C:\Users<user>\AppData\Roaming<yourAppName>\CacheLinux:/home//.config//Cache操作系统:/Users//Library/ApplicationSupport//Cacheletcache=app.getPath('cache');//获取缓存的路径constcachePath=path.......
  • springboot 缓存-cacheManager
    日常项目中如果对接口响应时间要求较高通常需要结合redis对接口进行缓存处理。1.pom文件中引入redisjar<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><......
  • 易优CMS插件html.php页面缓存配置
    插件html.php页面缓存配置作用于插件前台,指定需要缓存的页面,这只在运营模式下才有效。参数规则:mca:weapp_控制器_操作名filename:生成在/data/runtime目录下的指定路径,建议参考以下p:当前url控制器的操作方法传入的全部参数变量名cache:页面缓存有效时间,单位是秒案例:假设......
  • windows10清理缓存命令,windows10清理缓存命令是什么
    在Windows10系统中,清除缓存可以通过多种方法实现,但严格来说,并没有一个单一的“指令”可以一键清除所有类型的缓存。不过,我可以为你介绍几种常用的方法来清除不同类型的缓存。一、使用磁盘清理工具磁盘清理工具是Windows10内置的一个非常实用的工具,可以帮助用户删除不需要的文......
  • 在 C# 中处理 HttpClient 实例时,使用单例模式和 IHttpClientFactory,DNS缓存问题
    在C#中处理HttpClient实例时,使用单例模式和IHttpClientFactory都有各自的优缺点,尤其是在高并发情况下。以下是它们的对比及性能考虑:1.单例模式使用HttpClient优势:减少资源消耗:HttpClient是设计为复用的类,创建一个单例可以避免频繁创建和销毁HttpClient实例,从而减......
  • 解决webview缓存问题
    解决webview缓存问题前言项目是通过web-view内嵌在小程序里的vue单页应用.然而前几天发现明明发布了代码,在小程序入口进去看到的还是旧页面,尝试了各种操作:手动退出小程序,再次进入;删除发现-小程序,重新进入;关闭微信,杀掉进程,重新进入修改Nginx关于Cache-Control的配置;用debugx......
  • fpga图像处理实战-图像缓存(FIFO)
    FPGA实现`timescale1ns/1ps////Company://Engineer:////CreateDate:2024/08/1813:47:22//DesignName://ModuleName:line_buffer//ProjectName://TargetDevices://ToolVersions://Description:////Dependencies:////Revision......
  • 高性能内存对象缓存Memcached原理与部署
    案例概述Memcached概述一套开源的高性能分布式内存对象缓存系统所有的数据都存储在内存中支持任意存储类型的数据提高网站的访问速度数据存储方式与数据过期方式数据存储方式:SlabAllocation按组分配内存,每次分配一个Slab,相当于一个大小为1M的页,然后再1M的空间里根......
  • ansible 开启facts_cache缓存
    目录1.常见的缓存插件及其存储位置2.如何查询缓存的变量总结通过facts_cache缓存的变量通常存储在由Ansible配置文件中指定的位置,具体位置取决于你使用的缓存插件。下面是几个常见的缓存插件和它们的存储方式,以及如何查询这些缓存变量。1.常见的缓存插件及其存储位置j......