一、概述

缓存雪崩
定义: 在同一时间段内,大量缓存键(key)同时过期失效或 Redis 服务宕机,导致所有请求直接涌向数据库,造成数据库瞬时压力过大而崩溃;
核心问题: 高并发请求穿透缓存,击垮数据库。
核心解决方案:

  1. 差异化过期时间: 给缓存设置过期时间时,添加一个随机值,避免同时失效。
  2. 高可用集群: 使用 Redis 哨兵或集群模式,防止单点故障。
  3. 服务降级与熔断: 当数据库压力过大时,对非核心服务进行降级或直接熔断,保护数据库。
  4. 永不过期 + 后台更新: 缓存不设过期时间,由程序异步更新(此方案较复杂,需权衡一致性)。

二、代码

2.1 controller层

package com.study.sredis.stept001.controller;

import com.study.sredis.stept001.domain.User;
import com.study.sredis.stept001.service.User4Service;
import com.study.sredis.utils.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.*;

/**
 * 缓冲雪崩
 */
@RestController
@RequestMapping("/avalanche2")
public class CacheTest4Controller {
    @Autowired
    private User4Service user4Service;

    /**
     * 获取用户信息 —— 集成所有缓存解决方案
     *
     * @param user
     * @return
     */
    @PostMapping("/getUser")
    public R getUser(@RequestBody User user) {
        long startTime = System.currentTimeMillis();
        User userInfo = user4Service.getUserById(user);

        HashMap<String, Object> result = new HashMap<>();
        result.put("success", true);
        result.put("data", userInfo);
        result.put("responseTime", System.currentTimeMillis() - startTime + "ms");
        result.put("timestamp", System.currentTimeMillis());
        return R.ok(result);
    }

    /**
     * 更新用户信息
     *
     * @param user
     * @return
     */
    @PostMapping("/updateUser")
    public R updateUser(@RequestBody User user) {
        HashMap<String, Object> result = new HashMap<>();
        try {
            String message = user4Service.updateUser(user);
            result.put("success", true);
            result.put("message", message);
            result.put("data", user);
        } catch (Exception e) {
            result.put("success", false);
            result.put("message", "更新失败:" + e.getMessage());
        }
        return R.ok(result);
    }

    /**
     * 获取批量用户信息
     *
     * @param user
     * @return
     */
    @PostMapping("/batchUserInfo")
    public R getBatchUserInfo(@RequestBody User user) {
        long startTime = System.currentTimeMillis();
        List<User> usersBatch = user4Service.getUsersBatch(user);
        HashMap<Object, Object> result = new HashMap<>();
        result.put("success", true);
        result.put("data", usersBatch);
        result.put("responseTime", System.currentTimeMillis() - startTime + "ms");

        return R.ok(result);
    }

    /**
     * 手动触发缓存预热
     *
     * @return
     */
    @PostMapping("/warmup")
    public R warmUpCache() {
        HashMap<String, Object> result = new HashMap<>();
        try {
            String message = user4Service.warmUpCache();
            result.put("success", true);
            result.put("message", message);
        } catch (Exception e) {
            result.put("success", false);
            result.put("message", "缓存预热失败:" + e.getMessage());
        }
        return R.ok(result);
    }

    /**
     * 获取缓存统计信息
     *
     * @return
     */
    @PostMapping("/stats")
    public R getCacheStats() {
        Map<String, Object> stats = user4Service.getCacheStats();
        stats.put("success", true);
        return R.ok(stats);
    }

    /**
     * 获取熔断器状态
     *
     * @return
     */
    @PostMapping("/getCircuitBreakerStatus")
    public R getCircuitBreakerStatus() {
        HashMap<String, Object> result = new HashMap<>();
        result.put("success", true);
        result.put("data", user4Service.getCircuitBreakerStatus());
        return R.ok(result);
    }

    /**
     * 重置熔断器
     *
     * @return
     */
    @PostMapping("/resetCircuitBreaker")
    public R resetCircuitBreaker() {
        HashMap<String, Object> result = new HashMap<>();
        try {
            String message = user4Service.resetCircuitBreaker();
            result.put("success", true);
            result.put("message", message);
        } catch (Exception e) {
            result.put("success", false);
            result.put("message", "重置失败:" + e.getMessage());
        }
        return R.ok(result);
    }

