缓存穿透、击穿、雪崩的应对策略

简介: 本文系统分析了缓存穿透、击穿与雪崩的成因及应对策略,涵盖布隆过滤器、空值缓存、分布式锁、逻辑过期、随机过期时间、多级缓存等解决方案,并结合代码示例与监控告警机制,助力构建高可用缓存体系。

缓存穿透、击穿、雪崩的应对策略

引言

在现代分布式系统中,缓存是提升系统性能和用户体验的关键组件。然而,不当的缓存策略可能导致缓存穿透、击穿、雪崩等问题,严重影响系统稳定性。理解这些问题的成因和解决方案,对于构建健壮的缓存系统至关重要。

缓存穿透问题分析与解决

缓存穿透的成因机制

缓存穿透是指查询一个不存在的数据,由于缓存中没有该数据,请求直接打到数据库,当大量不存在的请求访问时,会导致数据库压力过大。

// 缓存穿透问题演示
public class CachePenetrationProblem {
   
    private RedisTemplate<String, Object> redisTemplate;
    private UserService userService;

    // 问题代码:直接查询不存在的数据
    public User getUserProblem(String userId) {
   
        String cacheKey = "user:" + userId;

        // 1. 从缓存获取
        User user = (User) redisTemplate.opsForValue().get(cacheKey);

        if (user == null) {
   
            // 2. 缓存未命中,查询数据库
            user = userService.findById(userId); // 可能返回null

            if (user != null) {
   
                // 3. 如果存在,写入缓存
                redisTemplate.opsForValue().set(cacheKey, user, Duration.ofMinutes(30));
            }
            // 4. 如果不存在,没有缓存,下次请求还会直达数据库!
        }
        return user;
    }

    // 恶意请求示例
    public void simulatePenetrationAttack() {
   
        List<String> nonExistentIds = generateNonExistentIds(1000);
        nonExistentIds.parallelStream().forEach(id -> {
   
            getUserProblem(id); // 每次都会查询数据库
        });
    }

    private List<String> generateNonExistentIds(int count) {
   
        List<String> ids = new ArrayList<>();
        for (int i = 0; i < count; i++) {
   
            ids.add("nonexistent_" + i);
        }
        return ids;
    }
}

布隆过滤器解决方案

布隆过滤器是解决缓存穿透的经典方案,通过概率性数据结构快速判断数据是否存在。

// 布隆过滤器实现
public class BloomFilter<T> {
   
    private final BitSet bitSet;
    private final int bitSetSize;
    private final double falsePositiveProbability;
    private final int numberOfHashFunctions;
    private final List<HashFunction> hashFunctions;

    public BloomFilter(int expectedElements, double falsePositiveProbability) {
   
        this.falsePositiveProbability = falsePositiveProbability;
        this.bitSetSize = (int) Math.ceil(expectedElements * 
            (-Math.log(falsePositiveProbability) / (Math.log(2) * Math.log(2))));
        this.numberOfHashFunctions = (int) Math.ceil(Math.log(2) * bitSetSize / expectedElements);

        this.bitSet = new BitSet(bitSetSize);
        this.hashFunctions = new ArrayList<>();

        // 初始化多个哈希函数
        for (int i = 0; i < numberOfHashFunctions; i++) {
   
            hashFunctions.add(new SimpleHash(bitSetSize, i));
        }
    }

    // 添加元素
    public void add(T element) {
   
        for (HashFunction hashFunction : hashFunctions) {
   
            int position = hashFunction.hash(element.toString());
            bitSet.set(position);
        }
    }

    // 检查元素是否存在
    public boolean contains(T element) {
   
        for (HashFunction hashFunction : hashFunctions) {
   
            int position = hashFunction.hash(element.toString());
            if (!bitSet.get(position)) {
   
                return false; // 肯定不存在
            }
        }
        return true; // 可能存在
    }

    // 哈希函数接口
    private interface HashFunction {
   
        int hash(String value);
    }

    // 简单哈希函数实现
    private static class SimpleHash implements HashFunction {
   
        private int cap;
        private int seed;

        public SimpleHash(int cap, int seed) {
   
            this.cap = cap;
            this.seed = seed;
        }

        @Override
        public int hash(String value) {
   
            int result = 0;
            int len = value.length();
            for (int i = 0; i < len; i++) {
   
                result = seed * result + value.charAt(i);
            }
            return (cap - 1) & result;
        }
    }
}

// 布隆过滤器缓存实现
public class BloomFilterCache {
   
    private final BloomFilter<String> bloomFilter;
    private final RedisTemplate<String, Object> redisTemplate;
    private final UserService userService;

    public BloomFilterCache() {
   
        // 初始化布隆过滤器,预计100万个元素,误判率0.01
        this.bloomFilter = new BloomFilter<>(1_000_000, 0.01);
        initializeBloomFilter();
    }

