Redis HyperLogLog 是什么?这些场景使用它~

d49232caeb60f3567664ee4c96c7ada7.gif

作者 | 就是码哥呀

来源 | 码哥字节

在移动互联网的业务场景中,数据量很大,我们需要保存这样的信息:一个 key 关联了一个数据集合,同时对这个数据集合做统计。

  • 统计一个 APP 的日活、月活数;

  • 统计一个页面的每天被多少个不同账户访问量(Unique Visitor,UV));

  • 统计用户每天搜索不同词条的个数;

  • 统计注册 IP 数。

通常情况下,我们面临的用户数量以及访问量都是巨大的,比如百万、千万级别的用户数量,或者千万级别、甚至亿级别的访问信息。

今天「码哥」分别使用不同的数据类型来实现统计一个页面的每天被多少个不同账户访问量这个功能,循序渐进的引出 HyperLogLog的原理与 Java 中整合 Redission 实战。

告诉大家一个技巧,Redis 官方网站现在能在线运行 Redis 指令了:https://redis.io/。如图:

9b95a5af885f77be93cbd2211100b3d3.png

Redis 在线运行

使用 Set 实现

一个用户一天内多次访问一个网站只能算作一次,所以很容易就想到通过 Redis 的 Set 集合来实现。

比如微信 ID 为「肖菜鸡」访问 「Redis 为什么这么快」这篇文章时,我们把这个信息存到 Set 中。

SADD Redis为什么这么快:uv 肖菜鸡 谢霸哥 肖菜鸡
(integer) 1

「肖菜鸡」多次访问「Redis 为什么这么快」页面,Set 的去重功能保证不会重复记录同一个「微信 ID」。

通过 SCARD 命令,统计「Redis 为什么这么快」页面 UV。指令返回一个集合的元素个数(也就是用户 ID)。

SCARD Redis为什么这么快:uv
(integer) 2

使用 Hash 实现

码老湿,还可以利用 Hash 类型实现,将用户 ID 作为 Hash 集合的 key,访问页面则执行 HSET 命令将 value 设置成 1。

即使「肖菜鸡」重复访问页面,重复执行命令,也只会把 key 等于「肖菜鸡」的 value 设置成 1。

最后,利用 HLEN 命令统计 Hash 集合中的元素个数就是 UV。

如下:

HSET Redis为什么这么快 肖菜鸡 1
// 统计 UV
HLEN Redis为什么这么快

使用 Bitmap 实现

Bitmap 的底层数据结构用的是 String 类型的 SDS 数据结构来保存位数组,Redis 把每个字节数组的 8 个 bit 位利用起来,每个 bit 位 表示一个元素的二值状态(不是 0 就是 1)。

Bitmap 提供了 GETBIT、SETBIT 操作,通过一个偏移值 offset 对 bit 数组的 offset 位置的 bit 位进行读写操作,需要注意的是 offset 从 0 开始。

可以将 Bitmap 看成是一个 bit 为单位的数组,数组的每个单元只能存储 0 或者 1,数组的下标在 Bitmap 中叫做 offset 偏移量。

为了直观展示,我们可以理解成 buf 数组的每个字节用一行表示,每一行有 8 个 bit 位,8 个格子分别表示这个字节中的 8 个 bit 位,如下图所示:

cb47d9399efcbc4e8fc9221f4f899d73.png

Bitmap

8 个 bit 组成一个 Byte,所以 Bitmap 会极大地节省存储空间。 这就是 Bitmap 的优势。

如何使用 Bitmap 来统计页面的独立用户访问量呢?

Bitmap 提供了 SETBIT 和 BITCOUNT 操作,前者通过一个偏移值 offset 对 bit 数组的 offset 位置的 bit 位进行写操作,需要注意的是 offset 从 0 开始。

后者统计给定指定的 bit 数组中,值 = 1 的 bit 位的数量。

需要注意的事,我们需要把「微信 ID」转换成数字,因为offset 是下标。

假设我们将「肖菜鸡」转换成编码6

第一步,执行下面指令表示「肖菜鸡」的编码为 6 。

