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

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

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

引言

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

缓存穿透问题分析与解决

缓存穿透的成因机制

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

// 缓存穿透问题演示
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生态和前沿技术分享

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



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




👍 点赞


收藏


👀 关注



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


目录
相关文章
|
21天前
|
Java Nacos Sentinel
Spring Cloud Alibaba 深度实战:Nacos + Sentinel + Gateway 整合指南
本指南深入整合Spring Cloud Alibaba核心组件:Nacos实现服务注册与配置管理,Sentinel提供流量控制与熔断降级,Gateway构建统一API网关。涵盖环境搭建、动态配置、服务调用与监控,助你打造高可用微服务架构。(238字)
534 10
|
存储 人工智能 监控
从代码生成到自主决策:打造一个Coding驱动的“自我编程”Agent
本文介绍了一种基于LLM的“自我编程”Agent系统,通过代码驱动实现复杂逻辑。该Agent以Python为执行引擎,结合Py4j实现Java与Python交互,支持多工具调用、记忆分层与上下文工程,具备感知、认知、表达、自我评估等能力模块,目标是打造可进化的“1.5线”智能助手。
1024 62
|
27天前
|
SQL 数据采集 人工智能
评估工程正成为下一轮 Agent 演进的重点
面向 RL 和在数据层(SQL 或 SPL 环境)中直接调用大模型的自动化评估实践。
886 215
|
安全 Java 开发者
Java 21 新特性详解(Record、Pattern Matching、Switch 改进)
Java 21发布,作为LTS版本带来Record模式匹配、Switch表达式增强等重要特性,提升代码简洁性与可读性。支持嵌套匹配、类型检查与条件判断,结合密封类实现安全多态,优化性能并减少冗余代码,助力开发者构建更高效、清晰的现代Java应用。
286 2
|
数据可视化 Java Nacos
OpenFeign + Sentinel 实现微服务熔断限流实战
本文介绍如何在Spring Cloud微服务架构中,结合OpenFeign与阿里巴巴开源组件Sentinel,实现服务调用的熔断、降级与限流。通过实战步骤搭建user-service与order-service,集成Nacos注册中心与Sentinel Dashboard,演示服务异常熔断、QPS限流控制,并支持自定义限流响应。借助Fallback降级机制与可视化规则配置,提升系统稳定性与高可用性,助力构建健壮的分布式应用。
401 155
|
Nacos 微服务 监控
Nacos:微服务架构中的“服务管家”与“配置中心”
Nacos是阿里巴巴开源的微服务“服务管家”与“配置中心”,集服务注册发现、动态配置管理、健康检查、DNS发现等功能于一体,支持多语言、多协议接入,助力构建高可用、易运维的云原生应用体系。
563 155
|
消息中间件 缓存 监控
缓存与数据库一致性问题的解决策略
本文系统探讨了缓存与数据库一致性问题的根源及解决方案,涵盖Cache-Aside、Read/Write-Through等主流策略,结合分布式锁、消息队列、布隆过滤器等技术应对缓存穿透、击穿与雪崩,并提出版本控制、事件驱动等高级保障机制,辅以监控告警与最佳实践,助力构建高性能、高一致性的分布式系统。
215 0
|
Java API 安全
Java 8 十大新特性详解:Lambda、Stream、Optional 一网打尽
Java 8 十大新特性全面解析,涵盖Lambda表达式、Stream API、Optional类、接口默认方法等核心内容。通过丰富代码示例,深入讲解函数式编程、流式操作、空值安全处理等现代Java开发关键技术,助你提升代码质量与开发效率。
323 0
|
24天前
|
Java 开发者
Java高级技术深度解析:性能优化与架构设计
本文深入解析Java高级技术,涵盖JVM性能调优、并发编程、内存模型与架构设计。从G1/ZGC垃圾回收到CompletableFuture异步处理,剖析底层机制与实战优化策略,助力构建高性能、高可用的Java系统。
175 47

热门文章

最新文章