【生产案例】日登录用户、月登录用户统计、聚合统计

参考: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 可以是 andORNOTXOR。当 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 实例阻塞。

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

 

posted @ 2025-08-08 20:04  飞翔在天  阅读(12)  评论(0)    收藏  举报