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

789a60742561a047f4681b8994e23220.gif

作者 | 码哥字节

来源 | 码哥字节

在移动应用的业务场景中,我们需要保存这样的信息:一个 key 关联了一个数据集合,同时还要对集合中的数据进行统计排序。

常见的场景如下:

  • 给一个 userId ,判断用户登陆状态;

  • 两亿用户最近 7 天的签到情况,统计 7 天内连续签到的用户总数;

  • 统计每天的新增与第二天的留存用户数;

  • 统计网站的对访客(Unique Visitor,UV)量

  • 最新评论列表

  • 根据播放量音乐榜单

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

所以,我们必须要选择能够非常高效地统计大量数据(例如亿级)的集合类型。

如何选择合适的数据集合,我们首先要了解常用的统计模式,并运用合理的数据来解决实际问题。

四种统计类型:

  1. 二值状态统计;

  2. 聚合统计;

  3. 排序统计;

  4. 基数统计。

本文将用到 String、Set、Zset、List、hash 以外的拓展数据类型 BitmapHyperLogLog来实现。

今天我们来看下剩下的三种统计类型。

文章涉及到的指令可以通过在线 Redis 客户端运行调试,地址:https://try.redis.io/,超方便的说。

基数统计

基数统计:统计一个集合中不重复元素的个数,常见于计算独立用户数(UV)。

实现基数统计最直接的方法,就是采用集合(Set)这种数据结构,当一个元素从未出现过时,便在集合中增加一个元素;如果出现过,那么集合仍保持不变。

当页面访问量巨大,就需要一个超大的 Set 集合来统计,将会浪费大量空间。

另外,这样的数据也不需要很精确,到底有没有更好的方案呢?

这个问题问得好,Redis 提供了 HyperLogLog 数据结构就是用来解决种种场景的统计问题。

HyperLogLog 是一种不精确的去重基数方案,它的统计规则是基于概率实现的,标准误差 0.81%,这样的精度足以满足 UV 统计需求了。

关于 HyperLogLog 的原理过于复杂,如果想要了解的请移步:

  • https://www.zhihu.com/question/53416615

  • https://en.wikipedia.org/wiki/HyperLogLog

网站的 UV

通过 Set 实现

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

用户编号 89757 访问 「Redis 为什么这么快 」时,我们将这个信息放到 Set 中。

SADD Redis为什么这么快:uv 89757

当用户编号 89757 多次访问「Redis 为什么这么快」页面,Set 的去重功能能保证不会重复记录同一个用户 ID。

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

SCARD Redis为什么这么快:uv

通过 Hash 实现

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

即使用户重复访问,重复执行命令,也只会把这个 userId 的值设置成 “1"。

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

如下:

HSET redis集群:uv userId:89757 1
// 统计 UV
HLEN redis集群

HyperLogLog 王者方案

码老湿,Set 虽好,如果文章非常火爆达到千万级别,一个 Set 就保存了千万个用户的 ID,页面多了消耗的内存也太大了。同理,Hash数据类型也是如此。咋办呢?

利用  Redis 提供的 HyperLogLog 高级数据结构(不要只知道 Redis 的五种基础数据类型了)。这是一种用于基数统计的数据集合类型,即使数据量很大,计算基数需要的空间也是固定的。

每个 HyperLogLog 最多只需要花费 12KB 内存就可以计算 2 的 64 次方个元素的基数。

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

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

PFADD

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

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

PFCOUNT

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

PFCOUNT Redis主从同步原理:uv

PFMERGE 使用场景

HyperLogLog 除了上面的 PFADDPFCOIUNT 外,还提供了 PFMERGE ,将多个 HyperLogLog  合并在一起形成一个新的 HyperLogLog 值。

语法

PFMERGE destkey sourcekey [sourcekey ...]

使用场景

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

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

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

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,只算访问了一次。

排序统计

Redis 的 4 个集合类型中(List、Set、Hash、Sorted Set),List 和 Sorted Set 就是有序的。

  • List:按照元素插入 List 的顺序排序,使用场景通常可以作为 消息队列、最新列表、排行榜;

  • Sorted Set:根据元素的 score 权重排序,我们可以自己决定每个元素的权重值。使用场景(排行榜,比如按照播放量、点赞数)。

最新评论列表

码老湿,我可以利用 List 插入的顺序排序实现评论列表

比如微信公众号的后台回复列表(不要杠,举例子),每一公众号对应一个 List,这个 List 保存该公众号的所有的用户评论。

每当一个用户评论,则利用 LPUSH key value [value ...] 插入到 List 队头。

LPUSH 码哥字节 1 2 3 4 5 6

接着再用 LRANGE key star stop 获取列表指定区间内的元素。

