Spring生态的缓存方案:多级缓存架构实现(Caffeine + Redis)
在Spring生态中选择合适的缓存方案需要根据应用场景、性能需求、分布式特性等多方面因素综合考虑。
以下是对Spring Cache、Caffeine、Ehcache、Redis、Guava缓存的对比分析与选型建议:
一、核心特性对比
| 特性 | Spring Cache(抽象层) | Caffeine | Ehcache | Redis | Guava(已淘汰) |
|---|---|---|---|---|---|
| 类型 | 缓存抽象接口 | 本地高性能缓存 | 本地/分布式缓存 | 分布式缓存 | 本地缓存(旧版) |
| 性能 | 依赖具体实现 | 极高性能(W-TinyLFU) | 中高(支持磁盘存储) | 高(网络延迟敏感) | 中(基于LRU) |
| 分布式支持 | 无 | 无 | 支持(RMI/JGroup) | 原生支持集群 | 无 |
| 持久化 | 无 | 无 | 支持(磁盘存储) | 支持(RDB/AOF) | 无 |
| 适用场景 | 注解化缓存管理 | 单节点高频访问 | 单机或简单分布式 | 跨进程共享/大规模数据 | 旧项目兼容 |
二、选型建议与适用场景
1. Spring Cache(抽象层)
- 定位:提供统一的缓存API,支持多种缓存实现(如Caffeine、Redis等),通过注解(
@Cacheable、@CacheEvict)简化开发13。 - 适用场景:需要快速集成缓存且希望代码与具体实现解耦的项目。适合结合Caffeine或Redis使用。
- 局限性:复杂场景(如动态刷新、多级缓存联动)需直接操作底层API。
2. Caffeine
-
优势:基于W-TinyLFU算法,命中率高;性能碾压Guava(读写吞吐量提升4倍以上)68。
-
适用场景:单机高并发场景(如电商秒杀),需要极低延迟的本地缓存。Spring 5+默认推荐替代Guava48。
-
配置示例:
Caffeine.newBuilder() .maximumSize(10_000) .expireAfterWrite(5, TimeUnit.MINUTES) .build();
3. Ehcache
- 优势:支持磁盘持久化和内存溢出保护,适合数据量较大的单机应用;与Spring集成良好39。
- 适用场景:需要缓存数据持久化(如重启后恢复)或简单分布式同步(通过RMI)。例如金融系统的静态配置缓存。
- 局限性:集群方案复杂,性能低于Caffeine57。
4. Redis
- 优势:分布式缓存、数据持久化、丰富数据结构(如Hash/List);支持哨兵和集群模式,高可用性强。
- 适用场景:跨服务共享数据(如会话缓存)、高频读写且需保证一致性的场景(如库存扣减)。
- 注意点:网络延迟可能成为瓶颈,需结合本地缓存(如Caffeine)形成多级缓存。
5. Guava(历史选择)
- 现状:已被Caffeine全面取代,Spring 5+不再支持。仅建议旧项目维护时使用。
- 缺陷:基于LRU算法,命中率低于Caffeine;不支持空值缓存。
1. Spring Cache(抽象层) 缓存机制核心原理剖析
Spring Cache 核心机制:为缓存操作搭建舞台
Spring Cache 在 Spring 框架中构建了一套完整的缓存抽象体系,其核心在于一系列精巧设计的注解
- @Cacheable :最常用的注解之一,用于指定方法的结果需要被缓存。当方法被调用时,Spring 会根据预设的规则检查缓存。若缓存命中,则直接返回缓存结果,避免了业务逻辑的重复执行;若缓存未命中,则执行方法并将结果存入缓存,以便后续请求能快速获取。
- @CacheEvict :用于清除缓存。在数据更新或删除等操作后,旧的缓存数据可能不再准确,此时可使用该注解按特定规则清除缓存,确保下次查询能获取到最新数据。
- @CachePut :它与 @Cacheable 有所区别,总是执行被注解的方法,并将方法结果更新到缓存中。这适用于那些希望在更新数据的同时刷新缓存的场景,让缓存能及时反映最新的业务状态。
这些注解的背后,是 Spring Cache 对缓存操作流程的精细化管理。它通过 CacheManager 和 Cache 的抽象接口,为不同的缓存实现提供了统一的接入规范,使得我们可以灵活地在各种缓存方案之间切换,而无需大幅修改代码逻辑。
1.1 Spring缓存抽象层架构
Spring缓存机制采用分层设计模式,通过CacheManager、Cache和KeyGenerator三个核心接口实现缓存抽象:
// 典型配置示例
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("userCache");
}
@Bean
public KeyGenerator customKeyGenerator() {
return new CustomCacheKeyGenerator();
}
}
1.2 缓存工作流程解析
DBServiceCacheClientalt[缓存命中][缓存未命中]请求数据(id=123)检查缓存是否存在返回缓存数据查询数据库返回数据写入缓存返回响应
2. @Cacheable高阶应用技巧
2.1 复合键生成策略: SpEL 表达式与参数引用
// 多参数复合键示例
@Cacheable(value = "orderCache",
key = "#userId + ':' + #orderType.code")
public List<Order> getUserOrders(Long userId, OrderType orderType) {
return orderRepository.findByUserAndType(userId, orderType);
}
// 自定义键生成器
public class CustomKeyGenerator implements KeyGenerator {
@Override
public Object generate(Object target, Method method, Object... params) {
return target.getClass().getSimpleName() + "_"
+ method.getName() + "_"
+ Arrays.deepHashCode(params);
}
}
2.2 条件缓存控制
// 条件缓存示例
@Cacheable(value = "productCache",
key = "#id",
condition = "#id > 1000",
unless = "#result.stock < 10")
public Product getProduct(Long id) {
return productService.findById(id);
}
2.3 并发缓存控制
// 防止缓存击穿配置
@Cacheable(value = "hotProductCache",
key = "#id",
sync = true)
public Product getHotProduct(Long id) {
return productService.getById(id);
}
3、@CacheEvict高级应用场景
3.1 批量操作缓存处理
// 批量删除示例
@CacheEvict(value = "userCache", allEntries = true)
public void batchUpdateUsers(List<User> users) {
userRepository.batchUpdate(users);
}
// 模式匹配删除
@CacheEvict(value = "orderCache",
key = "T(com.util.CacheKeyUtil).getPattern(#userId)")
public void clearUserOrders(Long userId) {
// 清理操作
}
3.2 事务性缓存清理
// 事务提交后清理缓存
@Transactional
@CacheEvict(value = "accountCache",
key = "#account.id",
beforeInvocation = false)
public void updateAccount(Account account) {
accountRepository.save(account);
// 如果事务回滚,缓存不会清除
}
4、多级缓存架构实现
4.1 本地+分布式缓存方案
// 二级缓存实现示例
@Service
public class ProductService {
@Cacheable(value = "localProductCache",
key = "#id",
cacheManager = "localCacheManager")
@Cacheable(value = "redisProductCache",
key = "#id",
cacheManager = "redisCacheManager")
public Product getProduct(Long id) {
return productRepository.findById(id);
}
@CacheEvict(cacheManagers = {"localCacheManager", "redisCacheManager"},
key = "#product.id")
public void updateProduct(Product product) {
productRepository.save(product);
}
}
4.2 缓存数据预热策略
@Component
public class CacheWarmUp implements ApplicationRunner {
@Autowired
private ProductService productService;
@Override
public void run(ApplicationArguments args) {
List<Long> hotProductIds = Arrays.asList(1001L, 1002L, 1003L);
hotProductIds.parallelStream().forEach(id -> {
productService.getProduct(id);
});
}
}
6、性能优化指标对比
6.1 缓存命中率优化
| 优化策略 | 命中率提升 | 实现复杂度 | 适用场景 |
|---|---|---|---|
| 缓存预热 | 15-20% | 低 | 热点数据 |
| 本地二级缓存 | 25-30% | 中 | 高频访问数据 |
| 数据压缩 | 5-10% | 高 | 大对象存储 |
| 分区缓存 | 10-15% | 中 | 海量数据场景 |
Spring Cache 深度集成 Caffeine 和 Redis 实战指南
Spring Cache 作为缓存抽象层,完美支持 Caffeine(本地缓存)和 Redis(分布式缓存)的集成。以下从 功能特性、配置实现 到 多级缓存架构 进行全方位解析:
一、功能支持对比
| 特性 | Caffeine(本地) | Redis(分布式) | 组合优势 |
|---|---|---|---|
| 数据存储 | JVM堆内存 | 独立服务器存储 | 本地快速访问+分布式共享 |
| 吞吐量 | 100万+ OPS(单机) | 10万+ OPS(网络依赖) | 本地扛流量,降低Redis压力 |
| 数据一致性 | 单机一致 | 集群强一致 | 最终一致性方案 |
| 适用场景 | 高频读/低延迟(商品详情) | 跨服务共享(库存扣减) | 多级缓存架构 |
二、Caffeine 集成配置(本地缓存)
1. 依赖引入(Maven)
<!-- Spring Boot 2.3+ 默认集成 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.8</version>
</dependency>
2. 配置类示例
@Configuration
@EnableCaching
public class CaffeineConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager manager = new CaffeineCacheManager();
manager.setCaffeine(Caffeine.newBuilder()
.initialCapacity(1000) // 初始容量
.maximumSize(10_000) // 最大条目数
.expireAfterWrite(5, TimeUnit.MINUTES) // 写入后过期时间
.recordStats()); // 开启统计
return manager;
}
}
3. 业务层使用
@Service
public class ProductService {
// 使用缓存名称"products",键为ID
@Cacheable(value = "products", key = "#id", unless = "#result == null")
public Product getProductById(Long id) {
// 数据库查询逻辑
return productRepository.findById(id).orElse(null);
}
@CacheEvict(value = "products", key = "#product.id")
public void updateProduct(Product product) {
productRepository.save(product);
}
}
三、Redis 集成配置(分布式缓存)
1. 依赖引入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2. 配置文件(application.yml)
spring:
redis:
host: redis-server
port: 6379
password: yourpassword
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 2
3. 缓存配置类
@Configuration
@EnableCaching
public class RedisCacheConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1)) // 默认1小时过期
.disableCachingNullValues() // 不缓存null
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.withInitialCacheConfigurations(getCacheConfigs())
.build();
}
// 不同缓存差异化配置
private Map<String, RedisCacheConfiguration> getCacheConfigs() {
Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
configMap.put("users", RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30)));
return configMap;
}
}
四、多级缓存架构实现(Caffeine + Redis)
Caffeine 与 Redis 的深度集成
在现代 Java 开发领域,Spring Cache 凭借其简洁高效的特性,成为了众多开发者优化应用性能的首选利器。它巧妙地将缓存逻辑与业务代码解耦,让我们只需通过简单的注解操作,便能轻松享受缓存带来的 “速度加成”。而 Spring Cache 的强大之处,还体现在它对多种缓存实现的支持,其中 Caffeine 和 Redis 无疑是备受瞩目的两种方案。
1. 架构原理