SETBIT 巧用Redis数据类型实现亿级数据统计 6 1

第二步,统计页面访问次数,使用 BITCOUNT 指令。该指令用于统计给定的 bit 数组中,值 = 1 的 bit 位的数量。

BITCOUNT 巧用Redis数据类型实现亿级数据统计

HyperLogLog 王者

Set 虽好,如果文章非常火爆达到千万级别,一个 Set 就保存了千万个用户的 ID,页面多了消耗的内存也太大了。

同理,Hash 数据类型也是如此。

至于 Bitmap,它更适合于「二值状态统计」的使用场景,统计精度高,虽然内存占用要比HashMap少,但是对于大量数据还是会占用较大内存。

咋办呢?

这些就是典型的「基数统计」应用场景,基数统计:统计一个集合中不重复元素的个数。

HyperLogLog 的优点在于它所需的内存并不会因为集合的大小而改变,无论集合包含的元素有多少个,HyperLogLog 进行计算所需的内存总是固定的,并且是非常少的

每个 HyperLogLog 最多只需要花费 12KB 内存,在标准误差 0.81%的前提下,就可以计算 2 的 64 次方个元素的基数。

Redis 实战

HyperLogLog 使用太简单了。PFADD、PFCOUNT、PFMERGE三个指令打天下。

PFADD

将访问页面的每个用户 ID 添加到 HyperLogLog 中。

PFADD Redis主从同步原理:uv userID1 userID 2 useID3

PFCOUNT

利用 PFCOUNT 获取 「Redis 主从同步原理」文章的 UV 值。

PFCOUNT Redis主从同步原理:uv

PFMERGE 使用场景

HyperLogLog 除了上面的 PFADD 和 PFCOIUNT 外,还提供了 PFMERGE

语法

PFMERGE destkey sourcekey [sourcekey ...]

比如在网站中我们有两个内容差不多的页面,运营说需要这两个页面的数据进行合并。

其中页面的 UV 访问量也需要合并,那这个时候 PFMERGE 就可以派上用场了,也就是同样的用户访问这两个页面则只算做一次

如下所示:Redis、MySQL 两个 HyperLogLog 集合分别保存了两个页面用户访问数据。

PFADD Redis数据 user1 user2 user3
PFADD MySQL数据 user1 user2 user4
PFMERGE 数据库 Redis数据 MySQL数据
PFCOUNT 数据库 // 返回值 = 4

将多个 HyperLogLog 合并(merge)为一个 HyperLogLog , 合并后的 HyperLogLog 的基数接近于所有输入 HyperLogLog 的可见集合(observed set)的并集。

user1、user2 都访问了 Redis 和 MySQL,只算访问了一次。

Redission 实战

详细源码「码哥」上传到 GitHub 了:https://github.com/MageByte-Zero/springboot-parent-pom.git

pom 依赖

<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.16.7</version>
</dependency>

添加数据到 Log

// 添加单个元素
public <T> void add(String logName, T item) {RHyperLogLog<T> hyperLogLog = redissonClient.getHyperLogLog(logName);hyperLogLog.add(item);
}// 将集合数据添加到 HyperLogLog
public <T> void addAll(String logName, List<T> items) {RHyperLogLog<T> hyperLogLog = redissonClient.getHyperLogLog(logName);hyperLogLog.addAll(items);
}

合并

/*** 将 otherLogNames 的 log 合并到 logName** @param logName       当前 log* @param otherLogNames 需要合并到当前 log 的其他 logs* @param <T>*/
public <T> void merge(String logName, String... otherLogNames) {RHyperLogLog<T> hyperLogLog = redissonClient.getHyperLogLog(logName);hyperLogLog.mergeWith(otherLogNames);
}

统计基数

public <T> long count(String logName) {RHyperLogLog<T> hyperLogLog = redissonClient.getHyperLogLog(logName);return hyperLogLog.count();
}

