Redis面试真题——如何用Redis统计网站的用户访问量【pdd】
pdd有数亿的用户,那么对于某个网页,怎么使用Redis来统计一个网站的用户访问数呢?
方案一、使用有序集合
每当一个用户上线时, 我们就执行 ZADD 命令, 将这个用户以及它的在线时间添加到指定的有序集合中:
ZADD "online_users" <user_id> <current_timestamp>
通过使用 ZSCORE 命令检查指定的用户 ID 在有序集合中是否有相关联的分值, 我们可以知道该用户是否在线:
ZSCORE "online_users" <user_id>
而通过执行 ZCARD 命令, 我们可以知道总共有多用户在线:
ZCARD "online_users"
使用有序集合储存在线用户的强大之处在于, 这一方案既可以通过有序集合的成员(也即是用户的 ID)进行聚合操作, 也可以根据有序集合的分值(也即是用户的登录时间)进行聚合操作。
首先, 通过 ZINTERSTORE 和 ZUNIONSTORE 命令, 我们可以对多个记录了在线用户的有序集合进行聚合计算:
此外, 通过 ZCOUNT 命令, 我们可以统计出在指定的时间段之内有多少用户在线, 而 ZRANGEBYSCORE 命令则可以让我们获取到这些用户的名单。
通过这一方法, 我们可以知道网站在不同时间段的上线人数以及上线用户名单, 比如说, 我们可以用这个方法来分别获知网站在早晨、上午、中午、下午和夜晚的上线人数。
方案二、使用集合
使用有序集合能够同时储存在线用户的名单以及各个用户的上线时间, 但如果我们只想要记录在线用户的名单, 而不想要储存用户的上线时间, 那么也可以使用集合来代替有序集合, 对在线的用户进行记录。
在这种情况下, 每当一个用户上线时, 我们就执行以下 SADD 命令, 将它添加到在线用户名单当中:
SADD "online_users" <user_id>
通过使用 SISMEMBER 命令, 我们可以检查一个指定的用户当前是否在线:
SISMEMBER "online_users" <user_id>
而统计在线人数的工作则可以通过执行 SCARD 命令来完成:
SCARD "online_users"
通过集合运算操作, 我们可以像有序集合方案一样, 对不同时间段或者日期的在线用户名单进行聚合计算。
方案三、使用Hash
哈希是Redis的一种基础数据结构,Redis底层维护的是一个开散列,会把不同的key映射到哈希表上,如果是遇到关键字冲突,那么就会拉出一个链表出来。
当一个用户访问的时候,如果用户登陆过,那么我们就使用用户的id,如果用户没有登陆过,那么我们也能够前端页面随机生成一个key用来标识用户
当用户访问的时候,我们可以使用HSET命令,key可以选择URI与对应的日期进行拼凑,field可以使用用户的id或者随机标识,value可以简单设置为1。
hset online_users <user_id> 1
当我们要统计某一个网站某一天的访问量的时候,就可以直接使用HLEN来得到最终的结果了。
Hlen online_users
优点:简单,容易实现,查询也是非常方便,数据准确性非常高。
缺点:占用内存过大,。随着key的增多,性能也会下降。小网站还行,拼多多这种数亿PV的网站肯定受不了
方案四、使用Bitset
我们知道,对于一个32位的int,如果我们只用来记录id,那么只能够记录一个用户,但如果我们转成2进制,每位用来表示一个用户,那么我们就能够一口气表示32个用户,空间节省了32倍!
对于有大量数据的场景,如果我们使用bitset,那么可以节省非常多的内存。Redis 的位图就是一个由二进制位组成的数组, 通过将数组中的每个二进制位与用户 ID 进行一一对应, 我们可以使用位图去记录每个用户是否在线。当一个用户上线时, 我们就使用 SETBIT 命令, 将这个用户对应的二进制位设置为 1
对于没有登陆的用户,我们也可以使用哈希算法,把对应的用户标识哈希成一个数字id。bitset非常的节省内存,假设有1亿个用户,也只需要100000000/8/1024/1024约等于12兆内存。
Redis已经为我们提供了SETBIT的方法,使用起来非常的方便,我们可以看看下面的例子
setbit online_users id 1
我们在item页面可以不停地使用SETBIT命令,设置用户已经访问了该页面。也可以使用GETBIT的方法查询某个用户是否访问。最后我们通过BITCOUNT可以统计该网页每天的访问数量。
bitcount online_users
优点:占用内存更小,查询方便,可以指定查询某个用户,数据可能略有瑕疵,对于非登陆的用户,可能不同的key映射到同一个id,否则需要维护一个非登陆用户的映射,有额外的开销。
缺点:如果用户非常的稀疏,那么占用的内存可能比方法一更大。
方案五、Hyperloglog算法
对于拼多多这种多个页面都可能非常多访问量的网站,如果所需要的数量不用那么准确,可以使用概率算法。
在Redis中,已经封装了HyperLogLog算法,他是一种基数评估算法。基数就是指一个集合中不同值的数目,HyperLogLog算法就是用来计算基数的。
并且每个 HyperLogLog 只需要耗费 12 KB 内存, 对于用户数量非常多但是内存却非常紧张的系统, 这一方案无疑是最佳之选。想要更深入的了解这种算法可以参考这篇文章。这种算法的特征,一般都是数据不存具体的值,而是存用来计算概率的一些相关数据。
PFADD "online_users" <user_id>
当用户访问网站的时候,我们可以使用PFADD命令,设置对应的命令,最后我们只要通过PFCOUNT就能顺利计算出最终的结果,因为这个只是一个概率算法,所以可能存在0.81%的误差。
PFCOUNT "online_users"
优点:占用内存极小,对于一个key,只需要12kb。对于拼多多这种超多用户的特别适用。
缺点:查询指定用户的时候,可能会出错,毕竟存的不是具体的数据。总数也存在一定的误差。
总结
以下表格总结了以上四个方案的特点:
方案 | 特点 |
---|---|
有序集合 | 能够同时储存在线用户的名单以及用户的上线时间,能够执行非常多的聚合计算操作,但是耗费的内存也非常多。 |
集合 | 能够储存在线用户的名单,也能够执行聚合计算,消耗的内存比有序集合少,但是跟有序集合一样,这个方案消耗的内存也会随着用户数量的增多而增多。 |
Hash | 容易实现,查询也是非常方便,数据准确性非常高。占用内存过大。随着key的增多,性能也会下降。无法进行聚合计算。 |
HyperLogLog | 无论需要统计的用户有多少,只需要耗费 12 KB 内存,但由于概率算法的特性,只能给出在线人数的估算值,并且也无法获取准确的在线用户名单。 |
Bitset | 在尽可能节约内存的情况下,记录在线用户的名单,并且能够对这些名单执行聚合操作。 |
因为 Redis 同时支持多种数据结构, 所以一个问题常常可以在 Redis 里面找多种不同的解法, 并且每种解法都有各自的优点和缺点, 本文介绍的问题就是一个很好的例子。
本专栏主要整理记录后端程序员面试过程中常见的基础知识点,包括数据结构,操作系统,计算机网络,数据库和各种中间件等。同时会整理大厂面经和面试技巧等文章。