微服务多级缓存架构设计
1 需求背景系统读多写少场景大量热点字典、基础业务信息请求全部打到 RedisRedis CPU / 带宽压力高。 引入本地内存缓存缩短访问链路同时解决多实例本地缓存脏数据问题。非目标不用于强一致性业务库存、余额这类数据禁止使用 L1 本地缓存不替代数据库只做查询加速2 总体架构2.1 层级定义L1Caffeine JVM 堆内存缓存纳秒级读取单机隔离弱一致性TTL 兜底热点只读数据L2Redis 分布式缓存毫秒级多实例共享L3MySQL源数据2.2 读写流程读流程查询 L1 本地缓存命中直接返回L1 未命中查询 Redis命中则回填 L1 并返回Redis 未命中查询数据库DB 结果回填 RedisL1返回结果DB 不存在数据写入空标记到 Redis短 TTL防止缓存穿透写流程Cache Aside更新数据库删除 Redis 对应 keyRedis 发布失效广播消息所有应用实例收到消息删除本机 L1 缓存原则更新 DB删缓存不更新缓存。2.3 一致性策略一致性模型最终一致性允许短暂脏数据保障手段失效广播 L1 短 TTL 兜底消息丢失时脏数据最多存活 L1 TTL 时长强一致业务禁用 L1 本地缓存只使用 Redis3 缓存数据分类与准入规范表格数据类型是否启用 L1示例静态基础字典✅地区、枚举、配置低频变更热点业务数据✅商品基础信息高频变更业务数据❌订单状态、库存冷数据❌历史归档数据4 缓存策略设计4.1 TTL 设计L1 Caffeine写入过期推荐 2~5minRedis基础 TTL 随机偏移防止雪崩推荐 30min 0~300s 随机空值缓存短 TTL30s4.2 缓存问题防护方案缓存穿透DB 不存在数据写入空标记超大 key 集合场景增加布隆过滤器缓存击穿热点 key 采用后台异步刷新或使用 Redis 分布式锁单请求加载 DB缓存雪崩TTL 随机Redis 集群服务熔断降级本地缓存容量上限防 OOM4.3 本地缓存失效方案方案Redis Pub/Sub 广播失效消息缺点PubSub 消息不可靠消息丢失会产生脏数据必须搭配 L1 短 TTL 兜底高可靠场景替换为 RocketMQ 消息做失效通知5 组件设计5.1 MultiCacheManager 核心组件能力多级 get、缓存失效 evict、缓存统计埋点、本地缓存开关降级不直接复用 Spring Cache 注解自定义封装原生 Spring Cache 不支持多级联动失效广播。5.2 缓存 Key 规范格式cache:{业务域}:{key}示例cache:user:15.3 序列化方案使用 FastJSON2 序列化对象禁止 JDK 序列化。6 监控指标设计表格指标说明告警阈值caffeine.hitRateL1 缓存命中率低于 70% 告警redis.hitRateRedis 缓存命中率低于 60% 告警cache.pass.through.count缓存穿透次数突增告警cache.invalid.msg.fail失效消息消费失败0 告警redis.cpuRedis CPU 使用率80% 告警7 实施与上线流程业务梳理识别热点数据划分是否允许 L1 缓存代码开发接入 MultiCacheManager埋点监控单元测试验证命中、失效、穿透场景压测高并发验证击穿、雪崩灰度上线小流量开启配置开关可一键关闭 L1全量上线持续监控指标8 风险与应急预案风险清单本地缓存脏数据PubSub 消息丢失预案配置中心一键关闭 L1 本地缓存降级为纯 RedisCaffeine 内存溢出预案强制配置 maximumSize限制缓存条目上限Redis 故障预案熔断直接访问 DB限流保护大 key禁止大对象存入缓存大 key 拆分9 使用约束高频更新数据禁止开启 L1 本地缓存强一致性业务禁用 L1L1 缓存必须设置 maxSize防止 OOM不使用缓存更新策略统一删缓存L1 TTL 不能设置过长10 后续优化方向热点 key 异步刷新布隆过滤器接入拦截不存在 keyMQ 替换 PubSub实现可靠失效通知接入 Prometheus 指标埋点缓存预热组件1. SpringBoot 完整可运行多级缓存 Demo技术栈SpringBoot 2.7.x/ 3.xCaffeineSpring Data RedisRedisson可选分布式锁Redis PubSub 做本地缓存失效广播 能力L1Caffeine 本地缓存L2Redis 分布式缓存Cache Aside空值防穿透缓存埋点开关控制本地缓存Redis PubSub 失效广播可一键关闭 L1 降级。 注意SpringBoot3 需要使用jakarta包下面代码默认 SpringBoot2按需替换。Maven pom.xml?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.15/version relativePath/ /parent groupIdcom.cache/groupId artifactIdmulti-level-cache-demo/artifactId version0.0.1-SNAPSHOT/version namemulti-level-cache-demo/name dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency dependency groupIdcom.github.benmanes.caffeine/groupId artifactIdcaffeine/artifactId version3.1.8/version /dependency dependency groupIdcom.alibaba/groupId artifactIdfastjson2/artifactId version2.0.32/version /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies /projectapplication.ymlspring: redis: host: 127.0.0.1 port: 6379 password: database: 0 multi-cache: # 本地缓存开关生产故障时一键关闭L1 enable-local-cache: true # Caffeine 最大缓存条目 caffeine-max-size: 10000 # Caffeine 写入后过期时间单位秒 caffeine-expire-write: 120 # Redis基础过期时间单位秒 redis-base-ttl: 1800 # Redis随机偏移防止雪崩单位秒 redis-random-ttl: 300 # 空值缓存TTL防穿透 null-cache-ttl: 30 cache-topic: multi:cache:invalidate:topic配置类MultiCacheConfig.javapackage com.cache.config; import com.alibaba.fastjson2.JSON; import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.LoadingCache; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.listener.ChannelTopic; import org.springframework.data.redis.listener.RedisMessageListenerContainer; import org.springframework.data.redis.listener.adapter.MessageListenerAdapter; import org.springframework.data.redis.serializer.StringRedisSerializer; import java.util.concurrent.TimeUnit; Slf4j Configuration public class MultiCacheConfig { Value(${multi-cache.enable-local-cache}) private boolean enableLocalCache; Value(${multi-cache.caffeine-max-size}) private long caffeineMaxSize; Value(${multi-cache.caffeine-expire-write}) private long caffeineExpireWrite; Value(${cache-topic}) private String cacheInvalidTopic; /** * Caffeine L1本地缓存 */ Bean public com.github.benmanes.caffeine.cache.CaffeineObject, Object caffeineBuilder() { return Caffeine.newBuilder() .maximumSize(caffeineMaxSize) .expireAfterWrite(caffeineExpireWrite, TimeUnit.SECONDS) .recordStats(); } Bean public RedisTemplateString, String redisTemplate(RedisConnectionFactory factory) { RedisTemplateString, String redisTemplate new RedisTemplate(); redisTemplate.setConnectionFactory(factory); StringRedisSerializer stringSerializer new StringRedisSerializer(); redisTemplate.setKeySerializer(stringSerializer); redisTemplate.setValueSerializer(stringSerializer); redisTemplate.afterPropertiesSet(); return redisTemplate; } /** * Redis Pub/Sub 消息容器监听缓存失效消息 */ Bean public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory factory, MessageListenerAdapter listenerAdapter) { RedisMessageListenerContainer container new RedisMessageListenerContainer(); container.setConnectionFactory(factory); container.addMessageListener(listenerAdapter, new ChannelTopic(cacheInvalidTopic)); return container; } Bean public MessageListenerAdapter messageListenerAdapter(CacheInvalidMessageHandler messageHandler) { return new MessageListenerAdapter(messageHandler, onMessage); } }失效消息处理器CacheInvalidMessageHandler.javapackage com.cache.config; import com.github.benmanes.caffeine.cache.Cache; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; Slf4j Component public class CacheInvalidMessageHandler { Autowired private CacheObject, Object caffeineCache; /** * 收到广播消息清除本机L1缓存 */ public void onMessage(String key) { log.info(收到缓存失效广播清除本地缓存key:{}, key); caffeineCache.invalidate(key); } }多级缓存核心工具类MultiCacheManager.javapackage com.cache.manager; import com.alibaba.fastjson2.JSON; import com.github.benmanes.caffeine.cache.Cache; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import java.util.Random; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; Slf4j Component public class MultiCacheManager { private static final String NULL_FLAG NULL_CACHE; Autowired private CacheObject, Object caffeineCache; Autowired private RedisTemplateString, String redisTemplate; Value(${multi-cache.enable-local-cache}) private boolean enableLocalCache; Value(${multi-cache.redis-base-ttl}) private long redisBaseTtl; Value(${multi-cache.redis-random-ttl}) private long redisRandomTtl; Value(${multi-cache.null-cache-ttl}) private long nullCacheTtl; Value(${cache-topic}) private String cacheInvalidTopic; private final Random random new Random(); /** * 多级缓存查询 * param key 缓存key * param clazz 返回对象类型 * param dbLoader 数据库查询逻辑 * return 业务对象 */ public T T get(String key, ClassT clazz, SupplierT dbLoader) { //1.优先读取L1本地缓存 if (enableLocalCache) { Object localVal caffeineCache.getIfPresent(key); if (localVal ! null) { log.debug(L1缓存命中 key{}, key); return (T) localVal; } } //2.读取Redis L2缓存 String redisStr redisTemplate.opsForValue().get(key); if (redisStr ! null) { if (NULL_FLAG.equals(redisStr)) { log.debug(Redis命中空缓存 key{}, key); return null; } T val JSON.parseObject(redisStr, clazz); //回填本地缓存 if (enableLocalCache) { caffeineCache.put(key, val); } log.debug(Redis缓存命中 key{}, key); return val; } log.debug(缓存全部未命中查询DB key{}, key); //3.查询数据库 T dbData dbLoader.get(); if (dbData null) { //写入空值防穿透 redisTemplate.opsForValue().set(key, NULL_FLAG, nullCacheTtl, TimeUnit.SECONDS); return null; } //4.回填Redis增加随机TTL防止雪崩 long realTtl redisBaseTtl random.nextLong(redisRandomTtl); redisTemplate.opsForValue().set(key, JSON.toJSONString(dbData), realTtl, TimeUnit.SECONDS); //回填本地缓存 if (enableLocalCache) { caffeineCache.put(key, dbData); } return dbData; } /** * 更新/删除数据后失效缓存 * param key 缓存key */ public void evict(String key) { // 删除Redis缓存 redisTemplate.delete(key); // 删除本机本地缓存 if (enableLocalCache) { caffeineCache.invalidate(key); } //广播失效消息通知其他应用实例清本地缓存 redisTemplate.convertAndSend(cacheInvalidTopic, key); log.info(缓存失效完成广播消息 key{}, key); } /** * 获取缓存统计信息用于监控 */ public String getCacheStats() { if (!enableLocalCache) { return local cache disabled; } return caffeineCache.stats().toString(); } }测试实体User.javapackage com.cache.entity; import lombok.Data; Data public class User { private Long id; private String username; private String phone; }Service 测试层UserService.javapackage com.cache.service; import com.cache.entity.User; import com.cache.manager.MultiCacheManager; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; Service RequiredArgsConstructor public class UserService { private final MultiCacheManager multiCacheManager; //模拟DB public User getById(Long id) { String cacheKey cache:user: id; return multiCacheManager.get(cacheKey, User.class, () - { //模拟数据库查询逻辑 if (id 1L) { User user new User(); user.setId(1L); user.setUsername(test); user.setPhone(13800138000); return user; } return null; }); } public void updateUser(Long id) { //1.更新DB省略 //2.失效缓存 String cacheKey cache:user: id; multiCacheManager.evict(cacheKey); } }Controller 测试入口CacheTestController.javapackage com.cache.controller; import com.cache.entity.User; import com.cache.service.UserService; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; RestController RequiredArgsConstructor public class CacheTestController { private final UserService userService; GetMapping(/user/get) public User getUser(RequestParam Long id) { return userService.getById(id); } PostMapping(/user/update) public String updateUser(RequestParam Long id) { userService.updateUser(id); return ok; } GetMapping(/cache/stats) public String stats() { return userService.getCacheStats(); } }Demo 说明多实例启动时更新接口触发evictRedis 发布消息所有实例收到消息清除本机 Caffeineenable-local-cache配置项可以关闭 L1降级为纯 Redis 缓存空值缓存防止穿透Redis TTL 随机防雪崩没有内置分布式锁热点 key 击穿场景可自行在 get 方法增加 Redisson 锁

