从0开始回顾Redis---系列十一

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 数据统计1、数据统计场景在Web和移动应用的业务场景中, 除了记录信息,我们往往还需要对集合中的数据进行统计,例如: ● 在移动应用中,需要统计每天的新增用户数和第二天的留存用户数;● 在电商网站的商品评论中,需要统计评论列表中的最新评论;● 在签到打卡中,需要统计一个月内连续打卡的用户数;● 在网页访问记录中,需要统计独立访客(Unique Visitor,UV)量。通常情况下,我们面临的用户数量以及访问量都是巨大的,比如百万、千万级别的用户数量,或者千万级别、甚至亿级别的访问信息。所以,我们必须要选择能够非常高效地统计大量数据(例如亿级)的集合类型。 2、常用的集合

数据统计

1、数据统计场景


在Web和移动应用的业务场景中, 除了记录信息,我们往往还需要对集合中的数据进行统计,例如:  

  • 在移动应用中,需要统计每天的新增用户数和第二天的留存用户数
  • 在电商网站的商品评论中,需要统计评论列表中的最新评论
  • 在签到打卡中,需要统计一个月内连续打卡的用户数
  • 在网页访问记录中,需要统计独立访客(Unique Visitor,UV)量

通常情况下,我们面临的用户数量以及访问量都是巨大的,比如百万、千万级别的用户数量,或者千万级别、甚至亿级别的访问信息。所以,我们必须要选择能够非常高效地统计大量数据(例如亿级)的集合类型。  

2、常用的集合统计模式

2.1 聚合统计

概念:统计多个集合元素的聚合结果,包括:统计多个集合的共有元素(交集统计);把两个集合相比,统计其中一个集合独有的元素(差集统计);统计多个集合的所有元素(并集统计)。  

比如:统计手机App每天的新增用户数和第二天的留存用户数,正好对应了聚合统计。  

思路:我们可以用一个集合记录所有登录过App的用户ID,同时,用另一个集合记录每一天登录过App的用户ID。然后,再对这两个集合做聚合统计。我们来看下具体的操作。  

  1. 记录所有登录过App的用户ID, 直接使用Set类型,把key设置为user280680,表示记录的是用户ID,value就是一个Set集合,里面是所有登录过App的用户ID,我们可以把这个Set叫作累计用户Set。
  2. 把每一天登录的用户ID,记录到一个新集合中,我们把这个集合叫作每日用户Set,它有两个特点:  
  1. key是user280680以及当天日期,例如user280680:20200803;  
  2. value是Set集合,记录当天登录的用户ID。  
  1. 在统计每天的新增用户时,我们只用计算每日用户Set和累计用户Set的差集就行。  

2.2 排序统计


场景

  • 电商网站上提供最新评论列表, 最新评论列表包含了所有评论中的最新留言,这就要求集合类型能对元素保序
  • 集合中的元素可以按序排列,这种对元素保序的集合类型叫作有序集合。在Redis常用的4个集合类型中(List、Hash、Set、Sorted Set),List和Sorted Set就属于有序集合。  

List是按照元素进入List的顺序进行排序的,而Sorted Set可以根据元素的权重来排序,我们可以自己来决定每个元素的权重值。比如说,我们可以根据元素插入Sorted Set的时间确定权重值,先插入的元素权重小,后插入的元素权重大。  

使用List:

  • 操作:每个商品对应一个List,这个List包含了对这个商品的所有评论,而且会按照评论时间保存这些评论,每来一个新评论,就用LPUSH命令把它插入List的队头。
  • 缺点:在只有一页评论的时候,我们可以很清晰地看到最新的评论 ,但是,在实际应用中,网站一般会分页显示最新的评论列表,一旦涉及到分页操作,List就可能会出现问题了。
  • 关键原因:List是通过元素在List中的位置来排序的,当有一个新元素插入时,原先的元素在List中的位置都后移了一位,比如说原来在第1位的元素现在排在了第2位。所以,对比新元素插入前后,List相同位置上的元素就会发生变化,用LRANGE读取时,就会读到旧元素。    

