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

news2025/9/20 12:26:49

学习文档


文章目录

    • 一、什么是 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);
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机网络扫盲(1)——因特网

一、概述 因特网是一个世界范围的计算机网络&#xff0c;即它是一个互联了遍及全世界数十亿计算设备的网络。大家对此应该并不陌生&#xff0c;我们身边有着不计其数的计算机设备被接入了因特网&#xff0c;如今计算机网络这个术语似乎已经有点过时了&#xff0c;用因特网的术语…

结合贝叶斯定理浅谈商业银行员工异常行为排查

1.贝叶斯定理的数学表达 贝叶斯方法依据贝叶斯定理。关于贝叶斯定理解释如下&#xff1a;首先我们设定在事件B条件下&#xff0c;发生事件A的条件概率&#xff0c;即 &#xff0c;从数学公式上&#xff0c;此条件概率等于事件A与事件B同时发生的概率除以事件B发生的概率。 上述…

MyBatis增删改查和配置文件

MyBatis增删改查 MyBatis新增 新增用户 持久层接口添加方法 void add(User user);映射文件添加标签 <insert id"add" parameterType"com.mybatis.pojo.User">insert into user(username,sex,address) values(# {username},# {sex},# {address}) <…

海林猴头菇 区域公用品牌形象正式发布

猴头菇是中国八大“山珍”之一&#xff0c;自古就有“山珍猴头&#xff0c;海味燕窝”之说&#xff0c;猴头菇在中国既是食用珍品&#xff0c;又是重要的药用菌。 海林市位于黑龙江省东南部&#xff0c;地处长白山脉张广才岭东麓&#xff0c;素有“林海雪原”之称。 海林猴头菇…

虚函数表和虚函数在内存中的位置

文章目录 结论验证 结论 虚函数表指针是虚函数表所在位置的地址。虚函数表指针属于对象实例。因而通过new出来的对象的虚函数表指针位于堆&#xff0c;声名对象的虚函数表指针位于栈 虚函数表位于只读数据段&#xff08;.rodata&#xff09;&#xff0c;即&#xff1a;C内存模…

《opencv实用探索·八》图像模糊之均值滤波简单理解

1、前言 什么是噪声&#xff1f; 该像素与周围像素的差别非常大&#xff0c;导致从视觉上就能看出该像素无法与周围像素组成可识别的图像信息&#xff0c;降低了整个图像的质量。这种“格格不入”的像素就被称为图像的噪声。如果图像中的噪声都是随机的纯黑像素或者纯白像素&am…

jionlp :一款超级强大的Python 神器!轻松提取地址中的省、市、县

在日常数据处理中&#xff0c;如果你需要从一个完整的地址中提取出省、市、县三级地名&#xff0c;或者乡镇、村、社区两级详细地名&#xff0c;你可以使用一个第三方库来实现快速解析。在使用之前&#xff0c;你需要先安装这个库。 pip install jionlp -i https://pypi.douba…

如何使用注解实现接口的幂等性校验

如何使用注解实现接口的幂等性校验 背景什么是幂等性为什么要实现幂等性校验如何实现接口的幂等性校验1. 数据库唯一主键2. 数据库乐观锁3. 防重 Token 令牌4. redis 如何将这几种方式都组装到一起结语 背景 最近在小组同学卷的受不了的情况下&#xff0c;我决定换一个方向卷去…

Docker Compose及Docker 知识点整理

目录 1、Docker Compose 简介 2、为什么要使用Docker Compose 3、Docker Compose安装使用&#xff08;Linux&#xff09; 3.1 下载 3.2 mkdir docker 文件夹目录 3.3 上传docker-compose到docker文件夹 3.4 移动到 /usr/local/bin 目录下 3.5 添加执行权限 3.6 修改文…

图文深入理解TCP三次握手

前言 TCP三次握手和四次挥手是面试题的热门考点&#xff0c;它们分别对应TCP的连接和释放过程&#xff0c;今天我们先来认识一下TCP三次握手过程&#xff0c;以及是否可以使用“两报文握手”建立连接&#xff1f;。 1、TCP是什么&#xff1f; TCP是面向连接的协议&#xff0c;…

关于Typora如何插入自己的云端视频的方法

关于Typora如何插入自己的云端视频的方法 文章目录 关于Typora如何插入自己的云端视频的方法前言&#xff1a;实现步骤&#xff1a;小结 前言&#xff1a; 我本来使用gitee来作为typora的图床&#xff0c;但我现在想要把我自己的视频上传到云端&#xff0c;然后通过超链接在ty…

2017年全国硕士研究生入学统一考试管理类专业学位联考英语(二)试题

文章目录 Section I Use of EnglishSection II Reading ComprehensionText 121-细节信息题22-细节信息题23-推断题24-细节信息题25-态度题 Text 226-细节信息题27-细节信息题28-细节信息题29-细节信息题30-细节信息题 Text 331-细节信息题32-细节信息题33-猜词题34-细节信息题3…

C语言之位段(详解)

C语言之位段 文章目录 C语言之位段1. 位段的介绍2. 位段的内存分配3. 位段跨平台问题4. 位段的应用5. 位段使用注意 1. 位段的介绍 位段&#xff08;bit-field&#xff09;是C语言中的一种特殊数据类型&#xff0c;它允许将一个字节分成几个部分&#xff0c;并为每个部分指定特…

设计模式-结构型模式之组合、享元设计模式

文章目录 四、组合模式五、享元模式 四、组合模式 组合模式&#xff08;Composite Pattern&#xff09;&#xff0c;又叫部分整体模式&#xff0c;是用于把一组相似的对象当作一个单一的对象。 组合模式依据树形结构来组合对象&#xff0c;用来表示部分以及整体层次。它创建了…

嵌入式Linux:ARM驱动+QT应用+OpenCV人脸识别项目实现

一、前言&#xff1a; 这个项目主要分为两部分&#xff0c;客户端&#xff08;ARM板端&#xff09;负责利用OpenCV采集人脸数据&#xff0c;利用TCP将人脸数据发送给服务器&#xff0c;然后服务器根据人脸数据进行人脸识别&#xff0c;将识别后的结果返还给客户端&#xff0c;客…

【教学类-06-13】20231202 0-9数字分合-房屋样式(二)-左空或者右空-升序-抽7题

作品展示&#xff1a; 背景需求&#xff1a; 【教学类-06-12】20231202 0-9数字分合-房屋样式&#xff08;一&#xff09;-下右空-升序-抽7题-CSDN博客文章浏览阅读102次。【教学类-06-12】20231202 0-9数字分合-房屋样式-下右空-升序https://blog.csdn.net/reasonsummer/arti…

Redis数据存储:高效、灵活、实时

目录 引言 1. Redis概述 1.1 什么是Redis&#xff1f; 1.2 Redis的数据结构 1.3 Redis的持久化机制 2. Redis的使用场景 2.1 缓存 2.2 会话存储 2.3 发布/订阅系统 2.4 计数器和排行榜 3. Redis最佳实践 3.1 数据模型设计 3.2 键的命名规范 3.3 事务和原子操作 3…

windows11 hosts文件没权限修改

1 win➕R 2 输入 cmd 3 同时按三个键 ctrl➕shift➕enter打开管理员权限 4 输入notepad回车,在记事本里直接点击文件-打开&#xff0c;选择路径:C:\Windows\System32\drivers\etc&#xff0c;继续选择所有文件&#xff0c;然后打开hosts文件 5 修改完之后&#xff0c;c…

九九乘法表-第11届蓝桥杯选拔赛Python真题精选

[导读]&#xff1a;超平老师的Scratch蓝桥杯真题解读系列在推出之后&#xff0c;受到了广大老师和家长的好评&#xff0c;非常感谢各位的认可和厚爱。作为回馈&#xff0c;超平老师计划推出《Python蓝桥杯真题解析100讲》&#xff0c;这是解读系列的第13讲。 九九乘法表&#…

SickOs1.2

信息收集 # Nmap 7.94 scan initiated Wed Nov 22 22:16:22 2023 as: nmap -sn -oN live.nmap 192.168.182.0/24 Nmap scan report for 192.168.182.1 (192.168.182.1) Host is up (0.00028s latency). MAC Address: 00:50:56:C0:00:08 (VMware) Nmap scan report for 192.168…