> LRANGE 码哥字节 0 4
1) "6"
2) "5"
3) "4"
4) "3"
5) "2"

注意,并不是所有最新列表都能用 List 实现,对于因为对于频繁更新的列表,list类型的分页可能导致列表元素重复或漏掉。

比如当前评论列表 List ={A, B, C, D},左边表示最新的评论,D 是最早的评论。

LPUSH 码哥字节 D C B A

展示第一页最新 2 个评论,获取到 A、B:

LRANGE 码哥字节 0 1
1) "A"
2) "B"

按照我们想要的逻辑来说,第二页可通过 LRANGE 码哥字节 2 3 获取 C,D。

如果在展示第二页之前,产生新评论 E,评论 E 通过 LPUSH 码哥字节 E 插入到 List 队头,List = {E, A, B, C, D }。

现在执行 LRANGE 码哥字节 2 3 获取第二页评论发现, B 又出现了。

LRANGE 码哥字节 2 3
1) "B"
2) "C"

出现这种情况的原因在于 List 是利用元素所在的位置排序,一旦有新元素插入,List = {E,A,B,C,D}

原先的数据在 List 的位置都往后移动一位,导致读取都旧元素。

4fdd91cf76189e44ed2856ff21f9f95a.png
List最新列表

小结

只有不需要分页(比如每次都只取列表的前 5 个元素)或者更新频率低(比如每天凌晨统计更新一次)的列表才适合用 List 类型实现。

对于需要分页并且会频繁更新的列表,需用使用有序集合 Sorted Set 类型实现。

另外,需要通过时间范围查找的最新列表,List 类型也实现不了,需要通过有序集合 Sorted Set 类型实现,如以成交时间范围作为条件来查询的订单列表。

排行榜

码老湿,对于最新列表的场景,List 和 Sorted Set 都能实现,为啥还用 List 呢?直接使用 Sorted Set 不是更好,它还能设置 score 权重排序更加灵活。

原因是 Sorted Set 类型占用的内存容量是 List 类型的数倍之多,对于列表数量不多的情况,可以用 Sorted Set 类型来实现。

比如要一周音乐榜单,我们需要实时更新播放量,并且需要分页展示。

除此以外,排序是根据播放量来决定的,这个时候 List 就无法满足了。

我们可以将音乐 ID 保存到 Sorted Set 集合中,score 设置成每首歌的播放量,该音乐每播放一次则设置 score = score +1。

ZADD

比如我们将《青花瓷》和《花田错》播放量添加到 musicTop 集合中:

ZADD musicTop 100000000 青花瓷 8999999 花田错

ZINCRBY

《青花瓷》每播放一次就通过 ZINCRBY指令将 score + 1。

> ZINCRBY musicTop 1 青花瓷
100000001

ZRANGEBYSCORE

最后我们需要获取 musicTop 前十播放量音乐榜单,目前最大播放量是 N ,可通过如下指令获取:

ZRANGEBYSCORE musicTop N-9 N WITHSCORES

65哥:可是这个 N 我们怎么获取呀?

ZREVRANGE

可通过 ZREVRANGE key start stop [WITHSCORES]指令。

其中元素的排序按 score 值递减(从大到小)来排列。

具有相同 score 值的成员按字典序的逆序(reverse lexicographical order)排列。

> ZREVRANGE musicTop 0 0 WITHSCORES
1) "青花瓷"
2) 100000000

小结

即使集合中的元素频繁更新,Sorted Set 也能通过 ZRANGEBYSCORE命令准确地获取到按序排列的数据。

在面对需要展示最新列表、排行榜等场景时,如果数据更新频繁或者需要分页显示,建议优先考虑使用 Sorted Set。

聚合统计

指的就是统计多个集合元素的聚合结果,比如说:

  • 统计多个元素的共有数据(交集);

  • 统计两个集合其中的一个独有元素(差集统计);

  • 统计多个集合的所有元素(并集统计)。

码老湿,什么样的场景会用到交集、差集、并集呢?

Redis 的 Set 类型支持集合内的增删改查,底层使用了 Hash 数据结构,无论是 add、remove 都是 O(1) 时间复杂度。

并且支持多个集合间的交集、并集、差集操作,利用这些集合操作,解决上边提到的统计问题。

交集-共同好友

比如 QQ 中的共同好友正是聚合统计中的交集。我们将账号作为 Key,该账号的好友作为 Set 集合的 value。

模拟两个用户的好友集合:

SADD user:码哥字节 R大 Linux大神 PHP之父
SADD user:大佬 Linux大神 Python大神 C++菜鸡
ecc038fc2394008bf5edebbb5f9f88bf.png
交集

统计两个用户的共同好友只需要两个 Set 集合的交集,如下命令:

