RedisCacheService.java 8.87 KB
package com.infoloop.tianting.cache;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * Redis分布式缓存服务实现(优化版)
 * 包含缓存穿透保护、性能优化等功能
 */
@Slf4j
public class RedisCacheService implements CacheService {

    private final StringRedisTemplate redisTemplate;
    private final ObjectMapper objectMapper;
    private static final String CACHE_PREFIX = "cache:";
    private static final String NULL_VALUE_MARKER = "::NULL::";

    public RedisCacheService(StringRedisTemplate redisTemplate, ObjectMapper objectMapper) {
        this.redisTemplate = redisTemplate;
        this.objectMapper = objectMapper;
    }

    @Override
    public Object get(String key) {
        if (key == null || key.trim().isEmpty()) {
            log.warn("Redis缓存key不能为空");
            return null;
        }
        
        try {
            String jsonValue = redisTemplate.opsForValue().get(CACHE_PREFIX + key);
            if (jsonValue != null) {
                // 处理null值标记,防止缓存穿透
                if (NULL_VALUE_MARKER.equals(jsonValue)) {
                    return null;
                }
                // 使用TypeReference保持类型信息
                return objectMapper.readValue(jsonValue, new TypeReference<Object>() {});
            }
        } catch (Exception e) {
            log.error("Redis缓存读取失败: key={}, error={}", key, e.getMessage(), e);
        }
        return null;
    }

    @Override
    public void put(String key, Object value, int ttlSeconds) {
        if (key == null || key.trim().isEmpty()) {
            log.warn("Redis缓存key不能为空");
            return;
        }
        
        if (ttlSeconds <= 0) {
            log.warn("Redis缓存TTL必须大于0: ttl={}", ttlSeconds);
            return;
        }
        
        try {
            String jsonValue;
            if (value == null) {
                // 缓存null值,防止缓存穿透
                jsonValue = NULL_VALUE_MARKER;
            } else {
                jsonValue = objectMapper.writeValueAsString(value);
            }
            
            redisTemplate.opsForValue().set(CACHE_PREFIX + key, jsonValue,
                Duration.ofSeconds(ttlSeconds));
            log.debug("Redis缓存设置成功: key={}, ttl={}s", key, ttlSeconds);
        } catch (Exception e) {
            log.error("Redis缓存写入失败: key={}, error={}", key, e.getMessage(), e);
        }
    }

    @Override
    public void evict(String key) {
        if (key == null || key.trim().isEmpty()) {
            log.warn("Redis缓存key不能为空");
            return;
        }
        
        try {
            Boolean deleted = redisTemplate.delete(CACHE_PREFIX + key);
            log.debug("Redis缓存删除: key={}, deleted={}", key, deleted);
        } catch (Exception e) {
            log.error("Redis缓存删除失败: key={}, error={}", key, e.getMessage(), e);
        }
    }

    @Override
    public void evictByPrefix(String prefix) {
        if (prefix == null || prefix.trim().isEmpty()) {
            log.warn("Redis缓存前缀不能为空");
            return;
        }
        
        try {
            // 使用SCAN命令替代KEYS命令,避免阻塞Redis
            List<String> keysToDelete = new ArrayList<>();
            String pattern = CACHE_PREFIX + prefix + "*";
            
            // 使用RedisCallback执行SCAN操作
            redisTemplate.execute((RedisCallback<Void>) connection -> {
                try (Cursor<byte[]> cursor = connection.scan(ScanOptions.scanOptions()
                        .match(pattern)
                        .count(100)
                        .build())) {
                        
                    while (cursor.hasNext()) {
                        String key = new String(cursor.next());
                        keysToDelete.add(key);
                        
                        // 批量删除,避免一次性删除过多key
                        if (keysToDelete.size() >= 100) {
                            Long deletedCount = redisTemplate.delete(keysToDelete);
                            log.debug("Redis缓存批量删除: count={}", deletedCount);
                            keysToDelete.clear();
                        }
                    }
                } catch (Exception e) {
                    log.error("SCAN操作异常: {}", e.getMessage(), e);
                }
                return null;
            });
            
            // 删除剩余的key
            if (!keysToDelete.isEmpty()) {
                Long deletedCount = redisTemplate.delete(keysToDelete);
                log.debug("Redis缓存前缀删除完成: prefix={}, total_deleted={}", prefix, deletedCount);
            }
        } catch (Exception e) {
            log.error("Redis缓存前缀删除失败: prefix={}, error={}", prefix, e.getMessage(), e);
        }
    }

    @Override
    public void clear() {
        try {
            // 使用SCAN命令替代KEYS命令,避免阻塞Redis
            List<String> keysToDelete = new ArrayList<>();
            String pattern = CACHE_PREFIX + "*";
            
            long totalDeleted = 0;
            
            // 使用RedisCallback执行SCAN操作
            Long result = redisTemplate.execute((RedisCallback<Long>) connection -> {
                long deleted = 0;
                try (Cursor<byte[]> cursor = connection.scan(ScanOptions.scanOptions()
                        .match(pattern)
                        .count(1000)
                        .build())) {
                        
                    while (cursor.hasNext()) {
                        String key = new String(cursor.next());
                        keysToDelete.add(key);
                        
                        // 批量删除,避免一次性删除过多key
                        if (keysToDelete.size() >= 1000) {
                            Long deletedCount = redisTemplate.delete(keysToDelete);
                            if (deletedCount != null) {
                                deleted += deletedCount;
                            }
                            keysToDelete.clear();
                        }
                    }
                } catch (Exception e) {
                    log.error("SCAN操作异常: {}", e.getMessage(), e);
                }
                return deleted;
            });
            
            if (result != null) {
                totalDeleted += result;
            }
            
            // 删除剩余的key
            if (!keysToDelete.isEmpty()) {
                Long deletedCount = redisTemplate.delete(keysToDelete);
                if (deletedCount != null) {
                    totalDeleted += deletedCount;
                }
            }
            
            if (totalDeleted > 0) {
                log.info("Redis缓存清空成功,删除{}个key", totalDeleted);
            } else {
                log.info("Redis缓存清空完成,无缓存数据");
            }
        } catch (Exception e) {
            log.error("Redis缓存清空失败: error={}", e.getMessage(), e);
        }
    }

    @Override
    public CacheStats getStats() {
        try {
            // Redis没有内置统计信息,这里返回基本信息
            return CacheStats.builder()
                .redisHits(0)
                .redisMisses(0)
                .totalRequests(0)
                .hitRate(0.0)
                .build();
        } catch (Exception e) {
            log.error("Redis缓存统计获取失败: error={}", e.getMessage(), e);
            return CacheStats.builder().build();
        }
    }

    @Override
    public boolean exists(String key) {
        if (key == null || key.trim().isEmpty()) {
            return false;
        }
        
        try {
            return Boolean.TRUE.equals(redisTemplate.hasKey(CACHE_PREFIX + key));
        } catch (Exception e) {
            log.error("Redis缓存存在性检查失败: key={}, error={}", key, e.getMessage(), e);
            return false;
        }
    }

    @Override
    public long getExpire(String key) {
        if (key == null || key.trim().isEmpty()) {
            return -2;
        }
        
        try {
            Long expire = redisTemplate.getExpire(CACHE_PREFIX + key, TimeUnit.SECONDS);
            return expire != null ? expire : -2;
        } catch (Exception e) {
            log.error("Redis缓存过期时间获取失败: key={}, error={}", key, e.getMessage(), e);
            return -2;
        }
    }
}