一、为什么会有一致性问题在高并发系统中数据库通常作为最终数据源Redis 作为缓存层提升查询性能。典型读流程是先查缓存缓存未命中再查数据库并把结果写入缓存。问题出现在写请求上。数据库和缓存是两个独立组件更新操作无法天然保证原子性。只要其中一步失败或者并发读写交错就可能出现缓存和数据库数据不一致。二、两种更新策略1.先删除缓存再更新数据库当线程 1 先删除缓存后如果在更新数据库的时候阻塞住了这个时候线程 2 先去读缓存缓存不存在然后去数据库中读取数据读到数据后重建缓存这个时候缓存中仍然是旧的数据。所以这种方案一般不推荐。(解决方案看后续的延迟双删模块2. 先更新数据库再删除缓存这种方案看起来更加合理一些。但是可能会出现短暂的数据不一致导致读到旧的缓存。不过这种情况一般都是可以接受的可以保证最终的一致性。三、延迟双删解决先删缓存的问题使用延迟双删缓解解决先删缓存存在的问题。删除缓存 - 更新数据库 - 延迟一段时间 - 再次删除缓存第二次删除是为了解决重建的缓存是旧的数据。为什么要进行延迟删除延迟删除的目的是为了防止出现线程 2 还没来得及进行旧数据缓存的重建导致删除是空的缓存旧的数据缓存还是被重建了这种情况。但是延迟的时间一般是不太好控制的。延迟双删保证了最终一致性解决了重建的是旧数据缓存请求拿到的是旧的数据的问题。四、MQ 补偿解决删除缓存失败无论是先删除缓存还是先更新数据库都有可能出现删除缓存失败的情况为了解决删除缓存失败可以使用 MQ 做补偿。更新数据库删除缓存如果删除失败发送 MQ 消息消费者异步重试删除缓存多次失败后记录日志/告警示例伪代码:public void updateData(Data data) { updateDatabase(data); try { redis.delete(cacheKey); } catch (Exception e) { mq.send(new CacheDeleteMessage(cacheKey)); } }消费者public void consume(CacheDeleteMessage message) { redis.delete(message.getKey()); }六、Canal进一步解耦业务和缓存删除MQ 补偿虽然能解决失败重试但业务代码里还是要写删除缓存或发送消息的逻辑。为了进一步解耦可以使用 Canal 监听 MySQL binlog。业务只更新 MySQLMySQL 产生 binlogCanal 监听 binlog解析变更表和主键发送缓存删除消息消费者删除 Redis优点业务代码不需要关心缓存删除多个服务修改同一张表也能统一监听缓存失效逻辑集中处理更适合复杂系统总结缓存一致性的关键不是追求绝对强一致而是明确数据库是最终事实来源缓存是可丢弃的加速层通过删除缓存、失败重试、TTL 和 binlog 监听等方式保证最终一致。