MySQL与Redis数据同步实践与优化

一、数据不一致的典型场景

  1. 写入顺序不一致

    当业务逻辑需要同时更新数据库和缓存时,若出现"先删缓存后更新DB"或"先更新DB后删缓存"操作失败,会导致缓存与数据库数据版本不一致。

  2. 并发读写冲突

    高并发场景下可能出现:

  • 线程A更新数据库
  • 线程B读取旧缓存
  • 线程A删除/更新缓存 此时缓存中残留旧数据
  1. 异步同步延迟

    基于消息队列或binlog解析的异步同步方案,在网络波动或系统负载时可能出现同步延迟

  2. 缓存穿透/雪崩

    恶意请求或突发流量导致:

  • 缓存穿透:大量请求直接访问数据库
  • 缓存雪崩:大量缓存同时过期
java 复制代码
// 典型双写示例(问题代码)
public void updateProduct(Product product) {
    // 先更新数据库
    productDao.update(product); 
    // 再更新缓存
    redisTemplate.opsForValue().set(product.getId(), product);
}

二、主流解决方案与Java实现

1. 延迟双删策略

java 复制代码
public void updateProductWithDelay(Product product) {
    // 第一次删除缓存
    redisTemplate.delete(product.getId());  
    // 更新数据库
    productDao.update(product);  
    // 延迟二次删除(使用异步线程)
    CompletableFuture.runAsync(() -> {
        try {
            Thread.sleep(1000); // 根据业务设置合理延迟
            redisTemplate.delete(product.getId());
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    });
}

2. 基于Binlog的同步(Canal实现)

架构流程

MySQL -> Canal Server -> Kafka -> 数据消费服务 -> Redis

java 复制代码
// Canal客户端示例
@KafkaListener(topics = "canal_topic")
public void handleMessage(String message) {
    CanalMessage canalMsg = JSON.parseObject(message, CanalMessage.class);
    if ("UPDATE".equals(canalMsg.getType())) {
        canalMsg.getData().forEach(item -> {
            String key = "product:" + item.get("id");
            redisTemplate.opsForValue().set(key, item);
        });
    }
}

3. 分布式锁保障一致性

java 复制代码
public Product getProduct(String id) {
    String cacheKey = "product:" + id;
    Product product = redisTemplate.opsForValue().get(cacheKey);
    
    if (product == null) {
        RLock lock = redissonClient.getLock("lock:" + cacheKey);
        try {
            lock.lock();
            // 双重检查锁
            product = redisTemplate.opsForValue().get(cacheKey);
            if (product == null) {
                product = productDao.findById(id);
                redisTemplate.opsForValue().set(cacheKey, product, 30, TimeUnit.MINUTES);
            }
        } finally {
            lock.unlock();
        }
    }
    return product;
}

三、优化实践方案

1. 异步批处理优化

java 复制代码
// 使用Guava的批量收集器
@Bean
public BatchProcessor<DataChangeEvent> batchProcessor() {
    return BatchProcessor.create(
        events -> {
            List<RedisCommand> commands = events.stream()
                .map(e -> new RedisCommand("SET", e.getKey(), e.getValue()))
                .collect(Collectors.toList());
            redisTemplate.executePipelined(commands);
        },
        500, // 批量大小
        100, // 缓冲时间(ms)
        4    // 并发线程数
    );
}

2. 熔断降级策略

java 复制代码
// 使用Resilience4j实现
CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("redis");
RateLimiter rateLimiter = RateLimiter.of(100, Duration.ofSeconds(1));

public Product getProductSafe(String id) {
    return Decorators.ofSupplier(() -> getProduct(id))
        .withCircuitBreaker(circuitBreaker)
        .withRateLimiter(rateLimiter)
        .withFallback(Exception.class, e -> productDao.findById(id))
        .get();
}

3. 数据版本控制

java 复制代码
// 添加版本号字段
@Data
public class Product {
    private Long id;
    private String name;
    private Long version; // 数据版本
}

// 更新时校验版本
public boolean updateWithVersion(Product product) {
    String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                       "redis.call('set', KEYS[1], ARGV[2]) return 1 else return 0 end";
    
    Long result = redisTemplate.execute(
        new DefaultRedisScript<>(luaScript, Long.class),
        Collections.singletonList("product:" + product.getId()),
        String.valueOf(product.getVersion() - 1),
        product.toString()
    );
    return result == 1;
}

四、监控指标体系建设

  1. 关键监控指标:

    • 同步延迟时间(Redis_Last_Update - DB_Update_Time)
    • 缓存命中率(keyspace_hits / (keyspace_hits + keyspace_misses))
    • 同步失败率(failed_sync_count / total_sync_count)
  2. 日志追踪方案:

java 复制代码
// 使用MDC实现请求链路追踪
public Product getProduct(String id) {
    MDC.put("traceId", UUID.randomUUID().toString());
    try {
        // 业务逻辑
    } finally {
        MDC.clear();
    }
}

五、总结与展望

通过组合使用延迟双删、binlog同步、分布式锁等方案,可构建不同一致性级别的同步系统。建议根据业务场景选择:

  • 强一致性场景:分布式锁 + 同步双写
  • 最终一致性场景:Canal + 消息队列
  • 高性能场景:多级缓存 + 异步批处理
相关推荐
养生技术人10 分钟前
Oracle OCP认证考试题目详解082系列第53题
数据库·sql·oracle·database·开闭原则·ocp
银帅1833503097134 分钟前
2018年下半年试题四:论NoSQL数据库技术及其应用
数据库·架构·nosql
liu****1 小时前
基于websocket的多用户网页五子棋(九)
服务器·网络·数据库·c++·websocket·网络协议·个人开发
liu****1 小时前
基于websocket的多用户网页五子棋(八)
服务器·前端·javascript·数据库·c++·websocket·个人开发
Elastic 中国社区官方博客1 小时前
Elasticsearch:使用推理端点及语义搜索演示
大数据·数据库·人工智能·elasticsearch·搜索引擎·ai·全文检索
武子康2 小时前
Java-143 深入浅出 MongoDB NoSQL:MongoDB、Redis、HBase、Neo4j应用场景与对比
java·数据库·redis·mongodb·性能优化·nosql·hbase
豆沙沙包?3 小时前
2025年--Lc171--H175 .组合两个表(SQL)
数据库·sql
麋鹿原3 小时前
Android Room 数据库之数据库升级
数据库·kotlin
源码集结号4 小时前
一套智慧工地云平台源码,支持监管端、项目管理端,Java+Spring Cloud +UniApp +MySql技术开发
java·mysql·spring cloud·uni-app·源码·智慧工地·成品系统
GanGuaGua5 小时前
MySQL:表的约束
数据库·mysql