DBRedisCaffeineClientalt[Redis命中][Redis未命中]alt[本地缓存命中][本地未命中]
- 请求数据返回数据
- 查询Redis返回数据写入本地缓存返回数据
- 查询数据库返回数据写入Redis返回数据写入本地缓存返回数据
2. 代码实现
@Service
public class MultiLevelCacheService {
@Autowired
private CacheManager caffeineCacheManager; // 本地缓存管理器
@Autowired
private CacheManager redisCacheManager; // Redis缓存管理器
public Product getProduct(Long id) {
// 先查本地缓存
Product product = caffeineCacheManager.getCache("products").get(id, Product.class);
if (product != null) {
return product;
}
// 再查Redis
product = redisCacheManager.getCache("products").get(id, Product.class);
if (product != null) {
// 回填本地缓存
caffeineCacheManager.getCache("products").put(id, product);
return product;
}
// 最后查数据库
product = productRepository.findById(id).orElse(null);
if (product != null) {
// 同时写入两级缓存
redisCacheManager.getCache("products").put(id, product);
caffeineCacheManager.getCache("products").put(id, product);
}
return product;
}
}
3. 缓存同步策略
// 使用Redis消息队列同步本地缓存
@Bean
public RedisMessageListenerContainer container(RedisConnectionFactory factory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(factory);
container.addMessageListener((message, pattern) -> {
String cacheKey = new String(message.getBody());
caffeineCacheManager.getCache("products").evict(cacheKey);
}, new ChannelTopic("cache:evict:products"));
return container;
}
// 更新数据时发布消息
public void updateProduct(Product product) {
productRepository.save(product);
redisTemplate.convertAndSend("cache:evict:products", product.getId());
}
五、选型建议与最佳实践
1. 场景决策树

