如果要统计一篇文章的阅读量,可以直接使用 Redis 的 incr 指令来完成。如果要求阅读量必须按用户去重,那就可以使用 set 来记录阅读了这篇文章的所有用户 id,获取 set 集合的长度就是去重阅读量。但是如果爆款文章阅读量太大,set 会浪费太多存储空间。这时候我们就要使用 Redis 提供的 HyperLogLog 数据结构来代替 set,它只会占用最多 12k 的存储空间就可以完成海量的去重统计。但是它牺牲了准确度,它是模糊计数,误差率约为 0.81%。

那么有没有一种不怎么浪费空间的精确计数方法呢?我们首先想到的就是位图,可以使用位图的一个位来表示一个用户id。如果一个用户id是32字节,那么使用位图就只需要占用 1/256 的空间就可以完成精确计数。但是如何将用户id映射到位图的位置呢?如果用户id是连续的整数这很好办,但是通常用户系统的用户id并不是整数,而是字符串或者是有一定随机性的大整数。

我们可以强行给每个用户id赋予一个整数序列,然后将用户id和整数的对应关系存在redis中。

$next_user_id = incr user_id_seq
set user_id_xxx   $next_user_id
$next_user_id = incr user_id_seq
set user_id_yyy  $next_user_id
$next_user_id = incr user_id_seq
set user_id_zzz   $next_user_id

有了这个映射关系,我们就很容易构造出每一篇文章的阅读打点位图,来一个用户,就将相应位图中相应的位置为一。如果位从0变成1,那么就可以给阅读数加1。这样就可以很方便的获得文章的阅读数。

而且我们还可以动态计算阅读了两篇文章的公共用户量有多少?将两个位图做一下 AND 计算,然后统计位图中位 1 的个数。同样,还可以有 OR 计算、XOR 计算等等都是可行的。

问题又来了!Redis 的位图是密集位图,什么意思呢?如果有一个很大的位图,它只有最后一个位是 1,其它都是零,这个位图还是会占用全部的内存空间,这就不是一般的浪费了。你可以想象大部分文章的阅读量都不大,但是它们的占用空间却是很接近的,和哪些爆款文章占据的内存差不多。

看来这个方案行不通,我们需要想想其它方案!这时咆哮位图(RoaringBitmap)来了。

它将整个大位图进行了分块,如果整个块都是零,那么这整个块就不用存了。但是如果位1比较分散,每个块里面都有1,虽然单个块里的1很少,这样只进行分块还是不够的,那该怎么办呢?我们再想想,对于单个块,是不是可以继续优化?如果单个块内部位 1 个数量很少,我们可以只存储所有位1的块内偏移量(整数),也就是存一个整数列表,那么块内的存储也可以降下来。这就是单个块位图的稀疏存储形式 —— 存储偏移量整数列表。只有单块内的位1超过了一个阈值,才会一次性将稀疏存储转换为密集存储。

咆哮位图除了可以大幅节约空间之外,还会降低 AND、OR 等位运算的计算效率。以前需要计算整个位图,现在只需要计算部分块。如果块内非常稀疏,那么只需要对这些小整数列表进行集合的 AND、OR 运算,如是计算量还能继续减轻。

这里既不是用空间换时间,也没有用时间换空间,而是用逻辑的复杂度同时换取了空间和时间。

咆哮位图的位长最大为 2^32,对应的空间为 512M(普通位图),位偏移被分割成高 16 位和低 16 位,高 16 位表示块偏移,低16位表示块内位置,单个块可以表达 64k 的位长,也就是 8K 字节。最多会有64k个块。现代处理器的 L1 缓存普通要大约 8K,这样可以保证单个块都可以全部放入 L1 Cache,可以显著提升性能。

如果单个块所有的位全是零,那么它就不需要存储。具体某个块是否存在也可以是用位图来表达,当块很少时,用整数列表表示,当块多了就可以转换成普通位图。整数列表占用的空间少,它还有类似于 ArrayList 的动态扩容机制避免反复扩容复制数组内容。当列表中的数字超出4096个时,会立即转变成普通位图。

用来表达块是否存在的数据结构和表达单个块数据的结构可以是同一个,因为块是否存在本质上也是 0 和 1,就是普通的位标志。

但是 Redis 并没有原生支持咆哮位图这个数据结构啊?我们该如何使用呢?

Redis 确实没有原生的,但是咆哮位图的 Redis Module 有。

https://github.com/aviggiano/redis-roaring

这个项目的 star 数量并不是很多,我们来看看它的官方性能对比

下面我们来观察一下源代码看看它的内部结构是怎样的

// 单个块
typedef struct roaring_array_s {int32_t size;int32_t allocation_size;void **containers;  // 指向整数数组或者普通位图uint16_t *keys;uint8_t *typecodes;uint8_t flags;
} roaring_array_t;// 所有块
typedef struct roaring_bitmap_s {roaring_array_t high_low_container;
} roaring_bitmap_t;
#define BITSET_CONTAINER_TYPE_CODE 1
#define ARRAY_CONTAINER_TYPE_CODE 2
#define RUN_CONTAINER_TYPE_CODE 3
#define SHARED_CONTAINER_TYPE_CODE 4