相关新闻

安全日志分析实战:从撞库、Webshell到横向移动的攻击链还原方法

安全日志分析实战:从撞库、Webshell到横向移动的攻击链还原方法

做安全运营这些年,我翻过的日志如果打印出来,大概能堆满一整面墙。网络攻击日志分析这件事,听起来很高大上,实际干起来往往是从一堆看似无关的字符里,把攻击者的行动轨迹一点点抠出来。你盯着几十万行访问记录&#xf…

2026/10/11 16:38:47 阅读更多 →
从SEO到GEO:AI时代企业为什么需要建立品牌知识资产?

从SEO到GEO:AI时代企业为什么需要建立品牌知识资产?

随着生成式AI快速进入企业营销体系,传统的搜索流量逻辑正在出现新的变化。 世界广告主联合会(WFA)最新调研显示,96%的受访大型品牌已经在使用生成式AI或智能体AI。 对于企业数字化团队而言,一个值得关注的问题是&#…

2026/10/11 16:38:47 阅读更多 →
beautify-github-readme 内容架构教程:价值→证明→机制,5 步重排 README 阅读顺序

beautify-github-readme 内容架构教程:价值→证明→机制,5 步重排 README 阅读顺序

【免费下载链接】beautify-github-readme 整理并设计仓库 README,让项目价值、真实案例、安装方式与使用边界更容易理解。 项目地址: https://gitcode.com/gh_mirrors/be/beautify-github-readme 点击查看 免费下载 很多开源仓库不缺信息,缺…

