如何保证db与缓存的数据一致性

探讨了系统开发中缓存双写导致的数据不一致性问题,分析了在多线程环境下,更新缓存可能导致的旧数据覆盖新数据现象。提出了通过消息队列如RabbitMQ、RocketMQ来保证更新有序性的解决方案,以实现数据的最终一致性。

为了提升性能,缓存在系统开发中具有普遍的应用。常见的模式是先查询/更新db后再去更新缓存,那么如何保证db和缓存的数据一致性的问题是实际开发中经常遇到的问题。这种场景下容易造成数据不一致的问题主要是缓存双写。

先查询或者更新db,然后再更新缓存,这里可能出现的一种不一致的情况是db更新或查询成功,但是缓存更新失败了,这个不一致的问题可以通过重试更新缓存的方式来解决,保证最终一致性。但是这个方案只适用于只有一个更新缓存的线程的场景。

如果有多个线程同时更新缓存呢,也就是双写,比如查询线程A查询到数据后要更新缓存,此时另一个更新线程B在完成db的更新后也要更新缓存,这个时候,线程A可能在查询时db还没有完成更新,查到的是老数据,如果最终线程A后于B更新缓存,则缓存中最终的数据则是旧的数据,就会出现db和缓存数据不一致的情况。解决这个问题的关键是如何保证多个线程更新有序性,化并行为串行是解决这个问题的基本思路。在这里我们可以考虑引入队列的方式来解决,通过队列来保证线程更新的有序性。mq的选型比较建议使用消息队列,比如rabbitmq,rocketmq等,消费端保证只有一个线程顺序消费消息即可。如果要增大吞吐量,可以使用多个队列,每个队列对应一个消费者。使用消息队列可以充分利用其特性,比如消息的持久化,消息消费失败后的重试等,可以更好的保证数据的最终一致性。

电商系统中保证数据库缓存数据一致性可从多个方面着手,以下是一些常见的解决方案: ### 最终一致性策略 多数场景可接受短暂不一致,可通过重试、定时任务(如每天全量同步)修复数据不一致问题。同时,设置合理的 TTL(生存时间),自动淘汰旧数据,还需监控缓存命中率、数据库 Redis 的数据差异,及时发现不一致并处理[^2]。 ### 常见一致性方案 1. **先更新 DB,再删除缓存(Cache - Aside)**:这是最常用的方案,能实现最终一致,性能高且复杂度低。不过在并发、异常、网络延迟下,可能出现时序错乱,例如线程 A 更新数据库成功后,删除缓存失败(网络超时),此时线程 B 查询缓存会命中旧数据,导致数据库已更新,缓存仍是旧值的不一致情况[^3]。 2. **延迟删(Delayed Double Delete)**:删除两次缓存,能防止并发问题,一致性较好,性能复杂度处于中等水平[^3]。 3. **先删缓存,再更新 DB(Write - Behind)**:此方案风险高,不推荐使用,一致性差,性能高但复杂度低[^3]。 4. **基于 Binlog 的异步更新(如 Canal)**:异步监听数据库变更,实现最终一致,性能复杂度都较高[^3]。 5. **分布式事务(如 Seata)**:能实现强一致,但性能差,复杂度高[^3]。 ### 针对不同并发情况的处理 对于并发几率很小的数据(如个人维度的订单数据、用户数据等),很少会发生缓存一致,可以给缓存数据加上过期时间,每隔一段时间触发读的主动更新。就算并发很高,如果业务上能容忍短时间的缓存数据不一致(如商品名称,商品分类菜单等),给缓存加上过期时间依然可以解决大部分业务对于缓存的要求[^4]。 ### 推荐方案 推荐采用 Cache - Aside + 主动失效的方案,这是生产级常用方案[^3]。 以下是一个简单的 Java 示例代码,展示先更新 DB,再删除缓存的基本实现: ```java import java.util.HashMap; import java.util.Map; // 模拟数据库 class Database { private Map<String, String> data = new HashMap<>(); public void updateData(String key, String value) { data.put(key, value); } public String getData(String key) { return data.get(key); } } // 模拟缓存 class Cache { private Map<String, String> cacheData = new HashMap<>(); public void deleteCache(String key) { cacheData.remove(key); } public String getCache(String key) { return cacheData.get(key); } public void setCache(String key, String value) { cacheData.put(key, value); } } public class CacheDatabaseConsistency { private Database database = new Database(); private Cache cache = new Cache(); public void updateDataAndDeleteCache(String key, String value) { // 先更新数据库 database.updateData(key, value); // 再删除缓存 cache.deleteCache(key); } public String getData(String key) { String cacheValue = cache.getCache(key); if (cacheValue != null) { return cacheValue; } // 缓存未命中,从数据库获取 String dbValue = database.getData(key); if (dbValue != null) { // 将数据存入缓存 cache.setCache(key, dbValue); } return dbValue; } public static void main(String[] args) { CacheDatabaseConsistency consistency = new CacheDatabaseConsistency(); consistency.updateDataAndDeleteCache("product1", "New Product Name"); String data = consistency.getData("product1"); System.out.println(data); } } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值