1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | /** * 缓存击穿 * @author * */ @RunWith (SpringJUnit4ClassRunner. class ) @ContextConfiguration (locations = { "classpath:config/spring/spring-dao.xml" , "classpath:config/spring/spring-bean.xml" , "classpath:config/spring/spring-redis.xml" }) public class CacheBreakDownTest { private static final Logger logger = LoggerFactory.getLogger(CacheBreakDownTest. class ); private static final int THREAD_NUM = 100 ; //线程数量 @Resource private UserDao UserDao; @Resource private RedisTemplate redisTemplate; private int count = 0 ; //初始化一个计数器 private CountDownLatch countDownLatch = new CountDownLatch(THREAD_NUM); private BloomFilter<String> bf; List<UserDto> allUsers; @PostConstruct public void init(){ //将数据从数据库导入到本地 allUsers = UserDao.getAllUser(); if (allUsers == null || allUsers.size()== 0 ){ return ; } //创建布隆过滤器(默认3%误差) bf = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), allUsers.size()); //将数据存入布隆过滤器 for (UserDto userDto : allUsers){ bf.put(userDto.getUserName()); } } @Test public void cacheBreakDownTest(){ for ( int i= 0 ;i<THREAD_NUM;i++){ new Thread( new MyThread()).start(); //计数器减一 countDownLatch.countDown(); } try { Thread.currentThread().join(); } catch (InterruptedException e) { e.printStackTrace(); } } class MyThread implements Runnable{ @Override public void run() { try { //所有子线程等待,当子线程全部创建完成再一起并发执行后面的代码 countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } //随机产生一个字符串 String randomUser = UUID.randomUUID().toString(); // String randomUser = allUsers.get(new Random().nextInt(allUsers.size())).getUserName(); String key = "Key:" +randomUser; //如果布隆过滤器中不存在这个用户直接返回,将流量挡掉 if (!bf.mightContain(randomUser)){ System.out.println( "bloom filter don't has this user" ); return ; } //查询缓存,如果缓存中存在直接返回缓存数据 ValueOperations<String,String> operation = (ValueOperations<String, String>) redisTemplate.opsForValue(); synchronized (countDownLatch) { Object cacheUser = operation.get(key); if (cacheUser!= null ){ System.out.println( "return user from redis" ); return ; } //如果缓存不存在查询数据库 List<UserDto> user = UserDao.getUserByUserName(randomUser); if (user == null || user.size() == 0 ){ return ; } //将mysql数据库查询到的数据写入到redis中 System.out.println( "write to redis" ); operation.set( "Key:" +user.get( 0 ).getUserName(), user.get( 0 ).getUserName()); } } } } |
demo2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | @RunWith (SpringRunner. class ) @SpringBootTest public class BloomFilterTest { private BloomFilter<Integer> bloomFilter; private int size = 1000000 ; @Before public void init(){ //不设置第三个参数时,误判率默认为0.03 //bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size); //进行误判率的设置,自动计算需要几个hash函数。bit数组的长度与size和fpp参数有关 //过滤器内部会对size进行处理,保证size为2的n次幂。 bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size, 0.01 ); for ( int i = 0 ; i < size; i++){ bloomFilter.put(i); } } @Test public void testBloomFilter(){ for ( int i = 0 ; i < size; i++){ if (!bloomFilter.mightContain(i)){ //不会打印,因为不存在的情况不会出现误判 System.out.println( "不存在的误判" + i); } } List<Integer> list = new ArrayList<>( 1000 ); for ( int i = size + 10000 ; i < size + 20000 ; i++) { if (bloomFilter.mightContain(i)) { list.add(i); } } //根据设置的误判率 System.out.println( "存在的误判数量:" + list.size()); } } |
布隆过滤器有以下应用场景:
1、黑名单,比如邮件黑名单过滤器,判端邮件地址是否在黑名单中。
2、网络爬虫,判端url是否已经被爬取过。
3、首次访问,判端访问网站的IP是否是第一次访问。
4、缓存击穿,防止非法攻击,频繁发送无法命中缓存的请求,导致缓存击穿,最总引起缓存雪崩。
5、检查英文单词是否拼写正确。
6、K-V系统快速判断某个key是否存在,典型的例子有Hbase,Hbase的每个Region中都包含一个BloomFilter,用于在查询时快速判断某个key在该region中是否存在,如果不存在,直接返回,节省掉后续的查询。
扩展,如何让布隆过滤器支持删除。
进行计数删除,但是计数删除需要存储一个数值,而不是原先的 bit 位,会增大占用的内存大小。这样的话,增加一个值就是将对应索引槽上存储的值加一,删除则是减一,判断是否存在则是看值是否大于0。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET 9 new features-C#13新的锁类型和语义
· Linux系统下SQL Server数据库镜像配置全流程详解
· 现代计算机视觉入门之:什么是视频
· 你所不知道的 C/C++ 宏知识
· 聊一聊 操作系统蓝屏 c0000102 的故障分析
· 不到万不得已,千万不要去外包
· C# WebAPI 插件热插拔(持续更新中)
· .NET 9 new features-C#13新的锁类型和语义
· 会议真的有必要吗?我们产品开发9年了,但从来没开过会
· 《SpringBoot》EasyExcel实现百万数据的导入导出