    // 初始化布隆过滤器
    private void initializeBloomFilter() {
   
        List<String> allUserIds = userService.getAllUserIds();
        allUserIds.forEach(id -> bloomFilter.add(id));
    }

    // 使用布隆过滤器的缓存获取
    public User getUserWithBloomFilter(String userId) {
   
        // 1. 先检查布隆过滤器
        if (!bloomFilter.contains(userId)) {
   
            return null; // 肯定不存在
        }

        // 2. 布隆过滤器通过,检查缓存
        String cacheKey = "user:" + userId;
        User user = (User) redisTemplate.opsForValue().get(cacheKey);

        if (user == null) {
   
            // 3. 缓存未命中,查询数据库
            user = userService.findById(userId);
            if (user != null) {
   
                // 4. 存在则写入缓存
                redisTemplate.opsForValue().set(cacheKey, user, Duration.ofMinutes(30));
            } else {
   
                // 5. 不存在也写入缓存(空值缓存)
                redisTemplate.opsForValue().set(cacheKey, "NULL", Duration.ofMinutes(5));
            }
        } else if ("NULL".equals(user)) {
   
            return null; // 空值缓存
        }

        return user;
    }

    // 动态更新布隆过滤器
    public void addUserToBloomFilter(String userId) {
   
        bloomFilter.add(userId);
    }

    public void removeUserFromBloomFilter(String userId) {
   
        // 布隆过滤器不支持删除,可以使用计数布隆过滤器或重新构建
    }
}

空值缓存解决方案

空值缓存是另一种解决缓存穿透的有效方案,将查询结果为空的键也缓存起来。

// 空值缓存实现
public class NullValueCache {
   
    private RedisTemplate<String, Object> redisTemplate;
    private UserService userService;

    public User getUserWithNullCache(String userId) {
   
        String cacheKey = "user:" + userId;
        Object cachedValue = redisTemplate.opsForValue().get(cacheKey);

        if (cachedValue != null) {
   
            // 检查是否是空值缓存
            if ("NULL".equals(cachedValue)) {
   
                return null;
            }
            return (User) cachedValue;
        }

        // 查询数据库
        User user = userService.findById(userId);

        if (user != null) {
   
            // 存在则正常缓存
            redisTemplate.opsForValue().set(cacheKey, user, Duration.ofMinutes(30));
        } else {
   
            // 不存在则缓存空值
            redisTemplate.opsForValue().set(cacheKey, "NULL", Duration.ofMinutes(5));
        }

        return user;
    }

    // 批量空值缓存
    public Map<String, User> batchGetUsersWithNullCache(List<String> userIds) {
   
        Map<String, User> result = new HashMap<>();

        // 批量获取缓存
        List<String> cacheKeys = userIds.stream()
            .map(id -> "user:" + id)
            .collect(Collectors.toList());

        List<Object> cachedValues = redisTemplate.opsForValue().multiGet(cacheKeys);

        List<String> needQueryIds = new ArrayList<>();
        for (int i = 0; i < userIds.size(); i++) {
   
            Object cachedValue = cachedValues.get(i);
            if (cachedValue == null) {
   
                needQueryIds.add(userIds.get(i));
            } else if (!"NULL".equals(cachedValue)) {
   
                result.put(userIds.get(i), (User) cachedValue);
            }
        }

        if (!needQueryIds.isEmpty()) {
   
            // 批量查询数据库
            Map<String, User> dbResults = userService.batchFindByIds(needQueryIds);

            // 批量设置缓存
            Map<String, Object> cacheToSet = new HashMap<>();
            for (String userId : needQueryIds) {
   
                User user = dbResults.get(userId);
                if (user != null) {
   
                    cacheToSet.put("user:" + userId, user);
                    result.put(userId, user);
                } else {
   
                    cacheToSet.put("user:" + userId, "NULL");
                }
            }

            redisTemplate.opsForValue().multiSet(cacheToSet);

            // 设置过期时间
            cacheToSet.keySet().forEach(key -> 
                redisTemplate.expire(key, Duration.ofMinutes(30)));
        }

        return result;
    }
}

请求校验与过滤

通过请求参数校验来过滤无效请求。

// 请求校验缓存
public class RequestValidationCache {
   
    private RedisTemplate<String, Object> redisTemplate;
    private UserService userService;

    // 用户ID格式校验
    private static final Pattern USER_ID_PATTERN = Pattern.compile("^[a-zA-Z0-9_-]{1,64}$");

    public User getUserWithValidation(String userId) {
   
        // 1. 参数校验
        if (!isValidUserId(userId)) {
   
            return null;
        }

        // 2. 使用空值缓存
        return getUserWithNullCache(userId);
    }

    private boolean isValidUserId(String userId) {
   
        return userId != null && USER_ID_PATTERN.matcher(userId).matches();
    }