使用Sorted Set:

  • 操作:我们可以按评论时间的先后给每条评论设置一个权重值,然后再把评论保存到Sorted Set中。
  • 优点:范围读取不会读到旧元素。
  • 关键原因:Sorted Set的ZRANGEBYSCORE命令就可以按权重排序后返回元素。这样的话,即使集合中的元素频繁更新,Sorted Set也能通过ZRANGEBYSCORE命令准确地获取到按序排列的数据。  


总结:在面对需要展示最新列表、排行榜等场景时,如果数据更新频繁或者需要分页显示,建议你优先考虑使用Sorted Set。  

2.3 二值状态统计

二值状态统计:这里的二值状态就是指集合元素的取值就只有0和1两种。在签到打卡的场景中,我们只用记录签到(1)或未签到(0),所以它就是非常典型的二值状态。

在签到统计时,每个用户一天的签到用1个bit位就能表示,一个月(假设是31天)的签到情况用31个bit位就可以,而一年的签到也只需要用365个bit位,根本不用太复杂的集合类型。这个时候,我们就可以选择Bitmap。  

Bitmap 实现原理  

  • Bitmap本身是用String类型作为底层数据结构实现的一种统计二值状态的数据类型。  
  • String类型是会保存为二进制的字节数组,所以,Redis就把字节数组的每个bit位利用起来,用来表示一个元素的二值状态。 因此可以把Bitmap看作是一个bit数组。  

Bitmap命令

  • Bitmap提供了GETBIT/SETBIT操作,使用一个偏移值offset对bit数组的某一个bit位进行读和写。  
  • Bitmap还提供了BITCOUNT操作,用来统计这个bit数组中所有“1”的个数。  

场景:假设我们要统计ID 3000的用户在2020年8月份的签到情况,就可以按照下面的步骤进行操作。

  1. 执行下面的命令,记录该用户8月3号已签到。  
SETBIT uid:sign:3000:202008 2 1
  1. 检查该用户8月3日是否签到。  
GETBIT uid:sign:3000:202008 2
  1. 统计该用户在8月份的签到次数。  
BITCOUNT uid:sign:3000:202008

问题:如果记录了1亿个用户10天的签到情况,你有办法统计出这10天连续签到的用户总数吗?  

解决:

  1. 可以把每天的日期作为key,每个key对应一个1亿位的Bitmap,每一个bit对应一个用户当天的签到情况。  
  2. 接下来,我们对10个Bitmap做“与”操作,得到的结果也是一个Bitmap。在这个Bitmap中,只有10天都签到的用户对应的bit位上的值才会是1。  
  3. 最后,我们可以用BITCOUNT统计下Bitmap中的1的个数,这就是连续签到10天的用户总数了。  

总结:

如果只需要统计数据的二值状态,例如商品有没有、用户在不在等,就可以使用Bitmap,因为它只用一个bit位就能表示0或1。在记录海量数据时,Bitmap能够有效地节省内存空间。

2.4 基数统计  

基数统计就是指统计一个集合中不重复的元素个数。对应到我们刚才介绍的场景中,就是统计网页的UV。

Set类型

网页UV的统计有个独特的地方,就是需要去重,一个用户一天内的多次访问只能算作一次。在Redis的集合类型中,Set类型默认支持去重,所以看到有去重需求时,我们可能第一时间就会想到用Set类型。  

具体如下:

  1. 有一个用户user1访问page1时,你把这个信息加到Set中:  
SADD page1:uv user1
  1. 用户1再来访问时,Set的去重功能就保证了不会重复记录用户1的访问次数,这样,用户1就算是一个独立访客。当你需要统计UV时,可以直接用SCARD命令,这个命令会返回一个集合中的元素个数。  

缺点:

如果page1非常火爆,UV达到了千万,这个时候,一个Set就要记录千万个用户ID。对于一个搞大促的电商网站而言,这样的页面可能有成千上万个,如果每个页面都用这样的一个Set,就会消耗很大的内存空间。

Hash类型  

  1. 把用户ID作为Hash集合的key,当用户访问页面时,就用HSET命令(用于设置Hash集合元素的值),对这个用户ID记录一个值“1”,表示一个独立访客,用户1访问page1后,我们就记录为1个独立访客,如下所示:
HSET page1:uv user1 1
  1. 即使用户1多次访问页面,重复执行这个HSET命令,也只会把user1的值设置为1,仍然只记为1个独立访客。当要统计UV时,我们可以用HLEN命令统计Hash集合中的所有元素个数。  