SINTERSTORE user:共同好友 user:码哥字节 user:大佬

命令的执行后,「user:码哥字节」、「user:大佬」两个集合的交集数据存储到 user:共同好友这个集合中。

差集-每日新增好友数

比如,统计某个 App 每日新增注册用户量,只需要对近两天的总注册用户量集合取差集即可。

比如,2021-06-01 的总注册用户量存放在 key = user:20210601 set 集合中,2021-06-02 的总用户量存放在 key = user:20210602 的集合中。

e4605487a033ea7e44a977d241504c46.png
set差集

如下指令,执行差集计算并将结果存放到 user:new 集合中。

SDIFFSTORE  user:new  user:20210602 user:20210601

执行完毕,此时的 user:new 集合将是 2021/06/02 日新增用户量。

除此之外,QQ 上有个可能认识的人功能,也可以使用差集实现,就是把你朋友的好友集合减去你们共同的好友即是可能认识的人。

并集-总共新增好友

还是差集的例子,统计 2021/06/01 和 2021/06/02 两天总共新增的用户量,只需要对两个集合执行并集。

SUNIONSTORE  userid:new user:20210602 user:20210601

此时新的集合 userid:new 则是两日新增的好友。

小结

Set 的差集、并集和交集的计算复杂度较高,在数据量较大的情况下,如果直接执行这些计算,会导致 Redis 实例阻塞。

所以,可以专门部署一个集群用于统计,让它专门负责聚合计算,或者是把数据读取到客户端,在客户端来完成聚合统计,这样就可以规避由于阻塞导致其他服务无法响应。

521472658fb5eb124a5ead21e9876a62.gif

往期推荐

Spring 完美导入 IDEA

k8s集群居然可以图形化安装了?

使用这个库,让你的服务操作 Redis 速度飞起

将 k8s 制作成 3D 射击游戏,好玩到停不下来

ce0714ff24db1c15ec64ae5b5a71cf1c.gif

点分享

ca5b9ca892e93b38c33f24cc411cd156.gif

点收藏

dae2ad632d579e134c8e3b2e1c21f979.gif

点点赞

08356c538189b3152a2286e68ec2313b.gif

点在看

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

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

相关文章

2021杭州·云栖大会来了!门票免费预约!

2021杭州云栖大会,定了! 10月19日-22日,就在杭州云栖小镇 2场重磅主论坛上百场分论坛 超4万平米科技展 今年,云栖大会将首次免费开放 门票可在官网免费预约 入口现已开启 戳此预约,我们不见不散! ​ …

js 可以做什么东西_Deno需要做什么才能取代Node.js?

全文共1843字,预计学习时长5分钟Deno是一个Javascript/TypeScript的运行时,旨在取代Node.js的地位。它拥有广泛功能,讨论度非常高,在Github上有将近68000个星星:既然这么受欢迎,那么有人要问了:…

37 手游基于 Flink CDC + Hudi 湖仓一体方案实践

简介: 介绍了 37 手游为何选择 Flink 作为计算引擎,并如何基于 Flink CDC Hudi 构建新的湖仓一体方案。 本文作者是 37 手游大数据开发徐润柏,介绍了 37 手游为何选择 Flink 作为计算引擎,并如何基于 Flink CDC Hudi 构建新的湖…

手把手搭建一个容器化+代理网关+可视化管理环境

作者 | togettoyou来源 | SuperGopher前言本文主要分享个人服务器的应用部署方案现状,容器化代理网关可视化管理。准备阶段我购买的是腾讯云服务器(2 核 4GB 3Mbps)域名也是在腾讯云备案过的,提前准备域名解析配置环境安装 Docker…

漫画 | 一口气搞懂 Serverless !

简介: 第二届云原生编程挑战赛为热爱技术的年轻人提供一个挑战世界级技术问题的舞台,希望用技术为全社会创造更大价值。 作者 | 刘欣 呃,我可能是别人眼中所说的不用奋斗的一代。 大家喜欢听的什么多姿多彩的生活,我都经历过一…

OpenKruise v0.10.0 新特性 WorkloadSpread 解读

简介: 针对需求,OpenKruise 在 v0.10.0 版本中新增了 WorkloadSpread 特性。目前它支持配合 Deployment、ReplicaSet、CloneSet 这些 workload,来管理它们下属 Pod 的分区部署与弹性伸缩。下文会深入介绍 WorkloadSpread 的应用场景和实现原理…

CSS 状态管理,玩出花样了!

作者 | 零一来源 | 前端印象CSS用于交互的方式无非就那么几种:伪类::hover、:link、:active ...动画:animation过渡动画:transition这些交互方式组合起来,真的可以玩出一些花样,例如我们本文的主题&#xf…