单元测试

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(classes = RedissionApplication.class)
public class HyperLogLogTest {@Autowiredprivate HyperLogLogService hyperLogLogService;@Testpublic void testAdd() {String logName = "码哥字节:Redis为什么这么快:uv";String item = "肖菜鸡";hyperLogLogService.add(logName, item);log.info("添加元素[{}]到 log [{}] 中。", item, logName);}@Testpublic void testCount() {String logName = "码哥字节:Redis为什么这么快:uv";long count = hyperLogLogService.count(logName);log.info("logName = {} count = {}.", logName, count);}@Testpublic void testMerge() {ArrayList<String> items = new ArrayList<>();items.add("肖菜鸡");items.add("谢霸哥");items.add("陈小白");String otherLogName = "码哥字节:Redis多线程模型原理与实战:uv";hyperLogLogService.addAll(otherLogName, items);log.info("添加 {} 个元素到 log [{}] 中。", items.size(), otherLogName);String logName = "码哥字节:Redis为什么这么快:uv";hyperLogLogService.merge(logName, otherLogName);log.info("将 {} 合并到 {}.", otherLogName, logName);long count = hyperLogLogService.count(logName);log.info("合并后的 count = {}.", count);}
}

基本原理

HyperLogLog 是一种概率数据结构,它使用概率算法来统计集合的近似基数。而它算法的最本源则是伯努利过程。

伯努利过程就是一个抛硬币实验的过程。抛一枚正常硬币,落地可能是正面,也可能是反面,二者的概率都是 1/2

伯努利过程就是一直抛硬币,直到落地时出现正面位置,并记录下抛掷次数k

比如说,抛一次硬币就出现正面了,此时 k1; 第一次抛硬币是反面,则继续抛,直到第三次才出现正面,此时 k 为 3。

对于 n 次伯努利过程,我们会得到 n 个出现正面的投掷次数值 k1, k2 ... kn, 其中这里的最大值是 k_max

根据一顿数学推导,我们可以得出一个结论:2^{k_ max} 来作为 n 的估计值。

也就是说你可以根据最大投掷次数近似的推算出进行了几次伯努利过程。

所以 HyperLogLog 的基本思想是利用集合中数字的比特串第一个 1 出现位置的最大值来预估整体基数,但是这种预估方法存在较大误差,为了改善误差情况,HyperLogLog 中引入分桶平均的概念,计算 m 个桶的调和平均值。

Redis 中 HyperLogLog 一共分了 2^14 个桶,也就是 16384 个桶。每个桶中是一个 6 bit 的数组。

Redis 对 HyperLogLog 的存储进行了优化,在计数比较小的时候,存储空间采用系数矩阵,占用空间很小。

只有在计数很大,稀疏矩阵占用的空间超过了阈值才会转变成稠密矩阵,占用 12KB 空间。

为何只需要 12 KB 呀?

HyperLogLog 实现中用到的是 16384 个桶,也就是 2^14,每个桶的 maxbits 需要 6 个 bits 来存储,最大可以表示 maxbits=63,于是总共占用内存就是2^14 * 6 / 8 = 12k字节。

参考资料

[1]:https://www.cnblogs.com/loveLands/articles/10987055.html

[2]:Redis 使用手册

[3]:https://zhuanlan.zhihu.com/p/265309426

ad098716c9262cd01d5e6773f43a7e30.gif

往期推荐

如果让你来设计网络

写时复制就这么几行代码,还是不会?

JavaScript 数组你都掰扯不明白,还敢说精通 JavaScript ?

明明还有大量内存,为啥报错“无法分配内存”?

28f4ecc7671413ab69402ea8f8999f84.gif

点分享

66935a1a4d676cb0636fd0c8566cc7f6.gif

点收藏

cf5e574dcf85b08adfdc983914bd9f42.gif

点点赞

4d51ec8d43ab87bbc329ad4de2a69a66.gif

点在看

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

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

相关文章

matlab三角形分割,MATLAB 2014b及以上版本中带有画家渲染器的三角形拆分补丁

在解决实际问题之前,这是一个值得怀疑的解决方法&#xff1a;对角线只是三角形之间的空白区域,所以我们看到的是补丁后面的白色空间.愚蠢的想法&#xff1a;让我们用匹配的颜色填充该空间而不是白色.为此,我们将复制所有对象,并通过一个tiiiiny位来抵消新对象.码&#xff1a;hi…