    private User getUserWithNullCache(String userId) {
   
        String cacheKey = "user:" + userId;
        Object cachedValue = redisTemplate.opsForValue().get(cacheKey);

        if (cachedValue != null) {
   
            return "NULL".equals(cachedValue) ? null : (User) cachedValue;
        }

        User user = userService.findById(userId);
        if (user != null) {
   
            redisTemplate.opsForValue().set(cacheKey, user, Duration.ofMinutes(30));
        } else {
   
            redisTemplate.opsForValue().set(cacheKey, "NULL", Duration.ofMinutes(5));
        }
        return user;
    }

    // 黑名单机制
    private final Set<String> blackList = ConcurrentHashMap.newKeySet();

    public void addToBlackList(String userId) {
   
        blackList.add(userId);
    }

    public boolean isBlackListed(String userId) {
   
        return blackList.contains(userId);
    }

    public User getUserWithBlackList(String userId) {
   
        if (isBlackListed(userId)) {
   
            return null; // 黑名单用户直接返回
        }
        return getUserWithValidation(userId);
    }
}

缓存击穿问题分析与解决

缓存击穿的成因机制

缓存击穿是指热点数据在缓存中过期的瞬间,大量请求同时访问数据库,导致数据库压力激增。

// 缓存击穿问题演示
public class CacheBreakdownProblem {
   
    private RedisTemplate<String, Object> redisTemplate;
    private UserService userService;

    // 问题代码:热点数据过期时的并发请求
    public User getHotUserProblem(String userId) {
   
        String cacheKey = "hot_user:" + userId;
        User user = (User) redisTemplate.opsForValue().get(cacheKey);

        if (user == null) {
   
            // 多个请求同时到达这里,都会查询数据库!
            user = userService.findById(userId);
            if (user != null) {
   
                redisTemplate.opsForValue().set(cacheKey, user, Duration.ofMinutes(30));
            }
        }
        return user;
    }

    // 模拟高并发场景
    public void simulateBreakdown() {
   
        String hotUserId = "hot_user_001";
        List<CompletableFuture<User>> futures = new ArrayList<>();

        for (int i = 0; i < 100; i++) {
   
            futures.add(CompletableFuture.supplyAsync(() -> 
                getHotUserProblem(hotUserId)));
        }

        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
            .join();
    }
}

分布式锁解决方案

使用分布式锁保证同一时间只有一个请求查询数据库并更新缓存。

// 分布式锁缓存实现
public class DistributedLockCache {
   
    private RedisTemplate<String, Object> redisTemplate;
    private UserService userService;

    // 使用Redis实现分布式锁
    public User getUserWithDistributedLock(String userId) {
   
        String cacheKey = "user:" + userId;
        String lockKey = "lock:user:" + userId;
        String lockValue = UUID.randomUUID().toString();

        User user = (User) redisTemplate.opsForValue().get(cacheKey);
        if (user != null) {
   
            return user;
        }

        try {
   
            // 获取分布式锁
            if (tryAcquireLock(lockKey, lockValue, Duration.ofSeconds(10))) {
   
                // 双重检查
                user = (User) redisTemplate.opsForValue().get(cacheKey);
                if (user != null) {
   
                    return user;
                }

                // 查询数据库
                user = userService.findById(userId);
                if (user != null) {
   
                    // 随机过期时间,避免缓存雪崩
                    Duration expireTime = Duration.ofMinutes(25 + new Random().nextInt(10));
                    redisTemplate.opsForValue().set(cacheKey, user, expireTime);
                }
                return user;
            } else {
   
                // 获取锁失败,等待一段时间后重试
                Thread.sleep(50);
                return getUserWithDistributedLock(userId);
            }
        } catch (InterruptedException e) {
   
            Thread.currentThread().interrupt();
            throw new RuntimeException("Interrupted while waiting for lock", e);
        } finally {
   
            // 释放锁
            releaseLock(lockKey, lockValue);
        }
    }

    // 尝试获取锁
    private boolean tryAcquireLock(String lockKey, String lockValue, Duration expireTime) {
   
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                       "return redis.call('del', KEYS[1]) else return 0 end";

        Boolean result = redisTemplate.execute(
            new DefaultRedisScript<>("return redis.call('set', KEYS[1], ARGV[1], 'NX', 'EX', ARGV[2])", Boolean.class),
            Collections.singletonList(lockKey),
            lockValue,
            String.valueOf(expireTime.getSeconds())
        );
        return Boolean.TRUE.equals(result);
    }

    // 释放锁
    private void releaseLock(String lockKey, String lockValue) {
   
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                       "return redis.call('del', KEYS[1]) else return 0 end";
        redisTemplate.execute(
            new DefaultRedisScript<>(script, Long.class),
            Collections.singletonList(lockKey),
            lockValue
        );
    }
}

逻辑过期解决方案

使用逻辑过期时间,让缓存数据在物理过期前异步更新。

// 逻辑过期缓存实现
public class LogicalExpireCache {
   
