点击关注公众号,Java干货及时送达👇

引言

大家好!今天我们来聊聊一个在分布式系统中非常常见但又十分棘手的问题——Redis与MySQL之间的双写一致性

相信很多朋友在项目中都遇到过类似的困扰,缓存是用Redis,数据库是用MySQL,但如何确保两者之间的数据一致性呢?

这也是大厂面试题的常客,我的朋友圈就有xd发文说连续面阿里和美团都被问到过,真是秦始皇学会影分身,双赢啊,哈哈!

别着急,接下来我会尽量简洁地为大家解析这个问题,并提供几个实战方案。

分布式系统一致性概览

在开始之前,我们先来了解一下分布式系统中的一致性概念。

  • 强一致性:所有节点的数据必须实时同步,保证任何时候读取到的数据都是最新的。
  • 弱一致性:系统允许数据暂时不一致,但最终会达到一致状态。
  • 最终一致性:数据更新后,经过一段时间,系统会逐步达到一致状态。这个时间不固定,但在业务允许的范围内。

双写一致性挑战

说到双写一致性,首先要明确一下什么是双写一致性。

简单来说,就是当数据同时存在于缓存(Redis)和数据库(MySQL)时,如何确保这两者之间的数据是一致的。

典型场景分析

  1. 写数据库后忘记更新缓存:这种情况最常见,当我们更新数据库后,缓存没有同步更新,导致读取到旧的数据。
  2. 删除缓存后数据库更新失败:在某些操作中,我们可能会先删除缓存,再更新数据库,但如果数据库更新失败,就会导致缓存和数据库的数据不一致。

策略一:Cache Aside Pattern

1、策略描述

Cache Aside Pattern是最常见的一种缓存使用模式,它的核心思想是以数据库为主,缓存为辅。

2、工作流程

  • 读取操作:先从缓存中读取数据,如果缓存命中则返回结果;如果缓存未命中,则从数据库中读取数据,并将数据写入缓存。
  • 更新操作:先更新数据库,再删除缓存中的旧数据。

3、示例代码

java

public class CacheAsidePattern {
    private RedisService redis;
    private DatabaseService database;

    // 读取操作
    public String getData(String key) {
        // 从缓存中获取数据 
        String value = redis.get(key);
        if (value == null) {
            // 缓存未命中,从数据库获取数据 
            value = database.get(key);
            if (value != null) {
                // 将数据写入缓存 
                redis.set(key, value);
            }
        }
        return value;
    }

    // 更新操作 
    public void updateData(String key, String value) {
        // 更新数据库 
        database.update(key, value);
        // 删除缓存中的旧数据 
        redis.delete(key);
    }
}
 

4、优缺点分析

优点

  • 简单易懂,易于实现。
  • 读性能高,因为大部分读操作都会命中缓存。

缺点

  • 存在短暂的不一致情况,更新数据库后缓存可能还没删除。
  • 删除缓存后,如果数据库更新失败,会导致数据不一致。

策略二:读写穿透模式

1、Read-Through

当缓存未命中时,自动从数据库加载数据,并写入缓存。

2、Write-Through

当缓存更新时,同步将数据写入数据库。

3、示例代码

java

public class ReadWriteThroughPattern {
    private RedisService redis;
    private DatabaseService database;

    // Read-Through 
    public String readThrough(String key) {
        // 从缓存中获取数据 
        String value = redis.get(key);
        if (value == null) {
            // 缓存未命中,从数据库获取数据 
            value = database.get(key);
            if (value != null) {
                // 将数据写入缓存 
                redis.set(key, value);
            }
        }
        return value;
    }

    // Write-Through 
    public void writeThrough(String key, String value) {
        // 将数据写入缓存 
        redis.set(key, value);
        // 同步将数据写入数据库
        database.update(key, value);
    }
}
 

4、优缺点分析

优点

  • 保证了数据的强一致性,缓存和数据库的数据始终同步。
  • 读写操作都由缓存处理,数据库压力较小。

缺点

  • 写操作的延迟较高,因为每次写入缓存时都需要同步写入数据库。
  • 实现复杂度较高,需要额外的缓存同步机制。

策略三:异步缓存写入(Write Behind)

1、策略描述

缓存更新后,异步批量写入数据库。这种策略适用于可以容忍一定数据不一致的高性能场景。

2、示例代码

java

public class WriteBehindPattern {
    private RedisService redis;
    private DatabaseService database;
    private UpdateQueue updateQueue;

    // 异步缓存写入 
    public void writeBehind(String key, String value) {
        // 将数据写入缓存 
        redis.set(key, value); // 异步将数据写入数据库
        asyncDatabaseUpdate(key, value);
    }

    private void asyncDatabaseUpdate(String key, String value) {
        // 异步操作,将更新请求放入队列 
        updateQueue.add(new UpdateTask(key, value));
    }
}
 

3、优缺点分析

优点

  • 写操作的性能非常高,因为只需更新缓存,数据库更新是异步进行的。
  • 适用于对写操作性能要求较高的场景。

缺点

  • 存在数据不一致的风险,缓存更新后数据库可能还未更新。
  • 实现复杂度较高,需要处理异步操作中的异常和重试。

实战解析

方案一:延时双删策略

1、策略详解

延时双删策略的核心思想是:在更新数据库后,先删除一次缓存,然后延迟一段时间再删除一次缓存,减少数据不一致的风险。

2、实施要点

