Redis缓存穿透与布隆过滤器解决方案
什么是缓存穿透
缓存穿透是指查询一个不存在的数据,由于缓存中不存在,每次请求都会穿透到数据库,导致数据库压力过大。

布隆过滤器原理
布隆过滤器是一种空间效率极高的概率型数据结构,用于判断一个元素是否在集合中。
特点:
- 判断不存在:100%准确
- 判断存在:可能存在误判(有概率说存在实际不存在)
- 无法删除元素(除非使用增强版Counting Bloom Filter)
使用布隆过滤器解决缓存穿透的流程
请求 -> 布隆过滤器 -> 缓存 -> 数据库
- 请求先经过布隆过滤器
- 布隆过滤器判断不存在 → 直接返回(拦截)
- 布隆过滤器判断存在 → 查询缓存
- 缓存命中 → 返回数据
- 缓存未命中 → 查询数据库
- 数据存在 → 写入缓存并返回
- 数据不存在 → 不写入缓存(或写入空值)
Java实现示例
使用Google Guava的布隆过滤器(单机版)
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.nio.charset.Charset;
@Component
public class BloomFilterService {
private BloomFilter<String> bloomFilter;
// 预计数据量
private static final int EXPECTED_INSERTIONS = 1000000;
// 误判率
private static final double FPP = 0.001;
@PostConstruct
public void init() {
bloomFilter = BloomFilter.create(
Funnels.stringFunnel(Charset.defaultCharset()),
EXPECTED_INSERTIONS,
FPP
);
// 初始化时加载所有用户ID到布隆过滤器
loadAllUserIds();
}
private void loadAllUserIds() {
// 从数据库加载所有用户ID
List<String> userIds = userMapper.getAllUserIds();
for (String userId : userIds) {
bloomFilter.put(userId);
}
}
public boolean mightContain(String userId) {
return bloomFilter.mightContain(userId);
}
// 新增用户时更新布隆过滤器
public void addUserId(String userId) {
bloomFilter.put(userId);
}
}
使用Redis BitMap实现布隆过滤器(分布式版)
import redis.clients.jedis.Jedis;
import org.springframework.stereotype.Component;
import java.util.BitSet;
@Component
public class RedisBloomFilter {
private static final String BLOOM_KEY = "user:bloom";
// 布隆过滤器参数
private static final int BIT_SIZE = 2 << 28; // 2^29 = 5.36亿
private static final int NUM_HASH_FUNCTIONS = 5;
private Jedis jedis;
/**
* 添加元素到布隆过滤器
*/
public void add(String value) {
int[] offsets = getHashOffsets(value);
for (int offset : offsets) {
jedis.setbit(BLOOM_KEY, offset, true);
}
}
/**
* 判断元素是否可能存在
*/
public boolean mightContain(String value) {
int[] offsets = getHashOffsets(value);
for (int offset : offsets) {
if (!jedis.getbit(BLOOM_KEY, offset)) {
return false; // 只要有一个位为0,肯定不存在
}
}
return true; // 所有位都为1,可能存在(有误判)
}
/**
* 计算多个哈希函数的偏移量
*/
private int[] getHashOffsets(String value) {
int[] offsets = new int[NUM_HASH_FUNCTIONS];
int h1 = Math.abs(value.hashCode());
int h2 = Math.abs(h1 >> 16);
for (int i = 1; i <= NUM_HASH_FUNCTIONS; i++) {
offsets[i-1] = Math.abs((h1 + i * h2) % BIT_SIZE);
}
return offsets;
}
}
使用Redisson(推荐方案)
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private RedissonClient redissonClient;
@Autowired
private UserRepository userRepository;
private RBloomFilter<String> bloomFilter;
@PostConstruct
public void init() {
// 创建布隆过滤器
bloomFilter = redissonClient.getBloomFilter("userBloomFilter");
// 初始化(预计数据量100万,误判率0.001)
bloomFilter.tryInit(1000000L, 0.001);
// 加载现有数据
loadExistingData();
}
private void loadExistingData() {
List<String> userIds = userRepository.getAllUserIds();
for (String userId : userIds) {
bloomFilter.add(userId);
}
}
public User getUserById(String userId) {
// 1. 布隆过滤器判断
if (!bloomFilter.contains(userId)) {
// 肯定不存在,直接返回
return null;
}
// 2. 查询缓存
String cacheKey = "user:" + userId;
User user = getFromCache(cacheKey);
if (user != null) {
return user;
}
// 3. 查询数据库
user = userRepository.findById(userId);
if (user != null) {
// 写入缓存
setToCache(cacheKey, user);
}
return user;
}
// 新增用户时更新布隆过滤器
public void addUser(User user) {
userRepository.save(user);
bloomFilter.add(user.getId());
}
}
项目中的完整缓存方案
@Service
public class OptimizedUserService {
@Autowired
private RedissonClient redissonClient;
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private UserMapper userMapper;
private RBloomFilter<String> bloomFilter;
@PostConstruct
public void init() {
bloomFilter = redissonClient.getBloomFilter("userBloomFilter");
bloomFilter.tryInit(1000000L, 0.001);
}
public User getUserById(String userId) {
// 1. 布隆过滤器过滤
if (!bloomFilter.contains(userId)) {
log.warn("用户不存在,已被布隆过滤器拦截: {}", userId);
return null;
}
// 2. 查询缓存
String cacheKey = "user:" + userId;
String cacheValue = redisTemplate.opsForValue().get(cacheKey);
// 3. 缓存命中
if (cacheValue != null) {
// 判断是否是空值缓存
if ("NULL".equals(cacheValue)) {
return null;
}
return JSON.parseObject(cacheValue, User.class);
}
// 4. 缓存未命中,查询数据库
User user = userMapper.findById(userId);
// 5. 缓存处理
if (user != null) {
// 数据存在,写入缓存
redisTemplate.opsForValue().set(
cacheKey,
JSON.toJSONString(user),
30,
TimeUnit.MINUTES
);
} else {
// 数据不存在,缓存空值(防止缓存穿透)
redisTemplate.opsForValue().set(
cacheKey,
"NULL",
5,
TimeUnit.MINUTES
);
}
return user;
}
}
布隆过滤器参数配置建议
| 参数 | 说明 | 建议值 |
|---|---|---|
| expectedInsertions | 预计数据量 | 根据业务评估,预留20%余量 |
| fpp (false positive probability) | 误判率 | 001 (千分之一) |
| bitSize | 位图大小 | N * ln(FPP) / (ln2)^2 |
| numHashFunctions | 哈希函数数量 | (bits / N) * ln2 |
优缺点分析
优点:
- 节省内存(比HashSet节省90%以上空间)
- 查询速度快(O(k),k为哈希函数数量)
- 能有效防止缓存穿透
缺点:
- 存在误判率(不能100%确定存在)
- 无法删除元素
- 需要定期重建(数据量变化大时)
最佳实践建议
- 定期重建:定期从数据库全量加载数据重建布隆过滤器
- 配合空值缓存:布隆过滤 + 空值缓存双重保险
- 监控误判率:监控实际误判率,适时调整参数
- 冷热数据分离:热点数据使用布隆过滤器,冷数据使用其他方案
这种方案能有效解决缓存穿透问题,将无效请求拦截在Redis之前,保护数据库安全。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播