2026/2/19 9:55:18
网站建设
项目流程
前端做兼职网站,惠来做网站,通号建设集团有限公司,合肥找工作最新招聘信息高并发缓存一致性实战#xff1a;Cache Aside、Write Through、Read Through 选型与落地
一、为什么高并发下缓存一致性这么难
核心矛盾在于#xff1a;数据库与缓存的两次写不是原子操作#xff0c;而请求执行顺序在并发场景下不可控#xff0c;导致短暂甚至较长时间的数据…高并发缓存一致性实战Cache Aside、Write Through、Read Through 选型与落地一、为什么高并发下缓存一致性这么难核心矛盾在于数据库与缓存的两次写不是原子操作而请求执行顺序在并发场景下不可控导致短暂甚至较长时间的数据不一致。典型时序问题包括先删缓存后写库期间读请求把旧值回填先写库后删缓存期间读请求命中旧缓存多写并发时缓存被旧值覆盖等。即便采用“先更新数据库再删除缓存”在极小窗口内仍可能读到旧值只能追求最终一致性。在读写分离架构下主从同步时延还会放大不一致窗口。为降低风险工程上通常配合TTL 过期兜底、延迟双删、异步失效等手段共同使用。二、三大缓存访问模式对比与选型模式读流程写流程一致性特征适用场景主要代价Cache Aside旁路缓存命中则返回未命中读库并回填缓存建议“先更新数据库再删除缓存”通常最终一致并发读写存在短暂旧值窗口读多写少、计算/聚合类缓存需处理删除失败、并发回填、主从时延Read Through读穿透命中则返回未命中由缓存层自动读库并回填应用视缓存为唯一数据源由缓存层同步写库读路径简化一致性取决于写路径实现希望读逻辑透明、统一缓存策略首次访问延迟可能缓存“一次性”数据Write Through写穿透命中则返回未命中同 Cache Aside应用写缓存缓存同步写库后返回写路径强一致读路径同 Cache Aside写后读频繁、对一致性要求高写延迟增加缓存写放大、可能“污染”说明Read/Write Through 常由缓存组件/代理封装实现对应用透明Write Behind写回异步批量写库虽能提升吞吐但一致性风险更高不在本文三种模式之列。三、高并发下的工程化落地清单写策略与顺序优先采用先更新数据库再删除缓存为兜底设置合理 TTL。在极端并发或“删后回填”风险高的场景增加延迟双删如写库后延迟数百毫秒再删一次降低脏数据驻留时间。删除失败与补偿写库成功后若删除缓存失败采用异步重试本地队列或消息队列确保最终删除或引入事件驱动如订阅MySQL binlog通过Canal/Kafka广播失效将缓存失效与业务解耦。读写分离与强一致读缓存未命中且业务要求强一致时读请求可强制走主库回填避免从库同步时延导致回填旧值代价是主库压力上升需结合熔断/降级策略。热点与并发控制对热点 Key 的缓存重建加分布式锁/互斥锁避免“缓存击穿”并发重建时只允许一个线程回种其他线程等待重试显著降低数据库瞬时压力。多级缓存一致性本地缓存如Caffeine 分布式缓存如Redis组合时失效需广播到所有实例如Redis Pub/Sub并给本地缓存设置较短 TTL与错峰过期避免“多级不一致”和“失效风暴”。四、模式落地代码示例Cache AsideSpring Boot Redis含 TTL 与互斥锁ServicepublicclassProductService{AutowiredprivateProductRepositoryrepo;AutowiredprivateStringRedisTemplateredis;privatestaticfinalDurationTTLDuration.ofMinutes(10);privatestaticfinalDurationLOCK_TTLDuration.ofSeconds(3);publicProductDTOget(Longid){Stringkeyproduct:id;// 1) 命中直接返回ProductDTOv(ProductDTO)redis.opsForValue().get(key);if(v!null)returnv;// 2) 未命中互斥重建StringlockKeylock:key;Booleanlockedredis.opsForValue().setIfAbsent(lockKey,1,LOCK_TTL);if(Boolean.TRUE.equals(locked)){try{vrepo.findById(id);if(v!null)redis.opsForValue().set(key,v,TTL);returnv;}finally{redis.delete(lockKey);}}else{// 未抢到锁短暂等待后重试一次Thread.sleep(50);returnget(id);}}publicvoidupdate(ProductDTOdto){// 1) 先更新数据库repo.save(dto);// 2) 再删除缓存失败可入MQ/定时补偿重试redis.delete(product:dto.getId());}}Read ThroughSpring Cache 抽象缓存层自动读库回填ConfigurationEnableCachingpublicclassCacheCfg{BeanpublicRedisCacheManagercacheManager(RedisConnectionFactorycf){RedisCacheConfigurationcfgRedisCacheConfiguration.defaultCacheConfig().serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(newStringRedisSerializer())).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(newGenericJackson2JsonRedisSerializer())).entryTtl(Duration.ofMinutes(10)).disableCachingNullValues();returnRedisCacheManager.builder(cf).cacheDefaults(cfg).build();}}ServicepublicclassProductReadThroughService{Cacheable(valueproducts,key#id,synctrue)publicProductDTOget(Longid){// 缓存未命中时由 Spring Cache 调用此方法读库并回填returnrepo.findById(id);}} 关注公众号【云技纵横】开始更新redis缓存进阶包含手写缓存注解缓存雪崩等内容哟提示Write Through 通常依赖支持写穿透的缓存组件/代理如部分企业级缓存网关应用将写请求发给缓存由缓存层负责同步写库代码形态与 Read Through 类似但写路径会“穿透”到数据库。