缓存
LCGYL Framework 提供统一的缓存抽象,支持多种缓存实现,帮助你提升应用性能。
缓存配置
基本配置
properties
# application.properties
cache.enabled=true
cache.type=memory
cache.default-ttl=3600
cache.max-size=10000内存缓存
properties
cache.type=memory
cache.memory.initial-capacity=100
cache.memory.maximum-size=10000
cache.memory.expire-after-write=3600
cache.memory.expire-after-access=1800Redis 缓存
properties
cache.type=redis
cache.redis.host=localhost
cache.redis.port=6379
cache.redis.password=secret
cache.redis.database=0
cache.redis.timeout=3000
cache.redis.pool.max-active=8
cache.redis.pool.max-idle=8
cache.redis.pool.min-idle=0缓存注解
@Cacheable
缓存方法返回值。
java
@Component
public class UserService {
@Inject
private UserRepository userRepository;
@Cacheable(value = "users", key = "#id")
public User findById(Long id) {
// 第一次调用会执行,结果被缓存
// 后续调用直接返回缓存
return userRepository.findById(id);
}
@Cacheable(value = "users", key = "#name")
public List<User> findByName(String name) {
return userRepository.findByName(name);
}
@Cacheable(value = "users", key = "'all'")
public List<User> findAll() {
return userRepository.findAll();
}
}@CachePut
更新缓存。
java
@Component
public class UserService {
@CachePut(value = "users", key = "#user.id")
public User save(User user) {
// 总是执行方法,并更新缓存
userRepository.save(user);
return user;
}
@CachePut(value = "users", key = "#result.id")
public User create(String name, String email) {
User user = new User(name, email);
userRepository.save(user);
return user;
}
}@CacheEvict
删除缓存。
java
@Component
public class UserService {
@CacheEvict(value = "users", key = "#id")
public void delete(Long id) {
userRepository.delete(id);
}
@CacheEvict(value = "users", allEntries = true)
public void clearCache() {
// 清除 users 缓存的所有条目
}
@CacheEvict(value = "users", key = "#user.id", beforeInvocation = true)
public void update(User user) {
// 在方法执行前删除缓存
userRepository.save(user);
}
}@Caching
组合多个缓存操作。
java
@Component
public class UserService {
@Caching(
put = {
@CachePut(value = "users", key = "#user.id"),
@CachePut(value = "usersByEmail", key = "#user.email")
},
evict = {
@CacheEvict(value = "userList", allEntries = true)
}
)
public User save(User user) {
userRepository.save(user);
return user;
}
}缓存键
SpEL 表达式
java
// 使用参数
@Cacheable(value = "users", key = "#id")
public User findById(Long id) { }
// 使用多个参数
@Cacheable(value = "users", key = "#name + ':' + #status")
public List<User> findByNameAndStatus(String name, UserStatus status) { }
// 使用对象属性
@Cacheable(value = "orders", key = "#order.userId + ':' + #order.status")
public List<Order> findOrders(OrderQuery order) { }
// 使用方法返回值
@CachePut(value = "users", key = "#result.id")
public User create(String name) { }
// 使用条件
@Cacheable(value = "users", key = "#id", condition = "#id > 0")
public User findById(Long id) { }
// 使用 unless
@Cacheable(value = "users", key = "#id", unless = "#result == null")
public User findById(Long id) { }自定义键生成器
java
@Component
public class CustomKeyGenerator implements KeyGenerator {
@Override
public Object generate(Object target, Method method, Object... params) {
return target.getClass().getSimpleName() + ":" +
method.getName() + ":" +
Arrays.stream(params)
.map(Object::toString)
.collect(Collectors.joining(":"));
}
}
// 使用
@Cacheable(value = "users", keyGenerator = "customKeyGenerator")
public User findById(Long id) { }缓存管理器
配置缓存管理器
java
@Component
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Arrays.asList(
new ConcurrentMapCache("users"),
new ConcurrentMapCache("orders"),
new ConcurrentMapCache("products")
));
return cacheManager;
}
}自定义缓存配置
java
@Component
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCacheSpecification("maximumSize=1000,expireAfterWrite=3600s");
return cacheManager;
}
@Bean
public CacheManager redisCacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1))
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.withCacheConfiguration("users",
config.entryTtl(Duration.ofMinutes(30)))
.withCacheConfiguration("orders",
config.entryTtl(Duration.ofMinutes(10)))
.build();
}
}编程式缓存
使用 CacheService
java
@Component
public class UserService {
@Inject
private CacheService cacheService;
@Inject
private UserRepository userRepository;
public User findById(Long id) {
String key = "user:" + id;
// 尝试从缓存获取
User user = cacheService.get(key, User.class);
if (user != null) {
return user;
}
// 从数据库查询
user = userRepository.findById(id);
// 存入缓存
if (user != null) {
cacheService.put(key, user, Duration.ofHours(1));
}
return user;
}
public void delete(Long id) {
userRepository.delete(id);
cacheService.evict("user:" + id);
}
public void clearUserCache() {
cacheService.evictByPrefix("user:");
}
}使用 Cache 接口
java
@Component
public class UserService {
@Inject
private CacheManager cacheManager;
public User findById(Long id) {
Cache cache = cacheManager.getCache("users");
// 获取缓存
Cache.ValueWrapper wrapper = cache.get(id);
if (wrapper != null) {
return (User) wrapper.get();
}
// 查询并缓存
User user = userRepository.findById(id);
if (user != null) {
cache.put(id, user);
}
return user;
}
// 使用 get 方法的回调
public User findByIdWithCallback(Long id) {
Cache cache = cacheManager.getCache("users");
return cache.get(id, () -> userRepository.findById(id));
}
}多级缓存
本地 + 远程缓存
java
@Component
public class MultiLevelCacheService {
private final Cache<String, Object> localCache;
private final RedisTemplate<String, Object> redisTemplate;
public MultiLevelCacheService() {
this.localCache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build();
}
public <T> T get(String key, Class<T> type) {
// 1. 先查本地缓存
T value = (T) localCache.getIfPresent(key);
if (value != null) {
return value;
}
// 2. 查 Redis
value = (T) redisTemplate.opsForValue().get(key);
if (value != null) {
// 回填本地缓存
localCache.put(key, value);
return value;
}
return null;
}
public <T> void put(String key, T value, Duration ttl) {
// 同时写入两级缓存
localCache.put(key, value);
redisTemplate.opsForValue().set(key, value, ttl);
}
public void evict(String key) {
localCache.invalidate(key);
redisTemplate.delete(key);
}
}缓存预热
启动时预热
java
@Component
public class CacheWarmer {
@Inject
private UserService userService;
@Inject
private ProductService productService;
@PostConstruct
public void warmUp() {
logger.info("开始缓存预热...");
// 预热热门用户
List<Long> hotUserIds = getHotUserIds();
hotUserIds.forEach(userService::findById);
// 预热热门商品
List<Long> hotProductIds = getHotProductIds();
hotProductIds.forEach(productService::findById);
logger.info("缓存预热完成");
}
}定时刷新
java
@Component
public class CacheRefresher {
@Inject
private CacheManager cacheManager;
@Inject
private UserRepository userRepository;
@Scheduled(fixedRate = 300000) // 每 5 分钟
public void refreshHotData() {
Cache cache = cacheManager.getCache("users");
// 刷新热门数据
List<User> hotUsers = userRepository.findHotUsers();
hotUsers.forEach(user -> cache.put(user.getId(), user));
}
}缓存穿透防护
缓存空值
java
@Component
public class UserService {
private static final Object NULL_VALUE = new Object();
@Inject
private CacheService cacheService;
public User findById(Long id) {
String key = "user:" + id;
Object cached = cacheService.get(key);
if (cached == NULL_VALUE) {
return null; // 缓存的空值
}
if (cached != null) {
return (User) cached;
}
User user = userRepository.findById(id);
if (user == null) {
// 缓存空值,防止穿透
cacheService.put(key, NULL_VALUE, Duration.ofMinutes(5));
} else {
cacheService.put(key, user, Duration.ofHours(1));
}
return user;
}
}布隆过滤器
java
@Component
public class BloomFilterService {
private final BloomFilter<Long> userIdFilter;
public BloomFilterService() {
this.userIdFilter = BloomFilter.create(
Funnels.longFunnel(),
1000000, // 预期元素数量
0.01 // 误判率
);
}
@PostConstruct
public void init() {
// 初始化布隆过滤器
List<Long> allUserIds = userRepository.findAllIds();
allUserIds.forEach(userIdFilter::put);
}
public boolean mightExist(Long userId) {
return userIdFilter.mightContain(userId);
}
public void add(Long userId) {
userIdFilter.put(userId);
}
}
@Component
public class UserService {
@Inject
private BloomFilterService bloomFilter;
public User findById(Long id) {
// 先检查布隆过滤器
if (!bloomFilter.mightExist(id)) {
return null; // 一定不存在
}
// 查缓存和数据库
return findByIdWithCache(id);
}
}缓存击穿防护
互斥锁
java
@Component
public class UserService {
@Inject
private CacheService cacheService;
@Inject
private LockService lockService;
public User findById(Long id) {
String key = "user:" + id;
// 查缓存
User user = cacheService.get(key, User.class);
if (user != null) {
return user;
}
// 获取锁
String lockKey = "lock:user:" + id;
if (lockService.tryLock(lockKey, Duration.ofSeconds(10))) {
try {
// 双重检查
user = cacheService.get(key, User.class);
if (user != null) {
return user;
}
// 查数据库
user = userRepository.findById(id);
if (user != null) {
cacheService.put(key, user, Duration.ofHours(1));
}
return user;
} finally {
lockService.unlock(lockKey);
}
} else {
// 等待并重试
Thread.sleep(100);
return findById(id);
}
}
}逻辑过期
java
@Component
public class UserService {
public User findById(Long id) {
String key = "user:" + id;
CacheEntry<User> entry = cacheService.getEntry(key);
if (entry == null) {
return loadAndCache(id);
}
// 检查逻辑过期
if (entry.isExpired()) {
// 异步刷新
CompletableFuture.runAsync(() -> loadAndCache(id));
}
// 返回旧数据
return entry.getValue();
}
private User loadAndCache(Long id) {
User user = userRepository.findById(id);
if (user != null) {
cacheService.put("user:" + id, user, Duration.ofHours(1));
}
return user;
}
}最佳实践
1. 选择合适的缓存键
java
// ✅ 推荐:有意义的键
@Cacheable(value = "users", key = "'user:' + #id")
// ❌ 不推荐:无意义的键
@Cacheable(value = "users", key = "#id")2. 设置合理的过期时间
java
// ✅ 推荐:根据数据特性设置
@Cacheable(value = "users", key = "#id") // 用户数据,1小时
@Cacheable(value = "config", key = "#key") // 配置数据,24小时
@Cacheable(value = "hotData", key = "#id") // 热点数据,5分钟3. 处理缓存失效
java
// ✅ 推荐:更新时同步清除缓存
@CacheEvict(value = "users", key = "#user.id")
public void update(User user) {
userRepository.save(user);
}4. 避免缓存大对象
java
// ✅ 推荐:只缓存必要的数据
@Cacheable(value = "userSummary", key = "#id")
public UserSummary getUserSummary(Long id) {
return new UserSummary(user.getId(), user.getName());
}
// ❌ 不推荐:缓存大对象
@Cacheable(value = "users", key = "#id")
public User getUser(Long id) {
return userRepository.findByIdWithAllRelations(id);
}常见问题
Q: 缓存和数据库不一致怎么办?
A:
- 使用 Cache-Aside 模式
- 更新数据时先删除缓存
- 设置合理的过期时间
- 使用消息队列同步
Q: 如何监控缓存命中率?
A:
java
@Component
public class CacheMetrics {
@Inject
private CacheManager cacheManager;
public CacheStats getStats(String cacheName) {
Cache cache = cacheManager.getCache(cacheName);
// 返回命中率、大小等统计信息
}
}Q: 缓存雪崩怎么处理?
A:
- 设置随机过期时间
- 使用多级缓存
- 限流降级
- 缓存预热