缺点:

当页面很多时,Hash类型也会消耗很大的内存空间。  


有什么办法既能完成统计,还能节省内存吗?  

HyperLogLog  

  • HyperLogLog是一种用于统计基数的数据集合类型,它的最大优势就在于,当集合元素数量非常多时,它计算基数所需的空间总是固定的,而且还很小。  
  • 在Redis中,每个 HyperLogLog只需要花费 12 KB 内存,就可以计算接近 2^64 个元素的基数。  

具体操作:

  1. 在统计UV时,你可以用PFADD命令(用于向HyperLogLog中添加新元素)把访问页面的每个用户都添加到HyperLogLog中。  
PFADD page1:uv user1 user2 user3 user4 user5
  1. 用PFCOUNT命令直接获得page1的UV值了,这个命令的作用就是返回HyperLogLog的统计结果。  
PFCOUNT page1:uv

缺点:

HyperLogLog的统计规则是基于概率完成的,所以它给出的统计结果是有一定误差的,标准误算率是0.81%。这也就意味着,你使用HyperLogLog统计的UV是100万,但实际的UV可能是101万。虽然误差率不算大,但是,如果你需要精确统计结果的话,最好还是继续用Set或Hash类型。  

3、总结


  1. Set和Sorted Set都支持多种聚合统计,不过,对于差集计算来说,只有Set支持。Bitmap也能做多个Bitmap间的聚合计算,包括与、或和异或操作。  
  2. 当需要进行排序统计时,List中的元素虽然有序,但是一旦有新元素插入,原来的元素在List中的位置就会移动,那么,按位置读取的排序结果可能就不准确了。而Sorted Set本身是按照集合元素的权重排序,可以准确地按序获取结果,所以建议你优先使用它。  
  3. 如果我们记录的数据只有0和1两个值的状态,Bitmap会是一个很好的选择,这主要归功于Bitmap对于一个数据只用1个bit记录,可以节省内存。  
  4. 对于基数统计来说,如果集合元素量达到亿级别而且不需要精确统计时,建议使用HyperLogLog。  

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
6月前
|
存储 NoSQL 定位技术
从0开始回顾Redis---系列三
数据结构 1、讲一讲Redis数据类型及底层数据结构? Redis 的五大常用数据类型:String(字符串)、List(列表)、Hash(哈希)、Set(集合)和Sorted Set(有序集合) 1.1 String(SDS) 简介 ● 是 Redis 最基本的数据类型,普通的key- value 存储都可以归为此类。二进制安全的,可以包含任何数据,比如 JPG 图片或者序列化的对象,最大能存储 512 MB。 应用场景:计数的场景,用户的访问次数、热点文章的点赞转发数量。 底层实现:String对象底层的数据结构实现主要是 int 和简单动态字符串 SDS。 struct sdshdr{
|
5月前
|
存储 NoSQL 关系型数据库
Redis系列学习文章分享---第一篇(Redis快速入门之初始Redis--NoSql+安装redis+客户端+常用命令)
Redis系列学习文章分享---第一篇(Redis快速入门之初始Redis--NoSql+安装redis+客户端+常用命令)
147 1
|
人工智能 监控 NoSQL
Redis监控利器---Redis State
Redis监控利器---Redis State
170 1
|
6月前
|
消息中间件 缓存 NoSQL
从0开始回顾Redis---系列八
缓存 1、缓存穿透? 缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。 解决方案: 1. 接口校验:在正常业务流程中可能会存在少量访问不存在 key 的情况,但是一般不会出现大量的情况,所以这种场景最大的可能性是遭受了非法攻击。可以在最外层先做一层校验:用户鉴权、数据合法性校验等,例如商品查询中,商品的ID是正整数,则可以直接对非正整数直接过滤等等。 2. 缓存空值:当访问缓存和DB都没有查询到值时,可以将空值写进缓存,但是设置较短的过期时间,该时间需要根据产品业务特性来
|
6月前
|
存储 消息中间件 NoSQL
从0开始回顾Redis---系列十
布隆过滤器 1、讲一讲布隆过滤器? 布隆过滤器,它是一个连续的数据结构,每个存储位存储都是一个bit,即0或者1, 可以用来快速判断某个数据是否存在。 标记某个数据时: 我们使用K个不同的哈希函数将这个数据映射为bit数组上的K个点,并把它们置为1。 查询某个数据时:先使用K个哈希函数得到这个数据在bit数组中对应的k个位置 ,然后判断bit值是不是1: ● 只要有一个不是1,就说明布隆过滤器没有对该数据做过标,即该数据不存在 ; ● 如果都是1,也只是表示数据可能存在。 优点: 1. 布隆过滤器的查询速度很快,一般只需要几毫秒; 2. 布隆过滤器只需要很少的空间,因为它只是一个位数组。
|
6月前
|
运维 监控 NoSQL
从0开始回顾Redis---系列六
哨兵机制 1、什么是哨兵,哨兵的作用是什么? 哨兵其实就是一个运行在特殊模式下的Redis进程,主从库实例运行的同时,它也在运行。哨兵主要负责的就是三个任务:监控、选主(选择主库)和通知。 ● 监控:哨兵进程在运行时,周期性地给所有的主从库发送PING命令,检测它们是否仍然在线运行。如果从库没有在规定时间内响应哨兵的PING命令,哨兵就会把它标记为“下线状态”;同样,如果主库也没有在规定时间内响应哨兵的PING命令,哨兵就会判定主库下线,然后开始自动切换主库的流程。 ● 选主:主库挂了以后,哨兵就需要从很多个从库里,按照一定的规则选择一个从库实例,把它作为新的主库。这一步完成后,现在的集群里
|
6月前
|
SQL 监控 NoSQL
从0开始回顾Redis---系列九
事务 1、Redis能实现ACID属性吗? ACID 1. 原子性: 一个事务中的多个操作必须都完成,或者都不完成。 2. 一致性: 数据库中的数据在事务执行前后是一致的。 3. 隔离性: 数据库在执行一个事务时,其它操作无法存取到正在执行事务访问的数据。 4. 持久性: 数据库执行事务后,数据的修改要被持久化保存下来。当数据库重启后,数据的值需要是被修改后的值。 Redis的事务机制可以保证一致性和隔离性,但是无法保证持久性。不过,因为Redis本身是内存数据库,持久性并不是一个必须的属性,我们更加关注的还是原子性、一致性和隔离性这三个属性。 原子性的情况比较复杂,只有当事务中使
|
6月前
|
NoSQL 算法 Redis
从0开始回顾Redis---系列七
切片集群 1、为什么要集群? 在实际应用Redis时,随着用户或业务规模的扩展,保存大量数据的情况通常是无法避免的。 我们可以用两种方案: 1. 纵向扩展:升级单个Redis实例的资源配置,包括增加内存容量、增加磁盘容量、使用更高配置的CPU。 2. 横向扩展:横向增加当前Redis实例的个数 。 那么,这两种方式的优缺点分别是什么呢? 1. 纵向扩展: ● 优点:实施起来简单、直接。 ● 缺点: ○ 当使用RDB对数据进行持久化时,如果数据量增加,需要的内存也会增加,主线程fork子进程时就可能会阻塞(比如刚刚的例子中的情况) ○ 纵向扩展会受到硬件和成本的限制。 2.
|
6月前
|
缓存 NoSQL 关系型数据库
从0开始回顾Redis---系列一
基础 1、Redis是什么?简述它的优缺点? Redis是用 C 语言开发的一个开源的高性能键值对(key-value)内存数据库。经常被用来做缓存,消息队列,分布式锁。 Redis 提供了多种数据类型来支持不同的业务场景,如 字符串(strings),散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets)与范围查询。 Redis 还支持事务 、持久化、Lua 脚本、多种集群方案。 优点: ● 读写性能极高, Redis能读的速度是110000次/s,写的速度是81000次/s。 ● 支持数据持久化,支持AOF和RDB两种持久化方式。 ●
|
6月前
|
负载均衡 NoSQL Redis
从0开始回顾Redis---系列五
主从复制 1、什么是Redis主从复制? ● 主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。 ● 默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。 2、主从复制有哪些好处? ● 读写分离:master 写、slave 读,提高服务器的读写负载能力; ● 负载均衡:基于主从结构,配合读写分离,由 slave 分担 master 负载,并根据需求的变化,改变 slave 的数量,通过多个从节点分担