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

在实际应用中,可

相关推荐
倔强的石头_12 小时前
kingbase备份与恢复实战(二)—— sys_dump库级逻辑备份与恢复(Windows详细步骤)
数据库
jiayou642 天前
KingbaseES 实战:深度解析数据库对象访问权限管理
数据库
于眠牧北2 天前
MySQL的锁类型,表锁,行锁,MVCC中所使用的临键锁
mysql
李广坤3 天前
MySQL 大表字段变更实践(改名 + 改类型 + 改长度)
数据库
Turnip12024 天前
深度解析:为什么简单的数据库"写操作"会在 MySQL 中卡住?
后端·mysql
爱可生开源社区4 天前
2026 年,优秀的 DBA 需要具备哪些素质?
数据库·人工智能·dba
随逸1774 天前
《从零搭建NestJS项目》
数据库·typescript
加号34 天前
windows系统下mysql多源数据库同步部署
数据库·windows·mysql
シ風箏4 天前
MySQL【部署 04】Docker部署 MySQL8.0.32 版本(网盘镜像及启动命令分享)
数据库·mysql·docker
李慕婉学姐4 天前
Springboot智慧社区系统设计与开发6n99s526(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·spring boot·后端