告别Kafka Stream,让轻量级流处理更加简单

简介: 还在花精力去选型Kafka组件去做清洗转化?来试试Kafka ETL任务功能! 一说到数据孤岛,所有技术人都不陌生。在 IT 发展过程中,企业不可避免地搭建了各种业务系统,这些系统独立运行且所产生的数据彼此独…

元宇宙“性骚扰”现象频出,Meta推出“个人结界”能保护好女玩家吗?

作者 | 小码君来源 | 抓码青年元宇宙,可以说是最近最炙手可热的概念了。各大厂纷纷入局元宇宙,Faceebook甚至将总公司名字都改名为Meta。不过盯上元宇宙的可不止资本大鳄,还有一些不怀好意的色狼。据外媒报道称,在Meta的Oculus设备…

【CDS技术揭秘系列 01】阿里云CDS-OSS容灾大揭秘

简介: 本文主要阐述 CDS 产品中 OSS 服务在容灾方面的部署形态以及实现的其本原理。 容灾功能可以保证用户一份数据在多个地方存在冗余备份,当某个机房出现极端异常(比如物理损毁)情况下,数据也不会出现丢失&#xff1…

如何促合作共赢?技术人的一点经验分享

简介: 本文作者将通过与詹韦团队一起合作的《树懒平台》,分享在工作过程中,我们什么情况下会有合作诉求?有了合作诉求之后,如何寻找对的人?锁定候选人之后,如何打动对方促成合作?合作…

不记得 Git 命令? 懒人版 Git 值得拥有!

作者 | Eason来源 | 程序员巴士Git的强大是所有开发者都心知肚明的事情,但是其多样的命令令人很是难受。不过在Github上有着这么一个开源项目lazygit[1]。Lazygit是由Jesseduffield开发并维护的项目,其代码托管在Github。通过使用Lazygit,我们…

Dubbo3.0|阿里巴巴服务框架三位一体的选择与实践

简介: 服务框架就像铁路的铁轨一样,是互通的基础,只有解决了服务框架的互通,才有可能完成更高层的业务互通,所以用相同的标准统一,合二为一并共建新一代的服务框架是必然趋势。Dubbo3.0 是 Dubbo2.0 与 HSF…

全球首款乘云而来的存储产品CDS诞生!

9月22日,阿里云发布全球首款“云定义存储”(Cloud Defined Storage,CDS)产品。作为一款本地部署的分布式存储产品,阿里云CDS拥有与公共云存储相同的技术架构,让企业客户在本地也能部署和使用与公共云一致的…

性能提升3倍、时延降低70%,阿里云企业级存储ESSD云盘再升级!

9月22日,阿里云存储年度新品发布会上,阿里云基础产品资深产品总监陈起鲲发布了其全球领先的旗舰级块存储产品ESSD的两款新规格(ESSD Auto PL、ESSD PL-X),并宣布了新增的多项企业级能力。 据了解,ESSD是阿…

一图看懂,什么是“云定义存储”

世界的诞生是从盘古开天辟地开始 而数据的存储 则由在龟甲上刻下的第一个字开始 经过数千年发展 数据存储也从最初的 龟壳、竹简等材料逐步进化到磁带、硬盘甚至云上 在之前几千年里 人们对于数据存储的需求并没那么高 仅仅通过纸张就能将所有数据记录下来 随着第一台计算机面…

Android 13 第一个开发者版本来了,网友直呼:Android 12 还没玩透!

整理 | 苏宓出品 | CSDN(ID:CSDNnews)2 月 10 日,Google 宣布 Android 13 首个预览版面向开发者开放,此版本重点聚焦隐私和安全、提供开发者生产力、应用兼容性,并保持与 OpenJDK 11 更新一致、提供主题图标…

「技术人生」第6篇:技术同学应该如何理解业务?

简介: 本文以大量理论论述解析业务,并提供多种基于不同场景的实操方法,帮助技术同学以科学、合理的方式开展日常工作、指导团队开展业务建设,保障顶层设计的落地执行。 一. 背景 目前已经发布《技术一号位的方法论》系列文章其实…

参数设置_变频器基本参数设置

工业设备的使用如何达到最大效能以及最佳效果,需要使用人员充分了解设备性能以及工艺要求,所以变频器参数设置或者优化是非常重要的环节,古人云失之毫厘差之千里就是这个道理。一、变频器基本参数设置参数设置可以是手持编程器操作&#xff0…

Morphling:云原生部署 AI , 如何把降本做到极致?

简介: Morphling 本意是游戏 Dota 中的英雄“水人”,他可以根据环境要求,通过灵活改变自身形态,优化战斗表现。我们希望通过 Morphling 项目,实现针对机器学习推理作业的灵活、智能的部署配置改变,优化服务…