EdgeRemover:Windows环境下浏览器深度管理工具
前言为什么加了 Redis 还是慢“接口 RT 300 ms → 优化到 30 ms”的常见路径把数据库 IO 砍掉 → 用缓存把网络 IO 砍掉 → 本地缓存把序列化砍掉 → 零拷贝远程 Redis 一次往返
ms 看似不多高并发下CPU 上下文 序列化 网络抖动会放大到
ms而本地缓存命中时只有几十纳秒。
本文用 Spring Boot 3 搭建「三级金字塔」L1 Caffeine本地 → L2 Redis远程 → L3 DB并给出背压、预热、热点 Key、大 Key 打散全套方案无额外依赖复制即运行。
金字塔模型 数据热度分布层级延迟容量命中率目标说明L1 Caffeine50 ns10 MB80%进程内零网络L2 Redis1 ms100 GB15%集群横向扩展L3 MySQL10 msTB5%最终一致性经验单机 QPS 1 w 时L1 每提升 1%CPU 下降 3%。
环境 依赖仅 3 个!-- pom.xml -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.github.ben-manes.caffeine/groupId artifactIdcaffeine/artifactId version
3.
8/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency无需额外组件本地直接 java -jar 启动。
配置让 Caffeine 和 Redis 同时生效spring: cache: type:caffeine # 默认走 L1 caffeine: spec:maximumSize10000,expireAfterWrite60s redis: host:
127.
0.
1 port:6379 timeout:200ms lettuce: pool: max-active:
核心封装三级缓存模板Component Slf4j publicclass CacheTemplateK, V { privatefinal CacheK, V local Caffeine.newBuilder() .maximumSize(10_
.expireAfterWrite(Duration.ofSeconds(
) .recordStats() // 命中率监控 .build(); Autowired private RedisTemplateK, V redisTemplate; /** * 金字塔查询 */ public V get(K key, SupplierV dbFallback) { // L1 本地 V v local.getIfPresent(key); if (v ! null) { log.debug(L1 hit {}, key); return v; } // L2 Redis v redisTemplate.opsForValue().get(key); if (v ! null) { local.put(key, v); // 回填 L1 log.debug(L2 hit {}, key); return v; } // L3 DB v dbFallback.get(); if (v ! null) { set(key, v); // 双写 } return v; } /** * 双写L1 L2 */ public void set(K key, V value) { local.put(key, value); redisTemplate.opsForValue().set(key, value, Duration.ofMinutes(
); } /** * 删除L1 L2 */ public void evict(K key) { local.invalidate(key); redisTemplate.delete(key); } Scheduled(fixedDelay 30_
public void printStats() { log.info(L1 hitRate{}, local.stats().hitRate()); } }
业务使用一行代码搞定缓存RestController RequestMapping(/api/item) RequiredArgsConstructor publicclass ItemController { privatefinal CacheTemplateLong, ItemDTO cache; privatefinal ItemRepository itemRepository; GetMapping(/{id}) public ItemDTO getItem(PathVariable Long id) { return cache.get(id, () - itemRepository.findById(id).orElse(null)); } PostMapping public void create(RequestBody ItemDTO dto) { ItemDTO saved itemRepository.save(dto); cache.set(saved.getId(), saved); } DeleteMapping(/{id}) public void delete(PathVariable Long id) { itemRepository.deleteById(id); cache.evict(id); } }启动后观察日志L1 hit
83 L2 hit
15 DB hit
02接口 RT 从 28 ms → 2 msCPU 下降 35%。
高并发下 4 个常见坑问题现象解决缓存穿透并发查不存在 Key → 压爆 DBget()里空值也缓存 5 秒热点 Key同一 Key 被打 → 单线程打满本地缓存已消化 80% 流量大 Keyvalue 5 MB → 网络打满拆成Hash分片或压缩雪崩60 s 同时失效 → 惊群Caffeine Redis 均加随机 TTL随机 TTL 工具private Duration randomTTL(long baseSec) { long delta ThreadLocalRandom.current().nextLong(0,
; //
min return Duration.ofSeconds(baseSec delta); }
本地预热 背压启动时异步预热热门 Key避免冷缓存瞬间穿透EventListener(ApplicationReadyEvent.class) public void warm() { ListLong hotIds itemRepository.findHotIds(PageRequest.of(0,
); hotIds.parallelStream().forEach(id - cache.set(id, itemRepository.findById(id).orElse(null))); }使用 parallelStream 控制并发度默认 ForkJoinPool.commonPool() 即可。
压测结果环境Mac M2 8G4 并发线程60 s工具wrk2 -R 5000 -d 60s -c 50指标纯 DBL2 RedisL1Caffeine提升平均 RT28 ms
1 ms
9 ms14×P99 RT120 ms18 ms4 ms30×CPU 占用65 %40 %25 %↓ 60%网络出流量180 MB/s12 MB/s
8 MB/s↓ 99%
监控 告警Caffeine 自带统计结合 Micrometer 输出到 PrometheusMeterBinder caffeineMetrics registry - CaffeineMetrics.monitor(registry, local, l1_cache);Grafana 面板关注l1_cache_hit_rate 70%告警l1_cache_eviction_count激增 → 容量不足Redis keyspace_hits / (hitsmisses) 50%→ 大 Key 或穿透
扩展多级组合注解Spring Cache 原生只支持单缓存可自定义 MultiCacheable 注解Target(METHOD) Retention(RUNTIME) public interface MultiCacheable { String[] cacheNames(); // {l1, l2} String key(); }AOP 拦截器按顺序 l1→l2→db 查询业务代码零侵入。
结语本地缓存不是“加一条 Cacheable”那么简单金字塔模型 → 数据热度分层背压 随机 TTL → 抗雪崩预热 监控 → 可观测把这三件事做完接口 10 倍加速是底线。
香香腐竹的官网入口免费阅读-香香腐竹的官网入口免费阅读应用