前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >给你一个亿的keys,Redis如何统计?

给你一个亿的keys,Redis如何统计?

作者头像
Bug开发工程师
发布于 2020-12-15 07:54:35
发布于 2020-12-15 07:54:35
1.2K00
代码可运行
举报
文章被收录于专栏:码农沉思录码农沉思录
运行总次数:0
代码可运行

前言

不知你大规模的用过Redis吗?还是仅仅作为缓存的工具了?在Redis中使用最多的就是集合了,举个例子,如下场景:

  1. 签到系统中,一天对应一系列的用户签到记录。
  2. 电商系统中,一个商品对应一系列的评论。
  3. 交友系统中,某个用户的一系列的好友。

Redis中集合的特点无非是一个Key对应一系列的数据, 但是数据的作用往往是为了统计的,比如:

  1. 交友系统中,需要统计每天的新增好友,以及双方的共同好友。
  2. 电商系统中,需要统计评论列表中的最新评论。
  3. 签到系统中,需要统计连续一个月的签到的用户数量。

大型互联网应用中,数据量是巨大的,少说百万,千万,甚至是一个亿,比如电商巨头淘宝,交友巨头微信、微博;办公巨头钉钉等,哪一个的用户不是上亿?

只有针对不同场景,选择合适的集合,统计才能更方便。

聚合统计

聚合统计指的是多个元素聚合的结果,比如统计多个集合的交集、并集、差集

在你需要对多个集合做聚合统计的时候,Set集合是个不错的选择,除了其中无重复的数据外,Redis还提供了对应的API

交集

在上述的例子中交友系统中统计双方的共同好友正是聚合统计中的交集

Redis中可以userid作为key,好友的userid作为value,如下图:

统计两个用户的共同好友只需要两个Set集合的交集,命令如下;

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
SINTERSTORE userid:new userid:20002 userid:20003

上述命令运行完成后,userid:new这个key中存储的将是userid:20002userid:20003两个集合的交集。

差集

举个例子:假设交友系统中需要统计每日新增的好友,此时就需要对临近两天的好友集合取差集了,比如2020/11/1日的好友是set12020/11/2日的好友是set2,此时只需要对set1set2做差集。

此时的结构应该如何设计呢?如下图:

userid:20201101这个key记录了userid用户的2020/11/1日的好友集合。

差集很简单,只需要执行SDIFFSTORE命令,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
SDIFFSTORE  user:new  userid:20201102 userid:20201101  

执行完毕,此时的user:new这集合将是2020/11/2日新增的好友。

这里还有一个更贴切的例子,微博上有个可能认识的人功能,可以使用差集,即是你朋友的好友减去你们共同的好友即是可能认识的人。

并集

还是差集的那个例子,假设需要统计2020/11/012020/11/2总共新增的好友,此时只需要对这两日新增好友的集合做一个并集。命令如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
SUNIONSTORE  userid:new userid:20201102 userid:20201101

此时新的集合userid:new则是两日新增的好友。

总结

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号打卡了,命令如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
SETBIT userid:10001:202011 1 1 

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
GETBIT userid:10001:202011 1 

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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。如下图:

最终可以使用BITCOUNT命令进行统计。

可以尝试计算下内存开销,每天使用 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中,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
PFADD p1:uv 10001 10002 10003 10004

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
PFCOUNT p1:uv

总结

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

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

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

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

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

对于基数统计,在大数据量、不要求精准的情况建议使用HyperLogLog,节省内存空间;对于精准的基数统计,最好还是使用Set集合。

