/**
* 缓存击穿
* @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 bf;
List 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
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 operation = (ValueOperations) redisTemplate.opsForValue();
synchronized (countDownLatch) {
Object cacheUser = operation.get(key);
if(cacheUser!=null){
System.out.println("return user from redis");
return;
}
//如果缓存不存在查询数据库
List 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
@RunWith(SpringRunner.class)
@SpringBootTest
public class BloomFilterTest {
private BloomFilter 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 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。