布隆过滤器,Redis之 bitmap,场景题【如果微博某个大V发了一条消息,怎么统计有多少人看过了】

学习文档


文章目录

    • 一、什么是 Bitmap
      • 1-1、Bitmap 相关命令
    • 二、Bitmap 和 Set 对比
      • 2-1、数据准备
      • 2-2、内存对比
      • 2-3、性能对比
    • 三、布隆过滤器
      • 3-1、理论
      • 3-2、代码实现
    • 四、Java中的 Hash 函数


最近面试时,遇到了一个场景题,面试官问如何统计一条微博大V的消息有多少人阅读。经过老大的指点,我总结了一下。



如果微博某个大V发了一条消息,怎么统计有多少人看过了。


每一个访问记录肯定是要入库的,但页面展示的时候,我们不可能都去数据库 count 一下。最开始我说使用redis的set数据结构把用户id存进去,但这并不是一个很好的答案,因为它消耗的内存太大。


Redis有一种数据结构 Bitmap,在特定的数据场景下,它很适合来做这种统计。为什么说是特定的场景,下面我们来分析。


一、什么是 Bitmap


Bitmap是一种精简而高效的数据结构,通过二进制位存储大规模布尔值信息,常用于快速处理用户在线状态、权限管理以及行为记录等应用场景。


可以简单把它想象成是趋于无限大的数组,每个位置只能存储 1 和 0。它可以快速统计出有多少个 1,也可以快速统计某个区间内有多少个 1。


基于此我们可以创建一个 bitmap, key 就是这条消息的id,每个位置就对应一个用户,1 就表示看过。


1-1、Bitmap 相关命令


描述命令
插入数据setbit key offset value
设置为 1setbit bitmap001 10000 1
设置为 0setbit bitmap001 10000 0
查询数据getbit key offset (每个位置默认是 0)
数据统计bitcount key [start end]
统计全部为 1bitcount bitmap001
按照范围统计为 1bitcount bitmap001 0 1000000
获取范围内第一个 offsetbitpos key value [start] [end]
获取第一个 1bitpos bitmap001 1
获取第一个 0bitpos bitmap001 0
获取 0, 100 中第一个 1bitpos bitmap001 1 0 100

二、Bitmap 和 Set 对比


如果只是想统计有多少个用户访问过,且某个用户是否访问过,其实 set类型,也可以满足我们的要求,实际上我上次也是这么回答的,但结果是不对的,下面来看分析。


看一种数据结构是否好,无非是看它消耗的存储空间和运行速率,基于此我们来对比一下 bitmap 和 set的内存消耗和运行速率。


2-1、数据准备


我们以 10w 数据为基准来进行测试。插入数据的脚本如下:

@Scheduled(fixedRate = 1000 * 60 * 60)
public void fun() {


    redisTemplate.setKeySerializer(new StringRedisSerializer());
    redisTemplate.setValueSerializer(new StringRedisSerializer());
    redisTemplate.setHashKeySerializer(new StringRedisSerializer());

    long start = System.currentTimeMillis();

    ValueOperations<String, Object> valueOps = redisTemplate.opsForValue();

    for (int i = 0; i < 100000; i++) {
        String uuid = UUID.randomUUID().toString();

        redisTemplate.opsForSet().add("set10w_uuid", uuid);
        redisTemplate.opsForSet().add("set10w_incr", String.valueOf(i));
        valueOps.setBit("bitMap10w_hash", Murmur3.hash_x86_32(uuid.getBytes(),  uuid.length(), 0),true);
        valueOps.setBit("bitMap10w_hash_size", Math.abs(Murmur3.hash_x64_128(uuid.getBytes(),  uuid.length(), 0)[0] % 100000),true);
        valueOps.setBit("bitMap10w_incr", i,true);

        System.out.println("progress " + i);
    }

    System.out.println("执行耗时: " + (System.currentTimeMillis() - start));
}

其实就是生成10w个uuid,把这个10w个uuid存入set,把这些uuid转成hash作为 bitmap的偏移量存入 bitmap,再把 i 存入另外一个set和bitmap,这样就构建了 4个数据。


另外还创建了一个特殊的 bitmap,它的生成只有一个添加语句,如下:

setbit bitMap_1_bitHash 1000000000 1

bitMap10w_hash_size 这个key展示先忽略,我们后面再说。


在这里插入图片描述


2-2、内存对比


使用 Redis 提供的命令查看每个 key 的内存消耗。

redis-cli memory usage keyName

在这里插入图片描述


上面的数据体现是不是很不可思议?为什么内存消耗会那么大? set 的数据就没什么好说了,就是按照字符串去存储的,我们主要来探讨一下 bitmap 。