另外,作者已经完成了两个专栏的文章Mybatis进阶、Spring Boot 进阶 ,已经将专栏文章整理成书,有需要的公众号回复关键词Mybatis 进阶Spring Boot 进阶免费获取。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-12-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码农沉思录 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Redis统计大法:挖掘数据的四重宝藏【redis第五部分】
数据是新时代的燃料,而Redis是解锁数据宝藏的奇妙工具。在这篇博客中,我们将揭示Redis的统计神器,包括聚合统计、排序统计、二值状态统计和基数统计,助你挖掘数据的四重宝藏。
一只牛博
2025/05/30
700
Redis统计大法:挖掘数据的四重宝藏【redis第五部分】
redis学习(九)
基数(cardinal number)在数学上,是集合论中刻画任意集合大小的一个概念。两个能够建立元素间一一对应的集合称为互相对等集合。例如3个人的集合和3匹马的集合可以建立一一对应,是两个对等的集合。
崔笑颜
2020/09/09
5380
【 Redis | 实战篇 扩展 】
分析:实现点赞功能,那么一个用户是不是只能点赞一次,如果能进行多次点赞(这不刷起来了吗)
张哈大
2025/05/31
530
【 Redis | 实战篇 扩展 】
面试系列之-Redis集合元素统计
统计多个集合元素的聚合结果,包括:统计多个集合的共有元素(交集统计);把两个集合相比,统计其中一个集合独有的元素(差集统计);统计多个集合的所有元素(并集统计);统计每天的新增用户时,我们只用计算每日用户 Set 和累计用户 Set 的差集就行; 例如 SUNIONSTORE user:id user:id user:id:20200803; 潜在风险:Set 的差集、并集和交集计算复杂度较高,在数据量较大的情况下,如果直接执行这些计算,会导致 Redis实例阻塞;可以从主从集群中选择一个从库,让它专门负责聚合计算,或者是把数据读取到客户端,在客户端来完成聚合统计,这样就可以规避阻塞主库实例和其他从库实例的风险; SINTERSTORE做并集、差集、交集时,会在Redis中生成一个新key,而从库默认是readonly不可写的,所以这些命令只能在主库使用。想在从库上操作,可以使用SUNION、SDIFF、SINTER,这些命令可以计算出结果,但不会生成新key;
用户4283147
2023/08/21
2550
面试系列之-Redis集合元素统计
有一亿个keys要统计,应该用哪种集合?
在Web和移动应用的业务场景中,我们经常需要保存这样一种信息:一个key对应了一个数据集合。我举几个例子。
玖柒的小窝
2021/10/25
3350
有一亿个keys要统计,应该用哪种集合?
Redis 实战篇:巧用数据类型实现亿级数据统计
在移动应用的业务场景中,我们需要保存这样的信息:一个 key 关联了一个数据集合,同时还要对集合中的数据进行统计排序。
码哥字节
2021/06/15
9000
Redis 实战篇:巧用数据类型实现亿级数据统计
学习 Redis 基础数据结构,不讲虚的。
很多在近期找过工作的同学一定都知道了,背诵八股文已经不是找工作的绝对王牌。企业最终要的是可以创造价值,或者首先需要干活的人,所以实战很重要。今天这篇文章就是给大家分享一下如何在我们实战生产中使用 redis。
JavaPub
2024/02/07
1270
学习 Redis 基础数据结构,不讲虚的。
如何使用Redis数据类型进行亿级别统计数据
在开发中我们Redis数据类型用到最多的是Set命令,但是不仅于此,还有很多数据类型,这些可用户我们很多统计需求的场景,看看这些场景你遇到过,或者再次遇到的时候会做如何进行方案选择,一起看看!
小许code
2023/10/17
1.2K3
如何使用Redis数据类型进行亿级别统计数据
【Redis基础】Redis新数据类型(Bitmaps,HyperLoglog,Geospatial)命令简介与案例演示
Bitmaps 并不是实际的数据类型,而是定义在String类型上的一个面向字节操作的集合。因为字符串是二进制安全的块,他们的最大长度是512M,最适合设置成2^32个不同字节。 bitmaps的位操作分成两类:1.固定时间的单个位操作,比如把String的某个位设置为1或者0,或者获取某个位上的值 2.对于一组位的操作,对给定的bit范围内,统计设定值为1的数目(比如人口统计)。 bitmaps最大的优势是在存储数据时可以极大的节省空间,比如在一个项目中采用自增长的id来标识用户,就可以仅用512M的内存来记录40亿用户的信息(比如用户是否希望收到新的通知,用1和0标识)
小尘要自信
2023/10/10
3120
Redis6发布订阅及Redis新数据类型
Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息
大忽悠爱学习
2021/11/15
5440
2 万字 + 20张图| 细说 Redis 九种数据类型和应用场景
我们都知道 Redis 提供了丰富的数据类型,常见的有五种:String(字符串),Hash(哈希),List(列表),Set(集合)、Zset(有序集合)。
小林coding
2022/10/27
1.9K1
2 万字  + 20张图| 细说 Redis 九种数据类型和应用场景
Redis 实战篇:巧用数据类型实现亿级数据统计
在移动应用的业务场景中,我们需要保存这样的信息:一个 key 关联了一个数据集合,同时还要对集合中的数据进行统计排序。
用户1263954
2021/12/15
5080
Redis 实战篇:巧用数据类型实现亿级数据统计
Redis系列:Redis的数据结构
Redis 的基本数据类型包括:二进制安全字符串 String、Hashes(哈希)、Lists 列表、Sets 集合 和 Sorted sets 有序集合;
栗筝i
2022/12/01
4480
既然有Map了,为什么还要有Redis?
String 是最基本的 key-value 结构,key 是唯一标识,value 是具体的值,value其实不仅是字符串, 也可以是数字(整数或浮点数),value 最多可以容纳的数据长度是 512M。
科技新语
2023/03/10
6930
Redis 新数据类型
命令大小写都可以,如果你只想单纯看 API,不想看例子,请移到最下面的 指令总结。
用户9615083
2022/12/25
6240
Redis-各数据类型常用命令(含使用示例)
Redis的全部命令详情可以在官网查询。 点此Redis系列文章专栏 命令不自己敲一遍,都是白搭,就算忘了也可以翻这篇博客。
唔仄lo咚锵
2020/09/15
4510
Redis的数据结构和使用场景
常用数据结构 string 最常用的,包含 incr自增 、decr自减、setnx 等 512M的容量 GetSet: 设置新值,返回旧值 应用场景 统计功能如粉丝数 分布式锁 共享Session 也可用于生成全局唯一的id set set集合 能放2^32-1个元素 用来做不重复元素保存 list 是一个双向链表,支持双向的Pop/Push ,江湖规矩一般从左端Push,右端Pop——LPush/RPop,配合lpush lpop rpush rpop 等可以组成栈或者队列 消息队列 栈
西柚dzh
2022/06/09
2820
如何用redis统计海量UV?
我们先思考一个常见的业务问题:如果你负责开发维护一个大型的网站,有一天老板找产品经理要网站每个网页每天的 UV 数据,然后让你来开发这个统计模块,你会如何实现?
程序员小饭
2021/08/13
1.3K0
万字干货,Redis6全数据类型详解
往日,大量的指令让我们无法只靠脑袋来完全的记住,于是乎,编写一个日常的redis常用命令文档,以备不时之需.
冷环渊
2021/10/19
1.2K1
Redis 数据类型总结
Redis是一个开源的,基于内存的数据结构存储系统,它可以用作数据库、缓存和消息中间件。
windealli
2024/05/20
2500
Redis 数据类型总结
相关推荐
Redis统计大法:挖掘数据的四重宝藏【redis第五部分】
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验