关键是如何确定延迟时间,这个时间需要根据系统的具体情况来调整,以平衡一致性和性能。

3、示例代码

java

public class DelayedDoubleDeletePattern {
    private RedisService redis;
    private DatabaseService database;
    private ScheduledExecutorService scheduledExecutorService;
    private long delay = 500;

    // 延迟时间,单位:毫秒 // 更新操作 
    public void updateDataWithDelay(String key, String value) {
        // 更新数据库 
        database.update(key, value);
        // 删除缓存中的旧数据 
        redis.delete(key);
        // 延迟一段时间再删除缓存 
        scheduledExecutorService.schedule(() -> redis.delete(key), delay, TimeUnit.MILLISECONDS);
    }
}
 
4、优缺点分析

优点

  • 简化了缓存和数据库的一致性问题。
  • 避免了缓存和数据库的同步更新,提高了系统性能。

缺点

  • 需要精确控制延迟时间,否则可能导致缓存和数据库不一致。
  • 实现相对复杂,需要额外的定时任务管理。

实战方案二:删除缓存重试机制

1、机制介绍

删除缓存时,如果失败,可以设置重试机制,以确保缓存最终被删除。

2、实现方式

通过使用Spring的@Retryable注解,可以简化重试逻辑。

3、示例代码

java

import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;

@Servicepublic
class CacheService {
    private RedisService redis;

    @Retryable(value = Exception.class, maxAttempts = 5, backoff = @Backoff(delay = 2000))
    public void deleteCache(String key) {
        // 删除缓存中的数据 
        redis.delete(key);
    }
}
 
4、优缺点分析

优点

  • 确保缓存最终被删除,降低数据不一致的风险。
  • 使用Spring的重试机制,简化实现逻辑。

缺点

  • 需要处理重试的多次失败情况,可能导致系统负载增加。
  • 适用于缓存删除失败率较低的场景。

实战方案三:监听binlog异步删除缓存

1、核心思路

利用数据库的binlog变更来异步更新缓存,通过消息队列和异步服务解耦缓存更新操作。

2、技术实现

通过订阅binlog,将变更记录放入消息队列,然后由异步服务处理缓存更新。

3、示例代码

java

public class BinlogListenerPattern {
    private RedisService redis;
    private MessageQueue messageQueue;

    // 订阅binlog 
    public void onBinlogChange(BinlogEntry entry) { // 将变更记录放入消息队列 
        messageQueue.send(new CacheUpdateMessage(entry.getKey()));
    } // 异步服务处理缓存更新 

    public void processCacheUpdate(CacheUpdateMessage message) { // 删除缓存中的数据 
        redis.delete(message.getKey());
    }
}
 
4、优缺点分析

优点

  • 利用数据库的变更日志,保证缓存和数据库的一致性。
  • 异步处理提高了系统性能,降低了实时更新的压力。

缺点

  • 实现复杂度较高,需要处理消息队列和异步服务。
  • 存在延迟更新的情况,可能导致短时间内的数据不一致。

总结与最佳实践

在实际项目中,我们需要根据具体的业务场景来选择最合适的一致性策略。

同时,在高并发场景下,可以结合分布式锁和消息队列来确保数据一致性。

异步处理中的异常处理和重试策略也非常重要,能够有效提高系统的稳定性和可靠性。


希望这篇文章能帮助大家更好地理解Redis与MySQL双写一致性的问题,并在实际项目中应用这些解决方案。

如果有任何问题或建议,欢迎在评论区讨论。

谢谢大家的阅读!加油!加油!加油!

关注公众号,回复关键词【面试】,或选择菜单 -学习资源 - 面试题,都可获取精心收集的Java面试题。

包含:【Java面试八股文10万字总结】【Java进阶架构核心手册】

后续会不断收集更多资源及干货放在里面

你的点赞+在看,是我持续分享干货的动力哦!

END

在看

往期推荐

未来迈向Java高级工程师绕不过的技能:JMeter压测

SpringBoot线程池参数搜一堆资料还是不会配,我花一天测试换你此生明白。

一文搞定CompletableFuture并行处理,成倍缩短查询时间。

我曾经的两个Java老师一个找不到工作了一个被迫转行了

从线上环境摘取了四个代码优化记录分享给大家

SpringBoot整合WebSocket+Stomp搭建群聊项目

一个包装过简历的新同事写完微信支付引起事故后果断离职了

我为什么极力推荐XXL-JOB作为中小厂的分布式任务调度平台

Springboot+Redisson自定义注解一次解决重复提交问题(含源码)

记一次最近生产环境项目中发生的两个事故及处理方法

SpringBoot+Minio搭建不再爆肝秃头的分布式文件服务器

最近整理了一批这些年来学习和工作手记的PDF文档目录,覆盖了Java核心技术、SpringBoot、SpringCloud微服务、数据库、ElasticSearch、MQ消息队列等主流技术,相信对渴望成长的人会有所帮助。

获取方式:点在看,关注公众号并回复资源领取,更多内容陆续奉上。

点个在看,证明你还爱我

PS:因公众号平台更改了推送规则,如果不想错过内容,记得读完点一下在看,加个星标,这样每次新文章推送才会第一时间出现在你的订阅列表里。“在看”支持一下呀!😀

本篇文章来源于微信公众号: Java分享客栈



微信扫描下方的二维码阅读本文

此作者没有提供个人介绍
最后更新于 2024-08-02