巧用 Redis Hyperloglog,轻松统计 UV 数据

举报
G-washington 发表于 2019/10/28 20:28:56 2019/10/28
【摘要】 如果你正在开发一个基于“事件”的应用程序,该应用程序可以处理来自不同用户的许多请求,那么你很大可能希望能够计算滑动窗口或指定时间范围内不同的用户操作。

如果你正在开发一个基于“事件”的应用程序,该应用程序可以处理来自不同用户的许多请求,那么你很大可能希望能够计算滑动窗口或指定时间范围内不同的用户操作。

计数不同用户行为的最快方法之一是写一个类似 SELECT COUNT(DISTINCT user) 的 SQL。但是,如果实时数据的量达到了上百万条,这可能会很昂贵。你可能会想到另一种方法,就是将用户保存在一个 Redis set 集合中,因为 set 天然具备去重的功能。

但是,这种解决方案也带来了它固有的问题。如果一个统计不同用户记录的应用程序运行有多个实例,那么我们需要具有巨大 RAM 大小的内存缓存解决方案。如果要处理 1000 万个不同的记录,每个记录分配 10 字节,那么仅在一个时间范围内我们就至少需要 100MB 的内存。因此,这不是内存有效的解决方案。

在本文中,我想向你展示如何通过在 Redis Cache 服务器中分配少于 2MB 的内存来处理一百万个不同的用户记录。

我们都知道,Redis 有好几种数据结构,比如StringBitMapSetSorted Set等。在这里我想特别强调一下Hyperloglog,因为它最适合通过减少内存消耗来统计不同的用户操作。


640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1redis-data


Hyper LogLog

Hyper LogLog 计数器的名称是具有自描述性的。 你可以仅仅使用loglog(Nmax)+ O(1)位来估计基数为 Nmax 的集合的基数。

Redis Hyperloglog 操作

要进行 Redis Hyperloglog 的操作,我们可以使用以下三个命令:

PFADDPFCOUNTPFMERGE

我们用一个实际的例子来解释这些命令。比如,有这么个场景,用户登录到系统,我们需要在一小时内统计不同的用户。 因此,我们需要一个 key,例如 USER:LOGIN:2019092818。 换句话说,我们要统计在 2019 年 09 月 28 日下午 18 点至 19 点之间发生用户登录操作的非重复用户数。对于将来的时间,我们也需要使用对应的 key 进行表示,比如 2019111100、2019111101、2019111102 等。

我们假设,用户 A、B、C、D、E 和 F 在下午 18 点至 19 点之间登录了系统。



image.png

当进行计数时,你会得到预期的 6。

image.png

如果 A 和 B 在这个时间内多次登录系统,你也将得到相同的结果,因为我们仅保留不同的用户。

image.png

如果用户 A~F 和另外一个其他用户 G 在下午 19 点至下午 20 点之间登录系统:

image.png

现在,我们有两个键 USER:LOGIN:2019092818 和 USER:LOGIN:2019092819,如果我们想知道在 18 点到 20 点(2 小时的时间间隔)之间有多少不同的用户登录到系统中,我们可以直接使用pfcount命令对两个键进行合并计数:



image.png

如果我们需要保留键值而避免一遍又一遍地计数,那么我们可以将键合并为一个键 USER:LOGIN:2019092818-19,然后直接对该键进行pfcount操作,如下所示。

image.png

接下来,我们写个程序,比较使用 SET、Hyperloglog 两种方式存储不同用户登录行为的内存占用。

image.png

我们看一下结果,对于 100 万用户,Set 可以精确存储,而 Hyperloglog 则稍有偏差,多出了 7336,误差率大概是在 0.7%。而在内存占用上,Set 消耗了 10888895B≈10MB,Hyperloglog 只消耗了 10481B≈10KB 的内存,几乎是 Set 的 1/1000。

image.png

serializedlength 参数表示该 key 存储的内容所占用的内存字节数。

滑动窗口的不同计数

要在滑动窗口中计算不同的用户,我们需要指定一个较小的粒度,在这种情况下,分钟级的就足够了,我们将用户行为保存在格式为 yyyyMMddHHmm 的键中,例如 USER:LOGIN:201909281820。

当我们要统计最后 5 分钟的不同用户操作时,只需要将 5 个键进行合并计算即可:


image.png


由此看来,统计最近一小时我们需要 60 个键,统计最近一天需要 1440 个键,最近 7 天则需要 10080 个键。 我们拥有的键越多,合并它们时就需要耗费更多的时间进行计算。 因此,我们应该减少键的数量,不仅要保留具有 yyyyMMddHHmm 格式的键,还应保留小时、日和月的时间间隔,并使用 yyyyMM,yyyyMMdd,yyyyMMddHH。

使用这些新键,pfcount 操作可以花费更少的时间,例如:

如果你要计算用户最近一天的操作并且仅使用分钟键,你需要合并所有 1440 个键。但是,如果你在分钟键之外还使用小时键,则只需要 60 个分钟键和 24 个小时键,因此我们只需要 84 个键。

image.png

image.png

image.png

image.png

我们来看一下两个日期之间计算出的样本键列表。 你可能已经意识到了,键的数量应该尽可能少,这样合并键进行统计时代价将会比较小。 因此,我们不仅要将时间范围划分为分钟,而且还要划分为小时、天、月等。

BEGIN=201909281800&END=201909281920


image.png


BEGIN=20190928191100&END=20190930163800



image.png


实例

其实,有了上面生成 key 的方法,我们便可以很轻松地在实际场景中应用 Redis 的 HyperLoglog 进行数据统计,比如我们要统计从此刻开始往前推一小时、一天、一周的 UV。

代码实现如下:

image.png

本文转载自微信公众号JavaGuide

原文链接:https://mp.weixin.qq.com/s/MpoP6fEgN4a_HspJ7Yh63g

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。