网易云音乐音视频算法的 Serverless 探索之路

简介&#xff1a; 网易云音乐最初的音视频技术大多都应用在曲库的数据处理上&#xff0c;基于音视频算法服务化的经验&#xff0c;云音乐曲库团队与音视频算法团队一起协作&#xff0c;一起共建了网易云音乐音视频算法处理平台&#xff0c;为整个云音乐提供统一的音视频算法处理…

小小的 likely 背后却大有玄机!

作者 | 张彦飞allen来源 | 开发内功修炼今天我给大家分享一个内核中常用的提升性能的小技巧。理解了它对你一定大有好处。在内核中很多地方都充斥着 likely、unlikely 这一对儿函数的使用。随便揪两处&#xff0c;比如在 TCP 连接建立的过程中的这两个函数。//file: net/ipv4/t…

阿里云马涛:因云进化的基础软件

简介&#xff1a; 基础软件的云原生化。 编者按&#xff1a;2021 年10 月20 日&#xff0c;在2021 云栖大会云计算产业升级峰会上&#xff0c;阿里云“因云而生”云原生心智大图正式发布&#xff0c;包含弹性计算、云网络、基础产品、基础设施、操作系统、云安全、开放平台等7个…

阿里云ECI如何6秒扩容3000容器实例?

简介&#xff1a; 2021年云栖大会现场&#xff0c;阿里云工程师演示了在6秒时间内成功启动3000个ECI&#xff0c;并全部进入到Running状态。本文将为你揭开阿里云ECI是如何做到极速扩容的。 引言 根据最新CNCF报告&#xff0c;有超过90%的用户在生产环境使用容器&#xff0c;…

巧用友盟+U-APM 实现移动端性能优化—启动速度

简介&#xff1a; 移动端性能对用户体验、留存有着至关重要的影响&#xff0c;作为开发者是不是被这样吐槽过&#xff0c;“这个 APP 怎么这么大&#xff1f;”、“怎么一直在 APP 封面图转悠&#xff0c;点不进去”、“进入详情效果有些卡”、“用 4G 使用你们的 APP&#xff…

第25版 OpenStack Yoga 已发布

OpenStack社区今日正式发布第25版-Yoga&#xff0c;该版本通过支持先进的硬件技术如SmartNIC DPUs&#xff0c;优化与云原生软件如Kubernetes、Prometheus等的集成以及减少技术债等方式来保持OpenStack内核的稳定性与可靠性。 OpenStack作为开源基础设施即服务&#xff08;Iaa…

项目实战总结以及接入U-APM

简介&#xff1a; 导致 App 性能低下的原因有很多&#xff0c;除去设备硬件和软件的外部因素&#xff0c;其中大部分是开发者错误地使用线、系统函数、编程范式、数据结构等导致的。即便是较有经验的程序员&#xff0c;也很难在开发时就能避免所有导致性能低下的“坑”&#xf…

oracle redo 200mb,Oracle的redo log在各场景下的恢复

Oracle的redo log非常重要&#xff0c;redo log损坏将导致数据库开法开启或数据丢失&#xff0c;针对redo log在各种场景下如何打开或恢复数据库&#xff0c;特别模拟测试说明&#xff1a;各场景包括如下(共6个场景):场景一.非归档下inactive状态的redo 恢复场景二.非归档下act…

站在原地就是退步——除了死磕通道,云通讯服务商还该做些什么?

受访嘉宾&#xff1a;吴佳钊&#xff0c;杭州云片网络科技有限公司联合创始人、CTO 当前&#xff0c;全球通信云已经步入2.0时代&#xff0c;最大的变化在于通信形式的变革&#xff1a;传统短信语音的通信形式将逐步向包括即时通讯IM实时音视频RTC的互联网通信转变。尤其在5G时…

Cube 技术解读 | 详解「支付宝」全新的卡片技术栈

