Redis应用(7)——Redis的项目应用(六):布隆过滤器---白名单 ----> Reids的问题,雪崩/ 击穿 / 穿透【重要】& 布隆过滤器

引出


1.Redis的问题,缓存雪崩,key不存在;击穿,热点key;穿透,redis没有,数据库没有;
2.布隆过滤器,返回没有,则结论一定正确;返回有,结论不一定正确;
3.布隆过滤器可以作为项目的白名单,htool工具包的使用;
4.项目应用BitMapBloomFilter;缓存预热+布隆过滤器初始化@Scheduled(cron = “0 01 18 * * ?”);
5.流程:布隆–>缓存–>数据库;

Redis的问题

在这里插入图片描述

缓存雪崩:key不存在

让key的失效时间随机

大量请求访问redis,redis的key不存在,数据大量请求发送到数据库,数据库压力瞬间变大;

当大量请求访问redis,redis的key不存在,大量请求打向数据库。

在这里插入图片描述

解决方案; 在过期时间设置为随机,防止同一时间消失。

redisTemplate.opsForValue().set("users", users, 
          new Random().nextInt(7) + 29, TimeUnit.MINUTES);

在这里插入图片描述

缓存击穿:热点key

让key永不过期

某个key访问量突然暴增,微博某个词条访问量瞬间很大,一直转圈
此时,数据库一直查询某个key,一直查询某个值;

在这里插入图片描述

解决方案:让key 永不过期

缓存穿透【重要】

指客户端请求的数据在缓存和数据库中都没有,这样缓存永远不会生效,这些请求都会打到数据库;

如果有人查询100w条数据都打到数据库,数据库就会瘫痪;

在这里插入图片描述

在这里插入图片描述

穿透的解决方案:布隆过滤器

在这里插入图片描述

布隆过滤器,就是一种数据结构,它是由一个长度为m bit的位数组与n个hash函数组成的数据结构,位数组中每个元素的初始值都是0。在初始化布隆过滤器时,会先将所有key进行n次hash运算,这样就可以得到n个位置,然后将这n个位置上的元素改为1。这样,就相当于把所有的key保存到了布隆过滤器中了。

结论:如果布隆过滤器中没有,则一定没有,常用作白名单;

即:布隆过滤器返回值:
如果布隆过滤器返回没有,则结论正确,阻止访问;
如果布隆过滤器返回有,则结论不一定正确;

项目应用:
如果白名单中没有,则对方恶意访问缓存,阻止访问;

问题:如何存储100w纯数字

在连续的空间存储100w个纯数字占用多少内存

package com.tianju.redisDemo.testDemo;

public class BigMapDemo {
    public static void main(String[] args) {
        int[] a = {1,2,3,4,5,6,7,8,9};
        int[] b = new int[1000000]; // 存100w个数字
        // 占用空间,1个int占用32 bits空间
        System.out.println(32*1000000.0/1024/1024+"MB");
    }
}

int— 32bits
100w x 32bits= 32,000,000(bits)
1kb = 1024 bits;
1M = 1024kb

结论:100w个纯数字占用了30MB的内存;

解决办法,用BigMap,1int,对应32个bits,可以用来表示0~31个数字,如下图所示,可以表示数字1,5;大大节省了空间

在这里插入图片描述

布隆过滤器

布隆过滤器(Bloom Filter)本质上是由长度为 m 的位向量或位列表(仅包含 0 或 1 位值的列表)组成,最初所有的值均设置为 0,如下图所示。

在这里插入图片描述

为了将数据项添加到布隆过滤器中,会提供 K 个不同的哈希函数,并将结果位置上对应位的值置为 “1”。

在这里插入图片描述

如上图所示,当输入 “semlinker” 时,预设的 3 个哈希函数将输出 2、4、6,我们把相应位置 1。假设另一个输入 ”kakuqo“,哈希函数输出 3、4 和 7。你可能已经注意到,索引位 4 已经被先前的 “semlinker” 标记了。此时,我们已经使用 “semlinker” 和 ”kakuqo“ 两个输入值,填充了位向量。当前位向量的标记状态为:

在这里插入图片描述

当对值进行搜索时,与哈希表类似,我们将使用 3 个哈希函数对 ”搜索的值“ 进行哈希运算,并查看其生成的索引值。假设,当我们搜索 ”fullstack“ 时,3 个哈希函数输出的 3 个索引值分别是 2、3 和 7:

在这里插入图片描述

从上图可以看出,相应的索引位都被置为 1,这意味着我们可以说 ”fullstack“ 可能已经插入到集合中。事实上这是误报的情形,产生的原因是由于哈希碰撞导致的巧合而将不同的元素存储在相同的比特位上。幸运的是,布隆过滤器有一个可预测的误判率(FPP):