    private RedisTemplate<String, Object> redisTemplate;
    private UserService userService;

    // 缓存对象包装类
    public static class CacheObject<T> {
   
        private T value;
        private long expireTime; // 逻辑过期时间

        public CacheObject(T value, long expireTime) {
   
            this.value = value;
            this.expireTime = expireTime;
        }

        public T getValue() {
    return value; }
        public long getExpireTime() {
    return expireTime; }
        public boolean isExpired() {
    return System.currentTimeMillis() > expireTime; }
    }

    public User getUserWithLogicalExpire(String userId) {
   
        String cacheKey = "user:" + userId;
        CacheObject<User> cacheObject = (CacheObject<User>) redisTemplate.opsForValue().get(cacheKey);

        if (cacheObject == null) {
   
            return loadAndSetCache(userId, cacheKey);
        }

        // 检查逻辑过期
        if (!cacheObject.isExpired()) {
   
            return cacheObject.getValue();
        }

        // 逻辑过期,异步更新缓存
        asyncUpdateCache(userId, cacheKey);
        return cacheObject.getValue(); // 返回过期数据,保证可用性
    }

    private User loadAndSetCache(String userId, String cacheKey) {
   
        User user = userService.findById(userId);
        if (user != null) {
   
            // 设置逻辑过期时间
            long logicalExpireTime = System.currentTimeMillis() + Duration.ofMinutes(25).toMillis();
            // 设置物理过期时间更长
            long physicalExpireTime = System.currentTimeMillis() + Duration.ofHours(2).toMillis();

            CacheObject<User> cacheObject = new CacheObject<>(user, logicalExpireTime);
            redisTemplate.opsForValue().set(cacheKey, cacheObject, Duration.ofHours(2));
        }
        return user;
    }

    private void asyncUpdateCache(String userId, String cacheKey) {
   
        // 使用线程池异步更新
        CompletableFuture.runAsync(() -> {
   
            String lockKey = "lock:async:" + cacheKey;
            String lockValue = UUID.randomUUID().toString();

            if (tryAcquireLock(lockKey, lockValue, Duration.ofSeconds(5))) {
   
                try {
   
                    User user = userService.findById(userId);
                    if (user != null) {
   
                        long logicalExpireTime = System.currentTimeMillis() + Duration.ofMinutes(25).toMillis();
                        CacheObject<User> cacheObject = new CacheObject<>(user, logicalExpireTime);
                        redisTemplate.opsForValue().set(cacheKey, cacheObject, Duration.ofHours(2));
                    }
                } finally {
   
                    releaseLock(lockKey, lockValue);
                }
            }
        });
    }

    private boolean tryAcquireLock(String lockKey, String lockValue, Duration expireTime) {
   
        return redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, expireTime);
    }

    private void releaseLock(String lockKey, String lockValue) {
   
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                       "return redis.call('del', KEYS[1]) else return 0 end";
        redisTemplate.execute(
            new DefaultRedisScript<>(script, Long.class),
            Collections.singletonList(lockKey),
            lockValue
        );
    }
}

信号量限流方案

使用信号量控制并发访问数据库的请求数量。

// 信号量限流缓存
public class SemaphoreCache {
   
    private RedisTemplate<String, Object> redisTemplate;
    private UserService userService;
    private final Map<String, Semaphore> semaphoreMap = new ConcurrentHashMap<>();

    public User getUserWithSemaphore(String userId) {
   
        String cacheKey = "user:" + userId;
        User user = (User) redisTemplate.opsForValue().get(cacheKey);

        if (user != null) {
   
            return user;
        }

        // 获取信号量
        Semaphore semaphore = semaphoreMap.computeIfAbsent(userId, k -> new Semaphore(1));

        try {
   
            if (semaphore.tryAcquire(5, TimeUnit.SECONDS)) {
   
                try {
   
                    // 双重检查
                    user = (User) redisTemplate.opsForValue().get(cacheKey);
                    if (user != null) {
   
                        return user;
                    }

                    // 查询数据库
                    user = userService.findById(userId);
                    if (user != null) {
   
                        redisTemplate.opsForValue().set(cacheKey, user, Duration.ofMinutes(30));
                    }
                    return user;
                } finally {
   
                    semaphore.release();
                }
            } else {
   
                // 获取信号量超时,返回默认值或抛出异常
                throw new RuntimeException("Too many concurrent requests for user: " + userId);
            }
        } catch (InterruptedException e) {
   
            Thread.currentThread().interrupt();
            throw new RuntimeException("Interrupted while waiting for semaphore", e);
        }
    }
}

缓存雪崩问题分析与解决

缓存雪崩的成因机制

缓存雪崩是指大量缓存同时失效,导致大量请求直达数据库,造成数据库压力过大甚至宕机。

// 缓存雪崩问题演示
public class CacheAvalancheProblem {
   
    private RedisTemplate<String, Object> redisTemplate;
    private UserService userService;

