百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

美团面试官:给你一个亿的keys,Redis如何统计?

mhr18 2024-11-20 18:43 20 浏览 0 评论

正文如下:

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


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


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


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


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


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


# 聚合统计


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

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


交集


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


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

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

SINTERSTORE userid:new userid:20002 userid:20003

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


差集


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


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

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


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

SDIFFSTORE  user:new  userid:20201102 userid:20201101  

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


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


并集


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

SUNIONSTORE  userid:new userid:20201102 userid:20201101

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


总结


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


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


  1. 在Redis集群中选一个从库专门负责聚合统计,这样就不会阻塞主库和其他的从库了
  2. 将数据交给客户端,由客户端进行聚合统计。


# 排序统计


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


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


Redis中的四种集合中List和Sorted Set属于有序集合。


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


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


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


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


# 二值状态统计


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


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


在签到统计中,0和1只占了一个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/01到2020/11/20天中连续打卡的人数,如何统计呢?


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

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

此时我们只需要对2020/11/1到2020/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中,如下:

PFADD p1:uv 10001 10002 10003 10004

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

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

PFCOUNT p1:uv

# 总结


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

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


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


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


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


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

相关推荐

【推荐】一个开源免费、AI 驱动的智能数据管理系统,支持多数据库

如果您对源码&技术感兴趣,请点赞+收藏+转发+关注,大家的支持是我分享最大的动力!!!.前言在当今数据驱动的时代,高效、智能地管理数据已成为企业和个人不可或缺的能力。为了满足这一需求,我们推出了这款开...

Pure Storage推出统一数据管理云平台及新闪存阵列

PureStorage公司今日推出企业数据云(EnterpriseDataCloud),称其为组织在混合环境中存储、管理和使用数据方式的全面架构升级。该公司表示,EDC使组织能够在本地、云端和混...

对Java学习的10条建议(对java课程的建议)

不少Java的初学者一开始都是信心满满准备迎接挑战,但是经过一段时间的学习之后,多少都会碰到各种挫败,以下北风网就总结一些对于初学者非常有用的建议,希望能够给他们解决现实中的问题。Java编程的准备:...

SQLShift 重大更新:Oracle→PostgreSQL 存储过程转换功能上线!

官网:https://sqlshift.cn/6月,SQLShift迎来重大版本更新!作为国内首个支持Oracle->OceanBase存储过程智能转换的工具,SQLShift在过去一...

JDK21有没有什么稳定、简单又强势的特性?

佳未阿里云开发者2025年03月05日08:30浙江阿里妹导读这篇文章主要介绍了Java虚拟线程的发展及其在AJDK中的实现和优化。阅前声明:本文介绍的内容基于AJDK21.0.5[1]以及以上...

「松勤软件测试」网站总出现404 bug?总结8个原因,不信解决不了

在进行网站测试的时候,有没有碰到过网站崩溃,打不开,出现404错误等各种现象,如果你碰到了,那么恭喜你,你的网站出问题了,是什么原因导致网站出问题呢,根据松勤软件测试的总结如下:01数据库中的表空间不...

Java面试题及答案最全总结(2025版)

大家好,我是Java面试陪考员最近很多小伙伴在忙着找工作,给大家整理了一份非常全面的Java面试题及答案。涉及的内容非常全面,包含:Spring、MySQL、JVM、Redis、Linux、Sprin...

数据库日常运维工作内容(数据库日常运维 工作内容)

#数据库日常运维工作包括哪些内容?#数据库日常运维工作是一个涵盖多个层面的综合性任务,以下是详细的分类和内容说明:一、数据库运维核心工作监控与告警性能监控:实时监控CPU、内存、I/O、连接数、锁等待...

分布式之系统底层原理(上)(底层分布式技术)

作者:allanpan,腾讯IEG高级后台工程师导言分布式事务是分布式系统必不可少的组成部分,基本上只要实现一个分布式系统就逃不开对分布式事务的支持。本文从分布式事务这个概念切入,尝试对分布式事务...

oracle 死锁了怎么办?kill 进程 直接上干货

1、查看死锁是否存在selectusername,lockwait,status,machine,programfromv$sessionwheresidin(selectsession...

SpringBoot 各种分页查询方式详解(全网最全)

一、分页查询基础概念与原理1.1什么是分页查询分页查询是指将大量数据分割成多个小块(页)进行展示的技术,它是现代Web应用中必不可少的功能。想象一下你去图书馆找书,如果所有书都堆在一张桌子上,你很难...

《战场兄弟》全事件攻略 一般事件合同事件红装及隐藏职业攻略

《战场兄弟》全事件攻略,一般事件合同事件红装及隐藏职业攻略。《战场兄弟》事件奖励,事件条件。《战场兄弟》是OverhypeStudios制作发行的一款由xcom和桌游为灵感来源,以中世纪、低魔奇幻为...

LoadRunner(loadrunner录制不到脚本)

一、核心组件与工作流程LoadRunner性能测试工具-并发测试-正版软件下载-使用教程-价格-官方代理商的架构围绕三大核心组件构建,形成完整测试闭环:VirtualUserGenerator(...

Redis数据类型介绍(redis 数据类型)

介绍Redis支持五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)及Zset(sortedset:有序集合)。1、字符串类型概述1.1、数据类型Redis支持...

RMAN备份监控及优化总结(rman备份原理)

今天主要介绍一下如何对RMAN备份监控及优化,这里就不讲rman备份的一些原理了,仅供参考。一、监控RMAN备份1、确定备份源与备份设备的最大速度从磁盘读的速度和磁带写的带度、备份的速度不可能超出这两...

取消回复欢迎 发表评论: