当前位置: 首页 > article >正文

Redis和MySQL的实时数据同步方案

针对 Redis 和 MySQL 的实时数据同步,需根据业务场景选择不同的技术方案,核心目标是保障数据一致性、降低延迟、提升系统可靠性。以下是几种典型方案及其适用场景:


方案一:基于 MySQL Binlog 的异步同步

原理
  1. 监听 MySQL 的 Binlog 日志(记录所有数据变更事件)。
  2. 解析 Binlog(如通过 Canal、Debezium 等工具)。
  3. 将变更事件推送到 Redis(更新或删除对应缓存)。
架构流程
MySQL → Binlog → 中间件(Canal) → 消息队列(Kafka) → Redis消费者 → 更新Redis
优势
  • 对应用透明:无需修改业务代码。
  • 高可靠性:Binlog 是 MySQL 原生日志,确保数据变更不丢失。
  • 支持复杂变更:可捕获 UPDATE、DELETE、INSERT 等操作。
缺点
  • 延迟稍高:异步处理通常有毫秒到秒级延迟。
  • 部署复杂度高:需维护中间件和消息队列。
适用场景
  • 缓存一致性要求高:如电商商品详情、库存同步。
  • 写操作频繁:需实时更新缓存的场景。
实现示例
// Canal客户端监听Binlog(伪代码)
CanalConnector connector = CanalConnectors.newClusterConnector("127.0.0.1:2181", "example", "", "");
connector.connect();
connector.subscribe(".*\\..*");
while (true) {
    Message message = connector.getWithoutAck(100);
    for (CanalEntry.Entry entry : message.getEntries()) {
        if (entry.getEntryType() == CanalEntry.EntryType.ROWDATA) {
            CanalEntry.RowChange rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
            for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
                String tableName = entry.getHeader().getTableName();
                String key = "cache:" + tableName + ":" + rowData.getBeforeColumns(0).getValue();
                if (rowChange.getEventType() == CanalEntry.EventType.DELETE) {
                    redis.del(key);  // 删除缓存
                } else {
                    redis.set(key, serialize(rowData.getAfterColumnsList()));  // 更新缓存
                }
            }
        }
    }
}

方案二:应用层双写(强一致性)

原理

在业务代码中同步写入 MySQL 和 Redis,通过事务保证原子性。

架构流程
应用层 → 开启事务 → 写入MySQL → 写入Redis → 提交事务
优势
  • 强一致性:Redis 与 MySQL 数据完全同步。
  • 低延迟:同步写入,无异步链路。
缺点
  • 性能损耗:双写增加请求耗时。
  • 事务复杂性:需处理分布式事务(如 Redis 写入失败时的回滚)。
适用场景
  • 写操作低频但强一致:如账户余额、支付状态。
  • 简单系统:无复杂中间件维护能力的小型项目。
实现示例
// 伪代码:双写(需结合事务管理器)
@Transactional
public void updateUser(User user) {
    // 先写MySQL
    userMapper.update(user);
    // 再写Redis
    String key = "user:" + user.getId();
    redisTemplate.opsForValue().set(key, user);
    // 若Redis写入失败,MySQL事务会回滚
}

方案三:消息队列解耦(最终一致性)

原理
  1. 应用层写入 MySQL 后,发送消息到消息队列(如 Kafka、RocketMQ)。
  2. 消费者异步消费消息,更新 Redis。
架构流程
应用层 → 写MySQL → 发消息到MQ → 消费者 → 更新Redis
优势
  • 解耦:业务层与缓存更新逻辑分离。
  • 削峰填谷:MQ 缓冲流量,避免 Redis 压力过大。
缺点
  • 最终一致性:存在秒级延迟。
  • 消息堆积风险:需监控消费者处理速度。
适用场景
  • 高并发写入:如社交平台动态发布后的缓存预热。
  • 允许短暂不一致:如文章阅读量统计。
实现示例
// 伪代码:写入MySQL后发消息
public void createOrder(Order order) {
    orderMapper.insert(order);  // 写MySQL
    kafkaTemplate.send("order-topic", order.getId());  // 发送消息
}

// 消费者更新Redis
@KafkaListener(topics = "order-topic")
public void listen(String orderId) {
    Order order = orderMapper.selectById(orderId);  // 查最新数据
    redisTemplate.opsForValue().set("order:" + orderId, order);
}