    //    ==================测试==============

    /**
     * 模拟缓存雪崩场景 —— 慎重使用
     *
     * @return
     */
    @PostMapping("/simulateCacheAvalanche")
    public R simulateCacheAvalanche() {
        HashMap<String, Object> result = new HashMap<>();
        try {
            String message = user4Service.simulateCacheAvalanche();
            result.put("success", true);
            result.put("message", message);
            result.put("warning", "这是一个测试接口,会在生产环境造成缓存雪崩,请慎重使用!");
        } catch (Exception e) {
            result.put("success", false);
            result.put("message", "模拟失败:" + e.getMessage());
        }
        return R.ok(result);
    }

    /**
     * 压力测试接口
     *
     * @return
     */
    @PostMapping("/pressureTest")
    public R pressureTest() {
        Map<String, Object> result = new HashMap<>();
        List<Map<String, Object>> testResults = new ArrayList<>();
//        模拟并发查询
        List<Integer> testIds = Arrays.asList(1, 2, 3, 4, 5);
        for (Integer id : testIds) {
            long startTime = System.currentTimeMillis();
            User user = new User();
            user.setId(id);
            User userInfo = user4Service.getUserById(user);
            long endTime = System.currentTimeMillis();

            HashMap<String, Object> testResult = new HashMap<>();
            testResult.put("id", id);
            testResult.put("userName", userInfo.getUserName());
            testResult.put("responseTime", endTime - startTime + "ms");
            testResults.add(testResult);
        }
        result.put("success", true);
        result.put("testResult", testResults);
        result.put("message", "压力测试完成,检查响应时间和熔断器状态");
        return R.ok(result);
    }

    /**
     * 健康检查接口
     *
     * @return
     */
    @PostMapping("/healthCheck")
    public R healthCheck() {
        HashMap<String, Object> health = new HashMap<>();
        health.put("status", "UP");
        health.put("service", "Cache Avalanche Solution");
        health.put("timestamp", System.currentTimeMillis());
        health.put("version", "1.0");
//        添加缓存系统健康状态
        try {
            Map<String, Object> cacheStats = user4Service.getCacheStats();
            health.put("cacheSystem", "HEALTHY");
            health.put("cacheStats", cacheStats);
        } catch (Exception e) {
            health.put("cacheSystem", "UNHEALTHY");
            health.put("error", e.getMessage());
        }
        return R.ok(health);
    }

}

2.2 service层

2.2.1 service接口

package com.study.sredis.stept001.service;

import com.study.sredis.stept001.domain.User;

import java.util.List;
import java.util.Map;

public interface User4Service {
    public User getUserById(User user);

    public String updateUser(User user);
    public List<User> getUsersBatch(User user);
    public String warmUpCache();
    public Map<String, Object> getCacheStats();
    public Map<String, Object> getCircuitBreakerStatus();
    public String resetCircuitBreaker();
    public String simulateCacheAvalanche();
}

2.2.2 service实现类

package com.study.sredis.stept001.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.study.sredis.stept001.domain.User;
import com.study.sredis.stept001.mapper.userMapper;
import com.study.sredis.stept001.service.User4Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;

/**
 * 缓存雪崩
 * 定义:在同一时间段内,大量缓存键(key)同时过期失效或 Redis 服务宕机,导致所有请求直接涌向数据库,造成数据库瞬时压力过大而崩溃;
 * 核心问题:高并发请求穿透缓存,击垮数据库。
 * 核心解决方案:
 * (1)差异化过期时间:给缓存设置过期时间时,添加一个随机值,避免同时失效。
 * (2)高可用集群:使用 Redis 哨兵或集群模式,防止单点故障。
 * (3)服务降级与熔断:当数据库压力过大时,对非核心服务进行降级或直接熔断,保护数据库。
 * (4)永不过期 + 后台更新:缓存不设过期时间,由程序异步更新(此方案较复杂,需权衡一致性)。
 */
