3.MySQL面试题之Redis 和 Mysql 如何保证数据一致性?

Redis 和 MySQL 数据一致性是分布式系统中的一个常见挑战。保证数据一致性通常涉及几种策略,我会详细解释这些策略并提供相应的代码示例。

  1. 先更新数据库,再更新缓存

这种方法先更新 MySQL,然后更新或删除 Redis 缓存。

java 复制代码
@Transactional
public void updateUser(User user) {
    // 1. 更新MySQL
    userMapper.updateUser(user);
    
    // 2. 更新Redis缓存
    // 方式1:更新缓存
    redisTemplate.opsForValue().set("user:" + user.getId(), user);
    
    // 方式2:删除缓存(推荐)
    redisTemplate.delete("user:" + user.getId());
}

优点:

  • 简单直接
  • 保证数据库有最新数据

缺点:

  • 如果更新缓存失败,会导致数据不一致
  1. 先删除缓存,再更新数据库

这种方法先删除 Redis 缓存,然后更新 MySQL。

java 复制代码
@Transactional
public void updateUser(User user) {
    // 1. 删除Redis缓存
    redisTemplate.delete("user:" + user.getId());
    
    // 2. 更新MySQL
    userMapper.updateUser(user);
}

优点:

  • 避免缓存更新失败导致的不一致

缺点:

  • 在高并发情况下可能出现数据不一致
  1. 延迟双删策略

这种方法在更新数据库前后都删除缓存,并在第二次删除时增加短暂延迟。

java 复制代码
@Transactional
public void updateUser(User user) {
    // 1. 删除Redis缓存
    redisTemplate.delete("user:" + user.getId());
    
    // 2. 更新MySQL
    userMapper.updateUser(user);
    
    // 3. 延迟一段时间后再次删除缓存
    CompletableFuture.runAsync(() -> {
        try {
            Thread.sleep(500); // 延迟500毫秒
            redisTemplate.delete("user:" + user.getId());
        } catch (InterruptedException e) {
            // 处理异常
        }
    });
}

优点:

  • 能够处理高并发场景下的数据一致性问题

缺点:

  • 实现较为复杂
  • 增加了系统延迟
  1. 使用消息队列

使用消息队列来保证数据一致性,先更新数据库,然后发送消息到队列,由消费者来更新缓存。

java 复制代码
@Transactional
public void updateUser(User user) {
    // 1. 更新MySQL
    userMapper.updateUser(user);
    
    // 2. 发送消息到消息队列
    kafkaTemplate.send("user-update-topic", JSON.toJSONString(user));
}

// 在消费者服务中
@KafkaListener(topics = "user-update-topic")
public void consumeUserUpdate(String message) {
    User user = JSON.parseObject(message, User.class);
    // 更新Redis缓存
    redisTemplate.opsForValue().set("user:" + user.getId(), user);
}

优点:

  • 解耦了数据库操作和缓存操作
  • 可以处理高并发场景

缺点:

  • 增加了系统复杂度
  • 可能引入短暂的数据不一致
  1. 使用 Canal 进行 MySQL binlog 同步

使用 Canal 监听 MySQL 的 binlog,然后更新 Redis 缓存。

java 复制代码
@Component
public class CanalClient {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @PostConstruct
    public void init() {
        CanalConnector connector = CanalConnectors.newSingleConnector(
            new InetSocketAddress("127.0.0.1", 11111), 
            "example", "", "");
        
        try {
            connector.connect();
            connector.subscribe(".*\\..*");
            
            while (true) {
                Message message = connector.getWithoutAck(100);
                long batchId = message.getId();
                List<CanalEntry.Entry> entries = message.getEntries();
                
                if (batchId != -1 && entries.size() > 0) {
                    for (CanalEntry.Entry entry : entries) {
                        if (entry.getEntryType() == CanalEntry.EntryType.ROWDATA) {
                            CanalEntry.RowChange rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
                            
                            if (rowChange.getEventType() == CanalEntry.EventType.UPDATE) {
                                for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
                                    // 处理更新操作,更新Redis缓存
                                    updateRedisCache(rowData);
                                }
                            }
                        }
                    }
                }
                connector.ack(batchId);
            }
        } finally {
            connector.disconnect();
        }
    }

    private void updateRedisCache(CanalEntry.RowData rowData) {
        // 根据rowData更新Redis缓存
        // 这里需要根据具体的数据结构来实现
    }
}

优点:

  • 实时性高
  • 对应用层代码无侵入

缺点:

  • 配置和维护相对复杂
  • 依赖 MySQL binlog 配置

总结:

  1. 选择哪种方案取决于具体的业务需求、系统架构和性能要求。
  2. 对于读多写少的场景,可以考虑使用"先更新数据库,再删除缓存"的策略。
  3. 对于高并发场景,可以考虑使用延迟双删或消息队列的方案。
  4. 对于实时性要求高的场景,可以考虑使用 Canal 进行 binlog 同步。
  5. 无论选择哪种方案,都需要考虑异常处理和重试机制,以提高系统的可靠性。

在实际应用中,可

相关推荐
数字扫地僧13 分钟前
WebLogic 版本升级的注意事项与流程
数据库
lwprain23 分钟前
常用docker应用部署,wordpress、mysql、tomcat、nginx、redis
mysql·docker·tomcat
Viktor_Ye30 分钟前
高效集成易快报与金蝶应付单的方案
java·前端·数据库
努力算法的小明1 小时前
SQL 复杂查询
数据库·sql
斗-匕1 小时前
MySQL 三大日志详解
数据库·mysql·oracle
代码中の快捷键1 小时前
MySQL数据库存储引擎
数据库·mysql
只因在人海中多看了你一眼1 小时前
数据库体系
数据库
尘浮生2 小时前
Java项目实战II基于微信小程序的电影院买票选座系统(开发文档+数据库+源码)
java·开发语言·数据库·微信小程序·小程序·maven·intellij-idea
六月闻君2 小时前
MySQL 报错:1137 - Can‘t reopen table
数据库·mysql
SelectDB技术团队2 小时前
兼顾高性能与低成本,浅析 Apache Doris 异步物化视图原理及典型场景
大数据·数据库·数据仓库·数据分析·doris