Redis学习笔记:Bitmap结合SpringBoot自研布隆过滤器案例

Redis学习笔记:Bitmap结合SpringBoot自研布隆过滤器案例

Bitmap的概念

Bitmap

由0和1状态表现的二进制位的bit数组

Bitmap的作用:

用于状态统计

使用Bitmap的需求

用户是否登陆过Y、N,比如京东每日签到送京东
电影、广告是否被点击播放过
钉钉打卡上下班,签到统计

京东每日签到送京豆案例

需求说明

签到日历仅展示当月签到数据
签到日历需展示最近连续签到天数
假设当前日期是20210618,且20210616未签到
若20210617已签到且0618未签到,则连续签到天数为1
若20210617已签到且0618已签到,则连续签到天数为2
连续签到天数越多,奖励越大
所有用户均可签到
截至2020年3月31日的12个月,京东年度活跃用户数3.87亿,同比增长24.8%,环比增长超2500万,此外,2020年3月移动端日均活跃用户数同比增长46%假设10%左右的用户参与签到,签到用户也高达3千万,用数据库实现不现实

解决方案:

在签到统计时,每个用户一天的签到用1个bit位就能表示,

一个月(假设是31天)的签到情况用31个bit位就可以,一年的签到也只需要用365个bit位,根本不用太复杂的集合类型

常用命令

#setbit 键 偏移位 只能零或者一
setbit key offset value
#getbit 获取指定的key的偏移量的value
getbit key offset
#strlen 统计字节数占用多少
strlen key
#bitcount 全部键里面含有1的有多少个
bitcount key
#bitop 在位图中查找第一个被设置为指定值的二进制位(只能是0或者1),并返回这个二进制位的偏移量。
bitop key value

布隆过滤器的概念

布隆过滤器是什么

由一个初始值都为0的bit数组和多个哈希函数构成,用来快速判断集合中是否存在某个元素

布隆过滤器的作用

高效地插入和查询,占用空间少,返回的结果是不确定性+不够完美
一个元素如果判断结果存在,元素不一定存在
一个元素如果判断结果不存在,则元素一定不存在
布隆过滤器可以添加元素,不能删除元素

布隆过滤器的原理

一个大型位数组和几个不同的无偏hash函数(无偏表示分布均匀)。由一个初值都为零的bit数组和多个个哈希函数构成,用来快速判断某个数据是否存在。
添加key时,使用多个hash函数对key进行hash运算得到一个整数索引值,对位数组长度进行取模运算得到一个位置,每个hash函数都会得到一个不同的位置,将这几个位置都置1就完成了add操作。
查询key时,只要有其中一位是零就表示这个key不存在,但如果都是1,则不一定存在对应的key。

布隆过滤器不能删除的原因

布隆过滤器的误判是指多个输入经过哈希之后在相同的bit位置1了,这样就无法判断究竟是哪个输入产生的,
因此误判的根源在于相同的 bit 位被多次映射且置 1。
这种情况也造成了布隆过滤器的删除问题,因为布隆过滤器的每一个 bit 并不是独占的,很有可能多个元素共享了某一位。
如果我们直接删除这一位的话,会影响其他的元素

布隆过滤器的使用场景

解决缓存穿透的问题,和redis结合bitmap使用
黑名单校验,识别垃圾邮件
安全连接网址,全球上10亿的网址判断

SpringBoot结合redis的bitmap自研布隆过滤器,解决缓存穿透
BloomFilterInit(白名单初始化):

@Component
@Slf4j
public class BloomFilterInit {

    @Resource
    private RedisTemplate redisTemplate;

    @PostConstruct//初始化布隆过滤器白名单
    public void init() {
        // 1.白名单客户加载到布隆过滤器
        String key = "customer:0";
        // 2.计算hashValue,由于存在计算出来负数的可能,我们取绝对值
        int hashValue = Math.abs(key.hashCode());
        // 3.通过hashValue和2的32次方取余,获得对应的下标坑位
        long index = (long) (hashValue % Math.pow(2, 32));
        // 4.设置redis里面的bitmap对应类型白名单:whitelistCustomer的坑位,将值设置为1
        redisTemplate.opsForValue().setBit("whitelistCustomer", index, true);
        log.info("customer:0,在whitelistCustomer的坑位是:" + index);
    }
}

CheckUtils类:

