MultiLevelCacheService.java 8.79 KB
package com.infoloop.tianting.cache;

import lombok.extern.slf4j.Slf4j;

/**
 * 多级缓存服务实现
 * 结合Caffeine本地缓存和Redis分布式缓存
 * 实现L1 + L2缓存架构
 * 
 * 缓存逻辑说明:
 * 1. 查询逻辑:先查Caffeine(60s) -> 未命中再查Redis -> 未命中再查数据库
 * 2. 写入逻辑:先写Redis(业务TTL) -> 再写Caffeine(固定60s)
 * 3. Caffeine过期后正确从Redis读取数据,避免直接查数据库
 */
@Slf4j
public class MultiLevelCacheService implements CacheService {

    private final CaffeineCacheService caffeineCacheService;
    private final RedisCacheService redisCacheService;
    private final CacheProperties cacheProperties;
    
    // 缓存空值的特殊标记
    private static final String NULL_VALUE_MARKER = "$$NULL$$";
    
    public MultiLevelCacheService(CaffeineCacheService caffeineCacheService, 
                                  RedisCacheService redisCacheService,
                                  CacheProperties cacheProperties) {
        this.caffeineCacheService = caffeineCacheService;
        this.redisCacheService = redisCacheService;
        this.cacheProperties = cacheProperties;
        log.info("多级缓存服务初始化完成,Caffeine TTL: {}s", cacheProperties.getCaffeine().getExpireAfterWrite());
    }

    @Override
    public Object get(String key) {
        if (key == null || key.trim().isEmpty()) {
            return null;
        }
        
        log.debug("开始缓存查询: key={}", key);
        
        // 1. 先查本地缓存(Caffeine - L1缓存)
        Object localValue = caffeineCacheService.get(key);
        if (localValue != null) {
            if (NULL_VALUE_MARKER.equals(localValue)) {
                log.debug("本地缓存命中(空值): {}", key);
                return null;
            }
            log.debug("本地缓存命中: {}", key);
            
            // 检查Redis中是否也存在,如果不存在则回写
            if (!redisCacheService.exists(key)) {
                try {
                    // 使用默认TTL回写到Redis
                    int defaultRedisTtl = cacheProperties.getDefaultTtl();
                    redisCacheService.put(key, localValue, defaultRedisTtl);
                    log.debug("本地缓存命中但Redis缺失,已回写: key={}, ttl={}s", key, defaultRedisTtl);
                } catch (Exception e) {
                    log.error("Redis回写失败: key={}, error={}", key, e.getMessage());
                }
            }
            return localValue;
        }

        // 2. 本地缓存未命中,查分布式缓存(Redis - L2缓存)
        log.debug("Caffeine未命中,检查Redis分布式缓存: key={}", key);
        
        Object redisValue = redisCacheService.get(key);
        if (redisValue != null) {
            if (NULL_VALUE_MARKER.equals(redisValue)) {
                // 将空值标记同步到本地缓存,避免重复查询
                caffeineCacheService.put(key, NULL_VALUE_MARKER, cacheProperties.getCaffeine().getExpireAfterWrite());
                log.debug("分布式缓存命中(空值),并同步到本地缓存: {}", key);
                return null;
            }
            // 同步到本地缓存
            caffeineCacheService.put(key, redisValue, cacheProperties.getCaffeine().getExpireAfterWrite());
            log.debug("分布式缓存命中,返回Redis数据: key={}", key);
            return redisValue;
        }

        // 3. 两级缓存都未命中,需要查询数据库
        log.debug("两级缓存都未命中,将查询数据库: key={}", key);
        return null;
    }

