【生产案例】日登录用户、月登录用户统计、聚合统计
参考:https://www.cnblogs.com/uniqueDong/p/15114305.html
"统计类型:二值状态统计;聚合统计;排序统计;基数统计。
1、当我们遇到的统计场景只需要统计数据的二值状态,比如用户是否存在、 ip 是否是黑名单、以及签到打卡统计等场景就可以考虑使用Redis Bitmap。
给一个 userId ,判断用户登陆状态;
两亿用户最近 7 天的签到情况,统计 7 天内连续签到的用户总数;
-- 定义7个BitMap分别代表一天。并对7天数据取XOR/OR; 整体内存评估( 一个一亿个位的 Bitmap占用的内存开销,大约占 12 MB 的内存(10^8/8/1024/1024),7 天的 Bitmap 的内存开销约为 84 MB。)
统计每天的新增与第二天的留存用户数;
2、基数统计:统计一个集合中不重复元素的个数,常见于计算独立用户数(UV)。
选型:Redis HyperLogLog(每个 HyperLogLog 最多只需要花费 12KB 内存就可以计算 2 的 64 次方个元素的基数。)
统计网站的对访客(Unique Visitor,UV)量
最新评论列表
根据播放量音乐榜单"
场景:判断用户是否登陆
二值状态场景,我们就可以利用 Bitmap 来实现。比如登陆状态我们用一个 bit 位表示,一亿个用户也只占用 一亿 个 bit 位内存 ≈ (100000000 / 8/ 1024/1024)12 MB。
Bitmap 提供了 GETBIT、SETBIT 操作,通过一个偏移值 offset 对 bit 数组的 offset 位置的 bit 位进行读写操作,需要注意的是 offset 从 0 开始。
只需要一个 key = login_status 表示存储用户登陆状态集合数据, 将用户 ID 作为 offset,在线就设置为 1,下线设置 0。通过 GETBIT判断对应的用户是否在线。 50000 万 用户只需要 6 MB 的空间。
SETBIT <key> <offset> <value>
GETBIT <key> <offset> 获取 key 的 value 在 offset 处的 bit 位的值,当 key 不存在时,返回 0。
判断 ID = 10086 的用户的登陆情况:
SETBIT login_status 10086 1 执行以下指令,表示用户已登录。
GETBIT login_status 10086 检查该用户是否登陆,返回值 1 表示已登录。
SETBIT login_status 10086 0 登出,将 offset 对应的 value 设置成 0。
场景:用户每个月的签到情况
在签到统计中,每个用户每天的签到用 1 个 bit 位表示,一年的签到只需要 365 个 bit 位。一个月最多只有 31 天,只需要 31 个 bit 位即可。
统计编号 89757 的用户在 2021 年 5 月份的打卡情况要如何进行?
key 可以设计成 uid:sign:{userId}:{yyyyMM},月份的每一天的值 - 1 可以作为 offset(因为 offset 从 0 开始,所以 offset = 日期 - 1)。
SETBIT uid:sign:89757:202105 15 1 记录用户在 2021 年 5 月 16 号打卡
GETBIT uid:sign:89757:202105 15 判断编号 89757 用户在 2021 年 5 月 16 号是否打卡
BITCOUNT uid:sign:89757:202105 统计该用户在 5 月份的打卡次数
如何统计这个月首次打卡时间呢?
Redis 提供了 BITPOS key bitValue [start] [end]指令,返回数据表示 Bitmap 中第一个值为 bitValue 的 offset 位置。
场景:连续签到用户总数
记录了一个亿的用户连续 7 天的打卡数据,如何统计出这连续 7 天连续打卡用户总数呢?
我们把每天的日期作为 Bitmap 的 key,userId 作为 offset,若是打卡则将 offset 位置的 bit 设置成 1。
key 对应的集合的每个 bit 位的数据则是一个用户在该日期的打卡记录。
一共有 7 个这样的 Bitmap,如果我们能对这 7 个 Bitmap 的对应的 bit 位做 『与』运算。
同样的 UserID offset 都是一样的,当一个 userID 在 7 个 Bitmap 对应对应的 offset 位置的 bit = 1 就说明该用户 7 天连续打卡。
结果保存到一个新 Bitmap 中,我们再通过 BITCOUNT 统计 bit = 1 的个数便得到了连续打卡 7 天的用户总数了。
Redis 提供了 BITOP operation destkey key [key ...]这个指令用于对一个或者多个 键 = key 的 Bitmap 进行位元操作。
opration 可以是 and、OR、NOT、XOR。当 BITOP 处理不同长度的字符串时,较短的那个字符串所缺少的部分会被当做 0 。
空的 key 也被看作是包含 0 的字符串序列。
// 与操作 BITOP AND destmap bitmap:01 bitmap:02 bitmap:03 // 统计 bit 位 = 1 的个数 BITCOUNT destmap
基数统计
统计一个集合中不重复元素的个数,常见于计算独立用户数(UV)
实现基数统计最直接的方法,就是采用集合(Set)这种数据结构,当一个元素从未出现过时,便在集合中增加一个元素;如果出现过,那么集合仍保持不变。
当页面访问量巨大,就需要一个超大的 Set 集合来统计,将会浪费大量空间。另外,这样的数据也不需要很精确,到底有没有更好的方案呢?
Redis HyperLogLog 是一种不精确的去重基数方案,它的统计规则是基于概率实现的,标准误差 0.81%,这样的精度足以满足 UV 统计需求了。
每个 HyperLogLog 最多只需要花费 12KB 内存就可以计算 2 的 64 次方个元素的基数。
Redis 对 HyperLogLog 的存储进行了优化,在计数比较小的时候,存储空间采用系数矩阵,占用空间很小。
只有在计数很大,稀疏矩阵占用的空间超过了阈值才会转变成稠密矩阵,占用 12KB 空间。
PFADD Redis主从同步原理:uv userID1 userID 2 useID3 将访问页面的每个用户 ID 添加到 HyperLogLog 中。
PFCOUNT Redis主从同步原理:uv 利用 PFCOUNT 获取 「Redis主从同步原理」页面的 UV值。
PFMERGE destkey sourcekey [sourcekey ...] 将多个 HyperLogLog 合并在一起形成一个新的 HyperLogLog 值
最新评论列表
微信公众号的后台回复列表(不要杠,举例子),每一公众号对应一个 List,这个 List 保存该公众号的所有的用户评论。
LPUSH 码哥字节 1 2 3 4 5 6
LRANGE key star stop 获取列表指定区间内的元素。
注意,并不是所有最新列表都能用 List 实现,对于因为对于频繁更新的列表,list类型的分页可能导致列表元素重复或漏掉。
排行榜
ZRANGEBYSCORE
ZREVRANGE
聚合统计
指的就是统计多个集合元素的聚合结果,比如说:
- 统计多个元素的共有数据(交集);
- 统计两个集合其中的一个独有元素(差集统计);
- 统计多个集合的所有元素(并集统计)。
码老湿,什么样的场景会用到交集、差集、并集呢?
Redis 的 Set 类型支持集合内的增删改查,底层使用了 Hash 数据结构,无论是 add、remove 都是 O(1) 时间复杂度。
并且支持多个集合间的交集、并集、差集操作,利用这些集合操作,解决上边提到的统计问题。
交集-共同好友
比如 QQ 中的共同好友正是聚合统计中的交集。我们将账号作为 Key,该账号的好友作为 Set 集合的 value。
模拟两个用户的好友集合:
SADD user:码哥字节 R大 Linux大神 PHP之父
SADD user:大佬 Linux大神 Python大神 C++菜鸡
统计两个用户的共同好友只需要两个 Set 集合的交集,如下命令:
SINTERSTORE user:共同好友 user:码哥字节 user:大佬
命令的执行后,「user:码哥字节」、「user:大佬」两个集合的交集数据存储到 user:共同好友这个集合中。
差集-每日新增好友数
比如,统计某个 App 每日新增注册用户量,只需要对近两天的总注册用户量集合取差集即可。
比如,2021-06-01 的总注册用户量存放在 key = user:20210601 set 集合中,2021-06-02 的总用户量存放在 key = user:20210602 的集合中。
如下指令,执行差集计算并将结果存放到 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 实例阻塞。
所以,可以专门部署一个集群用于统计,让它专门负责聚合计算,或者是把数据读取到客户端,在客户端来完成聚合统计,这样就可以规避由于阻塞导致其他服务无法响应。
浙公网安备 33010602011771号