2. 性能优化技巧
-
Caffeine调优:
Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.refreshAfterWrite(1, TimeUnit.MINUTES) // 后台刷新
.recordStats() // 开启统计
- **Redis管道优化**:
```kotlin
List<Object> results = redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
for (Long id : ids) {
connection.get(("product:" + id).getBytes());
}
return null;
});
3. 监控指标
| 指标 | Caffeine | Redis |
|---|---|---|
| 命中率 | cache.getIfPresent统计 | INFO stats命令查看keyspace_hits |
| 内存使用 | JMX或cache.policy() | used_memory指标 |
| 并发问题 | 通过recordStats监控eviction | 监控blocked_clients |
六、迁移升级策略(Guava → Caffeine)
1. 依赖替换
<!-- 移除 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<!-- 新增 -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
2. 配置调整
// Before(Guava)
@Bean
public CacheManager cacheManager() {
GuavaCacheManager manager = new GuavaCacheManager();
manager.setCacheBuilder(CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES));
return manager;
}
// After(Caffeine)
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager manager = new CaffeineCacheManager();
manager.setCaffeine(Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES));
return manager;
}
七、生产环境问题解决方案
7.1 缓存穿透防护组合拳
// 布隆过滤器+空值缓存
@Service
public class UserService {
private BloomFilter<Long> userBloomFilter;
@PostConstruct
public void init() {
// 初始化布隆过滤器
userBloomFilter = BloomFilter.create(...);
}
@Cacheable(value = "userCache",
key = "#id",
unless = "#result == null")
public User getUser(Long id) {
if (!userBloomFilter.mightContain(id)) {
return null;
}
User user = userRepository.findById(id);
if (user == null) {
// 缓存空值防止穿透
return new NullUser();
}
return user;
}
}
7.2 分布式缓存一致性方案
1. 延时双删策略(推荐方案)
// 延时双删策略实现
@Transactional
public void updateProduct(Product product) {
// 第一次删除
cacheManager.getCache("productCache").evict(product.getId());
productRepository.update(product);
// 异步延时删除:延时双删
CompletableFuture.runAsync(() -> {
try {
Thread.sleep(500);
cacheManager.getCache("productCache").evict(product.getId());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
typescript 体验AI代码助手 代码解读复制代码public class ProductService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Transactional
public void updateProduct(Product product) {
// 第一次删除缓存
redisTemplate.delete("product:" + product.getId());
// 更新数据库
productDao.update(product);
// 提交事务后异步延时删除
CompletableFuture.runAsync(() -> {
try {
Thread.sleep(500); // 等待业务读取完成
redisTemplate.delete("product:" + product.getId());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
}
2. 基于版本号的强一致性方案
sql
ALTER TABLE products ADD COLUMN version INT DEFAULT 0;
入参版本号大于缓存版本号,主动更新缓存
public class ProductService {
@CachePut(value = "products", key = "#product.id",
condition = "#product.version > cache.get(#product.id)?.version")
public Product updateProduct(Product product) {
int affected = productDao.update(
"UPDATE products SET name=?, price=?, version=version+1 WHERE id=? AND version=?",
product.getName(), product.getPrice(), product.getId(), product.getVersion());
if (affected == 0) {
throw new OptimisticLockException("数据版本冲突");
}
product.setVersion(product.getVersion() + 1);
return product;
}
}
3. 事务消息方案(RocketMQ实现)
// 生产者
@Transactional
public void updateWithTransaction(Product product) {
productDao.update(product);
TransactionMQProducer producer = new TransactionMQProducer("GroupName");
Message msg = new Message("CacheEvictTopic",
("product:" + product.getId()).getBytes());
producer.sendMessageInTransaction(msg, null);
}
// 消费者
public class CacheEvictListener implements MessageListener {
@Override
public void consumeMessage(List<MessageExt> messages) {
messages.forEach(msg -> {
String key = new String(msg.getBody());
redisTemplate.delete(key);
});
}
}
- 异常处理机制设计
4.1 缓存操作重试策略
@Retryable(maxAttempts = 3, backoff = @Backoff(delay = 100))
public void safeCacheDelete(String key) {
try {
redisTemplate.delete(key);
} catch (RedisConnectionFailureException ex) {
// 记录到重试队列
retryQueue.add(new RetryEntry(key, System.currentTimeMillis()));
throw ex;
}
}
@Scheduled(fixedDelay = 5000)
public void processRetryQueue() {
while (!retryQueue.isEmpty()) {
RetryEntry entry = retryQueue.poll();
if (System.currentTimeMillis() - entry.timestamp < 60000) {
redisTemplate.delete(entry.key);
}
}
}
4.2 数据库与缓存操作原子性保障
// 使用Redis事务
public void atomicUpdate(String key, Product product) {
redisTemplate.execute(new SessionCallback<>() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
operations.watch(key);
operations.multi();
operations.delete(key);
productDao.update(product);
return operations.exec();
}
});
}
面试 :怎么保证缓存和数据库数据的一致性?

| 策略 | 实现复杂度 | 数据一致性 | 性能影响 | 适用场景 |
|---|---|---|---|---|
| 写时更新 | 低 | 高 | 高 | 金融交易系统 |
| 写后更新 | 中 | 中 | 中 | 社交网络应用 |
| 定时刷新缓存 | 低 | 低 | 低 | 新闻资讯类应用 |
| 缓存预热 | 中 | 高 | 低 | 电商促销活动页面 |
| 延迟双删策略 | 高 | 高 | 中 | 高并发场景,如电商秒杀活动 |
| 缓存数据带版本号 | 中 | 高 | 中 | 用户配置信息 |
| 发布/订阅机制 | 高 | 高 | 低 | 分布式系统和微服务架构 |
| 读时更新 | 低 | 中 | 中 | 内容管理系统 |
- 写时更新策略(Write-Through)
- 描述:在更新数据库时,同时更新缓存。这种方式可以确保缓存和数据库的数据始终保持一致。
- 优点:数据一致性高。
- 缺点:每次写操作都需要两次I/O操作(一次数据库,一次缓存),增加了写操作的复杂性和延迟。
- 适用场景:对数据一致性要求极高的场景,如金融交易系统。
- 写后更新策略(Write-Behind):(异常处理机制缓存操作失败重试+缓存过期时间范围可控)
- 描述:先更新数据库,然后再异步更新缓存。这种方式可以通过异步操作减少写操作的延迟。
- 优点:减少写操作的延迟,提高系统吞吐量。
- 缺点:存在短暂的数据不一致窗口,如果异步更新失败,可能导致缓存和数据库数据不一致。
- 适用场景:对数据一致性要求较高,但可以接受短暂不一致的场景,如社交网络应用。
- 定时刷新缓存(TTL - Time To Live)
- 描述:为缓存数据设置过期时间,缓存数据在过期后自动失效,下次访问时重新从数据库加载数据。
- 优点:简单易实现,适合大多数场景。
- 缺点:可能存在缓存过期时间设置不合理导致的数据不一致。
- 适用场景:对数据一致性要求不高的场景,如新闻资讯类应用。
- 缓存预热
- 描述:在系统启动或某个时间点,主动将热点数据加载到缓存中,以减少首次访问的延迟。
- 优点:提高首次访问的响应速度。
- 缺点:需要预估热点数据,可能占用较多缓存空间。
- 适用场景:需要快速响应的场景,如电商促销活动页面、用户年度报告
- 延迟双删策略(Delayed Double Deletion)
- 描述:在更新数据库时,先删除缓存,然后更新数据库,最后异步再次删除缓存,确保所有并发读取操作都完成后,缓存中的旧数据不会被重新写入。
- 优点:解决缓存击穿问题,确保数据一致性。
- 缺点:实现复杂,需要异步处理。
- 适用场景:高并发场景,如电商秒杀活动。
- 缓存数据带版本号
- 描述:在缓存数据时,附带一个版本号,每次更新数据库时,同时更新缓存的版本号。访问缓存时,先检查版本号是否匹配。
- 优点:可以有效避免缓存击穿和雪崩问题。
- 缺点:需要额外存储和管理版本号。
- 适用场景:需要细粒度控制缓存更新的场景,如用户配置信息。
- 发布/订阅机制(Pub/Sub)
- 描述:使用消息队列或发布/订阅系统,在数据库更新时发布一个事件,缓存系统订阅该事件并更新缓存。
- 优点:解耦缓存和数据库的更新操作,适合分布式系统。
- 缺点:增加了系统的复杂性,需要额外的基础设施支持。
- 适用场景:分布式系统和微服务架构。
- 读时更新策略(Read-Through)
- 描述:在读取数据时,如果缓存中没有数据或数据过期,则从数据库加载数据,并更新缓存。
- 优点:简单易实现,适合读多写少的场景。
- 缺点:在缓存未命中时,读操作的延迟较高。
- 适用场景:读操作远多于写操作的场景,如内容管理系统。
7.3 最佳实践总结
注解使用场景对照表
| 场景 | 推荐注解 | 参数组合示例 |
|---|---|---|
| 读多写少 | @Cacheable | value+key+unless |
| 写后立即读 | @CachePut | value+key+condition |
| 事务性数据更新 | @CacheEvict | beforeInvocation=true |
| 批量数据操作 | @CacheEvict | allEntries=true |
| 高并发热点数据 | @Cacheable | sync=true |
缓存设计黄金法则
- 粒度控制:对象缓存优于集合缓存
- 失效策略:随机过期时间避免雪崩
- 读写策略:写穿透+延时双删保证一致性
- 监控预警:实时监控命中率与内存使用
- 降级方案:缓存故障时自动切换数据库
// 降级方案示例
@Cacheable(value = "userCache",
key = "#id",
fallback = "getUserFromDB")
public User getUserWithFallback(Long id) {
return userRepository.findById(id);
}
private User getUserFromDB(Long id) {
// 记录降级日志
log.warn("Cache degraded, query from DB: {}", id);
return userRepository.findById(id);
}
7.4 监控指标与告警配置
Prometheus监控指标示例
- name: cache_hit_rate
type: GAUGE
help: "缓存命中率"
query: |
rate(redis_commands_total{command="get",status="hit"}[5m])
/
rate(redis_commands_total{command="get"}[5m])
- name: db_cache_delay
type: HISTOGRAM
help: "数据库与缓存操作延迟差"
buckets: [0.005, 0.01, 0.05, 0.1, 0.5]
query: |
histogram_quantile(0.95,
sum(rate(db_update_duration_seconds_bucket[5m]))
-
histogram_quantile(0.95,
sum(rate(cache_update_duration_seconds_bucket[5m])))
告警规则配置
groups:
- name: cache-alerts
rules:
- alert: HighCacheMissRate
expr: cache_hit_rate < 0.7
for: 5m
labels:
severity: warning
annotations:
summary: "缓存命中率过低"
description: "当前缓存命中率 {{ $value }},低于阈值 0.7"
- alert: DataInconsistency
expr: db_cache_delay > 0.5
for: 10m
labels:
severity: critical
annotations:
summary: "数据库与缓存数据延迟过高"
description: "数据库与缓存操作延迟差达到 {{ $value }} 秒"
7.5 避坑指南
禁止:先删除缓存再更新数据库 (解决办法 :延时双删策略(推荐方案))
1. **数据不一致问题**:当线程A删除缓存后,线程B查询缓存发现没有数据,于是从数据库中读取旧数据并存入缓存。如果线程A的数据库更新操作失败,那么缓存中的数据将是旧的,而数据库中的数据是新的,导致数据不一致。
1. **并发问题**:在并发环境下,多个线程可能会同时进行读操作和写操作。
1. **业务场景问题**:如果业务场景中写数据库的频率远高于读数据库的频率,采用这种策略会导致缓存频繁被删除和重建,浪费性能)
避免:无限制的缓存时间(必须设置TTL) 谨慎:使用allEntries=true清除整个缓存区域 必须:处理缓存穿透(布隆过滤器)和雪崩(随机过期时间)
总结:Spring Cache 通过统一的抽象接口,使开发者可以无缝切换 Caffeine 和 Redis 等缓存实现。建议:
- 单体应用优先使用 Caffeine 获得极致性能
- 分布式系统必须使用 Redis 保证数据一致性
- 高并发场景采用多级缓存架构,综合两者的优势
- 始终通过监控指标指导缓存策略调整
8. Cache-Aside 模式深度解析与实战指南
Cache-Aside(旁路缓存)是应用最广泛的缓存策略之一,其核心思想是将缓存作为数据库的辅助存储,由应用程序显式控制缓存读写。本文从 设计原理、实现细节 到 生产级优化 进行全方位解析。
一、核心工作流程

DBCacheAppClientalt[缓存命中][缓存未命中]读请求查询缓存返回缓存数据响应请求查询数据库返回数据写入缓存响应请求写请求更新数据库删除缓存项
二、实现要点与代码示例
1. 基础实现(Java + Spring Boot)
@Service
public class ProductService {
@Autowired
private ProductRepository repository;
@Autowired
private CacheManager cacheManager;
// 读操作实现
public Product getProduct(Long id) {
Product product = cacheManager.getCache("products").get(id, Product.class);
if (product == null) {
product = repository.findById(id).orElse(null);
if (product != null) {
cacheManager.getCache("products").put(id, product);
}
}
return product;
}
// 写操作实现
@Transactional
public Product updateProduct(Product product) {
Product updated = repository.save(product);
cacheManager.getCache("products").evict(product.getId());
return updated;
}
}
2. 并发控制优化
public Product getProductWithLock(Long id) {
Product product = cacheManager.getCache("products").get(id, Product.class);
if (product != null) return product;
Lock lock = redissonClient.getLock("product_lock:" + id);
try {
if (lock.tryLock(3, TimeUnit.SECONDS)) {
// 双重检查锁
product = cacheManager.getCache("products").get(id, Product.class);
if (product != null) return product;
product = repository.findById(id).orElse(null);
if (product != null) {
cacheManager.getCache("products").put(id, product);
}
return product;
}
} finally {
lock.unlock();
}
throw new RuntimeException("获取锁超时");
}
三、关键问题与解决方案
1. 缓存一致性挑战

解决方案:
- 延时双删策略:数据库更新后异步二次删除缓存
- 版本号校验:数据版本比对防止旧值覆盖
- 事务消息:通过MQ保证最终一致性
2. 缓存穿透防护
@Cacheable(value = "products",
key = "#id",
unless = "#result == null")
public Product getProduct(Long id) {
Product product = repository.findById(id).orElse(null);
if (product == null) {
// 缓存空值防止穿透
cacheManager.getCache("products").put(id, NullProduct.INSTANCE);
}
return product;
}
3. 缓存雪崩预防
// 随机过期时间配置
Caffeine.newBuilder()
.expireAfterWrite(30 + new Random().nextInt(15), TimeUnit.MINUTES)
四、生产级最佳实践
1. 多级缓存架构
2. 监控指标体系
| 指标 | 采集方式 | 告警阈值 |
|---|---|---|
| 缓存命中率 | Redis INFO命令/Caffeine统计 | < 70% |
| 缓存加载时间 | Micrometer Timer | > 500ms |
| 缓存空值比例 | 自定义计数器 | > 20% |
| 数据库查询QPS | JDBC连接池监控 | 突增300% |
3. Spring Cache集成配置
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
return new CompositeCacheManager(
new CaffeineCacheManager("local_cache"),
new RedisCacheManager(redisConnectionFactory())
);
}
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(
new RedisStandaloneConfiguration("redis-host", 6379));
}
}
五、性能优化对比
不同实现方式性能测试(JMeter压测结果)
| 场景 | QPS | 平均响应 | 数据库负载 |
|---|---|---|---|
| 无缓存 | 1,200 | 82ms | 100% |
| 基础Cache-Aside | 8,500 | 11ms | 35% |
| 带锁优化版 | 6,200 | 15ms | 28% |
| 多级缓存架构 | 15,000 | 3ms | 12% |

769

被折叠的 条评论
为什么被折叠?