    // 问题代码:统一设置相同的过期时间
    public void setUserWithSameExpireTime(String userId, User user) {
   
        String cacheKey = "user:" + userId;
        // 所有缓存都在同一时间过期!
        redisTemplate.opsForValue().set(cacheKey, user, Duration.ofMinutes(30));
    }

    // 批量设置缓存,导致雪崩
    public void batchSetCache(List<User> users) {
   
        users.forEach(user -> {
   
            setUserWithSameExpireTime(user.getId(), user);
        });
    }

    // 模拟缓存雪崩
    public void simulateAvalanche() {
   
        List<User> hotUsers = userService.getHotUsers(1000);
        batchSetCache(hotUsers);

        // 30分钟后,所有缓存同时失效
        // 大量请求直达数据库,造成雪崩
    }
}

随机过期时间方案

为缓存设置随机的过期时间,避免同时失效。

// 随机过期时间缓存
public class RandomExpireCache {
   
    private RedisTemplate<String, Object> redisTemplate;
    private UserService userService;

    public void setUserWithRandomExpire(String userId, User user) {
   
        String cacheKey = "user:" + userId;

        // 基础过期时间
        Duration baseDuration = Duration.ofMinutes(20);
        // 随机范围:0-10分钟
        Duration randomRange = Duration.ofMinutes(new Random().nextInt(10));
        // 总过期时间
        Duration finalDuration = baseDuration.plus(randomRange);

        redisTemplate.opsForValue().set(cacheKey, user, finalDuration);
    }

    // 分层缓存策略
    public void setUserWithTieredExpire(String userId, User user) {
   
        String cacheKey = "user:" + userId;

        // 主缓存:较短的随机过期时间
        Duration mainExpire = Duration.ofMinutes(20 + new Random().nextInt(10));
        redisTemplate.opsForValue().set(cacheKey, user, mainExpire);

        // 备份缓存:较长的固定过期时间
        String backupKey = "user_backup:" + userId;
        Duration backupExpire = Duration.ofHours(2);
        redisTemplate.opsForValue().set(backupKey, user, backupExpire);
    }

    public User getUserWithTieredCache(String userId) {
   
        String cacheKey = "user:" + userId;
        String backupKey = "user_backup:" + userId;

        // 先查主缓存
        User user = (User) redisTemplate.opsForValue().get(cacheKey);
        if (user != null) {
   
            return user;
        }

        // 主缓存未命中,查备份缓存
        user = (User) redisTemplate.opsForValue().get(backupKey);
        if (user != null) {
   
            // 重新设置主缓存
            setUserWithRandomExpire(userId, user);
            return user;
        }

        // 都未命中,查询数据库
        user = userService.findById(userId);
        if (user != null) {
   
            setUserWithRandomExpire(userId, user);
        }
        return user;
    }
}

多级缓存架构

使用多级缓存架构来分散缓存失效的压力。

// 多级缓存实现
public class MultiLevelCache {
   
    private RedisTemplate<String, Object> redisTemplate;
    private UserService userService;

    // 本地缓存(L1)
    private final LoadingCache<String, User> localCache = Caffeine.newBuilder()
        .maximumSize(1000)
        .expireAfterWrite(Duration.ofMinutes(5))
        .build(this::loadFromRemoteCache);

    // 远程缓存(L2)
    public User getUserWithMultiLevel(String userId) {
   
        try {
   
            return localCache.get(userId);
        } catch (Exception e) {
   
            log.error("Error getting user from local cache", e);
            // 降级到远程缓存
            return getUserFromRemote(userId);
        }
    }

    private User loadFromRemoteCache(String userId) {
   
        return getUserFromRemote(userId);
    }

    private User getUserFromRemote(String userId) {
   
        String cacheKey = "user:" + userId;
        User user = (User) redisTemplate.opsForValue().get(cacheKey);

        if (user == null) {
   
            user = userService.findById(userId);
            if (user != null) {
   
                setUserWithRandomExpire(cacheKey, user);
            }
        }
        return user;
    }

    private void setUserWithRandomExpire(String cacheKey, User user) {
   
        Duration expireTime = Duration.ofMinutes(20 + new Random().nextInt(10));
        redisTemplate.opsForValue().set(cacheKey, user, expireTime);
    }

    // 缓存预热
    @PostConstruct
    public void cacheWarmUp() {
   
        List<String> hotUserIds = userService.getHotUserIds();
        hotUserIds.parallelStream().forEach(userId -> {
   
            try {
   
                User user = userService.findById(userId);
                if (user != null) {
   
                    localCache.put(userId, user);
                    String cacheKey = "user:" + userId;
                    Duration expireTime = Duration.ofMinutes(20 + new Random().nextInt(10));
                    redisTemplate.opsForValue().set(cacheKey, user, expireTime);
                }
            } catch (Exception e) {
   
                log.error("Error warming up cache for user: " + userId, e);
            }
        });
    }
}

