一、缓存穿透核心问题
1. 问题定义
sql
复制
下载
缓存穿透 = 查询不存在的数据 → 缓存不命中 → 直接访问数据库
影响:恶意攻击可导致数据库被压垮
2. 三种缓存异常场景对比
| 问题 | 发生条件 | 影响程度 | 解决难度 |
|---|---|---|---|
| 缓存穿透 | 查询不存在数据 | ⭐⭐⭐⭐⭐ | ⭐⭐ |
| 缓存击穿 | 热点key过期瞬间 | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| 缓存雪崩 | 大量key同时过期 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
二、基础解决方案
1. 空值缓存(基础方案)
java
复制
下载
@Component
public class CachePenetrationService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String NULL_VALUE = "NULL";
private static final long NULL_CACHE_TTL = 300; // 5分钟
public User getUserById(Long userId) {
String cacheKey = "user:" + userId;
// 1. 先查缓存
Object cacheValue = redisTemplate.opsForValue().get(cacheKey);
// 2. 缓存命中
if (cacheValue != null) {
// 处理空值标记
if (NULL_VALUE.equals(cacheValue)) {
return null; // 缓存了空值,直接返回null
}
return (User) cacheValue;
}
// 3. 缓存未命中,查数据库
User user = userDao.findById(userId);
// 4. 写入缓存
if (user != null) {
redisTemplate.opsForValue().set(
cacheKey,
user,
1, TimeUnit.HOURS // 正常数据TTL
);
} else {
// 空值缓存,防止穿透
redisTemplate.opsForValue().set(
cacheKey,
NULL_VALUE,
NULL_CACHE_TTL, TimeUnit.SECONDS // 短TTL
);
}
return user;
}
}
2. 布隆过滤器(Bloom Filter)
2.1 基础实现
java
复制
下载
@Component
public class BloomFilterService {
// Guava布隆过滤器
private BloomFilter<String> bloomFilter;
@PostConstruct
public void init() {
// 预估元素数量100万,误判率0.01%
bloomFilter = BloomFilter.create(
Funnels.stringFunnel(Charset.defaultCharset()),
1_000_000,
0.001
);
// 预热数据
loadExistingKeys();
}
public boolean mightExist(String key) {
return bloomFilter.mightContain(key);
}
public void addKey(String key) {
bloomFilter.put(key);
}
// Redis原生布隆过滤器(Redis 4.0+)
public boolean redisBloomFilterCheck(Long userId) {
String key = "user_bloom";
String item = "user:" + userId;
// BF.EXISTS 检查是否存在
// BF.ADD 添加元素
// BF.MADD 批量添加
// BF.RESERVE 初始化布隆过滤器
return redisTemplate.execute(
(RedisCallback<Boolean>) connection ->
connection.execute("BF.EXISTS", key.getBytes(), item.getBytes())
);
}
}
2.2 分布式布隆过滤器
java
复制
下载
@Component
public class DistributedBloomFilter {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String BLOOM_KEY = "global:bloom:user";
/**
* 使用Redis位图实现分布式布隆过滤器
*/
public boolean mightExist(Long userId) {
// 1. 多个哈希函数计算位位置
int[] positions = hashPositions(userId);
// 2. 检查所有位是否为1
List<Object> results = redisTemplate.executePipelined(
(RedisCallback<Object>) connection -> {
for (int pos : positions) {
connection.getBit(BLOOM_KEY.getBytes(), pos);
}
return null;
}
);
// 3. 所有位都为1才返回true(可能有误判)
return results.stream().allMatch(result ->
Boolean.TRUE.equals(result)
);
}
public void add(Long userId) {
int[] positions = hashPositions(userId);
redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
for (int pos : positions) {
connection.setBit(BLOOM_KEY.getBytes(), pos, true);
}
return null;
});
}
private int[] hashPositions(Long userId) {
// 使用多个哈希函数
String key = "user:" + userId;
return new int[] {
Math.abs(key.hashCode()) % 1000000,
Math.abs(murmur3Hash(key)) % 1000000,
Math.abs(fnv1aHash(key)) % 1000000
};
}
}
三、高级防护方案
1. 限流与熔断
1.1 请求限流
java
复制
下载
@Component
public class RateLimiterService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 滑动窗口限流
public boolean allowRequest(String key, int maxRequests, long windowSeconds) {
long currentTime = System.currentTimeMillis();
long windowStart = currentTime - (windowSeconds * 1000);
String redisKey = "rate_limit:" + key;
// 使用ZSET记录请求时间戳
redisTemplate.opsForZSet().removeRangeByScore(
redisKey, 0, windowStart
);
// 获取当前窗口请求数
Long count = redisTemplate.opsForZSet().zCard(redisKey);
if (count < maxRequests) {
// 允许请求,记录时间戳
redisTemplate.opsForZSet().add(
redisKey,
String.valueOf(currentTime),
currentTime
);
redisTemplate.expire(redisKey, windowSeconds + 1, TimeUnit.SECONDS);
return true;
}
return false;
}
// 针对不存在key的请求特殊限流
public boolean allowNullKeyRequest(String keyPrefix, Long id) {
String key = keyPrefix + ":null:" + id;
return allowRequest(key, 10, 60); // 60秒最多10次
}
}
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案
【点击此处即可/免费获取】
1.2 熔断器模式
java
复制
下载
@Component
public class CircuitBreakerService {
private static final int FAILURE_THRESHOLD = 10;
private static final long TIMEOUT = 30000; // 30秒
private final AtomicInteger failureCount = new AtomicInteger(0);
private final AtomicLong lastFailureTime = new AtomicLong(0);
private volatile State state = State.CLOSED;
enum State { CLOSED, OPEN, HALF_OPEN }
public <T> T execute(Supplier<T> supplier, T fallbackValue) {
if (state == State.OPEN) {
// 熔断器打开,检查是否需要进入半开状态
if (System.currentTimeMillis() - lastFailureTime.get() > TIMEOUT) {
state = State.HALF_OPEN;
} else {
return fallbackValue; // 快速失败
}
}
try {
T result = supplier.get();
if (state == State.HALF_OPEN) {
// 半开状态成功,关闭熔断器
state = State.CLOSED;
failureCount.set(0);
}
return result;
} catch (Exception e) {
handleFailure();
return fallbackValue;
}
}
private void handleFailure() {
long currentTime = System.currentTimeMillis();
lastFailureTime.set(currentTime);
if (failureCount.incrementAndGet() >= FAILURE_THRESHOLD) {
state = State.OPEN;
}
}
}
2. 异步更新与预热
2.1 缓存预热
java
复制
下载
@Component
public class CacheWarmUpService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 启动时预热热点数据
@EventListener(ApplicationReadyEvent.class)
public void warmUpCache() {
List<Long> hotUserIds = userDao.findHotUserIds(1000);
// 批量预热
Map<String, User> cacheData = new HashMap<>();
for (Long userId : hotUserIds) {
cacheData.put("user:" + userId, userDao.findById(userId));
}
redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
for (Map.Entry<String, User> entry : cacheData.entrySet()) {
byte[] key = entry.getKey().getBytes();
byte[] value = serialize(entry.getValue());
connection.setEx(key, 3600, value); // 1小时过期
}
return null;
});
}
// 定时预热
@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点
public void scheduledWarmUp() {
warmUpCache();
}
}
2.2 异步更新策略
java
复制
下载
@Component
public class AsyncUpdateService {
@Autowired
private ThreadPoolTaskExecutor taskExecutor;
private final Map<String, CompletableFuture<?>> updateTasks =
new ConcurrentHashMap<>();
public User getUserAsync(Long userId) {
String cacheKey = "user:" + userId;
// 1. 先返回缓存旧数据
User cachedUser = (User) redisTemplate.opsForValue().get(cacheKey);
// 2. 异步更新
if (cachedUser != null && isNeedUpdate(cachedUser)) {
String taskKey = "update:" + userId;
if (!updateTasks.containsKey(taskKey)) {
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
try {
User freshUser = userDao.findById(userId);
redisTemplate.opsForValue().set(
cacheKey, freshUser, 1, TimeUnit.HOURS
);
} finally {
updateTasks.remove(taskKey);
}
}, taskExecutor);
updateTasks.put(taskKey, future);
}
}
return cachedUser;
}
private boolean isNeedUpdate(User user) {
// 根据业务规则判断是否需要更新
return System.currentTimeMillis() - user.getUpdateTime().getTime()
> 5 * 60 * 1000; // 超过5分钟
}
}
四、多级缓存架构
1. 本地缓存 + Redis
java
复制
下载
@Component
@Slf4j
public class MultiLevelCacheService {
// 本地缓存(Caffeine)
private final Cache<String, Object> localCache = Caffeine.newBuilder()
.maximumSize(10000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.recordStats()
.build();
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public User getUserMultiLevel(Long userId) {
String cacheKey = "user:" + userId;
// 1. 查本地缓存
User user = (User) localCache.getIfPresent(cacheKey);
if (user != null) {
log.debug("命中本地缓存: {}", userId);
return user;
}
// 2. 查Redis
user = (User) redisTemplate.opsForValue().get(cacheKey);
if (user != null) {
// 回填本地缓存
localCache.put(cacheKey, user);
log.debug("命中Redis缓存: {}", userId);
return user;
}
// 3. 查数据库(带分布式锁防止缓存击穿)
user = getFromDbWithLock(userId, cacheKey);
return user;
}
private User getFromDbWithLock(Long userId, String cacheKey) {
// 分布式锁key
String lockKey = "lock:" + cacheKey;
String lockValue = UUID.randomUUID().toString();
try {
// 尝试获取锁
boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, lockValue, 10, TimeUnit.SECONDS);
if (locked) {
// 获取到锁,查询数据库
User user = userDao.findById(userId);
if (user != null) {
// 写入多级缓存
redisTemplate.opsForValue().set(
cacheKey, user, 1, TimeUnit.HOURS
);
localCache.put(cacheKey, user);
} else {
// 空值缓存
redisTemplate.opsForValue().set(
cacheKey, "NULL", 5, TimeUnit.MINUTES
);
}
return user;
} else {
// 没获取到锁,等待重试
Thread.sleep(50);
return (User) redisTemplate.opsForValue().get(cacheKey);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
} finally {
// 释放锁(Lua脚本保证原子性)
String luaScript =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
redisTemplate.execute(
new DefaultRedisScript<>(luaScript, Long.class),
Collections.singletonList(lockKey),
lockValue
);
}
}
}
2. 热点Key探测与隔离
java
复制
下载
@Component
public class HotKeyDetector {
private final ConcurrentHashMap<String, AtomicLong> accessCounter =
new ConcurrentHashMap<>();
private final Set<String> hotKeys = ConcurrentHashMap.newKeySet();
@Scheduled(fixedDelay = 10000) // 每10秒检测一次
public void detectHotKeys() {
long threshold = 1000; // 10秒内访问超过1000次
accessCounter.forEach((key, counter) -> {
long count = counter.getAndSet(0);
if (count > threshold) {
hotKeys.add(key);
log.info("检测到热点Key: {}, 访问次数: {}", key, count);
// 热点Key特殊处理
processHotKey(key);
}
});
}
public void recordAccess(String key) {
accessCounter
.computeIfAbsent(key, k -> new AtomicLong())
.incrementAndGet();
}
private void processHotKey(String key) {
// 1. 本地缓存热点Key
// 2. 增加热点Key的副本数
// 3. 调整过期策略
// 4. 监控告警
}
public boolean isHotKey(String key) {
return hotKeys.contains(key);
}
}
五、智能防御方案
1. 基于机器学习的异常检测
java
复制
下载
@Component
public class AnomalyDetectionService {
// 使用统计学习检测异常访问模式
private final Map<String, AccessPattern> patternMap =
new ConcurrentHashMap<>();
public boolean isMaliciousRequest(String key, Long userId) {
String patternKey = "pattern:" + key + ":" + userId;
AccessPattern pattern = patternMap.computeIfAbsent(
patternKey, k -> new AccessPattern()
);
pattern.recordAccess();
// 检测异常模式
return pattern.isAnomalous();
}
static class AccessPattern {
private final CircularFifoQueue<Long> accessTimes =
new CircularFifoQueue<>(1000);
private volatile long lastAccessTime = 0;
public void recordAccess() {
long currentTime = System.currentTimeMillis();
accessTimes.add(currentTime);
lastAccessTime = currentTime;
}
public boolean isAnomalous() {
if (accessTimes.size() < 10) return false;
// 计算访问频率
List<Long> times = new ArrayList<>(accessTimes);
Collections.sort(times);
// 检查是否短时间内高频访问
long timeWindow = 1000; // 1秒
int maxAllowed = 50; // 最多50次
int count = 0;
for (int i = times.size() - 1; i >= 0; i--) {
if (times.get(times.size() - 1) - times.get(i) <= timeWindow) {
count++;
} else {
break;
}
}
return count > maxAllowed;
}
}
}
2. 动态规则引擎
java
复制
下载
@Component
public class DynamicRuleEngine {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 规则配置(可从配置中心动态加载)
private volatile RuleConfig ruleConfig = loadDefaultConfig();
public boolean shouldBlock(String key, String userId, String ip) {
// 1. 检查IP黑名单
if (isIpBlacklisted(ip)) {
return true;
}
// 2. 检查用户行为
if (isUserSuspicious(userId)) {
return true;
}
// 3. 检查访问模式
if (isAccessPatternAnomalous(key, userId)) {
return true;
}
// 4. 动态规则匹配
for (Rule rule : ruleConfig.getRules()) {
if (rule.matches(key, userId, ip)) {
return true;
}
}
return false;
}
private boolean isIpBlacklisted(String ip) {
String key = "blacklist:ip:" + ip;
return Boolean.TRUE.equals(redisTemplate.hasKey(key));
}
// 动态更新规则
@Scheduled(fixedRate = 60000)
public void refreshRules() {
RuleConfig newConfig = loadConfigFromRemote();
if (newConfig != null) {
ruleConfig = newConfig;
}
}
}
六、完整防御架构
1. 多层防御架构
java
复制
下载
@Component
public class CompleteDefenseService {
@Autowired
private BloomFilterService bloomFilter;
@Autowired
private RateLimiterService rateLimiter;
@Autowired
private CircuitBreakerService circuitBreaker;
@Autowired
private AnomalyDetectionService anomalyDetection;
public User getUserWithFullProtection(Long userId, String clientIp) {
String key = "user:" + userId;
// 第一层:基础校验
if (!validateRequest(userId, clientIp)) {
return null;
}
// 第二层:布隆过滤器
if (!bloomFilter.mightExist(key)) {
log.warn("布隆过滤器拦截: {}", userId);
return null;
}
// 第三层:限流
if (!rateLimiter.allowRequest("user_query", 100, 60)) {
log.warn("限流拦截: {}", userId);
return null;
}
// 第四层:熔断器
return circuitBreaker.execute(
() -> getFromCacheOrDB(userId),
null
);
}
private boolean validateRequest(Long userId, String clientIp) {
// 1. 参数校验
if (userId == null || userId <= 0) {
return false;
}
// 2. IP校验
if (anomalyDetection.isMaliciousRequest("user", userId)) {
return false;
}
// 3. 频率校验
String freqKey = "freq:user:" + userId;
if (!rateLimiter.allowRequest(freqKey, 10, 60)) {
return false;
}
return true;
}
private User getFromCacheOrDB(Long userId) {
// 带空值缓存的查询逻辑
// ...
return null;
}
}
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案
【点击此处即可/免费获取】
2. 监控与告警
java
复制
下载
@Component
@Slf4j
public class CachePenetrationMonitor {
private final MeterRegistry meterRegistry;
// 监控指标
private final Counter penetrationCounter;
private final Timer queryTimer;
public CachePenetrationMonitor(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.penetrationCounter = Counter.builder("cache.penetration.count")
.tag("type", "null_query")
.register(meterRegistry);
this.queryTimer = Timer.builder("cache.query.duration")
.register(meterRegistry);
}
public User getUserWithMonitoring(Long userId) {
return queryTimer.record(() -> {
try {
User user = getUser(userId);
if (user == null) {
// 记录穿透
penetrationCounter.increment();
// 告警逻辑
if (penetrationCounter.count() > 1000) {
sendAlert();
}
}
return user;
} catch (Exception e) {
log.error("查询异常", e);
return null;
}
});
}
private void sendAlert() {
// 发送告警通知
// 1. 企业微信/钉钉
// 2. 短信
// 3. 邮件
log.warn("缓存穿透告警: 超过阈值");
}
}
七、实战案例与配置
1. Redis配置优化
yaml
复制
下载
# application-redis.yml
spring:
redis:
lettuce:
pool:
max-active: 200
max-idle: 50
min-idle: 10
max-wait: 1000ms
timeout: 2000ms
cluster:
nodes: ${REDIS_NODES}
max-redirects: 3
# Redisson配置
redisson:
singleServerConfig:
address: "redis://127.0.0.1:6379"
connectionPoolSize: 64
connectionMinimumIdleSize: 24
idleConnectionTimeout: 10000
connectTimeout: 10000
timeout: 3000
retryAttempts: 3
retryInterval: 1500
2. 防护策略配置
yaml
复制
下载
# application-defense.yml
cache:
defense:
# 布隆过滤器配置
bloom-filter:
enabled: true
expected-insertions: 1000000
false-probability: 0.001
# 空值缓存配置
null-cache:
enabled: true
ttl: 300s # 5分钟
# 限流配置
rate-limit:
enabled: true
window-size: 60s
max-requests: 100
# 熔断器配置
circuit-breaker:
enabled: true
failure-threshold: 10
timeout: 30s
# 热点Key配置
hot-key:
enabled: true
detection-interval: 10s
access-threshold: 1000
八、总结与最佳实践
防御策略选择矩阵
| 场景 | 推荐方案 | 原因 | 实施难度 |
|---|---|---|---|
| 低频不存在查询 | 空值缓存 | 简单有效 | ⭐ |
| 海量不存在查询 | 布隆过滤器 | 内存效率高 | ⭐⭐ |
| 恶意攻击防护 | 限流+熔断 | 系统保护 | ⭐⭐⭐ |
| 热点数据查询 | 多级缓存 | 性能最优 | ⭐⭐⭐⭐ |
| 生产环境 | 组合方案 | 全面防护 | ⭐⭐⭐⭐⭐ |
最佳实践清单
markdown
复制
下载
✅ 必做事项:
- 所有查询都做参数校验
- 关键接口必须设置限流
- 生产环境启用监控告警
- 定期review缓存命中率
✅ 推荐事项:
- 使用布隆过滤器过滤非法请求
- 实现多级缓存架构
- 热点数据特殊处理
- 建立熔断降级机制
✅ 高级事项:
- 实施智能异常检测
- 建立动态规则引擎
- 进行定期压测演练
- 建立故障应急预案
性能与安全性平衡
java
复制
下载
// 权衡公式
public class DefenseBalance {
// 防御强度 vs 性能影响
// 误判率 vs 内存消耗
// 实时性 vs 准确性
public DefenseStrategy chooseStrategy(
SecurityRequirement security,
PerformanceRequirement performance,
CostConstraint cost
) {
if (security.level == HIGH && performance.requirement == LOW) {
return DefenseStrategy.AGGRESSIVE;
} else if (security.level == LOW && performance.requirement == HIGH) {
return DefenseStrategy.LIGHTWEIGHT;
} else {
return DefenseStrategy.BALANCED;
}
}
}
核心原则:
-
防御层层递进:从简单到复杂,从轻量到重量
-
监控驱动优化:基于数据做决策,持续改进
-
平衡的艺术:在安全、性能、成本间找到最佳平衡点
-
持续演进:随着业务发展和攻击手段变化而调整策略