当前位置: 首页 > news >正文

Redisson布隆过滤器原理以及解决Redis缓存穿透方案

目录

1. 什么是缓存穿透?

2. 布隆过滤器简介

3. Redisson布隆过滤器实现原理

3.1 核心数据结构

3.2 哈希函数实现

3.3 添加元素过程

3.4 查询元素过程

4. 实际应用场景实例

4.1 创建布隆过滤器

4.2 商品查询防穿透示例

4.3 用户ID防穿透示例

5. 布隆过滤器的优缺点

5.1 优点

5.2 缺点

6. 其他缓存穿透解决方案对比

6.1 缓存空值

6.2 互斥锁

7. 实践建议


1. 什么是缓存穿透?

缓存穿透是指查询一个根本不存在的数据,由于缓存中没有,所以每次请求都会打到数据库上,导致数据库压力过大。常见场景包括:

  • 恶意攻击:攻击者故意查询不存在的key
  • 业务逻辑:查询条件为空或无效参数
  • 数据过期:热点数据突然过期,大量请求同时访问

2. 布隆过滤器简介

布隆过滤器(Bloom Filter)是一个很长的二进制向量和一系列随机映射函数,用于判断一个元素是否在集合中。它的特点是:

  • 空间效率高:使用很少的内存就能表示大量数据
  • 查询速度快:O(k)时间复杂度,k为哈希函数个数
  • 存在误判:可能误判不存在的元素为存在,但不会误判存在的元素为不存在

3. Redisson布隆过滤器实现原理

3.1 核心数据结构

// Redisson布隆过滤器的核心数据结构
public class RBloomFilter<T> {// 底层使用Redis的BitMap实现private final RBitSet bits;// 哈希函数数量private final int hashIterations;// 预期元素数量private final long expectedInsertions;// 误判率private final double falseProbability;
}

3.2 哈希函数实现

Redisson使用多个哈希函数来减少冲突:

// 简化的哈希函数实现示例
public class BloomFilterHash {/*** 计算多个哈希值* @param key 要哈希的key* @param hashIterations 哈希函数数量* @param size 位图大小* @return 哈希值数组*/public static long[] getHashPositions(String key, int hashIterations, long size) {long[] positions = new long[hashIterations];// 使用双重哈希技术long hash1 = hash(key);long hash2 = hash(key + "salt");for (int i = 0; i < hashIterations; i++) {// 计算第i个哈希位置positions[i] = Math.abs((hash1 + i * hash2) % size);}return positions;}private static long hash(String key) {// 使用MurmurHash算法return MurmurHash3.hash32(key);}
}

3.3 添加元素过程

/*** 向布隆过滤器添加元素*/
public boolean add(T element) {// 1. 计算多个哈希位置long[] positions = getHashPositions(element.toString(), hashIterations, size);// 2. 检查是否所有位置都已设置boolean allSet = true;for (long position : positions) {if (!bits.get(position)) {allSet = false;break;}}// 3. 如果所有位置都已设置,说明元素可能已存在if (allSet) {return false;}// 4. 设置所有哈希位置为1for (long position : positions) {bits.set(position);}return true;
}

3.4 查询元素过程

/*** 检查元素是否可能存在*/
public boolean contains(T element) {// 1. 计算多个哈希位置long[] positions = getHashPositions(element.toString(), hashIterations, size);// 2. 检查所有位置是否都为1for (long position : positions) {if (!bits.get(position)) {// 如果任何一个位置为0,则元素一定不存在return false;}}// 所有位置都为1,元素可能存在(存在误判)return true;
}

4. 实际应用场景实例

4.1 创建布隆过滤器

@Component
public class BloomFilterService {@Autowiredprivate RedissonClient redissonClient;/*** 创建布隆过滤器*/public <T> RBloomFilter<T> createBloomFilter(String name, long expectedInsertions, double falseProbability) {RBloomFilter<T> bloomFilter = redissonClient.getBloomFilter(name);// 初始化布隆过滤器bloomFilter.tryInit(expectedInsertions, falseProbability);return bloomFilter;}
}

4.2 商品查询防穿透示例

@Service
public class ProductService {@Autowiredprivate BloomFilterService bloomFilterService;@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Autowiredprivate ProductMapper productMapper;private RBloomFilter<String> productBloomFilter;@PostConstructpublic void init() {// 创建布隆过滤器,预期100万商品,误判率0.01productBloomFilter = bloomFilterService.createBloomFilter("product_bloom", 1000000, 0.01);// 初始化时加载所有商品ID到布隆过滤器loadAllProductIds();}/*** 查询商品信息(防穿透)*/public Product getProductById(String productId) {// 1. 先检查布隆过滤器if (!productBloomFilter.contains(productId)) {log.info("商品ID {} 在布隆过滤器中不存在", productId);return null;}// 2. 查询Redis缓存String cacheKey = "product:" + productId;Product product = (Product) redisTemplate.opsForValue().get(cacheKey);if (product != null) {log.info("从Redis缓存获取商品信息: {}", productId);return product;}// 3. 查询数据库product = productMapper.selectById(productId);if (product != null) {// 4. 更新缓存redisTemplate.opsForValue().set(cacheKey, product, Duration.ofMinutes(30));log.info("从数据库获取商品信息并更新缓存: {}", productId);} else {// 5. 缓存空值,防止缓存穿透redisTemplate.opsForValue().set(cacheKey, null, Duration.ofMinutes(5));log.warn("商品ID {} 在数据库中不存在,缓存空值", productId);}return product;}/*** 加载所有商品ID到布隆过滤器*/private void loadAllProductIds() {List<String> allProductIds = productMapper.selectAllProductIds();for (String productId : allProductIds) {productBloomFilter.add(productId);}log.info("成功加载 {} 个商品ID到布隆过滤器", allProductIds.size());}/*** 新增商品时同步更新布隆过滤器*/public void addProduct(Product product) {// 保存到数据库productMapper.insert(product);// 添加到布隆过滤器productBloomFilter.add(product.getId());// 更新缓存String cacheKey = "product:" + product.getId();redisTemplate.opsForValue().set(cacheKey, product, Duration.ofMinutes(30));log.info("新增商品并更新布隆过滤器: {}", product.getId());}
}

4.3 用户ID防穿透示例

@Service
public class UserService {@Autowiredprivate RedissonClient redissonClient;private RBloomFilter<String> userBloomFilter;@PostConstructpublic void init() {// 创建用户布隆过滤器userBloomFilter = redissonClient.getBloomFilter("user_bloom");userBloomFilter.tryInit(10000000, 0.001); // 1000万用户,误判率0.001}/*** 检查用户是否存在*/public boolean isUserExists(String userId) {return userBloomFilter.contains(userId);}/*** 批量检查用户是否存在*/public Map<String, Boolean> batchCheckUsers(List<String> userIds) {Map<String, Boolean> result = new HashMap<>();for (String userId : userIds) {result.put(userId, userBloomFilter.contains(userId));}return result;}
}

5. 布隆过滤器的优缺点

5.1 优点