缓存预热和降级策略

在系统启动或高峰期前进行缓存预热,同时实现降级策略。

// 缓存预热和降级
public class CacheWarmupAndFallback {
   
    private RedisTemplate<String, Object> redisTemplate;
    private UserService userService;
    private final AtomicBoolean isHealthy = new AtomicBoolean(true);

    // 缓存预热
    @EventListener(ApplicationReadyEvent.class)
    public void warmupCache() {
   
        try {
   
            List<String> hotUserIds = userService.getHotUserIds();
            log.info("Starting cache warmup for {} hot users", hotUserIds.size());

            hotUserIds.parallelStream().forEach(userId -> {
   
                try {
   
                    User user = userService.findById(userId);
                    if (user != null) {
   
                        String cacheKey = "user:" + userId;
                        Duration expireTime = Duration.ofMinutes(20 + new Random().nextInt(10));
                        redisTemplate.opsForValue().set(cacheKey, user, expireTime);
                    }
                } catch (Exception e) {
   
                    log.error("Error warming up cache for user: " + userId, e);
                }
            });

            log.info("Cache warmup completed");
        } catch (Exception e) {
   
            log.error("Cache warmup failed", e);
        }
    }

    // 定时缓存刷新
    @Scheduled(fixedRate = 300000) // 每5分钟执行
    public void scheduledCacheRefresh() {
   
        if (!isHealthy.get()) {
   
            return; // 系统不健康时跳过刷新
        }

        List<String> hotUserIds = userService.getHotUserIds();
        hotUserIds.parallelStream().forEach(userId -> {
   
            try {
   
                String cacheKey = "user:" + userId;
                Object cachedValue = redisTemplate.opsForValue().get(cacheKey);

                if (cachedValue != null) {
   
                    // 延长缓存时间
                    redisTemplate.expire(cacheKey, Duration.ofMinutes(30));
                } else {
   
                    // 重新加载数据
                    User user = userService.findById(userId);
                    if (user != null) {
   
                        Duration expireTime = Duration.ofMinutes(20 + new Random().nextInt(10));
                        redisTemplate.opsForValue().set(cacheKey, user, expireTime);
                    }
                }
            } catch (Exception e) {
   
                log.error("Error refreshing cache for user: " + userId, e);
            }
        });
    }

    // 降级策略
    public User getUserWithFallback(String userId) {
   
        if (!isHealthy.get()) {
   
            // 系统降级,返回默认值或从数据库直接获取
            return userService.findById(userId);
        }

        try {
   
            String cacheKey = "user:" + userId;
            User user = (User) redisTemplate.opsForValue().get(cacheKey);

            if (user == null) {
   
                user = userService.findById(userId);
                if (user != null) {
   
                    Duration expireTime = Duration.ofMinutes(20 + new Random().nextInt(10));
                    redisTemplate.opsForValue().set(cacheKey, user, expireTime);
                }
            }
            return user;
        } catch (Exception e) {
   
            log.error("Error getting user from cache", e);
            isHealthy.set(false);
            // 降级到数据库
            return userService.findById(userId);
        }
    }

    // 健康检查
    @Scheduled(fixedRate = 60000) // 每分钟检查一次
    public void healthCheck() {
   
        try {
   
            String testKey = "health_check";
            String testValue = "test";

            redisTemplate.opsForValue().set(testKey, testValue, Duration.ofSeconds(10));
            Object result = redisTemplate.opsForValue().get(testKey);

            if (testValue.equals(result)) {
   
                isHealthy.set(true);
            } else {
   
                isHealthy.set(false);
            }
        } catch (Exception e) {
   
            isHealthy.set(false);
            log.error("Health check failed", e);
        }
    }
}

综合解决方案

统一缓存管理器

将各种解决方案整合到一个统一的缓存管理器中。

// 统一缓存管理器
public class UnifiedCacheManager {
   
    private RedisTemplate<String, Object> redisTemplate;
    private UserService userService;
    private BloomFilter<String> bloomFilter;
    private final Map<String, Semaphore> semaphoreMap = new ConcurrentHashMap<>();

    // 初始化布隆过滤器
    @PostConstruct
    public void initBloomFilter() {
   
        List<String> allUserIds = userService.getAllUserIds();
        this.bloomFilter = new BloomFilter<>(allUserIds.size() * 2, 0.01);
        allUserIds.forEach(id -> bloomFilter.add(id));
    }

    public User getUser(String userId) {
   
        // 1. 参数校验
        if (!isValidUserId(userId)) {
   
            return null;
        }

        // 2. 布隆过滤器检查
        if (!bloomFilter.contains(userId)) {
   
            return null;
        }

        // 3. 尝试从缓存获取
        String cacheKey = "user:" + userId;
        User user = (User) redisTemplate.opsForValue().get(cacheKey);

        if (user != null) {
   
            if ("NULL".equals(user)) {
   
                return null;
            }
            return user;
        }

        // 4. 使用分布式锁防止缓存击穿
        return getUserWithLock(userId, cacheKey);
    }