方案四:延迟双删(应对缓存穿透)

原理
  1. 先删除 Redis 缓存
  2. 更新 MySQL
  3. 延迟一定时间后再次删除 Redis(处理并发脏读)。
架构流程
应用层 → 删除Redis → 写MySQL → 延迟 → 再删Redis
优势
  • 简单有效:减少缓存与数据库不一致时间窗口。
缺点
  • 无法完全避免不一致:极端并发下仍有脏数据可能。
  • 依赖延迟时间设置:需根据业务调整。
适用场景
  • 读多写少:如配置信息更新。
  • 对一致性要求不苛刻:允许短暂旧数据存在。
实现示例
// 伪代码:延迟双删
public void updateConfig(Config config) {
    String key = "config:" + config.getId();
    redisTemplate.delete(key);        // 第一次删除
    configMapper.update(config);     // 写MySQL
    // 延迟1秒后再删一次(异步执行)
    scheduledExecutor.schedule(() -> redisTemplate.delete(key), 1, TimeUnit.SECONDS);
}

方案五:结合数据库触发器(慎用)

原理
  1. 在 MySQL 中设置触发器(Trigger),监听数据变更。
  2. 触发器调用外部程序(如 UDF 或 HTTP API)更新 Redis。
缺点
  • 性能影响大:触发器会增加数据库负载。
  • 维护困难:需编写存储过程或外部接口。
适用场景
  • 遗留系统改造:无法修改应用代码时的临时方案。

方案选型对比

方案一致性延迟性能影响复杂度适用场景
Binlog 异步同步最终一致毫秒~秒级高频写、要求最终一致
应用层双写强一致毫秒级低频写、强一致(如支付)
消息队列解耦最终一致秒级高并发写、允许延迟
延迟双删最终一致秒级读多写少、简单系统
数据库触发器强一致毫秒级遗留系统改造(不推荐)

关键注意事项

  1. 缓存失效策略
    • 主动更新(推荐):通过同步机制更新。
    • 被动淘汰:设置合理 TTL,防止长期脏数据。
  2. 数据回环问题
    • 避免同步操作再次触发写 MySQL(如 Binlog 同步到 Redis 后,不应反向写回 MySQL)。
  3. 监控与告警
    • 监控 Redis 与 MySQL 的数据差异(如定时对比关键表)。
    • 监控同步延迟(如 Kafka 消费 Lag)。
  4. 降级方案
    • 同步失败时,可降级为直接读 MySQL,并记录日志告警。

总结

  • 优先推荐 Binlog + 消息队列方案:适合多数高并发场景,平衡一致性与性能。
  • 强一致性场景选择双写+事务:需结合分布式事务框架(如 Seata)。
  • 简单场景用延迟双删:快速实现,但需容忍短暂不一致。
  • 避免过度设计:根据实际业务需求选择最简单有效的方案。

http://www.kler.cn/a/567920.html

相关文章:

  • 后台数据报表导出数据量过大问题
  • 嵌入式轻量化SDK设计,EasyRTC音视频通话SDK压缩至500K-800K
  • 云和恩墨亮相PolarDB开发者大会,与阿里云深化数据库服务合作
  • 视频推拉流EasyDSS点播平台云端录像播放异常问题的排查与解决
  • 9. 【.NET 8 实战--孢子记账--从单体到微服务--转向微服务】--微服务基础工具与技术--Ocelot 网关--请求聚合
  • leetcode 73. 矩阵置零
  • 【数据结构】从位图到布隆过滤器
  • 新时代,科技助力运动旅游开启新潮流
  • Android 数据库查询对比(APN案例)
  • 【Django REF】Django REF 常用知识点汇总
  • Qt 自带颜色属性
  • LVS+Keepalived 高可用集群搭建
  • 智能图像处理平台:图片管理
  • MySQL DBA技能指南
  • 低代码与开发框架的一些整合[3]
  • 从“0”开始入门PCB之(1)--PCB的结构与制作工艺
  • Halcon算子 binary_threshold、auto_threshold、dyn_threshold
  • 理解文件系统
  • Suspense 使用方法
  • 机器学习决策树