2026/10/11 16:38:46 阅读更多 →

最新新闻

铁路窗口售票系统需求分析:从业务边界到异常流的完整拆解

铁路窗口售票系统需求分析:从业务边界到异常流的完整拆解

简介:中国铁路窗口售票系统需求分析文档,面向软件开发人员、软件工程专业学生、需求分析学习者以及铁路售票系统设计初学者,可作为课程报告、毕业设计或实际项目需求阶段的参考蓝本。文档围绕系统总体目标、功能要求、体系架构、业务需求、票…

2026/10/11 17:23:15 阅读更多 →
铁路窗口售票系统需求分析:从票额、席位到日终结账的规则拆解

铁路窗口售票系统需求分析:从票额、席位到日终结账的规则拆解

简介:中国铁路窗口售票系统需求分析文档,是一份面向软件工程学生、系统分析师及铁路售票系统开发人员的完整需求说明书,适用于课程设计、毕业设计或实际项目前期的需求梳理。文档依据V3.0.0版本,从系统总体目标和功能要求入手&…

2026/10/11 17:23:15 阅读更多 →
搜云社工库源码实战:从部署PHP检索服务到索引调优与避坑指南

搜云社工库源码实战:从部署PHP检索服务到索引调优与避坑指南

简介:搜云社工库源码是一套基于网站的社会工程信息管理与查询程序,属于社工库概念的一种具体实现,主要面向网络安全学习者、渗透测试入门者以及PHP开发者,帮助理解敏感信息系统的搭建方式与基础安全防护。资源包共含28个文件&…