bitmap是一个二进制存储结构,所以当它的偏移量越大,所占用的内存也就越大。 incr就是自增的id,所以最大偏移量也就是 100000,那它占用内存当然很小。而通过uuid转成的hashCode值是很大的。


2-3、性能对比


说实在的它们俩的类型不同,其实不太好去对比。这里只是简单的对比一下:获取数据总量和查询某个值是否存在

@Scheduled(fixedRate = 1000 * 60 * 60)
public void fun2() {
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    redisTemplate.setValueSerializer(new StringRedisSerializer());
    redisTemplate.setHashKeySerializer(new StringRedisSerializer());
    ValueOperations<String, Object> valueOps = redisTemplate.opsForValue();
    // 预热一下
    Long xxxxx = redisTemplate.opsForSet().size("set10w_incr");


    long one = System.currentTimeMillis();

    Long set10w_uuid = redisTemplate.opsForSet().size("set10w_uuid");
    redisTemplate.opsForSet().isMember("set10w_uuid", "xxxxx");

    long two = System.currentTimeMillis();
    System.out.println("set10w_uuid = " + set10w_uuid + " " + (two - one));

    Long set10wIncr = redisTemplate.opsForSet().size("set10w_incr");
    redisTemplate.opsForSet().isMember("set10w_incr", "1");
    long three = System.currentTimeMillis();
    System.out.println("set10wIncr = " + set10wIncr+ " " + (three - two));

    Object bitMap10w_incr = redisTemplate.execute((RedisCallback<Long>) connection ->
            connection.bitCount("bitMap10w_incr".getBytes())
    );
    valueOps.getBit("bitMap10w_incr", 1000);
    long four = System.currentTimeMillis();
    System.out.println("bitMap10w_incr = " + bitMap10w_incr+ " " + (four - three));


    Object bitMap10w_hash = redisTemplate.execute((RedisCallback<Long>) connection ->
            connection.bitCount("bitMap10w_hash".getBytes())
    );
    valueOps.getBit("bitMap10w_hash", 1000);
    long five = System.currentTimeMillis();
    System.out.println("bitMap10w_hash = " + bitMap10w_hash+ " " + (five - four));
}

执行结果如下

set10w_uuid = 100000 7
set10wIncr = 100000 3
bitMap10w_incr = 100000 35
bitMap10w_hash = 99998 141

看似是set好像性能更好,但术业有专攻,不应该这样对比的。


三、布隆过滤器


3-1、理论


布隆过滤器 本质就是一个bigmap,目的就是在做业务操作之前,先过滤掉一些不正当的请求

和上面的需求差不多当然也可以用set来做,但这样的内存的消耗就大了,而且容易产生大key


布隆过滤器 有两个重要的参数

  1. hash函数, 一个好的hash函数可以尽可能的防止冲突的发生。且我们可以设置多个hash函数
  2. 存储大小 size, 我们不可能设置一个无限大的空间,这样会导致数据过于分散不好统计

使用Redis的bitmap来搭建布隆过滤器的大致步骤如下,先基于业务场景定义好 size和hash函数,每一个offset的取值按照这个公式,offset = hash % size,这样计算的目的是防止hash大于 size


3-2、代码实现


下面是布隆过滤器的Java代码实现:(GPT写的,很好理解)

// Java代码示例
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;

import java.util.BitSet;

public class BloomFilter {

    @Autowired
    private RedisTemplate redisTemplate;
    private final String filterKey;
    private final int size;
    private final int[] hashFunctions;

    public BloomFilter(String filterKey, int size, int numHashFunctions) {
        this.filterKey = filterKey;
        this.size = size;
        this.hashFunctions = new int[numHashFunctions];
        initializeHashFunctions();
    }
    
    // 自定义多个hash函数
    private void initializeHashFunctions() {
        for (int i = 0; i < hashFunctions.length; i++) {
            hashFunctions[i] = (int) (Math.random() * Integer.MAX_VALUE);
        }
    }

    public void add(String element) {
        // 多次hash计算,最大程度保证可用
        for (int hashFunction : hashFunctions) {
            // 防止hash超出为负数
            int index = Math.abs(hashFunction % size);
            redisTemplate.opsForValue().setBit(filterKey, index, true);
        }
    }

    public boolean contains(String element) {
        for (int hashFunction : hashFunctions) {
            int index = Math.abs(hashFunction % size);
            if (!redisTemplate.opsForValue().getBit(filterKey, index)) {
                return false;
            }
        }
        return true;
    }
    
    // 测试代码
    public void test() {
        BloomFilter bloomFilter = new BloomFilter(redisTemplate, "bloomFilter", 10000, 3);

        // Add elements to the Bloom Filter
        bloomFilter.add("element1");
        bloomFilter.add("element2");
        bloomFilter.add("element3");

        // Check if an element is in the Bloom Filter
        System.out.println(bloomFilter.contains("element1")); // true
        System.out.println(bloomFilter.contains("element4")); // false
    }
}