@Service
public class User4ServiceImpl extends ServiceImpl<userMapper, User> implements User4Service {
    @Autowired
    private userMapper userMapper;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    /**
     * 解决方案1:本地缓存——多级缓存
     */
    private final Cache<String, Object> localCache = Caffeine.newBuilder()
            .expireAfterWrite(10, TimeUnit.MINUTES)
            .maximumSize(1000)
            .build();
    /**
     * 解决方案2:熔断器状态
     */
    private final AtomicInteger failureCount = new AtomicInteger(0);
    private volatile boolean circuitOpen = false;
    private long circuitOpenTime = 0;
    private static final int FAILURE_THRESHOLD = 5;
    private static final long CIRCUIT_TIMEOUT = 30000;
    /**
     * 缓存key常量
     */
    private static final String USER_KEY_PREFIX = "user:";
    private static final String PROOUCT_KEY_PREFIX = "product";

    //    ========================解决方案1:随机过期时间================

    /**
     * 获取随机过期时间(30-60分钟)
     *
     * @return
     */
    private int getRandomExpireTime() {
        return 30 + new Random().nextInt(31);  //30-60分钟
    }

    /**
     * 设置缓存带随机过期时间
     *
     * @param key
     * @param value
     */
    private void setWithRandomTtl(String key, Object value) {
        int expireMinutes = getRandomExpireTime();
        redisTemplate.opsForValue().set(key, value, expireMinutes, TimeUnit.MINUTES);
    }

    //    ===========================解决方案2:多级缓存策略=======================
    public <T> T getMultiLevelCache(String key, Class<T> type, Supplier<T> databaseSupplier) {
//        1.检查本地缓存
        T value = (T) localCache.getIfPresent(key);
        if (value != null) {
            return value;
        }
//        2.检查Redis缓存
        value = (T) redisTemplate.opsForValue().get(key);
        if (value != null) {
//            回填本地缓存
            localCache.put(key, value);
            return value;
        }
//        3.查询数据库
        value = databaseSupplier.get();
        if (value != null) {
//            异步写入多级缓存
            writeToCacheAsync(key, value);
        }
        return value;
    }

    /**
     * 异步写入多级缓存
     *
     * @param key
     * @param value
     * @param <T>
     */
    private <T> void writeToCacheAsync(String key, T value) {
        CompletableFuture.runAsync(() -> {
            try {
//                写入本地缓存
                localCache.put(key, value);
//                写入Redis,使用随机过期时间
                setWithRandomTtl(key, value);
            } catch (Exception e) {

            }
        });
    }

    //    ===============================解决方案3:熔断降级==========================

    /**
     * 检查熔断器状态
     *
     * @return
     */
    private boolean checkCircuitBreaker() {
        if (!circuitOpen) {
            return true;
        }
//        检查是否超过熔断超时时间
        if (System.currentTimeMillis() - circuitOpenTime > CIRCUIT_TIMEOUT) {
            circuitOpen = false;
            failureCount.set(0);
            return true;
        }
        return false;
    }

    /**
     * 记录成功
     */
    private void recordSuccess() {
        failureCount.set(0);
    }

    /**
     * 记录失败
     */
    private void recordFailure() {
        int failures = failureCount.incrementAndGet();
        if (failures >= FAILURE_THRESHOLD && !circuitOpen) {
            circuitOpen = true;
            circuitOpenTime = System.currentTimeMillis();
        }
    }

    /**
     * 获取熔断器状态
     *
     * @return
     */
    @Override
    public Map<String, Object> getCircuitBreakerStatus() {
        HashMap<String, Object> status = new HashMap<>();
        status.put("circuitOpen", circuitOpen);
        status.put("failureCount", failureCount);
        status.put("openTime", circuitOpenTime);
        status.put("currentTime", System.currentTimeMillis());
        return status;
    }

    //    ======================解决方案4:缓存预热======================
    @Override
    public String warmUpCache() {
//        预热热点用户数据
        List<User> hotUsers = userMapper.selectList(
                new QueryWrapper<User>()
                        .orderByDesc("create_time")
                        .last("LIMIT 50")
        );
        int successCount = 0;
        for (User user : hotUsers) {
            try {
                String cacheKey = USER_KEY_PREFIX + user.getId();
                setWithRandomTtl(cacheKey, user);
                localCache.put(cacheKey, user);
                successCount++;
            } catch (Exception e) {
            }
        }
        return String.format("缓存预热完成,成功预热%d个用户数据", successCount);
    }


