Redis(三):缓存雪崩及其解决方案(SpringBoot+mybatis-plus)
·
一、概述
缓存雪崩
定义: 在同一时间段内,大量缓存键(key)同时过期失效或 Redis 服务宕机,导致所有请求直接涌向数据库,造成数据库瞬时压力过大而崩溃;
核心问题: 高并发请求穿透缓存,击垮数据库。
核心解决方案:
- 差异化过期时间: 给缓存设置过期时间时,添加一个随机值,避免同时失效。
- 高可用集群: 使用 Redis 哨兵或集群模式,防止单点故障。
- 服务降级与熔断: 当数据库压力过大时,对非核心服务进行降级或直接熔断,保护数据库。
- 永不过期 + 后台更新: 缓存不设过期时间,由程序异步更新(此方案较复杂,需权衡一致性)。
二、代码
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 "熔断器已重置";
}
}
更多推荐
所有评论(0)