数据库与缓存一致性保障方案全景解析


前言:面试官的灵魂拷问——缓存一致性这道送命题怎么破?

“说说缓存和数据库一致性怎么保证?”

当面试官抛出这道经典问题时,会议室空气突然凝固。你额头渗出细密的汗珠,脑海中闪过以下场景:
1. 初级程序员版本:
“更…更新数据库后删缓存?”
面试官冷笑:“并发请求下确定不会出问题?”
2. 中级程序员版本:
“可以用延迟双删!”
面试官挑眉:“延迟时间怎么定?网络抖动怎么办?”
3. 高级程序员版本:
“建议结合binlog+消息队列…”
面试官突然打断:“如果消息积压了怎么处理?”

本文将为你揭秘:数据库和缓存数据一致性方案


一、双写策略

1.1 标准模板

public void updateProduct(Product product) {
    // 先写数据库
    productDao.update(product);
    // 再删缓存
    cache.delete(product.getId());
}

1.2 面试官の死亡连问

  • Q1:如果删缓存失败了怎么办?
    解决方案:
@Retryable(maxAttempts = 3, backoff = @Backoff(delay = 100))
public void safeDelete(String key) {
    try {
        cache.delete(key);
    } catch (Exception e) {
        // 记录到补偿队列
        repairQueue.add(new RepairTask(key, OperationType.DELETE));
    }
}
  • Q2:两个线程并发更新怎么办?
    解决方案:
public void updateWithVersion(Product product) {
    long version = System.currentTimeMillis(); // 用数据库版本号更佳
    productDao.update(product);
    cache.update(product.getId(), product, version); 
    // 缓存端比较版本号更新
}
  • Q3:这样性能不会炸吗?
    太极推手:
    “适合低频修改场景,比如用户基本信息维护”

二、延迟双删

2.1 标准姿势

public void updateProduct(Product product) {
    // 第一次删除
    cache.delete(product.getId());
    // 更新数据库
    productDao.update(product);
    // 提交延迟任务
    delayQueue.add(new DelayTask(() -> {
        cache.delete(product.getId()); // 二次删除
    }, 1000)); // 延迟1秒
}

2.2 面试官の灵魂暴击

  • Q1:延迟时间怎么确定?
    延时时间很难确定,其计算逻辑源于对数据库操作全链路的系统性观测和分析:

理论延迟时间 = 主从同步耗时 + SQL执行耗时 + 网络传输抖动缓冲
≈ 平均SQL执行时间 × 2 + 200ms

  • Q2:消息丢失怎么办?
// 使用支持持久化的延迟队列
public class PersistentDelayQueue {
    private final RocksDB rocksDB; // 本地持久化
    private final DelayQueue<DelayTask> memoryQueue;
    
    public void addTask(DelayTask task) {
        rocksDB.put(task.getId(), serialize(task));
        memoryQueue.add(task);
    }
}
  • Q3:极端情况下还是不一致怎么办?
    “结合定时任务全量比对,我们系统设置凌晨3点做数据巡检”

三、订阅数据库变更

3.1 Binlog监听大法

// 使用Debezium监听MySQL
@Bean
public DebeziumEngine<ChangeEvent<String, String>> debeziumEngine() {
    Configuration config = Configuration.create()
        .with("connector.class", "io.debezium.connector.mysql.MySqlConnector")
        .with("database.history", "io.debezium.relational.history.FileDatabaseHistory")
        .build();

    return DebeziumEngine.create(Connect.class)
        .using(config)
        .notifying(record -> {
            // 解析变更事件
            processChangeEvent(record);
        }).build();
}

private void processChangeEvent(SourceRecord record) {
    // 这里处理缓存更新逻辑
    cache.update(record.key(), record.value());
}

3.2 面试官の必杀技

  • Q1:消息顺序如何保证?
    “采用分区有序消息队列,同一主键的变更路由到相同分区”
  • Q2:历史数据怎么处理?
public void handleSnapshot() {
    // 全量数据快照处理
    jdbcTemplate.query("SELECT * FROM products", rs -> {
        cache.update(rs.getString("id"), rs.getString("data"));
    });
}
  • Q3:怎么防止雪崩?
    防御性编程:
// 缓存更新限流
RateLimiter limiter = RateLimiter.create(1000); // 每秒1000次
public void safeCacheUpdate(String key, Object value) {
    if (limiter.tryAcquire()) {
        cache.update(key, value);
    } else {
        // 进入降级队列
        downgradeQueue.add(new UpdateTask(key, value));
    }
}
  • Q4:如果消息积压了怎么处理?
    Binlog消息积压本质是生产者-消费者速率失衡问题,应急处理的话首先会立即扩容消费者组,同时启用消息过滤降低处理负载。同时积压数量到阈值时,自动触发降级。

四、分布式锁

4.1 Redisson实现版

public void updateWithLock(Product product) {
    RLock lock = redissonClient.getLock("product_lock:" + product.getId());
    try {
        lock.lock();
        // 1. 查询最新数据
        Product latest = productDao.get(product.getId());
        // 2. 业务校验
        if (latest.getStock() < 0) {
            throw new BusinessException("库存不足");
        }
        // 3. 更新数据库
        productDao.update(product);
        // 4. 删除缓存
        cache.delete(product.getId());
    } finally {
        lock.unlock();
    }
}

4.2 面试官の终极大招

  • Q1:锁失效了怎么办?
    “采用看门狗机制自动续期,设置锁超时时间为业务耗时×3,避免锁长期无效占用”
  • Q2:集群环境下怎么保证?
    “使用RedLock红锁算法,需要半数以上节点加锁成功”
  • Q3:性能瓶颈怎么办?
    调优秘籍:大部分加锁场景我们都可以细化锁粒度
// 细粒度锁优化
public void updateStock(String productId, int delta) {
    // 只锁具体商品ID
    RLock lock = redissonClient.getLock("stock_lock:" + productId);
    // ...
}

方案选型决策树

方案选型决策树


总结

本文系统剖析了保障数据库与缓存一致性的四大核心方案:
双写策略通过事务锁实现强一致性却面临并发覆盖风险,适合金融等高敏感场景;延迟双删以异步二次删除规避旧数据复活,需配合熔断机制应对缓存击穿,常用于电商读多写少业务;订阅变更(CDC) 依托数据库日志实现准实时同步,虽扩展性强但需处理消息积压,适合微服务架构;分布式锁确保原子操作却可能引发性能瓶颈,需结合细粒度锁优化,适用于秒杀等高并发场景。所有方案均需配套三位一体监控(命中率、延迟、不一致告警)、混沌工程验证及动态熔断策略,技术选型本质是在业务容忍度、实现成本与系统复杂度间的精准权衡,没有银弹,唯有对症下药。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值