    // ==================== 业务方法 ====================

    /**
     * 获取用户信息 - 集成所有解决方案
     */
    @Override
    public User getUserById(User user) {
        String cacheKey = USER_KEY_PREFIX + user.getId();

        // 1. 检查熔断器
        if (!checkCircuitBreaker()) {
            return getFallbackUser(user);
        }

        try {
            // 2. 使用多级缓存获取数据
            User userInfo = getMultiLevelCache(cacheKey, User.class, () -> {
                User dbUser = userMapper.selectById(user.getId());
                if (dbUser == null) {
                    // 缓存空值防止缓存穿透
                    cacheNullValue(cacheKey);
                }
                return dbUser;
            });

            // 3. 记录成功
            recordSuccess();
            return userInfo;

        } catch (Exception e) {
            // 4. 记录失败
            recordFailure();
            return getFallbackUser(user);
        }
    }

    /**
     * 更新用户信息
     */
    @Override
    public String updateUser(User user) {
        try {
            // 1. 更新数据库
            int result = userMapper.updateById(user);
            if (result > 0) {
                // 2. 删除缓存
                String cacheKey = USER_KEY_PREFIX + user.getId();
                evictCache(cacheKey);

                // 3. 异步重新加载缓存
                CompletableFuture.runAsync(() -> {
                    getMultiLevelCache(cacheKey, User.class, () -> user);
                });
                return "用户更新成功";
            }
            return "用户更新失败";
        } catch (Exception e) {
            recordFailure();
            return "用户更新异常: " + e.getMessage();
        }
    }

    /**
     * 批量获取用户信息 - 测试缓存雪崩场景
     */
    @Override
    public List<User> getUsersBatch(User user) {
        List<User> users = new ArrayList<>();
        for (Integer userId:user.getIds()) {
            User user1 = new User();
            user1.setId(userId);
            User userInfo = getUserById(user1);
            if (userInfo != null && userInfo.getId() != null) {
                users.add(userInfo);
            }
        }
        return users;
    }

    /**
     * 模拟大量缓存同时过期 - 用于测试
     */
    @Override
    public String simulateCacheAvalanche() {
        List<User> users = userMapper.selectList(new QueryWrapper<User>().last("LIMIT 100"));
        for (User user : users) {
            String cacheKey = USER_KEY_PREFIX + user.getId();
            // 设置相同的短过期时间,模拟雪崩
            redisTemplate.opsForValue().set(cacheKey, user, 1, TimeUnit.MINUTES);
        }
        return "已设置100个用户缓存,1分钟后同时过期,模拟缓存雪崩场景";
    }

    // ==================== 工具方法 ====================

    /**
     * 删除多级缓存
     */
    private void evictCache(String key) {
        localCache.invalidate(key);
        redisTemplate.delete(key);
        System.out.println("缓存已清除: " + key);
    }

    /**
     * 缓存空值防止缓存穿透
     */
    private void cacheNullValue(String key) {
        String nullKey = key + ":null";
        redisTemplate.opsForValue().set(nullKey, "NULL", 5, TimeUnit.MINUTES);
    }

    /**
     * 熔断降级数据
     */
    private User getFallbackUser(User user) {
        User fallback = new User();
        fallback.setId(user.getId());
        fallback.setUserName("系统繁忙,请稍后重试");
        return fallback;
    }

    /**
     * 获取缓存统计信息
     */
    @Override
    public Map<String, Object> getCacheStats() {
        Map<String, Object> stats = new HashMap<>();
        stats.put("localCacheSize", localCache.estimatedSize());
        stats.put("circuitBreakerStatus", getCircuitBreakerStatus());
        stats.put("currentTime", System.currentTimeMillis());
        return stats;
    }

    /**
     * 重置熔断器 - 用于测试
     */
    @Override
    public String resetCircuitBreaker() {
        circuitOpen = false;
        failureCount.set(0);
        circuitOpenTime = 0;
        return "熔断器已重置";
    }
}
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