LoadingCache 是 Google Guava 库中的一个接口,它提供了一种方便的方式来管理缓存,具备自动加载缓存值的功能。以下从 5W2H 的角度来详细介绍它:
1. What(是什么)
LoadingCache 是一个接口,属于 Google Guava 库中的缓存工具类。它扩展自Cache
接口, 允许在请求缓存值不存在时,自动通过指定的加载函数来加载并缓存这个值。比如在电商系统中,频繁获取商品详情信息,就可以使用 LoadingCache,当缓存中没有该商品详情时,自动从数据库加载并缓存,下次请求时直接从缓存获取,提升查询效率。
2. Why(为什么要用)
- 提升性能:减少对数据库、远程服务等后端数据源的频繁访问,对于热点数据,直接从内存缓存中获取数据,响应速度快,极大地提升了系统整体性能 。例如在新闻资讯平台中,热门新闻的详情数据,通过 LoadingCache 缓存后,用户请求时可以快速返回,降低响应时间。
- 降低资源消耗:减少对数据库连接、网络请求等资源的占用,降低后端系统的负载。比如在高并发的秒杀系统中,大量用户请求商品信息,利用 LoadingCache 缓存商品库存、价格等信息,能有效减少数据库的压力 。
3. Who(谁用)
- Java 开发者:在 Java 项目开发过程中,尤其是在需要缓存机制来优化性能的场景下,Java 开发者可以使用 LoadingCache 。
- 应用于各类系统:如 Web 应用程序、后端服务系统、数据处理系统等,只要存在对数据进行缓存加速需求的场景,开发人员都可以考虑使用 LoadingCache。例如,一个企业内部的员工信息管理系统,频繁查询员工基础信息时,使用 LoadingCache 来缓存员工信息数据。
4. When(什么时候用)
- 数据相对静态:数据更新频率较低,但是访问频率很高的场景。比如在一个在线教育平台中,课程分类信息、教师基本介绍信息等,这类数据不会频繁变动,却会被大量查询,适合用 LoadingCache 进行缓存。
- 存在高并发读操作:在高并发的读多写少场景中,LoadingCache 可以有效应对高并发请求,避免大量请求同时打到后端数据源上。像电商平台的商品类目数据,在大促期间,大量用户浏览商品,类目数据几乎不会变化,通过 LoadingCache 缓存,能很好地应对高并发的读请求 。
5. Where(在哪里用)
- 互联网应用:在各类互联网产品的后端服务中,像电商平台、社交平台、内容平台等,用于缓存商品信息、用户信息、帖子内容等数据 。
- 企业级应用:在企业内部的 ERP 系统、OA 系统等,缓存员工信息、部门信息、审批流程配置等数据 。
6. How(怎么用)
- 引入依赖 :如果是 Maven 项目,在
pom.xml
文件中添加 Guava 库的依赖:
xml
XML
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version> <!-- 根据需要选择合适版本 -->
</dependency>
- 创建 LoadingCache :通过
CacheBuilder
来构建LoadingCache
实例,示例代码如下:
java
运行
java
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.LoadingCache;
import java.util.concurrent.ExecutionException;
public class LoadingCacheExample {
public static void main(String[] args) {
LoadingCache<String, Integer> cache = CacheBuilder.newBuilder()
.maximumSize(100) // 设置缓存最大容量
.build(key -> {
// 这里是缓存加载函数,当缓存中不存在key对应的值时,会调用此函数加载值
return key.length();
});
try {
// 获取缓存值,如果缓存中不存在,会自动调用加载函数
Integer value = cache.get("example");
System.out.println(value);
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
java
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 优惠券模板缓存服务:管理优惠券模板的内存缓存,减少数据库访问
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class CouponTemplateCacheService {
// 依赖注入:优惠券模板DAO(实际项目中可能是Mapper或Repository)
private final CouponTemplateDao couponTemplateDao;
// 核心缓存对象:Key为模板ID,Value为模板对象(用Optional避免空指针)
private final LoadingCache<Long, Optional<CouponTemplate>> couponTemplateCache =
CacheBuilder.newBuilder()
// 1. 基础容量配置
.initialCapacity(500) // 初始容量:根据业务预估(如日常活跃模板约300个)
.maximumSize(2000) // 最大容量:防止缓存过大导致OOM(预留扩容空间)
// 2. 并发控制
.concurrencyLevel(Runtime.getRuntime().availableProcessors()) // 并发级别=CPU核心数,保证线程安全
// 3. 过期策略:根据业务数据更新频率设置
.expireAfterWrite(30, TimeUnit.MINUTES) // 写入后30分钟过期(模板更新后30分钟内缓存失效)
.expireAfterAccess(10, TimeUnit.MINUTES) // 10分钟未访问则过期(清理冷数据)
// 4. 缓存移除通知(可选,用于监控或统计)
.removalListener(notification -> {
Long templateId = notification.getKey();
String reason = notification.getCause().name(); // 移除原因:EXPIRED(过期)、SIZE(容量满)等
log.info("优惠券模板缓存移除:templateId={}, 原因={}", templateId, reason);
})
// 5. 加载逻辑:缓存不存在时如何获取数据
.build(new CacheLoader<Long, Optional<CouponTemplate>>() {
/**
* 当缓存中没有templateId对应的数据时,自动调用此方法加载数据
* @param templateId 优惠券模板ID
* @return 数据库查询结果(用Optional包装,允许null)
* @throws Exception 加载过程中的异常(会被包装为ExecutionException)
*/
@Override
public Optional<CouponTemplate> load(Long templateId) throws Exception {
log.info("缓存未命中,从数据库加载优惠券模板:templateId={}", templateId);
// 实际业务逻辑:从数据库查询模板
CouponTemplate template = couponTemplateDao.selectById(templateId);
// 若数据库中不存在,返回Optional.empty(),避免缓存null值(Guava默认不允许缓存null)
return Optional.ofNullable(template);
}
});
/**
* 从缓存获取单个优惠券模板
* @param templateId 模板ID
* @return 模板对象(可能为null,需判断)
*/
public CouponTemplate getTemplateById(Long templateId) {
try {
// 调用get()方法:若缓存存在则直接返回,不存在则触发load()方法加载
Optional<CouponTemplate> templateOpt = couponTemplateCache.get(templateId);
return templateOpt.orElse(null); // 若Optional为空,返回null
} catch (ExecutionException e) {
// 处理加载过程中的异常(如数据库连接失败)
log.error("获取优惠券模板缓存异常:templateId={}", templateId, e);
return null; // 异常时返回null,由上层处理
}
}
/**
* 批量获取模板(减少多次缓存查询的开销)
* @param templateIds 模板ID列表
* @return 键为ID、值为模板的Map(不存在的ID对应值为null)
*/
public Map<Long, CouponTemplate> getTemplatesByIds(List<Long> templateIds) {
try {
// 批量查询缓存:返回Map<ID, Optional<模板>>
Map<Long, Optional<CouponTemplate>> resultMap = couponTemplateCache.getAll(templateIds);
// 转换为Map<ID, 模板>,并处理空值
return resultMap.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> entry.getValue().orElse(null)
));
} catch (ExecutionException e) {
log.error("批量获取优惠券模板缓存异常:ids={}", templateIds, e);
return Map.of(); // 异常时返回空Map
}
}
/**
* 主动刷新缓存(当模板更新时调用,避免缓存与数据库不一致)
* @param template 最新的模板对象
*/
public void refreshCache(CouponTemplate template) {
if (template == null || template.getId() == null) {
log.warn("刷新缓存失败:模板对象或ID为空");
return;
}
// 主动将新数据放入缓存,覆盖旧值(触发写入时间更新,延长过期时间)
couponTemplateCache.put(template.getId(), Optional.of(template));
log.info("主动刷新优惠券模板缓存:templateId={}", template.getId());
}
/**
* 主动移除缓存(当模板删除时调用)
* @param templateId 模板ID
*/
public void removeCache(Long templateId) {
if (templateId == null) {
log.warn("移除缓存失败:模板ID为空");
return;
}
couponTemplateCache.invalidate(templateId); // 立即从缓存中移除该条目
log.info("主动移除优惠券模板缓存:templateId={}", templateId);
}
}
7. How Much(有多大效果)
- 性能提升方面:对于热点数据的查询,响应时间可以降低 80% 甚至更多。比如在一个查询商品库存的接口中,使用 LoadingCache 前响应时间平均为 200ms,使用后,对于缓存命中的请求,响应时间可以降低到 20ms 以内。
- 资源消耗方面:能显著减少后端数据源的负载,以数据库为例,查询请求量可能会降低 50% - 80% ,减少数据库连接的占用,提升数据库的稳定性和可用性 。