    @Override
    public void put(String key, Object value, int ttlSeconds) {
        if (key == null || key.trim().isEmpty()) {
            log.error("缓存key不能为空");
            return;
        }
        
        if (ttlSeconds <= 0) {
            log.error("缓存TTL必须大于0: key={}, ttl={}", key, ttlSeconds);
            return;
        }

        // 处理空值缓存
        Object actualValue = (value == null) ? NULL_VALUE_MARKER : value;
        
        // 写入策略:先写Redis,再写Caffeine
        boolean redisSuccess = false;
        boolean localSuccess = false;
        
        try {
            // 写入分布式缓存Redis,使用注解指定的TTL
            redisCacheService.put(key, actualValue, ttlSeconds);
            redisSuccess = true;
            log.debug("分布式缓存写入成功: key={}, ttl={}s", key, ttlSeconds);
        } catch (Exception e) {
            log.error("分布式缓存写入失败: key={}, error={}", key, e.getMessage(), e);
        }

        try {
            // 2. 写入本地缓存Caffeine,使用配置的过期时间
            int caffeineTtl = cacheProperties.getCaffeine().getExpireAfterWrite();
            caffeineCacheService.put(key, actualValue, caffeineTtl);
            localSuccess = true;
            log.debug("本地缓存写入成功: key={}, ttl={}s", key, caffeineTtl);
        } catch (Exception e) {
            log.error("本地缓存写入失败: key={}, error={}", key, e.getMessage(), e);
        }
        
        if (!localSuccess && !redisSuccess) {
            log.error("缓存写入完全失败: key={}", key);
        }
    }

    @Override
    public void evict(String key) {
        // 双删策略:同时删除本地缓存和分布式缓存
        try {
            caffeineCacheService.evict(key);
            log.debug("本地缓存删除成功: {}", key);
        } catch (Exception e) {
            log.error("本地缓存删除失败: key={}, error={}", key, e.getMessage());
        }

        try {
            redisCacheService.evict(key);
            log.debug("分布式缓存删除成功: {}", key);
        } catch (Exception e) {
            log.error("分布式缓存删除失败: key={}, error={}", key, e.getMessage());
        }
    }

    @Override
    public void evictByPrefix(String prefix) {
        // 双删策略:同时删除本地缓存和分布式缓存的前缀
        try {
            caffeineCacheService.evictByPrefix(prefix);
            log.debug("本地缓存前缀删除成功: {}", prefix);
        } catch (Exception e) {
            log.error("本地缓存前缀删除失败: prefix={}, error={}", prefix, e.getMessage());
        }

        try {
            redisCacheService.evictByPrefix(prefix);
            log.debug("分布式缓存前缀删除成功: {}", prefix);
        } catch (Exception e) {
            log.error("分布式缓存前缀删除失败: prefix={}, error={}", prefix, e.getMessage());
        }
    }

    @Override
    public void clear() {
        // 清空所有缓存
        try {
            caffeineCacheService.clear();
            log.info("本地缓存清空成功");
        } catch (Exception e) {
            log.error("本地缓存清空失败: error={}", e.getMessage());
        }

        try {
            redisCacheService.clear();
            log.info("分布式缓存清空成功");
        } catch (Exception e) {
            log.error("分布式缓存清空失败: error={}", e.getMessage());
        }
    }

    @Override
    public CacheStats getStats() {
        CacheStats localStats = caffeineCacheService.getStats();
        CacheStats redisStats = redisCacheService.getStats();

        return CacheStats.builder()
                .localHits(localStats.getLocalHits())
                .localMisses(localStats.getLocalMisses())
                .redisHits(redisStats.getRedisHits())
                .redisMisses(redisStats.getRedisMisses())
                .totalRequests(calculateTotalRequests(localStats, redisStats))
                .hitRate(calculateHitRate(localStats, redisStats))
                .localSize(localStats.getLocalSize())
                .build();
    }

    @Override
    public boolean exists(String key) {
        // 只要任一缓存存在就返回true
        return caffeineCacheService.exists(key) || redisCacheService.exists(key);
    }

    @Override
    public long getExpire(String key) {
        // 优先返回分布式缓存的过期时间
        long redisExpire = redisCacheService.getExpire(key);
        if (redisExpire >= 0) {
            return redisExpire;
        }

        // 如果Redis没有,则返回本地缓存的过期时间
        return caffeineCacheService.getExpire(key);
    }

    private long calculateTotalRequests(CacheStats localStats, CacheStats redisStats) {
        return localStats.getLocalHits() + localStats.getLocalMisses() +
               redisStats.getRedisHits() + redisStats.getRedisMisses();
    }

    private double calculateHitRate(CacheStats localStats, CacheStats redisStats) {
        long totalHits = localStats.getLocalHits() + redisStats.getRedisHits();
        long totalRequests = calculateTotalRequests(localStats, redisStats);
        return totalRequests > 0 ? (double) totalHits / totalRequests : 0.0;
    }
}