    private User getUserWithLock(String userId, String cacheKey) {
   
        String lockKey = "lock:user:" + userId;
        String lockValue = UUID.randomUUID().toString();

        try {
   
            if (tryAcquireLock(lockKey, lockValue, Duration.ofSeconds(10))) {
   
                // 双重检查
                User user = (User) redisTemplate.opsForValue().get(cacheKey);
                if (user != null && !"NULL".equals(user)) {
   
                    return (User) user;
                }

                // 查询数据库
                user = userService.findById(userId);

                if (user != null) {
   
                    // 设置随机过期时间防止雪崩
                    Duration expireTime = Duration.ofMinutes(25 + new Random().nextInt(10));
                    redisTemplate.opsForValue().set(cacheKey, user, expireTime);
                } else {
   
                    // 空值缓存
                    redisTemplate.opsForValue().set(cacheKey, "NULL", Duration.ofMinutes(5));
                }

                return user;
            } else {
   
                // 获取锁失败,等待后重试
                Thread.sleep(50);
                return getUser(userId);
            }
        } catch (InterruptedException e) {
   
            Thread.currentThread().interrupt();
            throw new RuntimeException("Interrupted while waiting for lock", e);
        } finally {
   
            releaseLock(lockKey, lockValue);
        }
    }

    private boolean isValidUserId(String userId) {
   
        return userId != null && userId.matches("^[a-zA-Z0-9_-]{1,64}$");
    }

    private boolean tryAcquireLock(String lockKey, String lockValue, Duration expireTime) {
   
        return redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, expireTime);
    }

    private void releaseLock(String lockKey, String lockValue) {
   
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                       "return redis.call('del', KEYS[1]) else return 0 end";
        redisTemplate.execute(
            new DefaultRedisScript<>(script, Long.class),
            Collections.singletonList(lockKey),
            lockValue
        );
    }

    // 批量获取
    public Map<String, User> batchGetUsers(List<String> userIds) {
   
        Map<String, User> result = new HashMap<>();

        // 过滤无效ID
        List<String> validIds = userIds.stream()
            .filter(this::isValidUserId)
            .filter(bloomFilter::contains)
            .collect(Collectors.toList());

        // 批量获取缓存
        List<String> cacheKeys = validIds.stream()
            .map(id -> "user:" + id)
            .collect(Collectors.toList());

        List<Object> cachedValues = redisTemplate.opsForValue().multiGet(cacheKeys);

        Map<String, User> dbResults = new HashMap<>();
        List<String> needQueryIds = new ArrayList<>();

        for (int i = 0; i < validIds.size(); i++) {
   
            Object cachedValue = cachedValues.get(i);
            String userId = validIds.get(i);

            if (cachedValue == null) {
   
                needQueryIds.add(userId);
            } else if ("NULL".equals(cachedValue)) {
   
                result.put(userId, null);
            } else {
   
                result.put(userId, (User) cachedValue);
            }
        }

        if (!needQueryIds.isEmpty()) {
   
            dbResults = userService.batchFindByIds(needQueryIds);

            // 批量设置缓存
            Map<String, Object> cacheToSet = new HashMap<>();
            for (String userId : needQueryIds) {
   
                User user = dbResults.get(userId);
                if (user != null) {
   
                    cacheToSet.put("user:" + userId, user);
                    result.put(userId, user);
                } else {
   
                    cacheToSet.put("user:" + userId, "NULL");
                }
            }

            if (!cacheToSet.isEmpty()) {
   
                redisTemplate.opsForValue().multiSet(cacheToSet);

                // 设置随机过期时间
                cacheToSet.keySet().forEach(key -> {
   
                    Duration expireTime = Duration.ofMinutes(25 + new Random().nextInt(10));
                    redisTemplate.expire(key, expireTime);
                });
            }
        }

        return result;
    }
}

监控和告警

建立完善的监控体系来检测缓存问题。

// 缓存监控
public class CacheMonitor {
   
    private MeterRegistry meterRegistry;
    private RedisTemplate<String, Object> redisTemplate;

    // 缓存命中率监控
    public void recordCacheHit(String cacheType, boolean hit) {
   
        Counter.builder("cache.requests")
            .tag("type", cacheType)
            .tag("hit", String.valueOf(hit))
            .register(meterRegistry)
            .increment();
    }

    // 缓存穿透监控
    public void recordCachePenetration(String userId) {
   
        Counter.builder("cache.penetration")
            .tag("user_id", userId)
            .register(meterRegistry)
            .increment();
    }

    // 缓存击穿监控
    public void recordCacheBreakdown(String userId) {
   
        Counter.builder("cache.breakdown")
            .tag("user_id", userId)
            .register(meterRegistry)
            .increment();
    }