  • 空间效率极高:1亿数据只需要约12MB内存
  • 查询效率高:O(k)时间复杂度
  • 误判可控:通过参数调整误判率

5.2 缺点

  • 存在误判:可能将不存在的元素误判为存在
  • 不支持删除:删除元素会影响其他元素
  • 不支持计数:只能判断存在性,不能统计数量

6. 其他缓存穿透解决方案对比

6.1 缓存空值

// 缓存空值方案
public Product getProductWithNullCache(String productId) {String cacheKey = "product:" + productId;Product product = (Product) redisTemplate.opsForValue().get(cacheKey);if (product != null) {return product;}// 查询数据库product = productMapper.selectById(productId);if (product != null) {redisTemplate.opsForValue().set(cacheKey, product, Duration.ofMinutes(30));} else {// 缓存空值,设置较短过期时间redisTemplate.opsForValue().set(cacheKey, null, Duration.ofMinutes(5));}return product;
}

6.2 互斥锁

// 互斥锁方案
public Product getProductWithLock(String productId) {String cacheKey = "product:" + productId;String lockKey = "lock:product:" + productId;Product product = (Product) redisTemplate.opsForValue().get(cacheKey);if (product != null) {return product;}// 获取分布式锁RLock lock = redissonClient.getLock(lockKey);try {if (lock.tryLock(5, 10, TimeUnit.SECONDS)) {// 双重检查product = (Product) redisTemplate.opsForValue().get(cacheKey);if (product != null) {return product;}// 查询数据库product = productMapper.selectById(productId);if (product != null) {redisTemplate.opsForValue().set(cacheKey, product, Duration.ofMinutes(30));} else {redisTemplate.opsForValue().set(cacheKey, null, Duration.ofMinutes(5));}}} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {if (lock.isHeldByCurrentThread()) {lock.unlock();}}return product;
}
http://www.dtcms.com/a/284552.html

相关文章:

  • 单片机(STM32-时钟系统)
  • js是实现记住密码自动填充功能
  • PyCharm 高效入门指南:从安装到进阶,解锁 Python 开发全流程
  • EXCEL VBA合并当前工作簿的所有工作表sheet
  • 切比雪夫不等式的理解以及推导【超详细笔记】
  • C语言---动态内存管理
  • 李宏毅《生成式人工智能导论》 | 第15讲-第18讲:生成的策略-影像有关的生成式AI
  • Google(谷歌)搜索引擎蜘蛛IP地址段
  • ubuntu--curl
  • 《Java Web 核心:Servlet、会话与过滤器笔记》
  • AndroidStudio环境搭建
  • vue svg实现一个环形进度条组件
  • 石子入水波纹效果:顶点扰动着色器实现
  • 【44】MFC入门到精通——MFC 通过Button按钮添加控件变量实现:按下 按钮变色 (比如开关 打开关闭状态) MFC更改button控颜色
  • Git简介与特点:从Linux到分布式版本控制的革命
  • 找不到或无法加载主类 org.gradle.wrapper.GradleWrapperMain
  • Linux Swap区深度解析:为何禁用?何时需要?
  • 【Java EE初阶 --- 网络原理】网络编程
  • Vue3 + WebSocket
  • 基于现代R语言【Tidyverse、Tidymodel】的机器学习方法
  • 3.2 函数参数与返回值
  • .vscode 扩展配置
  • 浅析网络安全面临的主要威胁类型及对应防护措施
  • 【C++指南】C++ list容器完全解读(四):反向迭代器的巧妙实现
  • 如何做好DNA-SIP?
  • 【41】MFC入门到精通——MFC中 GetLBText()、GetWindowText()、SetWindowText区别
  • 扭蛋机小程序开发:开启线上娱乐新风尚
  • 分布式光伏发电系统中的“四可”指的是什么?
  • 教资科三【信息技术】— 学科知识: 第一章(信息技术基础)
  • 基于springboot+vue+mysql技术的实验室管理系统(源码+论文)