在这里插入图片描述

  • n 是已经添加元素的数量;
  • k 哈希的次数;
  • m 布隆过滤器的长度(如比特数组的大小);

极端情况下,当布隆过滤器没有空闲空间时(满),每一次查询都会返回 true 。这也就意味着 m 的选择取决于期望预计添加元素的数量 n ,并且 m 需要远远大于 n 。

实际情况中,布隆过滤器的长度 m 可以根据给定的误判率(FFP)的和期望添加的元素个数 n 的通过如下公式计算:

在这里插入图片描述

了解完上述的内容之后,我们可以得出一个结论,

当我们搜索一个值的时候,

若该值经过 K 个哈希函数运算后的任何一个索引位为 ”0“,那么该值肯定不在集合中。

但如果所有哈希索引值均为 ”1“,则只能说该搜索的值可能存在集合中

项目应用:布隆过滤器≈白名单

在这里插入图片描述

如果布隆过滤器中没有,则一定没有,可以证明没有;

结论:
如果布隆过滤器返回没有,这个结论一定正确;
如果布隆过滤器返回有,这个结论不一定正确;

如果白名单中没有,则对方恶意访问缓存,则不通过,阻止访问;

htool工具包案例

https://www.bookstack.cn/read/hutool-5.6.0-zh

在这里插入图片描述

    // 初始化
    BitMapBloomFilter filter = new BitMapBloomFilter(10);
    filter.add("123");
    filter.add("abc");
    filter.add("ddd");
    // 查找
    filter.contains("abc")

Redis项目应用(六)布隆过滤器—白名单

业务逻辑

后台查询用户,防止穿透缓存到数据库查询用户,导致数据库压力大;

  • 预热的时候,用户信息加载到布隆过滤器中;
  • 查询业务,比如查询用户时,先用布隆过滤器过滤,再去redis;

在这里插入图片描述

在这里插入图片描述

以一个根据用户名查询用户是否在数据库中案例为例;

1.先到布隆过滤器中,在controller层解决,拦阻非法请求;
2.通过布隆过滤器,在redis缓存中查找,查询成功返回;
3.通过布隆过滤器,在redis缓存中没找到,查询数据库,查询成功返回,并且更新缓存

布隆过滤器工具类 BitMapBloomFilter

WhiteListBloomFilter.java工具类

package com.tianju.redisDemo.util;

import cn.hutool.bloomfilter.BitMapBloomFilter;

/**
 * 白名单的布隆过滤器
 */
public class  WhiteListBloomFilter {

    // Params: m – M值决定BitMap的大小
    private static BitMapBloomFilter bloomFilter = new BitMapBloomFilter(10);

    // TODO:布隆过滤器有,缓存没有,需要从数据库查询

    /**
     * 将str加入布隆过滤器中,作为白名单使用
     * @param str
     */
    public static void addBloom(String str){
        bloomFilter.add(str);
    }

    /**
     * 判断str是否在布隆过滤器中
     * @param str
     * @return true表示可能在布隆过滤器中;false表示一定不在布隆过滤器中
     */
    public static Boolean isInBloom(String str){
        return bloomFilter.contains(str);
    }
}

缓存预热+布隆过滤器初始化@Scheduled(cron = “0 01 18 * * ?”)

UsernamesPreHot.java缓存预热

package com.tianju.redisDemo.job;

import com.tianju.redisDemo.dao.UserMapper;
import com.tianju.redisDemo.entity.User;
import com.tianju.redisDemo.util.WhiteListBloomFilter;
import lombok.extern.slf4j.Slf4j;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.annotation.Schedules;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.List;

/**
 * 采用 @Scheduled(cron = "0 54 21 * * ?")进行预热
 */
@Slf4j
@Component
public class UsernamesPreHot {
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private UserMapper userMapper;

    @Scheduled(cron = "0 01 18 * * ?")
    public void preHot() {
        // 1.清除缓存中的数据
        stringRedisTemplate.delete("usernames");

        // 2.更新缓存中的数据
        // 3.也要加入到布隆过滤器中
        List<User> userList = userMapper.selectList(null);
        // TODO:布隆过滤器有,缓存没有,需要从数据库查询
        WhiteListBloomFilter.addBloom("Arya");
        userList.forEach(user ->{
                    stringRedisTemplate.opsForSet().add("usernames", user.getUsername());
                    // 放到布隆过滤器中
                    WhiteListBloomFilter.addBloom(user.getUsername());
                }
        );
        log.debug("redis缓存预热 + 布隆过滤器预热,usernames");
    }
}

service层:布隆–>缓存–>数据库

