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. 无论选择哪种方案,都需要考虑异常处理和重试机制,以提高系统的可靠性。

在实际应用中,可

相关推荐
2603_965148115 小时前
如何解析JSON数据?API返回的商品信息处理教程
开发语言·数据库·python·自动化·json·api
ltl5 小时前
RocksDB 经典故障排查:L0、compaction 与 write stall
数据库
xfhuangfu6 小时前
Oracle中建立到CDB和PDB的连接
数据库·oracle·rpc
小小龙学IT6 小时前
DuckDB 深度实战:用 C++ 在进程内跑一个「分析型数据库」
数据库·c++
NineData6 小时前
DTCC 2026 预告|NineData CEO& 创始人叶正盛:面向 AI Agent 的数据库 DevOps 与数据复制实践
数据库·人工智能·数据库开发·devops·ninedata·数据库技术·dtcc
J_bean7 小时前
MySQL 事务是否必须手动开启?
数据库·mysql·数据库事务·自动提交·手动提交
J_bean7 小时前
MySQL InnoDB 如何检测死锁、判定死锁、处理死锁
数据库·mysql·死锁·死锁检测·处理死锁·死锁判定
浪子明X8 小时前
从 MongoDB 文档到关系模型:构建可重跑、可对账的数据迁移流水线
数据库·mongodb·oracle
杜子不疼.8 小时前
国产数据库撑起固井软件自主化:金仓 × 中海油服「海恒 Cemsol」落地解析
数据库
隔窗听雨眠8 小时前
GBase 8s并发控制深度解析:封锁机制、隔离级别与死锁处理全攻略
服务器·数据库·oracle