Redis 自2.8.9起可用
时间复杂度: O(1)添加每个元素。
HyperLogLog 是一种概率数据结构,用来估算数据的基数。数据集可以是网站访客的 IP 地址,E-mail 邮箱或者用户 ID。
Redis 的 HyperLogLog 通过牺牲准确率来减少内存空间的消耗,只需要12K内存,在标准误差0.81%的前提下,能够统计2^64个数据。所以 HyperLogLog 是否适合在比如统计日活月活此类的对精度要不不高的场景。
如果你正在开发一个基于“事件”的应用程序,该应用程序可以处理来自不同用户的许多请求,那么你很大可能希望能够计算滑动窗口或指定时间范围内不同的用户操作。
计数不同用户行为的最快方法之一是写一个类似 SELECT COUNT(DISTINCT user) 的 SQL。但是,如果实时数据的量达到了上百万条,这可能会很昂贵。你可能会想到另一种方法,就是将用户保存在一个 Redis set 集合中,因为 set 天然具备去重的功能。
但是,这种解决方案也带来了它固有的问题。如果一个统计不同用户记录的应用程序运行有多个实例,那么我们需要具有巨大 RAM 大小的内存缓存解决方案。如果要处理 1000 万个不同的记录,每个记录分配 10 字节,那么仅在一个时间范围内我们就至少需要 100MB 的内存。因此,这不是内存有效的解决方案。
在本文中,我想向你展示如何通过在 Redis Cache 服务器中分配少于 2MB 的内存来处理一百万个不同的用户记录。
我们都知道,Redis 有好几种数据结构,比如:String、BitMap、Set、Sorted Set 等。在这里我想特别强调一下Hyperloglog,因为它最适合通过减少内存消耗来统计不同的用户操作。
Hyper LogLog
Hyper LogLog 计数器的名称是具有自描述性的。 你可以仅仅使用loglog(Nmax)+ O(1)位来估计基数为 Nmax 的集合的基数。
Redis Hyperloglog 操作
要进行 Redis Hyperloglog 的操作,我们可以使用以下三个命令:
PFADD PFCOUNT PFMERGE
我们用一个实际的例子来解释这些命令。比如,有这么个场景,用户登录到系统,我们需要在一小时内统计不同的用户。 因此,我们需要一个 key,例如 USER:LOGIN:2019092818。 换句话说,我们要统计在 2019 年 09 月 28 日下午 18 点至 19 点之间发生用户登录操作的非重复用户数。对于将来的时间,我们也需要使用对应的 key 进行表示,比如 2019111100、2019111101、2019111102 等。
我们假设,用户 A、B、C、D、E 和 F 在下午 18 点至 19 点之间登录了系统。
127.0.0.1:6379> pfadd USER:LOGIN:2019092818 A (integer) 1 127.0.0.1:6379> pfadd USER:LOGIN:2019092818 B C D E F (integer) 1
当进行计数时,你会得到预期的 6。
127.0.0.1:6379> pfcount USER:LOGIN:2019092818 (integer) 6
如果 A 和 B 在这个时间内多次登录系统,你也将得到相同的结果,因为我们仅保留不同的用户。
127.0.0.1:6379> pfadd USER:LOGIN:2019092818 A B (integer) 0 127.0.0.1:6379> pfcount USER:LOGIN:2019092818 (integer) 6
如果用户 A~F 和另外一个其他用户 G 在下午 19 点至下午 20 点之间登录系统:
127.0.0.1:6379> pfadd USER:LOGIN:2019092819 A B C D E F G (integer) 1 127.0.0.1:6379> pfcount USER:LOGIN:2019092819 (integer) 7
现在,我们有两个键 USER:LOGIN:2019092818 和 USER:LOGIN:2019092819,如果我们想知道在 18 点到 20 点(2 小时)之间有多少不同的用户登录到系统中,我们可以直接使用pfcount命令对两个键进行合并计数:
127.0.0.1:6379> pfcount USER:LOGIN:2019092818 USER:LOGIN:2019092819 (integer) 7
如果我们需要保留键值而避免一遍又一遍地计数,那么我们可以将键合并为一个键 USER:LOGIN:2019092818-19,然后直接对该键进行pfcount操作,如下所示。
127.0.0.1:6379> pfmerge USER:LOGIN:2019092818-19 USER:LOGIN:2019092818 USER:LOGIN:2019092819 OK 127.0.0.1:6379> pfcount USER:LOGIN:2019092818-19 (integer) 7
对于 100 万用户,Set 可以精确存储,而 Hyperloglog 则稍有偏差,多出了 7336,误差率大概是在 0.7%。而在内存占用上,Set 消耗了 10888895B≈10MB,Hyperloglog 只消耗了 10481B≈10KB 的内存,几乎是 Set 的 1/1000。
127.0.0.1:6379> scard SET:USER:LOGIN:2019082811 (integer) 1000000 127.0.0.1:6379> pfcount PF:USER:LOGIN:2019082811 (integer) 1007336 127.0.0.1:6379> debug object SET:USER:LOGIN:2019082811 Value at:00007FD74F841940 refcount:1 encoding:hashtable serializedlength:10888895 lru:9308508 lru_seconds_idle:53 127.0.0.1:6379> debug object PF:USER:LOGIN:2019082811 Value at:00007FD74F7A5940 refcount:1 encoding:raw serializedlength:10481 lru:9308523 lru_seconds_idle:50
serializedlength 参数表示该 key 存储的内容所占用的内存字节数。
滑动窗口的不同计数
要在滑动窗口中计算不同的用户,我们需要指定一个较小的粒度,在这种情况下,分钟级的就足够了,我们将用户行为保存在格式为 yyyyMMddHHmm 的键中,例如 USER:LOGIN:201909281820。
当我们要统计最后 5 分钟的不同用户操作时,只需要将 5 个键进行合并计算即可:
127.0.0.1:6379> pfcount 201909281821 201909281822 201909281823 201909281824 201909281825 (integer) 6
- 本文固定链接: https://www.phpmianshi.com/?id=27
- 转载请注明: admin 于 PHP面试网 发表
《本文》有 0 条评论