    // 缓存雪崩监控
    public void recordCacheAvalanche() {
   
        Counter.builder("cache.avalanche")
            .register(meterRegistry)
            .increment();
    }

    // 定时检查缓存健康度
    @Scheduled(fixedRate = 60000) // 每分钟检查
    public void checkCacheHealth() {
   
        try {
   
            // 检查缓存命中率
            String hitRateKey = "cache.hit.rate";
            String hitCount = redisTemplate.opsForValue().get("cache.hit.count");
            String totalCount = redisTemplate.opsForValue().get("cache.total.count");

            if (hitCount != null && totalCount != null) {
   
                double hitRate = Double.parseDouble(hitCount) / Double.parseDouble(totalCount);
                Gauge.builder("cache.hit.rate")
                    .register(meterRegistry, () -> hitRate);

                if (hitRate < 0.8) {
    // 命中率低于80%
                    log.warn("Cache hit rate is low: {}", hitRate);
                }
            }
        } catch (Exception e) {
   
            log.error("Error checking cache health", e);
        }
    }
}

缓存穿透、击穿、雪崩是缓存系统设计中的三大挑战。通过合理的策略选择和实现,可以有效解决这些问题。在实际应用中,应根据业务特点和性能要求,选择最适合的解决方案,并建立完善的监控体系来保障系统稳定性。



关于作者



🌟 我是suxiaoxiang,一位热爱技术的开发者

💡 专注于Java生态和前沿技术分享

🚀 持续输出高质量技术内容



如果这篇文章对你有帮助,请支持一下:




👍 点赞


收藏


👀 关注



您的支持是我持续创作的动力!感谢每一位读者的关注与认可!


目录
相关文章
|
9月前
|
Java 开发者
Java高级技术深度解析:性能优化与架构设计
本文深入解析Java高级技术,涵盖JVM性能调优、并发编程、内存模型与架构设计。从G1/ZGC垃圾回收到CompletableFuture异步处理,剖析底层机制与实战优化策略,助力构建高性能、高可用的Java系统。
426 47
|
9月前
|
Java Nacos Sentinel
Spring Cloud Alibaba 深度实战:Nacos + Sentinel + Gateway 整合指南
本指南深入整合Spring Cloud Alibaba核心组件:Nacos实现服务注册与配置管理,Sentinel提供流量控制与熔断降级,Gateway构建统一API网关。涵盖环境搭建、动态配置、服务调用与监控,助你打造高可用微服务架构。(238字)
2216 10
|
监控 前端开发 JavaScript
不了解 QPS、TPS、RT、并发数、吞吐量,劝你简历别写熟悉高并发
分布式、微服务、Service Mesh目前都是大家耳熟能详的词语了,现在随便一个互联网公司说出来大家都是在搞微服务。 但我们搞来搞去,怎么样来衡量一个应用当前的状态到底是怎么样的?到底需不需要扩容?是需要横向扩容还是进行项目重构?
11764 2
|
SQL 关系型数据库 数据库
学习分布式事务Seata看这一篇就够了,建议收藏
学习分布式事务Seata看这一篇就够了,建议收藏
25847 2
|
6月前
|
人工智能 运维 架构师
老金开源Agent Teams编排Skill:一句话自动组队,手动挡时代结束了
加我进AI讨论学习群,公众号右下角“联系方式” 文末有老金的 **开源知识库地址·全免费** --- 术语说明:"Swarm/蜂群"是多Agent协作的通用说法(OpenAI有官方项目叫Swarm),但 Claude Code的官方概念是Agent Teams。本文使用官方术语Agent Teams,保留"蜂群"作为通俗说明。 先说老金我昨儿开源了[老金开源10万字Claude Code中文
5413 10
老金开源Agent Teams编排Skill:一句话自动组队,手动挡时代结束了
|
Java Spring 开发者
Spring Boot 常用注解详解:让你的开发更高效
本文详细解析Spring Boot常用注解,涵盖配置、组件、依赖注入、Web请求、数据验证、事务管理等核心场景,结合实例帮助开发者高效掌握注解使用技巧,提升开发效率与代码质量。
1871 2
|
9月前
|
消息中间件 缓存 NoSQL
Redis + Java 架构实战:从锁机制到消息队列的整合
本文深入解析Redis与Java的整合实践,涵盖分布式锁、消息队列、缓存策略、高性能数据结构及容错机制。结合电商场景,助力构建高并发、高可用的分布式系统。
403 8
|
11月前
|
Java 开发工具
JDK多版本设置并且切换
本文介绍了如何在不卸载旧版本的前提下,配置并切换多个JDK版本。通过统一安装路径、设置环境变量(如JAVA_HOME、JAVA_HOME8、JAVA_HOME17),并调整系统PATH顺序,实现快速切换。最终通过CMD和开发工具验证切换是否生效。
JDK多版本设置并且切换