@Component
@Slf4j
public class CheckUtils {
    @Autowired
    private RedisTemplate redisTemplate;

    public boolean checkWithBloomFilter(String checkItem, String key) {
        int hashValue = Math.abs(key.hashCode());
        long index = (long) (hashValue % Math.pow(2, 32));
        Boolean result = redisTemplate.opsForValue().getBit(checkItem, index);
        log.info("--->key:" + key + "对应坑位下标index:" + index + "是否存在:" + result);
        return  result;
    }
}

CustomerService类:

@Service
@Slf4j
public class CustomerServiceImpl implements CustomerService {
    public static final String CACHE_KEY_CUSTOMER = "customer:";

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private CustomerMapper customerMapper;

    @Autowired
    private CheckUtils checkUtils;


    @Override
    public void addCustomer(Customer customer) {
        int result = customerMapper.insertOneCustomer(customer);
        if (result > 0) {
            //mysql插入成功,需要重新查询一次,将数据捞出来,写进redis
            Customer customerResult = customerMapper.queryOneCustomerById(customer.getId());
            //redis缓存key
            String key = CACHE_KEY_CUSTOMER + customer.getId();
            //捞出来的数据写进redis
            redisTemplate.opsForValue().set(key, customerResult);
        }
    }

    @Override
    public Customer queryCustomerById(Integer customerId) {
        Customer customer = null;
        //缓存redis的key名称
        String key = CACHE_KEY_CUSTOMER + customerId;

        // 1.先去redis查询
        customer = (Customer) redisTemplate.opsForValue().get(key);
        // 2.redis有直接返回,没有再进去查询mysql
        if (customer == null) {
            // 3.查询mysql
            customer = customerMapper.queryOneCustomerById(customerId);
            // 3.1mysql有,redis无
            if (customer != null) {
                // 3.2把mysql查询出来的数据放进 redis
                redisTemplate.opsForValue().set(key, customer);
            }
        }
        return customer;
    }

    @Override
    public Customer queryCustomerByIdWithBloomFilter(Integer customerId) {
        Customer customer = null;
        //缓存redis的key名称
        String key = CACHE_KEY_CUSTOMER + customerId;

        // 布隆过滤器check,无是绝对无,有是可能有
        //=================================
        if (!checkUtils.checkWithBloomFilter("whitelistCustomer", key)) {
            log.info("白名单无此顾客,不可访问:" + key);
            //执行停止操作
        }


        // 1.先去redis查询
        customer = (Customer) redisTemplate.opsForValue().get(key);
        // 2.redis有直接返回,没有再进去查询mysql
        if (customer == null) {
            // 3.查询mysql
            customer = customerMapper.queryOneCustomerById(customerId);
            // 3.1mysql有,redis无
            if (customer != null) {
                // 3.2把mysql查询出来的数据放进 redis
                redisTemplate.opsForValue().set(key, customer);
            }
        }
        return customer;
    }
}

CustomerController类:

@RestController
@RequestMapping("/customer")
public class CustomerController {
    @Autowired
    private CustomerService customerService;

    @ApiOperation("数据库初始化2条Customer记录插入")
    @PostMapping("/add")
    public void addCustomer() {
        for (int i = 0; i < 2; i++) {
            Customer customer = new Customer();

            customer.setId(i);
            customer.setCname("customer" + i);
            customer.setAge(new Random().nextInt(30) + 1);
            customer.setPhone("1371111XXXX");
            customer.setSex((byte) new Random().nextInt(2));
            customer.setBirth(Date.from(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant()));

            customerService.addCustomer(customer);
        }
    }

    @ApiOperation("单个用户查询,按照id查询")
    @GetMapping("/queryOne/{customerId}")
    public Customer queryOneCustomerById(@PathVariable int customerId) {
        return customerService.queryCustomerById(customerId);
    }

    @ApiOperation("布隆过滤器,按照id查询")
    @GetMapping("/customerBloomFilter/{customerId}")
    public Customer queryOneCustomerByIdWithBloomFilter(@PathVariable int customerId) {
        Customer customer = customerService.queryCustomerByIdWithBloomFilter(customerId);
        return customer;
    }
}

测试结果:
mysql中的数据
在这里插入图片描述
queryOneCustomerByIdWithBloomFilter:
在这里插入图片描述
白名单内只有customer0,所以customer1是无法访问的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值