在 web 和 移动应用的业务场景中,我们经常要保存这样的信息:单key对应的数据集合。
- 手机APP的用户日登录信息:一天对应一系列的用户ID或移动设备ID
- 电商网站上商品用户评论列表:一个商品对应一系列的评论
- 用户在手机APP上的签到打卡信息:一天对应一系列的用户签到记录
- 应用网站上的网页访问信息:一个网页对应一系列用户的访问点击。
我们知道,redis集合类型的特点是一个键对应一系列的数据,所以非常适合用来存取这些数据。
但是在这些场景中,除了记录信息,我们往往还需要对集合中数据进行统计。
- 移动应用中,统计每天的新增用户数(DAU)和第二天的用户留存数;
- 电商网站的商品评论中,需统计评论列表的最新评论;
- 签到打开中,需要统计一个月连续打卡的用户数
- 网页访问记录中,需要统计独立访客(UV unique visitor)量
通常情况下,我们面临的用户数量以及访问量都是巨大的,百千万,甚至亿级别。因此必须要选择高效统计大量数据的集合类型。
下面一起了解些常用的集合统计模式,及其对应的高效结构(省时省空间)。
聚合统计
指统计多个集合元素的聚合结果,如统计多集合的交集,差集,并集。
新增用户数和第二天用户留存。
累积用户set(Su):user:id -> set (登录过app的用户id)
每日用户set(Sd):user:id:date -> set (date当天登录过app的用户id)
统计新增用户:Sn = Sd - Su
SDIFFSTORE user:new user:id:20200804 user:id
Su的每日迭代:Su = Su ∪ Sd
SUNIONSTORE user:id user:id user:id:20200803
统计留存用户:Ss = Sd2 - Sd1
SDIFFSTORE user:id:rem user:id:20200804 user:id:20200803
聚合统计合适的结构为set。风险点在于集合运算操作复杂度高,大数据量将导致实例阻塞。可通过指定从库专门负责局和计算,或由客户端进行的聚合统计的方式规避主库和其他从库实例阻塞的风险。
排序统计
指对集合元素排序。
如电商网站商品最新评论。
要求集合保持元素有序,即有序集合。redis的常用4个集合类型(List,Hash,Set,Sorted Set)中,List和Sorted Set就属于有序集合。
List是按照元素进入List的顺序进行排序的,而Sorted Set可根据元素权重排序。
二者区别在当List分页读取(LRANGE)和插入(LPUSH)同时进行的时候,分页会读到前一页的数据(由于数据变更)。Sorted Set 则可通过 ZRANGEBSCORE 获取不变的按序排列数据。
LRANGE product1 3 5
1) "C"
2) "D"
3) "E"
ZRANGEBYSCORE comments N-9 N
二值状态统计
二值状态指集合元素的取值只有0和1两种。
如签到打卡,就只有签到或未签到。
使用BitMap可以记录单用户的各天的签到情况。BitMap底层为String类型,String类型会保存为二进制的字节数组。提供GetBit/SetBit操作,使用offset对bit数组的某一bit位进行读写,偏移量从0开始。BitCount用于统计1的个数。
// 用户id为3000的在8月份的第三天(0开始的idx)的打卡
SETBIT uid:sign:3000:202008 2 1
// 检查是否签到
GETBIT uid:sign:3000:202008 2
// 统计用户在8月份的签到次数
BITCOUNT uid:sign:3000:202008
这样,就可知道用户8月的签到情况。如果记录了1亿用户10天的签到,如何统计10天连续签到用户总数。
BITMAP支持多个map间按位做 “与”,“或”,“异或” 的操作,结果保留到新的BITMAP。
因此按每天日期为key,value是1亿的bitmap。10个bitmap做与后BITCOUNT即可。内存开销 1亿bitmap = 10^8 * 8 * 2^10 * 2^10 = 12MB,10天就是120MB,不算太大。实际应用设置bitmap过期时间,节省。
基数统计
指统计一个集合中不重复元素的个数。
统计网页UV。一个用户一天多次访问只能算一次。Redis的集合类型中Set支持去重。Hash也可以。
// A访问了page1
SADD page1:uv A
HSET page1:uv A 1
// page1的UV
SCARD page1:uv
HLEN page1:uv
当页面或者访问量多,内存消耗过大。可使用HyperLogLog。其是一种用于统计基数的数据集合类型,最大优势在于,集合元素数量非常多时,计算基数所需空间固定且小。
每个HyperLogLog只需花费12KB的内存,就可以计算接近 2^64个元素的基数。
// 5个用户访问了page1
PFADD page1:uv user1 user2 user3 user4 user5
// 返回统计结果(UV)
PFCOUNT page1:uv
HyperLogLog的统计规则是基于概率完成的,所以给出的统计结果有一定误差,标准误算率是 0.81%。意指若得到 UV = 100万 (± 1万),误差不大,若追求精确使用线性空间复杂度的Set和Hash类型。
小结
结合新增用户和留存用户、最新评论列表、用户签到数、网页独立访客量、学习4中集合统计模式:聚合、排序、二值状态、基数统计。
数据类型 | 聚合统计 | 排序统计 | 二值状态统计 | 基数统计 |
---|---|---|---|---|
Set | -,∪,∩ | 精确,效率低,吃内存 | ||
SortedSet | ∪,∩ | 支持 | 精确,效率低,吃内存 | |
Hash | 精确,效率低,吃内存 | |||
List | 支持 | |||
BitMap | ∧, V, ⊕ | 支持,效率高,省内存 | 精确,开销大于HyperLogLog | |
HyperLogLog | 概率统计,非常省内存 |
思考
是否遇到过其他的统计模式,如何使用redis解决?记录积累