当前位置: 首页 > news >正文

亿级流量缓存架构:多级缓存(本地→分布式→CDN)的一致性挑战

亿级流量缓存架构:多级缓存(本地→分布式→CDN)的一致性挑战

一、引言

缓存是应对高并发的第一道防线,但多级缓存的引入也带来了一个经典难题:同一个数据在本地缓存、分布式缓存和CDN中可能存在三个不同的版本。当数据更新时,如何保证各层缓存的一致性,同时不损耗缓存带来的性能收益,是架构设计的核心挑战。

本文将设计一套Caffeine(本地)→ Redis(分布式)→ CDN(边缘)的三级缓存架构,重点探讨各级间的更新策略和最终一致性方案。

graph TB CLIENT[客户端请求] CDN[CDN边缘节点<br/>TTL: 30s-5min] LB[负载均衡] APP1[应用实例-A] APP2[应用实例-B] LOCAL1[Caffeine本地缓存<br/>实例A<br/>TTL: 5s] LOCAL2[Caffeine本地缓存<br/>实例B<br/>TTL: 5s] REDIS[(Redis集群<br/>分布式缓存<br/>TTL: 30s-1h)] DB[(MySQL主库)] CANAL[Canal监听binlog] MQ[Kafka消息队列] CLIENT --> CDN CDN -->|Cache Miss| LB LB --> APP1 LB --> APP2 APP1 --> LOCAL1 APP2 --> LOCAL2 LOCAL1 -->|Cache Miss| REDIS LOCAL2 -->|Cache Miss| REDIS REDIS -->|Cache Miss| DB DB -->|binlog| CANAL CANAL -->|数据变更事件| MQ MQ -->|缓存失效通知| REDIS MQ -->|广播失效| APP1 MQ -->|广播失效| APP2 style CDN fill:#e74c3c,color:#fff style REDIS fill:#f39c12,color:#fff style LOCAL1 fill:#3498db,color:#fff style LOCAL2 fill:#3498db,color:#fff

二、三级缓存架构设计

2.1 本地缓存层:Caffeine

Caffeine作为JVM内缓存,提供微秒级访问延迟。其W-TinyLFU驱逐策略在高命中率和低内存占用之间取得了优秀的平衡。

/** * 本地缓存管理器 - 基于Caffeine * 关键设计:短TTL + 主动失效通知 */ @Component public class LocalCacheManager { private final Cache<String, CacheEntry<?>> cacheStore; private final RedisMessageListener redisListener; // 本地缓存TTL设为5秒——短到足以让最终一致性收敛 private static final Duration LOCAL_TTL = Duration.ofSeconds(5); // 最大缓存条目数 private static final long MAX_SIZE = 10_000; public LocalCacheManager(RedisMessageListener redisListener) { this.redisListener = redisListener; this.cacheStore = Caffeine.newBuilder() .maximumSize(MAX_SIZE) .expireAfterWrite(LOCAL_TTL) .recordStats() // 开启统计用于监控命中率 .removalListener((key, value, cause) -> { if (cause == RemovalCause.EXPIRED) { metricsCollector.incrementLocalCacheExpired(); } }) .build(); // 订阅Redis的失效通知频道 this.redisListener.subscribe("cache:invalidate", this::onInvalidationEvent); } /** * 读取缓存(带降级策略) */ @SuppressWarnings("unchecked") public <T> Optional<T> get(String key, Class<T> type) { CacheEntry<?> entry = cacheStore.getIfPresent(key); if (entry == null) { return Optional.empty(); } // 逻辑过期检查:即使物理未过期,标记为stale的数据也需要异步刷新 if (entry.isStale()) { asyncRefresh(key); // 仍然返回旧值,避免缓存击穿 } return Optional.ofNullable((T) entry.getData()); } public void put(String key, Object data, Duration ttl) { cacheStore.put(key, new CacheEntry<>(data, ttl)); } /** * 处理来自Redis广播的失效事件 */ private void onInvalidationEvent(String message) { try { InvalidationEvent event = objectMapper.readValue(message, InvalidationEvent.class); cacheStore.invalidate(event.getCacheKey()); log.debug("Local cache invalidated: key={}", event.getCacheKey()); } catch (JsonProcessingException e) { log.error("Failed to parse invalidation event: {}", e.getMessage()); } } private <T> void asyncRefresh(String key) { CompletableFuture.runAsync(() -> { try { // 从Redis重新加载最新数据 T freshData = redisCache.get(key, type); if (freshData != null) { put(key, freshData, LOCAL_TTL); } } catch (Exception e) { log.warn("Async refresh failed for key={}: {}", key, e.getMessage()); } }, refreshExecutor); } }

2.2 分布式缓存层:Redis

Redis层承担着跨实例共享缓存和读流量削峰的角色:

/** * Redis分布式缓存管理层 * 实现Cache Aside模式 + 逻辑过期双检 */ @Component public class RedisCacheManager { private final StringRedisTemplate redis; private final RedissonClient redisson; private static final String LOCK_PREFIX = "lock:cache:rebuild:"; private static final String STALE_PREFIX = "stale:"; /** * 带逻辑过期的缓存查询(防止缓存击穿) */ public <T> T getWithLogicalExpire(String key, Class<T> type, Function<String, T> dbLoader, Duration physicalTtl, Duration logicalTtl) { String cacheKey = key; String staleKey = STALE_PREFIX + key; String lockKey = LOCK_PREFIX + key; // 1. 查询缓存 String cachedJson = redis.opsForValue().get(cacheKey); if (cachedJson != null) { // 检查逻辑过期标记 Boolean isStale = redis.hasKey(staleKey); if (Boolean.TRUE.equals(isStale)) { // 异步重建,当前返回旧值 asyncRebuildCache(key, dbLoader, physicalTtl, logicalTtl, lockKey); } return objectMapper.readValue(cachedJson, type); } // 2. 缓存未命中——加锁重建(防止缓存击穿和雪崩) RLock lock = redisson.getLock(lockKey); try { if (lock.tryLock(3, 10, TimeUnit.SECONDS)) { // 双重检查 cachedJson = redis.opsForValue().get(cacheKey); if (cachedJson != null) { return objectMapper.readValue(cachedJson, type); } // 从数据库加载 T data = dbLoader.apply(key); if (data != null) { String json = objectMapper.writeValueAsString(data); redis.opsForValue().set(cacheKey, json, physicalTtl); // 设置逻辑过期时间(短于物理TTL) redis.opsForValue().set(staleKey, "1", logicalTtl); } return data; } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } catch (Exception e) { log.error("Cache rebuild failed for key={}: {}", key, e.getMessage()); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } // 3. 获取锁失败,返回null(由上层降级处理) return null; } }

三、缓存更新策略对比

flowchart LR subgraph CacheAside CA1[更新DB] --> CA2[删除缓存] CA3[读取] --> CA4{缓存命中?} CA4 -->|Yes| CA5[返回缓存] CA4 -->|No| CA6[查DB] --> CA7[写缓存] --> CA5 end subgraph WriteBehind WB1[更新DB] --> WB2[记录变更日志] WB2 --> WB3[异步批量写缓存] end subgraph WriteThrough WT1[更新DB + 同步写缓存] end
策略适用场景优点缺点
Cache Aside读多写少实现简单,缓存只存热数据首次查询可能穿透,需处理并发
Write Through强一致性需求缓存始终与DB同步写入延迟加倍,写多时性能差
Write Behind写密集、可容忍短期不一致写入性能最高缓存与DB短暂不一致,丢失风险

对于三级缓存架构,推荐Cache Aside + Canal监听binlog的组合策略:应用层更新DB后删除Redis缓存(Cache Aside),Canal异步监听binlog变更,通过MQ广播失效通知到各应用实例的本地缓存。

四、缓存穿透、击穿与雪崩的治理

/** * 缓存异常场景防御组件 */ @Component public class CacheDefenseComponent { private final StringRedisTemplate redis; private final BloomFilter<String> bloomFilter; // 布隆过滤器容量:1000万条 private static final long BLOOM_EXPECTED_INSERTIONS = 10_000_000L; private static final double BLOOM_FPP = 0.001; // 0.1%误判率 public CacheDefenseComponent() { this.bloomFilter = BloomFilter.create( Funnels.stringFunnel(StandardCharsets.UTF_8), BLOOM_EXPECTED_INSERTIONS, BLOOM_FPP ); } /** * 穿透防御:布隆过滤器 + 空值缓存 */ public <T> Optional<T> getWithPenetrationDefense(String key, Class<T> type, Function<String, T> dbLoader) { // 第一道防线:布隆过滤器(判断key是否可能存在) if (!bloomFilter.mightContain(key)) { return Optional.empty(); // 一定不存在,直接返回 } // 第二道防线:空值缓存(防止恶意查询不存在的key穿透到DB) String nullKey = "null:" + key; if (Boolean.TRUE.equals(redis.hasKey(nullKey))) { return Optional.empty(); } T data = dbLoader.apply(key); if (data == null) { // 缓存空值,TTL较短(30秒) redis.opsForValue().set(nullKey, "NULL", Duration.ofSeconds(30)); return Optional.empty(); } bloomFilter.put(key); return Optional.of(data); } /** * 雪崩防御:过期时间加随机偏移 */ public Duration withJitter(Duration baseTtl, double jitterRatio) { long baseMs = baseTtl.toMillis(); long jitterMs = (long) (baseMs * jitterRatio * Math.random()); return Duration.ofMillis(baseMs + jitterMs); } // 使用示例:TTL = 30min + [0, 6min] 随机偏移 // Duration ttl = withJitter(Duration.ofMinutes(30), 0.2); }

五、总结

三级缓存的一致性本质上是CAP理论在缓存层的体现——我们需要在一致性、可用性和性能延迟之间做权衡。Caffeine+Redis+CDN的架构中,Caffeine层5秒TTL加上Redis广播失效提供了一个最终一致性窗口(最长5秒),CDN层30秒到5分钟的TTL适用于对一致性敏感度低但访问量大的静态资源。

生产实践中的两个关键数字:Caffeine本地命中率应维持在85%以上(否则说明TTL过短或缓存容量不足),Redis命中率应维持在95%以上(否则需要检查缓存预热策略)。缓存穿透的布隆过滤器误判率建议控制在0.1%以内,对应的位数组大小约为插入量的10倍。这些数字不是绝对的,但可以作为架构评审时的参考基线。

http://www.jsqmd.com/news/1174855/

相关文章:

  • 2026大模型就业指南:六大热门岗位详解+免费学习资源(建议收藏)
  • ImageSharp:高性能跨平台.NET图像处理解决方案的技术架构与实践指南
  • 0 基础入门React Native鸿蒙跨平台开发:硬盘容量转换器功能实战
  • 2026 武汉高中读英语、日语还是俄语?这所学校三大语种全覆盖 - 湖北找学校
  • PlantUML vs Mermaid:5个维度对比,为你的架构图选择最佳Markdown工具
  • Apache OpenOffice 4.1.16 + JodConverter 2.2.2:3大操作系统Word转PDF服务部署与7个常见问题解决
  • 电商退货率分析实战:3步定位Top 5高退货SKU与根因(附BI看板)
  • Toast、Snackbar 与 Dialog 的微交互时机选择与状态管理
  • 仅限本周开放|Midjourney景深精度提升训练营(含Depth Map预处理工具包+32个行业级景深案例库)
  • 抖音内容动态监控与推送系统技术解析
  • Classy与Interface Builder无缝集成:可视化开发新体验
  • Vivado 2022.2 自定义IP驱动编译:从Makefile错误到生成libxil.a的完整修复流程
  • Hackertab.dev vs 传统资讯平台:为什么开发者更需要这款新标签页扩展
  • 纽扣电池续航优化:NBM5100A与STM32能量管理方案
  • Point Transformer V3:点云序列化注意力机制的技术突破与实践路径
  • MacBook Pro M1 安装 Miniforge3 后 Jupyter Kernel 崩溃的 2 种排查与修复方案
  • ComfyUI-LTXVideo:3步实现专业级AI视频生成,突破4K创作限制
  • 2026年7月最新北京戴尔DELL官方售后服务热线与网点地址查询 - 品牌资讯服务
  • Creative View Pager自定义属性完全手册:XML配置与代码动态设置
  • 别再用规则引擎硬扛了!AI Agent自动订单处理的3代演进史(含2023年Gartner验证的决策树-图神经网络混合架构)
  • 如何通过深度强化学习技术将AI斗地主模型应用于实战游戏?
  • 0 基础入门React Native鸿蒙跨平台开发:温度计算单位换算功能实战
  • GPT-5.6与Gemini 3.5国内应用实战:从API接入到项目集成
  • Canva AI演示文稿模板使用手册,2024Q2最新API接入+自定义字体嵌入+离线缓存三合一方案
  • go2_ros2_sdk完全解析:让Unitree GO2 AIR/PRO/EDU轻松接入ROS2生态
  • 英雄联盟Akari助手:5大智能模块彻底改变你的游戏体验
  • Flame引擎斜45度视角渲染系统架构深度解析:从组件化设计到渲染管线优化
  • 2026年7月最新重庆劳力士官方售后联系电话与客户服务中心网点地址 - 劳力士官方服务中心
  • 如何快速掌握智能翻译代理:面向新手的完整指南
  • 如何快速解决Windows文件锁定问题:File Locksmith终极指南