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 锁