Java缓存刷新案例怎么实操

wen java案例 27

本文目录导读:

Java缓存刷新案例怎么实操

  1. 基础缓存刷新案例
  2. 使用缓存框架(Caffeine)的刷新机制
  3. 使用Redis的缓存刷新
  4. 使用Spring Cache的刷新机制
  5. 实际业务场景:商品库存缓存刷新
  6. 生产环境使用的缓存监控和刷新工具类
  7. 测试类
  8. 最佳实践建议

我来通过一个完整的实操案例,演示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("缓存已清除");
    }
}

最佳实践建议

  1. 选择合适的刷新策略

    • 定时刷新:适合数据变化不频繁的场景
    • 事件驱动刷新:数据变化时立即刷新
    • 懒加载刷新:访问时检查并刷新
  2. 注意缓存一致性

    • 写操作时主动刷新或删除缓存
    • 使用版本号或时间戳避免脏数据
  3. 缓存穿透防护

    • 空值缓存
    • 布隆过滤器
    • 限流保护
  4. 监控告警

    • 监控缓存命中率
    • 监控缓存大小
    • 设置告警阈值

这个实操案例包含了从基础到高级的缓存刷新实现,你可以根据实际需求选择合适的方案。

抱歉,评论功能暂时关闭!