2026/10/11 17:23:15 阅读更多 →
计算机视觉入门到落地:任务选型、基线方案与工程避坑指南

计算机视觉入门到落地:任务选型、基线方案与工程避坑指南

简介:计算机视觉技术(CV)简要介绍是一份面向入门学习者、算法工程师及科研人员的PDF文档,系统讲解CV的核心概念、完整处理流程与主流任务类型。文档从图像获取、前期处理、特征提取到图像分析与解释逐层展开,梳理了传统…

2026/10/11 17:23:15 阅读更多 →
DGActivityIndicatorView实战:网络请求Loading遮罩与全局加载动画封装

DGActivityIndicatorView实战:网络请求Loading遮罩与全局加载动画封装

【免费下载链接】DGActivityIndicatorView DGActivityIndicatorView is a great way to make loading spinners in your application look nicer. It contains 32 different indicator view styles. 项目地址: https://gitcode.com/gh_mirrors/dg/DGActivityIndicat…

2026/10/11 17:23:15 阅读更多 →
县城外卖平台怎么设置商家抽成比例:从费率拆分到系统后台配置

县城外卖平台怎么设置商家抽成比例:从费率拆分到系统后台配置

县城外卖平台设置商家抽成比例,建议把“抽成”拆成平台技术服务费与履约配送费两部分分别设置:技术服务费按配送方式与品类分档,配送费按距离和时段单独计价,再在系统后台用费率模板、计费基数、保底扣费和账单分项落地&#xff0…

2026/10/11 17:22:15 阅读更多 →

日新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:53 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:54 阅读更多 →