使用这种紧凑的bitmap(定义了size大小), 100w的空间所需要的内存也是极少的。

布隆过滤器的过滤并不是 100%,当发生hash冲突的时候就可能误杀,上面不限制大小的冲突是 0.002%,限制10w大小,生成 10w个数据的冲突是 36.84%

在这里插入图片描述


四、Java中的 Hash 函数


Murmur 是一个计算 hash 的工具类,使用和引入依赖如下:

<!-- Maven依赖 -->
<dependency>
    <groupId>com.sangupta</groupId>
    <artifactId>murmur</artifactId>
    <version>1.0.0</version>
</dependency>
public static void main(String[] args) {
    
    // 在32位机器上计算hash
    long l = Murmur3.hash_x86_32(UUID.randomUUID().toString().getBytes(), 36, 0);

    // 在64位机器上计算hash
    long[] longs = Murmur3.hash_x64_128(UUID.randomUUID().toString().getBytes(), 36, 0);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/210397.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

pandas基础操作2

数据读取 我们想要使用 Pandas 来分析数据&#xff0c;那么首先需要读取数据。大多数情况下&#xff0c;数据都来源于外部的数据文件或者数据库。Pandas 提供了一系列的方法来读取外部数据&#xff0c;非常全面。下面&#xff0c;我们以最常用的 CSV 数据文件为例进行介绍。 …

邮件迁移-邮件同步-批量完成邮件迁移解决方案-imapsync

背景&#xff1a; 公司原来使用的邮箱服务器实现方式是james的cassandra-app&#xff0c;如今要启用新的邮件服务器&#xff0c;架构用的是james的distributed-app,升级后&#xff0c;要求邮件数据不丢失&#xff0c;因此要平滑完成邮件的迁移工作&#xff0c;保障升级后邮件不…

Selenium page object模式Python

目录 概述 优点 示例 项目结构&#xff1a; 基础页面类BasePage 业务页面类BaiduHomePage 测试类test_baidu&#xff1a; 文件工具类file_util 运行日志&#xff1a; 测试结果&#xff1a; 概述 在web应用程序的UI中&#xff0c;有一些区域可以与测试交互。页面对象…

[数据结构]红黑树的定义以及添加原则

红黑树是一种自平衡的二叉查找树&#xff0c;是一种常用的数据结构 1972年出现&#xff0c;在当时被称为平衡二叉B树。后来1978年被修改为如今的“红黑树” 它是一个特殊的二叉查找树&#xff0c;红黑树的每一个节点上都有储存位表示节点的颜色 每一个节点可以是红或者黑&#…

KNN实战-图像识别

数据说明 是在循环0-9的数字一直循环500次所得到的数据&#xff0c;然后以手写照片的形式存在 识别的步骤 加载数据构建目标值构建模型参数调优可视化展示 加载数据 import numpy as np import matplotlib.pyplot as plt # 记载数据 data np.load(./digit.npy) data构建目…

什么是Ros(二)- 结构和通讯概述

目录 1.架构 2.通讯 参考文献 上接&#xff1a;什么是Ros&#xff08;一&#xff09;-CSDN博客 1.架构 共三层&#xff1a;OS 层&#xff0c;中间层&#xff0c;应用层。 OS 层&#xff1a;OS 层是操作系统层也就是我们现在使用的ubuntu&#xff08;linux&#xff09;&…

【Java Web学习笔记】 2 - CSS入门

项目代码 零、 CSS引出 CSS 教程 官方教学文档 1.在没有CSS之前&#xff0c;我们想要修改HTML元素的样式需要为每个HTML元素单独定义样式属性&#xff0c;费心费力。所以CSS就出现了。 2.使用CSS将HTML页面的内容与样式分离提高web开发的工作效率&#xff08;针对前端开发&a…

Spring Cloud笔记 —— 什么是Spring Cloud?

引言&#xff1a; 在写这篇博客之前&#xff0c;其实吧&#xff0c;博主很久之前有过一段时间的Spring Cloud的案例项目开发经验&#xff0c;就是一个案例项目开发而已&#xff0c;也说不上有多高大上&#xff0c;那个时候&#xff0c;我其实也是从众而已罢了&#xff0c;毕竟现…

Java 设计模式系列:代理模式

文章目录 介绍静态代理基本介绍应用实例静态代理优缺点 动态代理基本介绍JDK 中生成代理对象的 API Cglib 代理基本介绍实现步骤 介绍 1&#xff09;代理模式&#xff1a;为一个对象提供一个替身&#xff0c;以控制对这个对象的访问。即通过代理对象访问目标对象 2&#xff09…

JOSEF 快速中间继电器 KZJ-4H-L DC220V 导轨安装

快速中间继电器KZJ-4H-LDC220V导轨安装导轨安装是广泛用于电力系统&#xff0c;能够断货开或开通大负载&#xff0c;并且具有较强的断弧能力&#xff0c;适用于交流50/60Hz。电压24380V,直流电压24280V自动控制电路中以增加保护和控制回路的触点数量与触点容量。 KZJ系列快速中…

leetcode 209. 长度最小的子数组(优质解法)

代码&#xff1a; //时间复杂度 O(N) ,空间复杂度 O(1) class Solution {//采用滑动窗口的方法解决public int minSubArrayLen(int target, int[] nums) {int numsLengthnums.length;int minLengthInteger.MAX_VALUE;int left0;int right0;int sum0;while (right<numsLengt…

详解原生Spring框架下的方法切入点表达式

&#x1f609;&#x1f609; 学习交流群&#xff1a; ✅✅1&#xff1a;这是孙哥suns给大家的福利&#xff01; ✨✨2&#xff1a;我们免费分享Netty、Dubbo、k8s、Mybatis、Spring...应用和源码级别的视频资料 &#x1f96d;&#x1f96d;3&#xff1a;QQ群&#xff1a;583783…

C++/Qt读写xml文件

今天介绍C/Qt如何读写xml文件&#xff0c;xml文件一般用于作为配置文件使用。 C C读写xml文件需要借助第三方来实现&#xff0c;比较好用的有tinyxml2和pugixml&#xff0c;对应的网址链接。 tinyxml2 pugixml 以tinyxml2为例&#xff0c;下载后进行解压可以看到以下文件&…

Python---格式化输出与%百分号----涉及转义符 \ 反斜杠的使用

相关链接Python--格式化输出中的转义符号----\t 制表符&#xff08;空格的&#xff09;和\n&#xff08;换行的&#xff09;_唯元素的博客-CSDN博客 Python---字符串&#xff08;用单、双引号、 三单/双引号定义。反斜杠 \ 转义&#xff0c;单在双内/双在单内 &#xff09;-CS…

【电路笔记】-串联和并联电阻

串联和并联电阻 文章目录 串联和并联电阻1、概述2、串联和并联电阻示例13、串联和并联电阻示例2 电阻器可以无限数量的串联和并联组合连接在一起&#xff0c;形成复杂的电阻电路。 1、概述 在之前的教程中&#xff0c;我们学习了如何将各个电阻器连接在一起以形成串联电阻器网…

混沌系统在图像加密中的应用(基于哈密顿能量函数的混沌系统构造1.5)

混沌系统在图像加密中的应用&#xff08;基于哈密顿能量函数的混沌系统构造1.5&#xff09; 前言一、自治非哈密顿系统的构造、动态特性分析1.相关理论基础2.两个四维自治非哈密顿系统3.数值分析 python代码 前言 续接混沌系统在图像加密中的应用&#xff08;基于哈密顿能量函…

重生奇迹MU再生原石

通过坎特鲁提炼之塔的NPC艾尔菲丝提炼成功就可以可获得再生宝石。 重生奇迹mu里的再生原石的用法&#xff1a; 1、打怪获得再生原石去提炼之塔&#xff08;进入坎特鲁遗址的141188位置的传送台&#xff09;。 2、找到&#xff08;艾儿菲丝&#xff09;把原石提炼成再生宝石。…

MySQL安全相关——TDE和数据脱敏功能介绍

MySQL作为一款广泛使用的开源关系型数据库管理系统(RDBMS)&#xff0c;其安全性一直是开发者和企业关注的重点。在MySQL中&#xff0c;有一些与安全相关的功能&#xff0c;其中包括Transparent Data Encryption(TDE)和数据脱敏。本文将对这些功能进行介绍。 一、Transparent Da…

深度学习实战63-利用自适应混合金字塔网络实现人脸皮肤美颜效果,快速部署与实现一键美颜功能

大家好,我是微学AI,今天给大家介绍一下深度学习实战63-利用自适应混合金字塔网络实现人脸皮肤美颜效果,快速部署与实现一键美颜功能。在本文中,我将介绍一种新颖的自适应混合金字塔网络(ABPN),该网络可以实现对超高分辨率照片的快速局部修饰。该网络主要由两个组件组成:一…

最简单的梅花吉凶表

以下是梅花易数吉凶表&#xff0c;使用方式&#xff1a; 随机报2组数字&#xff1a;第1个数除8得余数作为上爻&#xff0c;第2个数除8得余数作为下爻&#xff0c;然后对照以下表格&#xff0c;得到吉凶预测结果。 说明&#xff1a;经过个人不断实践&#xff0c;[大吉转大吉] …