package com.tianju.redisDemo.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.tianju.redisDemo.dao.UserMapper;
import com.tianju.redisDemo.entity.User;
import com.tianju.redisDemo.service.IUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Service
@Transactional
@Slf4j
public class UserServiceImpl implements IUserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public String findByUsername(String username) {
        // 1.先到布隆过滤器中,在controller层解决

        // 2.然后到redis中
        log.debug(">>>> 是否在redis缓存中");
        Boolean isInRedis = stringRedisTemplate.opsForSet().isMember("usernames", username);
        if (isInRedis){ // 缓存中有
            log.debug(username+"在redis缓存中查找成功,,,");
            Set<String> usernames = stringRedisTemplate.opsForSet().members("usernames");
            return usernames.stream().filter(u -> username.equals(u)).collect(Collectors.toList()).get(0);

            // 3.最后到数据库
        }else { // 缓存中也没有,到数据库查询
            log.debug("缓存中不存在,从数据库中进行读取....");
            QueryWrapper<User> wrapper = new QueryWrapper<>();
            wrapper.eq("username", username); // 列名和参数

            User user = userMapper.selectOne(wrapper);
            log.debug("数据库查询成功,更新缓存....");
            stringRedisTemplate.opsForSet().add("usernames", user.getUsername());
            return user.getUsername();
        }
    }
}

controller层:布隆无,非法;布隆有–>service

package com.tianju.redisDemo.controller;

import com.tianju.redisDemo.dto.HttpResp;
import com.tianju.redisDemo.dto.ResultCode;
import com.tianju.redisDemo.service.IUserService;
import com.tianju.redisDemo.util.WhiteListBloomFilter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;

@RestController
@RequestMapping("/api/user")
@Slf4j
public class UserController {
    
    @Autowired
    private IUserService userService;

    @GetMapping("findByUsername")
    public HttpResp findByUsername(String username){
        // 1.先过布隆过滤器
        Boolean inBloom = WhiteListBloomFilter.isInBloom(username);

        // 如果布隆过滤器里没有,则一定没有,非法请求
        if (!inBloom){
            log.debug(">>>>>非法请求。布隆过滤器未通过");
            return HttpResp.results(ResultCode.USER_FIND_ERROR,new Date(),"非法用户禁止操作");
        }
        
        // 然后到缓存 --- 》 到数据库
        String byUsername = userService.findByUsername(username);
        return HttpResp.results(ResultCode.BOOK_RUSH_SUCCESS,new Date(),byUsername);

    }
}

测试结果模拟

在这里插入图片描述

1.布隆过滤器未通过,非法请求

在这里插入图片描述

2.布隆过滤器通过,缓存中查询成功

在这里插入图片描述

3.布隆过滤器通过,缓存没有,从数据库查询

一开始数据库

在这里插入图片描述

现在布隆过滤器器里有,数据库里面有,缓存没有

在这里插入图片描述

数据库后台改动

在这里插入图片描述
在这里插入图片描述


总结

1.Redis的问题,缓存雪崩,key不存在;击穿,热点key;穿透,redis没有,数据库没有;
2.布隆过滤器,返回没有,则结论一定正确;返回有,结论不一定正确;
3.布隆过滤器可以作为项目的白名单,htool工具包的使用;
4.项目应用BitMapBloomFilter;缓存预热+布隆过滤器初始化@Scheduled(cron = “0 01 18 * * ?”);
5.流程:布隆–>缓存–>数据库;

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
这个错误可能是因为 Maven 无法找到 `org.springframework.boot:spring-boot-starter-data-redis:2.7.2` 这个依赖。你可以尝试以下几个解决方案: 1. 检查你的 Maven 配置文件,确认是否添加了 Spring Boot 仓库。可以在 `pom.xml` 文件中添加以下配置: ```xml <repositories> <repository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <snapshots> <enabled>true</enabled> </snapshots> </repository> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> ``` 2. 检查你的依赖是否写错了,应该是 `spring-boot-starter-data-redis` 而不是 `spring-boot-starter-redis` 3. 尝试更新 Maven 仓库,可以使用以下命令: ```bash mvn clean install -U ``` 如果以上方法都不行,你可以尝试手动下载依赖并安装到本地 Maven 仓库中。具体步骤如下: 1. 在 [https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis/2.7.2](https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis/2.7.2) 下载 `spring-boot-starter-data-redis-2.7.2.jar` 和 `spring-boot-starter-data-redis-2.7.2.pom` 2. 运行以下命令将依赖安装到本地 Maven 仓库中: ```bash mvn install:install-file -Dfile=spring-boot-starter-data-redis-2.7.2.jar -DpomFile=spring-boot-starter-data-redis-2.7.2.pom ``` 安装完成后,再次运行你的项目即可。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Perley620

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值