简介&#xff1a; 魔方卡片&#xff08;Cube&#xff09;&#xff0c;让 App 首页实现敏捷更新。 CodeHub#7 正式落幕&#xff0c;来自蚂蚁集团的技术专家「京君」与掘金社区的开发者们分享了「支付宝」全新的卡片技术栈——魔方卡片&#xff08;Cube&#xff09;。 京君围绕 C…

庖丁解InnoDB之REDO LOG

简介&#xff1a; 数据库故障恢复机制的前世今生一文中提到&#xff0c;今生磁盘数据库为了在保证数据库的原子性(A, Atomic) 和持久性(D, Durability)的同时&#xff0c;还能以灵活的刷盘策略来充分利用磁盘顺序写的性能&#xff0c;会记录REDO和UNDO日志&#xff0c;即ARIES方…

Web 自动化神器,批量下载美图,可直接导入使用

‍‍作者 | 小碗汤来源 | 进击云原生今天为大家分享一款前端自动化操作神器: Automa。Automa介绍它是一款 Chrome 插件&#xff0c;即使你不会写代码&#xff0c;也能按照自己的需求&#xff0c;完成一系列自动化操作。利用它&#xff0c;你可以将一些重复性的任务实现自动化、…

RocketMQ 5.0 POP 消费模式探秘

简介&#xff1a; POP Consumer—使客户端无状态&#xff0c;更轻量&#xff01; 作者&#xff1a;凯易&耘田 前言&#xff1a;随着 RocketMQ 5.0 preview 的发布&#xff0c;5.0 的重大特性逐步与大家见面。POP Consumer 作为 5.0 的一大特性&#xff0c;POP 消费模式展现…

【ESSD技术解读-01】 云原生时代,阿里云块存储 ESSD 快照服务如何被企业级数据保护所集成?

简介&#xff1a; 本文描述了阿里云块存储快照服务基于高性能 ESSD 云盘提升快照服务性能&#xff0c;提供轻量、实时的用户体验及揭秘背后的技术原理。依据行业发展及云上数据保护场景&#xff0c;为企业用户及备份厂商提供基于快照高级特性的数据保护的技术方案&#xff0c;满…

一把王者的时间,我就学会了Nginx

作者 | 步尔斯特来源 | CSDN博客Nginx 简介Nginx("engine x")是一个高性能的 HTTP 和反向代理服务器,特点是占有内存少&#xff0c;并发能力强&#xff0c;事实上 nginx 的并发能力确实在同类型的网页服务器中表现较好&#xff0c;中国大陆使用 nginx 网站用户有&…

【ESSD技术解读-02】企业级利器,阿里云 NVMe 盘和共享存储

简介&#xff1a; 当前 NVMe 云盘结合了业界最先进的软硬件技术&#xff0c;在云存储市场&#xff0c;首创性同时实现了 NVMe 协议 共享访问 IO Fencing 技术。它在 ESSD 之上获得了高可靠、高可用、高性能&#xff0c;同时基于 NVMe 协议实现了丰富的企业特性&#xff0c;如…

php数组json函数,php数组转json的函数是什么

php数组转json的函数是json_encode()。json_encode()函数可以对变量进行JSON编码&#xff0c;将其转换为json字符串数据&#xff0c;语法格式“json_encode (value)”。本教程操作环境&#xff1a;windows7系统、PHP7.1版&#xff0c;DELL G3电脑php数组如何转为json&#xff1…

使用友盟+的APM服务实现对移动端APP的性能监控

简介&#xff1a; 对于信息系统服务&#xff0c;一般我们的重点监控对象都是核心的后端服务&#xff0c;通常会采用一些主流的APM(Application Performance Management)框架进行监控、告警、分析。那么对于移动端的APP、小程序的运行时状态如何进行实时监控与分析呢&#xff1f…

首届“中国物联网数据基础设施最佳案例评选”结果出炉

供稿 | 映云科技 出品 | CSDN云计算 随着物联网技术的成熟与普及&#xff0c;如今的世界早已进入万物互联的时代&#xff0c;全球年活跃连接的物联网设备已达数百亿规模 &#xff08;IoT Analytics, 2021&#xff09;。海量物联设备产生的数据&#xff0c;需要通过统一汇聚和…