写点什么

你不会仅仅把 Redis 作为缓存的工具吧?给你一亿个 keys,如何高效统计

用户头像
极客good
关注
发布于: 刚刚

总结

Set集合的交差并的计算复杂度很高,如果数据量很大的情况下,可能会造成 Redis 的阻塞。


那么如何规避阻塞呢?建议如下:


  1. Redis集群中选一个从库专门负责聚合统计,这样就不会阻塞主库和其他的从库了

  2. 将数据交给客户端,由客户端进行聚合统计。

排序统计

在一些电商网站中可以看到商品的评论总是最新的在上面,这个是怎么做的呢?


最新评论列表包含了所有的评论,这就要集合对元素进行保序存储了。也就是说集合中的元素必须按序存储,称之为有序集合。


Redis中的四种集合中ListSorted Set属于有序集合。


但是ListSorted Set有何区别呢?到底使用哪一种呢?


List 是按照元素进入顺序进行排序,而 Sorted Set 可以根据元素权重来排序。 比如可以根据元素插入集合的时间确定权值,先插入的元素权重小,后插入的元素权重大。


针对这一例子中,显然这两种都是能够满足要求的,List 中分页查询命令LRANGESorted Set分页查询命令ZRANGEBYSCORE


但是就灵活性来说,List 肯定不适合,List 只能根据先后插入的顺序排序,但是大多数的场景中可能并不只是按照时间先后排序,可能还会按照一些特定的条件,此时Sorted Set就很合适了,只需要根据独有的算法生成相应的权重即可。

二值状态统计

二值状态指的是取值 0 或者 1 两种;在签到打卡的场景中,只需要记录签到(1)和未签到(0)两种状态,这就是典型的二值状态统计。


二值状态的统计可以使用Redis的扩展数据类型Bitmap,底层使用String类型实现,可以把它看成是一个bit数组。关于详细内容后续介绍.........


在签到统计中,01只占了一个bit,即使一年的签到数据才 365 个bit位。大大减少了存储空间。


Bitmap 提供了GETBIT/SETBIT 操作,使用一个偏移值 offset 对 bit 数组的某一个 bit 位进行读和写。不过,需要注意的是,Bitmap 的偏移量是从 0 开始算的,也就是说 offset 的最小值是 0。当使用 SETBIT 对一个 bit 位进行写操作时,这个 bit 位会被设置为 1。Bitmap 还提供了 BITCOUNT 操作,用来统计这个 bit 数组中所有1的个数。


键值如何设计呢?key 可以是userid:yyyyMM,即是唯一 id 加上月份。假设员工 id 为10001,需要统计2020/11月份的签到打卡记录。


第一步,执行命令设置值,假设 11 月 2 号打卡了,命令如下:


SETBIT userid:10001:202011 1 1


BitMap 是从下标 0 开始,因此 2 号则是下标为 1,值设置为 1 则表示成功打卡了。


第二步,检查该用户 11 月 2 号是否打卡了,命令如下:


GETBIT userid:10001:202011 1


第三步,统计 11 月的打卡次数,命令如下:


BITCOUNT userid:10001:202011


那么问题来了,需要统计你这个签到系统中连续 20 天的签到打卡的用户的总数,如何处理呢?假设用户一个亿。


比如需要统计2020/11/012020/11/20天中连续打卡的人数,如何统计呢?


Bitmap中还支持同时对多个 BitMap 按位做异或操作,命令如下图:



思路来了,我们可以将每天的日期作为一个key,对应的BitMap存储一亿个用户当天的打卡情况。如下图:



此时我们只需要对2020/11/12020/11/20号的Bitmap做按位操作,最终得到的一个Bitmap中每个 bit 位置对应的值则代表连续 20 天打卡的情况,只有连续 20 天全部打卡,所在的 bit 位的值才为 1。如下图:



最终可以使用`BITCO


【一线大厂Java面试题解析+核心总结学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


UNT`命令进行统计。


可以尝试计算下内存开销,每天使用 1 个 1 亿位的 Bitmap,大约占 12MB 的内存(10^8/8/1024/1024),20 天的 Bitmap 的内存开销约为 240MB,内存压力不算太大。不过,在实际应用时,最好对 Bitmap 设置过期时间,让 Redis 自动删除不再需要的签到记录,以节省内存开销。


如果涉及到二值状态,比如用户是否存在,签到打卡,商品是否存在等情况可以使用 Bitmap,可以有效的节省内存空间。

基数统计

基数统计指统计一个集合中不重复元素的个数。


举个栗子:电商网站中通常需要统计每个网页的UV来确定权重,网页的 UV 肯定是需要去重的,在 Redis 类型中Set支持去重,第一时间肯定想到的是 Set。


但是这里有一个问题,Set底层使用的是哈希表和整数数组,如果一个网页的 UV 达到千万级别的话(一个电商网站中何止一个页面),那么对于内存的消耗极大。


Redis 提供了一个扩展类型 HyperLogLog 用于基数统计,计算 2^64 个元素大概只需要 12KB 的内存空间


是不是很心动?但是HyperLogLog存在误差的,大概是在0.81%,如果需要精准的统计,还是需要使用Set。对于这种网页的 UV 来说,足够了。


在统计网页 UV 的时候,只需要将用户的唯一 id 存入 HyperLogLog 中,如下:


PFADD p1:uv 10001 10002 10003 10004


如果存在重复的元素,将会自动去重。


统计也很简单,使用PFCOUNT命令,如下:


PFCOUNT p1:uv

总结

本文介绍了统计的几种类型以及应该用什么集合存储,为了方便理解,作者将支持情况和优缺点汇总了一张表格,如下图:



SetSorted Set支持交集、并集的聚合运算,但是Sorted Set不支差集运算。


Bitmap也能对多个 Bitmap 做与、异或、或的聚合运算。


ListSortedSet都支持排序统计,但是 List 是根据元素先后插入顺序排序,Sorted Set 支持权重,相对于 List 排序来说更加灵活。


对于二值状态统计,判断某个元素是否存在等场景,建议使用Bitmap,节省的内存空间。

用户头像

极客good

关注

还未添加个人签名 2021.03.18 加入

还未添加个人简介

评论

发布
暂无评论
你不会仅仅把Redis作为缓存的工具吧?给你一亿个keys,如何高效统计