【Redis分布式缓存实战】第19章 多级缓存架构设计实战
本地缓存(Caffeine/Guava)+ Redis分布式缓存架构搭建
一、架构概述
架构选型
- 一级缓存(本地缓存):Caffeine(替代 Guava,Spring 官方推荐,性能碾压 Guava,内存级读写,速度纳秒级)
- 二级缓存(分布式缓存):Redis(分布式共享缓存,解决本地缓存数据不一致问题)
- 底层数据源:MySQL / 数据库
- 框架:SpringBoot 2.x/3.x
核心流程
- 读流程:请求 → 查 Caffeine 本地缓存 → 命中直接返回 → 未命中查 Redis → 命中回写本地缓存 → 未命中查数据库 → 回写本地 + Redis 缓存
- 写流程:更新数据库 →删除Redis 缓存 → 发布 Redis 消息 → 所有服务节点删除本地 Caffeine 缓存(保证分布式一致性)
- 淘汰策略:本地缓存设置短过期时间+最大容量,Redis 设置过期时间+内存淘汰
核心优势
- 极致性能:本地缓存无网络开销,扛高并发读
- 降低压力:大幅减少 Redis 访问频次,避免 Redis 成为瓶颈
- 高可用:Redis 宕机时,本地缓存可兜底服务
二、项目搭建(完整可运行)
核心 Maven 依赖
<?xml version="1.0" encoding="UTF-8"?> <dependencies> <!-- SpringBoot 缓存启动器 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <!-- Caffeine 本地缓存(替代Guava) --> <dependency> <groupId>com.github.benmanes.caffeine</groupId> <artifactId>caffeine</artifactId> </dependency> <!-- Redis 分布式缓存 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- Redis 连接池 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <!-- Web + 工具类 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>配置文件(application.yml)
统一配置 Redis 连接、Caffeine 本地缓存参数:
yaml
spring: # Redis 配置 redis: host: localhost port: 6379password: database: 0 # 连接池配置 lettuce: pool: max-active: 8 max-idle: 8 min-idle: 0 max-wait: -1ms # 缓存配置 cache: type: caffeine caffeine: # Caffeine 核心配置:初始容量/最大容量/写入后过期 spec: initialCapacity=100,maximumSize=10000,expireAfterWrite=5m # Redis 缓存过期时间(分钟) redis-expire: 10 # Redis 发布订阅主题(用于同步本地缓存) topic: cache: evict: topic核心配置类
(1)RedisTemplate 序列化配置
解决 Redis 存储乱码、序列化冗余问题:
import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); // String 序列化器(key) StringRedisSerializer stringSerializer = new StringRedisSerializer(); template.setKeySerializer(stringSerializer); template.setHashKeySerializer(stringSerializer); // JSON 序列化器(value) Jackson2JsonRedisSerializer<Object> jsonSerializer = new Jackson2JsonRedisSerializer<>(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.activateDefaultTyping(om.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL); jsonSerializer.setObjectMapper(om); template.setValueSerializer(jsonSerializer); template.setHashValueSerializer(jsonSerializer); template.afterPropertiesSet();return template; } }(2)Caffeine 本地缓存实例配置
import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import org.springframework.beans.factory.annotation.Value; import org.springframework.cache.caffeine.CaffeineCacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.concurrent.TimeUnit; @Configuration public class CaffeineConfig { @Value("${spring.cache.caffeine.spec}") private String caffeineSpec; /** * 手动创建Caffeine本地缓存(推荐手动控制,比注解更灵活) */ @Beanpublic Cache<String, Object> caffeineCache() { return Caffeine.newBuilder() // 写入后5分钟过期(与配置文件一致) .expireAfterWrite(5, TimeUnit.MINUTES) // 最大缓存对象数.maximumSize(10000) // 初始容量 .initialCapacity(100).build(); } /** * Spring 注解式缓存管理器(可选) */ @Bean public CaffeineCacheManager cacheManager() { CaffeineCacheManager cacheManager = new CaffeineCacheManager(); cacheManager.setCaffeine(Caffeine.from(caffeineSpec)); return cacheManager; } }(3)Redis 发布订阅配置(解决分布式缓存一致性)
核心问题:分布式环境下,一个服务更新数据后,仅删除自己的本地缓存,其他服务的本地缓存仍为旧数据 → 通过 Redis 发布订阅同步所有节点的本地缓存
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.listener.PatternTopic; import org.springframework.data.redis.listener.RedisMessageListenerContainer; import org.springframework.data.redis.listener.adapter.MessageListenerAdapter; @Configurationpublic class RedisPubSubConfig { @Value("${cache.topic}") private String cacheTopic; /** * 消息监听器:接收缓存删除消息 */ @Bean public MessageListenerAdapter listenerAdapter(CacheMessageListener listener) { return new MessageListenerAdapter(listener, "onMessage"); } /** * Redis 消息容器:绑定主题和监听器 */ @Bean public RedisMessageListenerContainer container(RedisConnectionFactory factory,MessageListenerAdapter adapter) { RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(factory); // 订阅缓存删除主题 container.addMessageListener(adapter, new PatternTopic(cacheTopic)); return container; } }核心工具类
(1)缓存消息监听器(接收删除通知,清空本地缓存)
import com.github.benmanes.caffeine.cache.Cache; import lombok.RequiredArgsConstructor; import org.springframework.data.redis.connection.Message; import org.springframework.data.redis.connection.MessageListener; import org.springframework.stereotype.Component; /** * 接收Redis发布的缓存删除消息,删除本地Caffeine缓存 */ @Component @RequiredArgsConstructor public class CacheMessageListener implements MessageListener { private final Cache<String, Object> caffeineCache; @Override public void onMessage(Message message, byte[] pattern) { // 解析需要删除的缓存key String cacheKey = new String(message.getBody()); // 删除本地缓存 caffeineCache.invalidate(cacheKey); System.out.println("分布式同步删除本地缓存,key:" + cacheKey); } }(2)双缓存核心操作类(读写删)
import com.github.benmanes.caffeine.cache.Cache; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import java.util.concurrent.TimeUnit; import java.util.function.Function; /** * 一级缓存(Caffeine) + 二级缓存(Redis) 核心工具类 */ @Component @RequiredArgsConstructor public class DoubleCache { private final Cache<String, Object> caffeineCache; private final RedisTemplate<String, Object> redisTemplate; @Value("${cache.redis-expire}") private Integer redisExpire; @Value("${cache.topic}") private String cacheTopic; // ====================== 读缓存 ====================== public <T> T get(String key, Function<String, T> dbLoader) { // 1. 查一级本地缓存 (Caffeine)T value = (T) caffeineCache.getIfPresent(key); if (value != null) {System.out.println("本地缓存命中,key:" + key); return value; } // 2. 查二级分布式缓存(Redis) value = (T) redisTemplate.opsForValue().get(key); if (value != null) { System.out.println("Redis缓存命中,key:" + key); // 回写本地缓存 caffeineCache.put(key, value); return value; } // 3. 查数据库 System.out.println("数据库查询,key:" + key); value = dbLoader.apply(key); // 4. 回写两级缓存(解决缓存穿透:空值也缓存) if (value != null) { this.put(key, value); } else { // 空值缓存1分钟,避免穿透 redisTemplate.opsForValue().set(key, null, 1, TimeUnit.MINUTES); } return value; } // ====================== 写缓存 ====================== public void put(String key, Object value) { // 写本地缓存 caffeineCache.put(key, value); // 写Redis缓存(带过期时间,加随机值避免缓存雪崩) long expire = redisExpire + (long) (Math.random() * 5); redisTemplate.opsForValue().set(key, value, expire, TimeUnit.MINUTES); } // ====================== 删除缓存 ====================== public void evict(String key) { // 1. 删除Redis缓存 redisTemplate.delete(key); // 2. 发布Redis消息,通知所有节点删除本地缓存 redisTemplate.convertAndSend(cacheTopic, key); System.out.println("删除Redis缓存并发布同步消息,key:" + key); } }业务层使用示例
import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor public class UserService { private final DoubleCache doubleCache; // 模拟数据库Mapper private final UserMapper userMapper; private static final String CACHE_KEY_PREFIX = "user:info:"; /** * 查询用户(双缓存读取) */ public User getUserById(Long userId) { String key = CACHE_KEY_PREFIX + userId; // Lambda表达式:数据库查询逻辑 return doubleCache.get(key, k -> userMapper.selectById(userId)); } /** * 更新用户(先更DB,再删缓存) */ public void updateUser(User user) { // 1. 优先更新数据库 userMapper.updateById(user); // 2. 删除缓存(核心:删除而非更新,避免并发不一致) String key = CACHE_KEY_PREFIX + user.getId(); doubleCache.evict(key); } /** * 删除用户 */ public void deleteUser(Long userId) { userMapper.deleteById(userId); String key = CACHE_KEY_PREFIX + userId; doubleCache.evict(key); } }三、解决缓存三大问题
缓存穿透
问题:查询不存在的数据,直接击穿缓存访问数据库解决方案:
- 空值缓存:查询数据库无结果时,缓存
null(短过期时间) - 布隆过滤器:高并发场景下,提前过滤不存在的 key
缓存击穿
问题:热点 key 过期,大量请求同时访问数据库解决方案:
- 互斥锁:查询数据库时加锁,保证只有一个请求查库
- 热点 key 永不过期
缓存雪崩
问题:大量 key 同时过期,Redis 压力剧增解决方案:
- 过期时间加随机值
- Redis 集群部署
- 本地缓存兜底
四、关键注意事项
- 缓存更新策略:先更新数据库,再删除缓存(绝对不要更新缓存,避免并发数据不一致)
- 过期时间:本地缓存过期时间 < Redis 过期时间(避免本地缓存脏数据)
- 本地缓存容量:Caffeine 设置
maximumSize,防止 OOM - 适用场景:读多写少、数据一致性要求不极高的业务(商品、配置、用户信息等)
- Guava 兼容:若需使用 Guava,仅需替换 Caffeine 依赖和配置,API 几乎一致
五、总结
- 本架构采用Caffeine(一级)+ Redis(二级)双缓存模式,兼顾性能与分布式一致性
- 通过Redis 发布订阅解决分布式环境下本地缓存同步问题
- 内置缓存穿透 / 击穿 / 雪崩防护,生产环境直接可用
- 核心原则:读缓存,写删缓存,保证数据最终一致性
多级缓存一致性、更新策略、失效同步方案
在分布式系统中,多级缓存(本地缓存 L1 + Redis 分布式缓存 L2 + 数据库 DB)是性能优化的标配架构:
- L1 本地缓存(Caffeine/Guava):应用内缓存,无网络开销、速度极致,但各节点独立、易不一致;
- L2 Redis 缓存:集群共享、大容量、高可用,有轻微网络开销;
- DB:最终数据源,性能最低。
核心痛点:数据更新时,如何保证本地缓存、Redis、数据库三者的强一致性,尤其是多节点本地缓存的同步失效问题。
本文从缓存架构→更新策略→一致性问题→同步方案→生产实践全流程讲解,所有方案均为工业级可用。
一、标准多级缓存读写流程(基础)
读流程(通用,无一致性风险)
请求 → 查L1本地缓存 → 命中直接返回 ↓(未命中) 查L2 Redis缓存 → 命中则回写L1本地缓存,返回 ↓(未命中) 查DB → 回写L2 Redis + L1本地缓存 → 返回写流程(一致性核心)
写操作会直接打破缓存一致性,所有策略都围绕「先操作数据库,再处理缓存」展开。
二、Redis 多级缓存更新策略
生产环境99% 场景使用旁路缓存模式,另外两种仅特殊场景使用:
旁路缓存模式(Cache Aside)✅ 生产首选
核心规则:读多写少,先更新数据库,再删除缓存
- 读:命中缓存直接返回,未命中查库并回写缓存;
- 写:更新 DB → 删除 Redis 缓存 → 同步失效本地缓存。
为什么是「删除缓存」而不是「更新缓存」?
- 避免无效写:数据频繁更新时,更新缓存会造成大量无用 IO;
- 避免脏数据:更新缓存可能导致并发读写的数据错乱。
为什么是「先 DB 后缓存」?
如果先删缓存再更新 DB,会出现致命脏数据:
读请求→缓存未命中→查DB旧数据 → 写请求→更新DB → 读请求→把旧数据写入缓存最终缓存永远是旧数据,必须先更新 DB,再删缓存。
写穿模式(Write Through)
核心:缓存与数据库同步写入,强一致
- 写操作同时更新 DB 和 Redis,业务无感知;
- 缺点:性能差,写请求延迟高,极少使用。
写回模式(Write Back)
核心:先写缓存,异步批量刷库
- 写操作只更新缓存,后台线程异步合并更新数据库;
- 优点:写性能极致;
- 缺点:存在数据丢失风险,一致性极差,仅用于日志等非核心数据。
三、多级缓存一致性核心问题
Redis 是分布式共享缓存,删除一次即可全局生效;本地缓存是应用节点私有,这是一致性的最大痛点:
- 数据更新后,仅删除 Redis,未失效本地缓存 → 其他节点读取旧本地缓存;
- 并发读写场景:短暂的缓存脏数据;
- 缓存失效、消息丢失导致的最终不一致。
四、多级缓存失效同步方案(核心解决方案)
按简单→可靠→无侵入排序,覆盖所有业务场景:
方案 1:短过期时间(兜底方案,最简单)
实现
给本地缓存设置极短 TTL(如 3~5 秒),Redis 设置较长过期时间(如 30 分钟)。
流程
数据更新→更新 DB→删除 Redis→不主动删本地缓存→本地缓存到期自动失效。
优缺点
- ✅ 零开发成本,无需中间件;
- ❌ 一致性差,最多存在几秒脏数据;
- 适用:对一致性要求极低的非核心数据(如统计数据、首页推荐)。
方案 2:消息通知同步(生产主流,实时性高)✅
核心:通过消息队列 / 发布订阅,通知所有应用节点删除本地缓存这是解决本地缓存同步的标准方案,分两种实现:
实现 1:Redis 发布 / 订阅(轻量,无额外中间件)
- 数据更新 → 更新 DB → 删除 Redis 缓存;
- 向 Redis 指定 Channel 发送缓存失效消息(key / 标识);
- 所有应用节点订阅该 Channel,收到消息后删除本地 L1 缓存。
实现 2:专业 MQ(RabbitMQ/Kafka,高可靠)
- 流程同上,仅将 Redis Pub/Sub 替换为 MQ;
- 优势:消息持久化、不丢失、支持重试;
- 适用:核心业务数据。
完整流程
写请求 → 更新DB → 删除Redis → 发送失效消息 → 所有节点消费消息 → 删除本地缓存优缺点
- ✅ 实时性高、一致性好、架构简单;
- ❌ Redis Pub/Sub 无持久化,消息可能丢失(MQ 可解决);
- 适用:绝大多数微服务业务。
方案 3:Canal 监听 Binlog(无侵入,终极方案)✅
核心:不侵入业务代码,通过数据库 Binlog 同步缓存失效适合:多个服务写入同一张表、无法在业务代码中加缓存删除逻辑的场景。
流程
- DB 执行更新 / 插入→生成 Binlog;
- Canal 组件监听 Binlog,解析数据变更;
- Canal 推送变更消息→删除 Redis 缓存→通知所有节点删除本地缓存;
- 业务代码完全不关心缓存,零耦合。
优缺点
- ✅ 业务无侵入、强一致、多写入口兼容;
- ❌ 需要部署 Canal 服务,运维成本高;
- 适用:核心交易数据、多服务共享表。
方案 4:延迟双删(解决并发脏数据)
针对并发读写的短暂脏数据问题,在方案 2 的基础上增加兜底:
更新DB → 删除Redis → 延迟200ms → 再次删除Redis → 发送消息删本地缓存延迟时间略大于业务读请求的平均耗时,彻底清理并发写入的旧缓存。
方案 5:版本号机制(强一致兜底)
给缓存 key 增加版本号,彻底杜绝并发脏数据:
- 数据更新时,DB 版本号 + 1;
- 删除缓存时携带新版本号;
- 读缓存时校验版本号,不一致则重新查库加载。
五、方案对比与选型建议
方案 | 一致性 | 复杂度 | 成本 | 适用场景 |
本地缓存短过期 | 差 | 极低 | 低 | 非核心、读多写少数据 |
Redis Pub/Sub 通知 | 良 | 低 | 低 | 普通业务、非核心数据 |
MQ 通知同步 | 优 | 中 | 中 | 核心业务、要求高一致性 |
Canal Binlog 同步 | 极优 | 高 | 高 | 多写入口、强一致核心数据 |
延迟双删 + 版本号 | 最优 | 中 | 中 | 并发极高、零脏数据要求 |
六、生产级最佳实践(必看)
- 组合方案(万金油)本地缓存短 TTL 兜底+Redis Pub/Sub/MQ 实时同步+延迟双删,平衡一致性与性能。
- 缓存禁用自动刷新本地缓存只做主动删除,不做定时刷新,避免无效数据加载。
- 防护机制
- 缓存穿透:查询不存在的数据,缓存空值;
- 缓存雪崩:Redis 过期时间加随机偏移,本地缓存错开 TTL;
- 消息重复消费:消费端做幂等校验。
- 监控监控缓存命中率、本地缓存失效成功率、消息消费失败率。
七、总结
- 多级缓存架构:本地 L1 + Redis L2 + DB,读流程自上而下,写流程核心是先 DB 后删缓存;
- 唯一推荐写策略:旁路缓存模式(Cache Aside);
- 本地缓存同步:生产用MQ/Redis 发布订阅,核心数据用Canal;
- 并发一致性:用延迟双删 + 版本号兜底,杜绝脏数据。
这套方案是互联网公司通用的多级缓存一致性解决方案,兼顾性能、一致性与开发成本。
多级缓存抗高并发、降成本架构最佳实践
多级缓存的核心价值:越靠近用户的缓存层级,速度越快、成本越低、抗并发能力越强。通过客户端缓存 → CDN → 本地堆缓存 → Redis 分布式缓存 → 数据库五层架构,将 99.9% 的请求拦截在前端 / 本地 / Redis 层,彻底避免高并发流量击穿到数据库,同时通过分层淘汰、资源优化大幅降低服务器 / 带宽 / 存储成本。
本文是生产环境落地的最佳实践,覆盖架构设计、读写策略、缓存一致性、成本优化、避坑全流程。
一、标准多级缓存分层架构(生产级)
按请求距离从近到远、访问速度从快到慢、成本从低到高划分 5 层,读请求优先走上层,写请求只落地到数据库:
表格
缓存层级 | 技术选型 | 速度 | 成本 | 核心作用 | 适用数据 |
1 级(客户端) | 浏览器 LocalStorage/APP 内存 / 磁盘 | 纳秒级 | 0 | 拦截重复请求,不进服务端 | 静态配置、用户非敏感信息、静态页面 |
2 级(CDN) | 阿里云 / 腾讯云 CDN、Cloudflare | 微秒级 | 极低 | 抗静态资源流量峰值 | 图片、JS/CSS、静态 HTML、短视频 |
3 级(本地堆缓存) | Caffeine(首选)、Guava、JDK Map | 纳秒级 | 0 | 扛热点数据极端并发,比 Redis 快 10~100 倍 | 秒杀商品、首页榜单、热榜、高频配置 |
4 级(Redis 分布式缓存) | Redis 集群(主从 + 哨兵 / 分片集群) | 微秒级 | 中 | 跨服务共享缓存,支撑动态数据查询 | 商品详情、用户会话、订单缓存、全量热数据 |
5 级(数据源) | MySQL/PostgreSQL(主从) | 毫秒级 | 高 | 只承担写操作 + 冷数据查询 | 源数据、实时强一致数据 |
核心设计原则
- 读请求自上而下:上层缓存命中,直接返回;未命中才查询下层,查询后回写上层缓存;
- 写请求自下而上:先写数据库,再删除 / 更新缓存(绝对不先更缓存);
- 热点数据上移:秒杀、首页等极端热点数据,只存在本地 + Redis,不查 DB;
- 最终一致性优先:99% 业务无需强一致,用异步同步缓存,兼顾性能与成本。
二、核心抗高并发策略(解决三大缓存问题)
高并发场景下,缓存雪崩、击穿、穿透是最大风险,多级缓存可通过分层兜底彻底解决:
缓存雪崩(大量缓存同时过期 / Redis 宕机)
- 多级兜底:Redis 宕机→直接读本地缓存 + 数据库,不雪崩;
- 过期时间随机:给缓存过期时间加随机偏移(如
30min + random(5min)),避免集体失效; - Redis 高可用:主从 + 哨兵集群,杜绝单节点故障;
- 热点数据永不过期:后台异步刷新,不设置过期时间。
缓存击穿(热点 Key 过期,流量直接打 DB)
- 本地缓存永不过期:极端热点数据只存在本地堆缓存,后台异步更新;
- 互斥锁重建:缓存失效时,用分布式锁(Redlock)保证只有一个线程查 DB;
- Redis 热点 Key 拆分:把
hot_key拆分为hot_key_1~hot_key_10,分散并发压力。
缓存穿透(查询不存在的数据,绕过缓存查 DB)
- 布隆过滤器:Redis 集成布隆过滤器,拦截不存在的 Key(秒杀、商品查询必备);
- 空值缓存:查询不存在的数据,缓存空值(短过期时间,如 1 分钟);
- 参数校验 + 黑名单:拦截非法请求,直接返回。
三、降成本最佳实践(核心!)
多级缓存的成本优化远大于单纯扩容 Redis/DB,这是企业级架构的核心竞争力:
缓存分层淘汰(减少内存占用)
- 本地缓存:用 Caffeine 的LFU(最不经常使用)淘汰策略,只保留热点数据,避免 OOM;
- Redis 缓存:设置合理过期时间,冷数据自动淘汰,不占用昂贵的内存资源;
- 禁用永久缓存:非核心数据绝不设置永不过期,定期清理冷数据。
Redis 成本极致优化(云厂商 Redis 成本最高)
- 内存压缩
- 数据序列化用
Protobuf替代 JSON,体积减少 50%+; - 大 Key 拆分:避免
>10KB的 Key,用 Hash/List 结构替代 String;
- 读写分离
- 读请求走 Redis 从节点,写请求走主节点,主节点只负责写入,降低集群规格;
- 淘汰策略
- 设置为
volatile-lfu:只淘汰带过期时间的冷数据,保留永久热点数据;
- 混合存储
- Redis 6.0+ 开启磁盘冷数据存储:内存存热数据,磁盘存冷数据,内存成本降低 70%;
- 集群选型
- 中小流量用主从 + 哨兵,替代高成本的分片集群;
- 非核心业务用按量付费云 Redis,核心业务包年包月。
减少回源请求(降低 DB/Redis 压力)
- CDN 预热:静态资源提前推送到边缘节点,避免回源到服务器;
- 缓存预热:服务启动时,自动加载热点数据到本地 + Redis;
- 本地缓存兜底:Redis 故障时,直接用本地缓存响应,不请求 DB。
静态资源全上 CDN(成本降低 90%)
- 图片、JS、CSS、短视频100% 托管 CDN,CDN 流量费比服务器带宽便宜 10 倍以上;
- 开启 CDN 缓存压缩、懒加载,进一步降低流量成本。
数据库成本优化
- 读写分离:读请求走从库,主库只负责写入,避免主库扩容;
- 分库分表:冷数据归档,只保留热数据在主库;
- 禁用数据库缓存:多级缓存已兜底,无需开启 DB 缓存,减少 CPU 消耗。
四、生产落地实战(Java 微服务示例)
技术栈
SpringBoot 3.x + Caffeine(本地缓存)+ Redis Cluster + MySQL + 布隆过滤器
核心读写流程(标准规范)
读流程(自上而下,命中返回,未命中回写)
客户端请求 → 1级客户端缓存 → 2级CDN → 3级本地缓存 → 4级Redis → 5级DB 命中:直接返回 未命中:查询下层 → 回写上层所有缓存 → 返回结果写流程(自下而上,先 DB 后删缓存,绝对不更新)
1. 写入数据库(主库) 2. 异步删除 Redis缓存 3. 广播通知所有服务节点 删除本地缓存 4. 客户端缓存:设置短过期,自动失效为什么用删除而非更新?并发场景下,更新缓存会产生脏数据,删除是最优解。
核心代码实现
(1)本地缓存配置(Caffeine,生产首选)
@Configuration public class CaffeineConfig { @Bean public Cache<String, Object> caffeineCache() { return Caffeine.newBuilder().maximumSize(10000) // 最大缓存数,防止OOM .expireAfterWrite(30, TimeUnit.MINUTES) // 基础过期时间 .expireAfterAccess(10, TimeUnit.MINUTES) // 空闲过期 .build(); } }(2)多级缓存工具类
@Service @RequiredArgsConstructor public class MultiLevelCacheService { private final Cache<String, Object> caffeineCache; private final StringRedisTemplate redisTemplate; private final ProductMapper productMapper; private final long REDIS_EXPIRE = 30; private final Random random = new Random(); // 多级缓存读取 public Object get(String key) { // 1. 查本地缓存(最快) Object localCache = caffeineCache.getIfPresent(key); if (localCache != null) return localCache; // 2. 查 RedisString redisValue = redisTemplate.opsForValue().get(key); if (redisValue != null) { caffeineCache.put(key, redisValue); // 回写本地缓存 return redisValue; } // 3. 查数据库(加分布式锁,防止击穿) RLock lock = redissonClient.getLock("lock:" + key); try { lock.lock(5, TimeUnit.SECONDS); // 双重校验,避免并发查库 redisValue = redisTemplate.opsForValue().get(key); if (redisValue != null) return redisValue; // 4. 查询 DBObject dbData = productMapper.selectById(key); if (dbData == null) { // 空值缓存,防穿透 redisTemplate.opsForValue().set(key, "null", 1, TimeUnit.MINUTES); return null; } String data = JSON.toJSONString(dbData); // 5. 回写Redis(随机过期,防雪崩) redisTemplate.opsForValue().set(key, data, REDIS_EXPIRE + random.nextInt(5), TimeUnit.MINUTES); // 6. 回写本地缓存 caffeineCache.put(key, data); return data; } finally { if (lock.isHeldByCurrentThread()) lock.unlock(); } } // 写操作:先DB,后删缓存 @Transactional public void update(String key, Object data) { // 1. 写数据库 productMapper.updateById(data); // 2. 异步删Redis缓存 taskExecutor.execute(() -> redisTemplate.delete(key)); // 3. 广播删本地缓存(MQ实现) rocketMQTemplate.convertAndSend("cache-topic", key); } }(3)本地缓存一致性(MQ 广播)
服务节点监听 MQ 消息,收到缓存删除通知后,清空本地缓存:
@RocketMQMessageListener(topic = "cache-topic", consumerGroup = "cache-group") @Component public class CacheClearListener implements RocketMQListener<String> { @Autowiredprivate Cache<String, Object> caffeineCache; @Override public void onMessage(String key) { caffeineCache.invalidate(key); // 删除本地缓存 } }五、缓存一致性方案(多级缓存核心难点)
最终一致性(99% 业务推荐)
- 方案:先写 DB → 异步删除缓存(MQ/Canal 监听 binlog);
- 优势:性能极高,无代码侵入,成本低;
- 适用:商品、订单、内容平台等非强一致业务。
强一致性(金融 / 支付核心场景)
- 方案:禁用多级缓存,只用 Redis 分布式锁 + 实时更新;
- 劣势:性能下降,成本升高,仅核心接口使用。
无侵入式缓存同步(Canal)
通过 Canal 监听 MySQL binlog,自动同步数据到 Redis,无需业务代码修改,生产首选。
六、高可用保障
- Redis 高可用:主从 + 哨兵集群,开启 RDB+AOF 混合持久化;
- 熔断降级:用 Sentinel 实现 Redis 宕机时,直接切换为本地缓存 + DB;
- 限流防护:接口限流 + 热点 Key 限流,防止流量打垮服务;
- 监控告警:
- 核心指标:缓存命中率(目标 > 95%,低于 90% 优化)、Redis 内存 / CPU、本地缓存大小;
- 告警:命中率过低、Redis 连接超限、DB 慢查询。
七、生产避坑指南
- 本地缓存不存大数据:避免服务 OOM,单 Key≤1KB;
- 杜绝 Redis 大 Key:≥10KB 的 Key 拆分,否则导致网络阻塞;
- 不使用固定过期时间:必须加随机偏移,防止雪崩;
- 写操作只删不更:绝对不更新缓存,避免并发脏数据;
- 客户端 / CDN 不存敏感数据:密码、密钥等禁止缓存;
- 冷数据不进 Redis:归档到数据库,节省内存成本。
总结
多级缓存最佳实践核心
- 五层架构层层拦截:把请求挡在最上层,最大化抗并发、最小化成本;
- 先 DB 后删缓存:保证数据一致性,杜绝脏数据;
- 热点上移、冷数据淘汰:本地存极端热点,Redis 存热数据,DB 存源数据;
- 成本极致优化:CDN 扛静态、Redis 读写分离、本地缓存兜底,整体成本降低 70%+;
- 最终一致性优先:兼顾性能与业务需求,是生产环境的最优解。
这套架构可支撑百万 QPS级高并发,同时将服务器 / 缓存 / 带宽成本降到最低,是互联网公司的标准解决方案。