而 SHARED 类型用于在多个咆哮位图之间共享块,它还提供了写复制功能。当这个块被修改时将会复制出新的一份。

咆哮位图的计算逻辑还有更多的细节,我们后面有空再继续介绍。

Redis 精确去重计数 —— 咆哮位图相关推荐

  1. redis统计用户日活量_Redis精确去重计数方法(咆哮位图)

    前言 如果要统计一篇文章的阅读量,可以直接使用 Redis 的 incr 指令来完成.如果要求阅读量必须按用户去重,那就可以使用 set 来记录阅读了这篇文章的所有用户 id,获取 set 集合的长度 ...

  2. Spark多维分析去重计数场景优化案例【BitMap精确去重的应用与踩坑】

    关注交流微信公众号:小满锅 场景 前几天遇到一个任务,从前也没太注意过这个任务,但是经常破9点了,执行时长正常也就2个小时. 看逻辑并不复杂,基本是几段SQL的JOIN操作,其中一个最耗时间的就是要根 ...

  3. 精确去重和Roaring BitMap

    精确去重和Roaring BitMap 互联网行业常见的一个业务需求就是求UV(日活)和N日留存,这就涉及到去重计数(COUNT DISTINCT)的计算. BitMap概述 精确去重算法主要通过Bi ...

  4. elasticsearch 去重计数

    我的个人博客:逐步前行STEP 去重计数不是精确计数,数据量大的情况下会有误差,官方文档说的是,默认的情况下百万级数据会有5%的误差,实测如下: 实际文档数:1924920 去重计数:1912715 ...

  5. 60-320-040-使用-去重-HyperLogLog 去重计数

    文章目录 1.视界 2.概述 3.依赖 4.使用 5.案例 5.1 普通使用 5.2 实现 5.2.1 优化 1.视界 2.概述 在需要对数据进行去重计数的场景里,实现方式是将数据明细存储在集合的数据 ...

  6. ClickHouse基于全局字典与物化视图的精确去重方案

    clickhouse具有bitmap, 但只支持int, 实测表明groupBitmap()这个agg比直接的count(distinct x)计算要快至少一倍以上, 按之前druid中的测试 经验表 ...

  7. Flink+Hologres亿级用户实时UV精确去重最佳实践

    简介:Flink+Hologres亿级用户实时UV精确去重最佳实践 UV.PV计算,因为业务需求不同,通常会分为两种场景: 离线计算场景:以T+1为主,计算历史数据 实时计算场景:实时计算日常新增的数 ...

  8. python去重计数_用Python实现透视表的value_sum和countdistinct功能

    在pandas库中实现Excel的数据透视表效果通常用的是df['a'].value_counts()这个函数,表示统计数据框(DataFrame) df的列a各个元素的出现次数:例如对于一个数据表如 ...

  9. python去重计数_用Python做透视表之value_sum和value_countdistinct功能

    在pandas库中实现Excel的数据透视表效果通常用的是df['a'].value_counts()这个函数,表示统计数据框(DataFrame) df的列a各个元素的出现次数:例如对于一个数据表如 ...

最新文章

  1. LeetCode Maximum Product Subarray(最大子数组乘积)
  2. Linux之bash shell基本命令
  3. xgboost调参指南
  4. Java 8日期和时间
  5. 关联关系与依赖关系的区别
  6. 【Linux】tee命令
  7. 2018华为网络技术大赛失败纪念
  8. SCTP协议与程序设计案例
  9. 软件工程考研复试、工作面试常见问题及答案
  10. 淘宝评论数据抓取简记
  11. dpi、dp、sp、px、mm之间的关系
  12. MKB0805心率血压模块使用方法
  13. 四、卷积、转置卷积(上卷积)大小计算公式
  14. 两个电脑主机共用一个显示器
  15. linux根目录硬盘空间不足的扩容与报错信息解决
  16. 人本是人,不必刻意去做人;世本是世,无须精心去处世
  17. Java——超市会员管理系统(JDBC+MySQL+Apache DBUtils)
  18. java调用fastdfs分布式文件系统
  19. 训练Cityscapes
  20. 流水灯之巅:光の翼广州塔!

热门文章

  1. 黑猴子的家:下载SVN服务端
  2. [ROS] 什么是ROS、ROS的优缺点
  3. html如何插入音乐,html怎么添加进去歌曲
  4. GO语言基础入门(二)
  5. 王下邀月熊_Chevalier的前端每周清单系列文章索引
  6. Kali linux tor浏览器重新启动无法启动
  7. 内网渗透 Metasploit(MSF)基础使用
  8. 国产电脑麒麟银河v10sp1定制桌面版系统开启nfs共享踩坑实录
  9. 极米H6-4K版参数 极米h6评测
  10. 当傻子的最好办法: 把别人当傻子