本文目录导读:

- 基础缓存刷新案例
- 使用缓存框架(Caffeine)的刷新机制
- 使用Redis的缓存刷新
- 使用Spring Cache的刷新机制
- 实际业务场景:商品库存缓存刷新
- 生产环境使用的缓存监控和刷新工具类
- 测试类
- 最佳实践建议
我来通过一个完整的实操案例,演示Java缓存刷新的实现方法。
基础缓存刷新案例
场景:用户信息缓存
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class UserCacheService {
// 使用ConcurrentHashMap存储用户缓存
private static final ConcurrentHashMap<Long, UserInfo> userCache = new ConcurrentHashMap<>();
// 定时任务执行器
private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
// 缓存过期时间(毫秒)
private static final long CACHE_EXPIRE_TIME = 5 * 60 * 1000; // 5分钟
// 存储每个缓存的过期时间
private static final ConcurrentHashMap<Long, Long> expireTimeMap = new ConcurrentHashMap<>();
/**
* 获取用户信息(带缓存)
*/
public UserInfo getUserInfo(Long userId) {
// 检查缓存是否存在
UserInfo cachedUser = userCache.get(userId);
if (cachedUser != null) {
// 检查是否过期
Long expireTime = expireTimeMap.get(userId);
if (System.currentTimeMillis() < expireTime) {
log.info("从缓存获取用户信息: {}", userId);
return cachedUser;
} else {
// 缓存过期,移除
log.info("缓存已过期,移除: {}", userId);
userCache.remove(userId);
expireTimeMap.remove(userId);
}
}
// 从数据库获取
UserInfo user = queryFromDatabase(userId);
// 放入缓存
if (user != null) {
userCache.put(userId, user);
expireTimeMap.put(userId, System.currentTimeMillis() + CACHE_EXPIRE_TIME);
}
return user;
}
/**
* 主动刷新缓存
*/
public void refreshCache(Long userId) {
log.info("主动刷新缓存: {}", userId);
userCache.remove(userId);
expireTimeMap.remove(userId);
getOneUserInfo(userId);
}
/**
* 批量刷新缓存
*/
public void batchRefreshCache() {
log.info("批量刷新所有缓存");
// 获取所有缓存的用户ID
userCache.keySet().forEach(userId -> {
refreshCache(userId);
});
}
/**
* 定时刷新缓存任务
*/
public void startScheduledRefresh() {
scheduler.scheduleAtFixedRate(() -> {
log.info("执行定时缓存刷新任务");
batchRefreshCache();
}, 0, CACHE_EXPIRE_TIME, TimeUnit.MILLISECONDS);
}
private UserInfo queryFromDatabase(Long userId) {
// 模拟数据库查询
return new UserInfo(userId, "用户" + userId, "user" + userId + "@example.com");
}
private UserInfo getOneUserInfo(Long userId) {
UserInfo user = queryFromDatabase(userId);
if (user != null) {
userCache.put(userId, user);
expireTimeMap.put(userId, System.currentTimeMillis() + CACHE_EXPIRE_TIME);
}
return user;
}
// 用户信息实体类
@Data
@AllArgsConstructor
public static class UserInfo {
private Long id;
private String name;
private String email;
}
}
使用缓存框架(Caffeine)的刷新机制
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.github.benmanes.caffeine.cache.Scheduler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.concurrent.TimeUnit;
@Slf4j
@Service
public class CaffeineCacheService {
private LoadingCache<String, String> refreshCache;
@PostConstruct
public void init() {
refreshCache = Caffeine.newBuilder()
// 缓存写入后10秒过期
.expireAfterWrite(10, TimeUnit.SECONDS)
// 缓存访问后10秒过期
.expireAfterAccess(10, TimeUnit.SECONDS)
// 开启缓存刷新功能
.refreshAfterWrite(5, TimeUnit.SECONDS)
// 设置缓存容量
.maximumSize(1000)
// 记录统计信息
.recordStats()
// 设置调度器
.scheduler(Scheduler.systemScheduler())
.build(key -> {
// 缓存失效后的加载方法
log.info("加载缓存数据: {}", key);
return loadDataFromDatabase(key);
});
}
/**
* 获取缓存数据
*/
public String getData(String key) {
return refreshCache.get(key);
}
/**
* 主动刷新特定缓存
*/
public void refreshData(String key) {
log.info("主动刷新缓存: {}", key);
// 使缓存失效
refreshCache.invalidate(key);
// 重新加载
refreshCache.get(key);
}
/**
* 批量刷新缓存
*/
public void refreshAllData() {
log.info("刷新所有缓存");
refreshCache.invalidateAll();
refreshCache.refresh("defaultKey");
}
/**
* 获取缓存统计信息
*/
public CacheStats getCacheStats() {
return refreshCache.stats();
}
private String loadDataFromDatabase(String key) {
// 模拟数据库查询
try {
Thread.sleep(100); // 模拟耗时
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "数据 " + key + " - " + System.currentTimeMillis();
}
}
使用Redis的缓存刷新
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class RedisCacheService {
private final JedisPool jedisPool;
// 缓存过期时间
private static final int CACHE_EXPIRE_SECONDS = 300; // 5分钟
public RedisCacheService() {
this.jedisPool = new JedisPool("localhost", 6379);
}
/**
* 获取缓存数据
*/
public String getCacheData(String key) {
try (Jedis jedis = jedisPool.getResource()) {
String data = jedis.get(key);
if (data != null) {
log.info("从Redis获取缓存: {}", key);
// 更新过期时间(滑动窗口策略)
jedis.expire(key, CACHE_EXPIRE_SECONDS);
return data;
}
// 从数据库加载
String value = loadFromDatabase(key);
if (value != null) {
jedis.setex(key, CACHE_EXPIRE_SECONDS, value);
}
return value;
}
}
/**
* 主动刷新缓存
*/
public void refreshCache(String key) {
log.info("刷新Redis缓存: {}", key);
try (Jedis jedis = jedisPool.getResource()) {
// 删除旧缓存
jedis.del(key);
// 重新加载
String value = loadFromDatabase(key);
if (value != null) {
jedis.setex(key, CACHE_EXPIRE_SECONDS, value);
}
}
}
/**
* 批量刷新缓存(使用管道)
*/
public void batchRefreshCache(String... keys) {
log.info("批量刷新Redis缓存");
try (Jedis jedis = jedisPool.getResource()) {
// 使用管道批量操作
var pipeline = jedis.pipelined();
for (String key : keys) {
pipeline.del(key);
String value = loadFromDatabase(key);
if (value != null) {
pipeline.setex(key, CACHE_EXPIRE_SECONDS, value);
}
}
pipeline.sync();
}
}
private String loadFromDatabase(String key) {
// 模拟数据库查询
return "数据库数据 " + key;
}
}
使用Spring Cache的刷新机制
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class SpringCacheService {
private final CacheManager cacheManager;
public SpringCacheService(CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
/**
* 获取数据(带缓存)
*/
@Cacheable(value = "userCache", key = "#userId")
public UserInfo getUserInfo(Long userId) {
log.info("从数据库获取用户信息: {}", userId);
return queryUserFromDatabase(userId);
}
/**
* 更新并刷新缓存
*/
@CachePut(value = "userCache", key = "#userId")
public UserInfo updateUserInfo(Long userId, UserInfo userInfo) {
log.info("更新用户信息并刷新缓存: {}", userId);
// 更新数据库
updateUserInDatabase(userId, userInfo);
return userInfo;
}
/**
* 清除缓存
*/
@CacheEvict(value = "userCache", key = "#userId")
public void deleteUserCache(Long userId) {
log.info("清除用户缓存: {}", userId);
}
/**
* 清除所有缓存
*/
@CacheEvict(value = "userCache", allEntries = true)
public void clearAllUserCache() {
log.info("清除所有用户缓存");
}
/**
* 组合操作:先清除再更新
*/
@Caching(
evict = @CacheEvict(value = "userCache", key = "#userId"),
put = @CachePut(value = "userCache", key = "#userId")
)
public UserInfo refreshCacheAndUpdate(Long userId, UserInfo userInfo) {
log.info("刷新并更新缓存: {}", userId);
updateUserInDatabase(userId, userInfo);
return userInfo;
}
}
实际业务场景:商品库存缓存刷新
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.concurrent.TimeUnit;
@Slf4j
@Service
public class ProductStockCacheService {
private LoadingCache<String, Integer> stockCache;
@PostConstruct
public void init() {
stockCache = CacheBuilder.newBuilder()
.maximumSize(10000) // 最大缓存数量
.expireAfterWrite(30, TimeUnit.SECONDS) // 写入30秒后过期
.refreshAfterWrite(10, TimeUnit.SECONDS) // 写入10秒后刷新
.recordStats()
.removalListener(notification -> {
log.info("缓存被移除: {} - 原因: {}",
notification.getKey(),
notification.getCause());
})
.build(new CacheLoader<String, Integer>() {
@Override
public Integer load(String key) {
return loadStockFromDatabase(key);
}
@Override
public Integer reload(String key, Integer oldValue) {
log.info("刷新缓存: {},旧值: {}", key, oldValue);
return loadStockFromDatabase(key);
}
});
}
/**
* 获取商品库存
*/
public Integer getStock(String productId) {
try {
return stockCache.get(productId);
} catch (Exception e) {
log.error("获取库存失败: {}", productId, e);
return 0;
}
}
/**
* 扣减库存并刷新缓存
*/
public Integer deductStock(String productId, Integer quantity) {
log.info("扣减库存: {} - {}", productId, quantity);
// 从数据库扣减
Integer currentStock = deductStockFromDatabase(productId, quantity);
// 主动刷新缓存
stockCache.refresh(productId);
return currentStock;
}
/**
* 批量刷新商品库存
*/
public void batchRefreshStock(List<String> productIds) {
log.info("批量刷新商品库存");
productIds.parallelStream().forEach(productId -> {
try {
// 从数据库加载最新库存
Integer stock = loadStockFromDatabase(productId);
// 放入缓存
stockCache.put(productId, stock);
log.info("刷新商品库存成功: {} -> {}", productId, stock);
} catch (Exception e) {
log.error("刷新商品库存失败: {}", productId, e);
}
});
}
private Integer loadStockFromDatabase(String productId) {
// 模拟从数据库加载库存
return 100; // 示例返回
}
private Integer deductStockFromDatabase(String productId, Integer quantity) {
// 模拟数据库库存扣减
return 98; // 示例返回
}
}
生产环境使用的缓存监控和刷新工具类
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class CacheManagementTool {
@Autowired
private CacheManager cacheManager;
@Autowired
private CaffeineCacheService caffeineCacheService;
/**
* 每5分钟检查缓存健康状态
*/
@Scheduled(fixedDelay = 5 * 60 * 1000)
public void checkCacheHealth() {
log.info("开始检查缓存健康状态");
// 获取缓存统计信息
CacheStats stats = caffeineCacheService.getCacheStats();
log.info("缓存统计信息:");
log.info("命中率: {}", stats.hitRate());
log.info("命中次数: {}", stats.hitCount());
log.info("未命中次数: {}", stats.missCount());
log.info("请求总数: {}", stats.requestCount());
// 如果命中率低于阈值,自动刷新缓存
if (stats.hitRate() < 0.8) {
log.warn("缓存命中率过低: {},准备刷新缓存", stats.hitRate());
caffeineCacheService.refreshAllData();
}
}
/**
* 手动刷新指定缓存
*/
public void manualRefresh(String cacheName, String key) {
log.info("手动刷新缓存: {} - {}", cacheName, key);
Cache cache = cacheManager.getCache(cacheName);
if (cache != null) {
cache.evict(key); // 移除缓存
log.info("缓存已清除: {} - {}", cacheName, key);
}
}
/**
* 查看所有缓存的内容
*/
public void viewAllCacheEntries() {
log.info("查看所有缓存条目");
for (String cacheName : cacheManager.getCacheNames()) {
Cache cache = cacheManager.getCache(cacheName);
log.info("缓存名称: {}, 类型: {}", cacheName, cache.getClass().getSimpleName());
}
}
}
测试类
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class CacheRefreshExampleTests {
@Autowired
private UserCacheService userCacheService;
@Autowired
private SpringCacheService springCacheService;
@Test
void testBasicCacheRefresh() throws InterruptedException {
// 测试基础缓存刷新
Long userId = 1L;
// 第一次获取(从数据库加载)
UserInfo user1 = userCacheService.getUserInfo(userId);
System.out.println("第一次获取: " + user1);
// 第二次获取(从缓存)
UserInfo user2 = userCacheService.getUserInfo(userId);
System.out.println("第二次获取: " + user2);
// 等待缓存过期
Thread.sleep(6000); // 等待6秒
// 缓存过期后重新获取
UserInfo user3 = userCacheService.getUserInfo(userId);
System.out.println("过期后获取: " + user3);
}
@Test
void testSpringCacheRefresh() {
// 测试Spring Cache刷新
Long userId = 1L;
UserInfo userInfo = new UserInfo(userId, "张三", "zhangsan@example.com");
// 更新用户信息并刷新缓存
UserInfo updatedUser = springCacheService.updateUserInfo(userId, userInfo);
System.out.println("更新结果: " + updatedUser);
// 清除缓存
springCacheService.deleteUserCache(userId);
System.out.println("缓存已清除");
}
}
最佳实践建议
-
选择合适的刷新策略:
- 定时刷新:适合数据变化不频繁的场景
- 事件驱动刷新:数据变化时立即刷新
- 懒加载刷新:访问时检查并刷新
-
注意缓存一致性:
- 写操作时主动刷新或删除缓存
- 使用版本号或时间戳避免脏数据
-
缓存穿透防护:
- 空值缓存
- 布隆过滤器
- 限流保护
-
监控告警:
- 监控缓存命中率
- 监控缓存大小
- 设置告警阈值
这个实操案例包含了从基础到高级的缓存刷新实现,你可以根据实际需求选择合适的方案。