Redis核心技术与实战【学习笔记】 - 6.Redis 的统计操作处理

1.前言

在 Web 业务场景中,我们经常保存这样一种信息:一个 key 对应了一个数据集合。比如:

  • 手机 APP 中的每天用户登录信息:一天对应一系列用户 ID。
  • 电商网站上商品的用户评论列表:一个商品对应了一些列的评论。
  • 用户在手机 APP 上的签到打卡信息:一天对应一系列用户的签到记录。
  • 应用网站上的网页访问信息:一个网页对应一些列的访问点击。

Redis 集合类型的特点就是一个键对应一系列数据,所以非常适合用来存取这类数据。但是,实际的使用场景中,除了记录信息,还需要对集合中的数据进行统计,例如:

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

通常情况下,我们面临的用户数量以及访问量是巨大的。所以,我们必须要选择能够非常高效地统计大量数据(比如亿级)的集合类型。要选选择合适的集合,就得了解常用的集合统计模式。集合类型常见的统计模式有四种,包括聚合统计、排序统计、二值状态统计和基数统计,下面分别分析下这些统计类型。


2.聚合统计

聚合统计就是指多个集合元素聚合结果,包括:

  • 统计多个集合的共有元素(交集统计);
  • 把两个集合相比,统计其中一个集合独有的元素(差异统计);
  • 统计多个集合的所有元素(并集统计);

在前言提到的场景中,统计手机 APP 每天的用户新增数和第二天的用户留存数,正好对应的聚合统计。

要完成这个统计任务,我们可以用一个集合记录所有登录过 APP 的用户 ID,同时,用另一个集合记录每天登录过 APP 的用户 ID。然后,再对这两个集合做聚合统计。

  • 记录所有登录过 APP 的用户 ID比较简单,可以直接使用 Set 类型,把 key 设置为 user:id,表示记录的是用户 ID,value 是一个 Set 集合,里面是所有登录过 APP 的用户 ID,如下图所示
    在这里插入图片描述
  • 把每天登录的用户 ID 记录到一个新集合中,这个集合叫做每日用户 Set:key 是 user:id:当天日期,例如 user:id:20240129;value 是 Set 集合,记录当天登录的用户 ID。
    在这里插入图片描述

假设 APP 是 2024 年 1 月 20 日上线,那么 1 月 20 日前是没有用户的。

  1. 此时,累计用户 Set 是空的。
  2. 当天登录的用户 ID 会被记录到 user:id:20240120 的 Set 中。
  3. 所以,user:id:20240120 这个 Set 中的用户就是当天的新增用户。
  4. 然后,我们计算累计用户 Set 和 user:id:20240120 Set 的并集结果,结果保存在 user:id 这个累计用户 Set 中,如下所示:
SUNIONSTORE user:id user:id user:id:20240120
  1. 此时, user:id 这个累计用户 Set 中就有了 1 月 20 日的用户 ID。
  2. 等到 1 月 21 日再统计时,我们把 1 月 21 日登录的用户 ID 记录到 user:id:20240121 的 Set 中。
  3. 接下来执行 SDIFFSTORE 命令计算 user:id:20240121 的 Set 和 累计用户 Set 的差集,将结果保存在 key 为 user:new 的 Set 中,如下所示:
SDIFFSTORE user:new user:id:20240121 user:id
  1. 这个差集的用户 ID 在 user:id:20240121 的 Set 中存在,但是不在累计用户 Set 中。所以,user:new 这个 Set 中记录的就是 1 月 21 日的新增用户。
  2. 当要计算 1 月 21 日的留存用户时,我们只需要再计算 user:id:20240120user:id:20240121 这个两个 Set 的交集,就可以得到同时在这两个集合中的用户 ID 了,这些就是在 1 月 20 日登录且在 1 月 21 日留存的用户。执行的命令如下:
SINTERSTORE user:id:rem user:id:20240120 user:id:20240121

当你要对多个集合进行聚合计算时,Set 类型会是一个一个非常不错的选择。不过,这里有个潜在的风险:Set 的差集、并集和交集的计算复杂度较高,在数据量较大的情况下,如果直接执行这些计算,会导致 Redis 实例阻塞。所以,可以从主从集群中选择一个从库,让它专门负责聚合计算,或者是把数据读取到客户端,在客户端来完成聚合统计,这样就可以规避阻塞主库实例和其他从库实例的风险了。

3.排序统计

排序统计,我们以商品的最新评论列表的场景为例,进行讲解。

最新评论列表包含了所有评论中的最新留言,这就要求集合类型能对元素保序,也就是说集合中的元素可以按序排列,这种对元素保序的集合类型叫做有序集合。

Redis 常用的 4 个集合类型中(List、Set、Hash、Sorted Set),List 和 Sorted Set 就属于有序集合。

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

我们该如何选择呢?

3.1 List 排序

每个商品对应一个 List,这个 List 包含了对这个商品的所有评论,而且会按照评论时间来保存这些评论,每当有一个新评论,就用 LPUSH 命令把它插入 List 的队头。

在只有一页面评论的时候,逻辑非常简单,但是在实际应用中,一般都会分页显示,一旦涉及到分页,List 就可能会出现问题。

假设 当前的评论 List 是 {a, b, c, d, e, f},其中 1 是最新评论,5 是最早评论。在展示第一页的 3 个评论时,可以用下面的命令,得到最新的三条评论 a、b、c :

LRANGE comments 0 2
1) "a"
2) "b"
3) "c"

然后在获取第二页的3个评论 d、e、f:

LRANGE comments 3 5
1) "d"
2) "e"
3) "f"

但是如果此时又产生了一条新评论 g,评论 G 就会被插入到评论 List 的队头,评论 List 就变成了 {g, a, b, c, d, e, f}。此时,再用刚才的命令获取第二页评论时,就会发现,评论 c 又被展示出来了, 也就是 c、d、e

LRANGE comments 3 5
1) "c"
2) "d"
3) "e"

这是因为新元素插入前后,List 相同位置上的元素就会发生变化,用 LRANGE 读取时,就会读到旧元素。

3.2 Sorted Set 排序

和 List 相比,Sorted Set 就不存在这个问题,因为它是根据元素的实际权重来排序和获取数据的。我们可以按照评论时间的先后给每条评论设置一个权重值,然后再把评论保存到 Sorted Set 中。Sorted Set 也能通过 ZRANGBYSCORE 命令准备地获取到按排序的数据。

假设越新的评论权重越大,目前最新评论的权重是 N,我们执行下面的命令时,就可以获得最新的 10 条评论:

ZRANGEBYSCORE comments N-9 N

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

4.二值状态统计

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

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

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

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

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

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

这样,我们就知道该用户在 8 月份的签到情况了。

你可以再考虑下:如果记录了 1 亿个用户 10 天的签到情况,有没有办法统计出 这 10 天连续签到用户的总数吗?

分析之前,我们要先知道,Bitmap 支持用 BITOP 命令对多个 Bitmap 按位做异或的操作,操作的结果会保存到一个新的 Bitmap 中。

我们以按位操作为例。下图中,可以看到三个 Bitmap,bm1、bm2、bm3,对应 bit 位做操作,结果保存到一个新的 Bitmap 中(BITOP AND resmap bm1 bm2 bm3)。
在这里插入图片描述
回到刚刚的问题,在统计 1 亿个用户连续 10 天的签到情况时,可以把日期作为 key,每个 key 对应一个 1 亿位的 Bitmap,每个 bit 对应一个用户当天的签到情况。

接下来,我们对 10 个 Bitmap 做操作,得到的结果也是一个 Bitmap。在这个 Bitmap 中,只有 10 天都签到的用户对应的 bit 位上的值才会是 1。最后,我们可以用 BITCOUNT 统计下 Bitmap 中 1 的个数,这就是 1 亿用户中连续签到 10 天的用户总数了。

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

所以,在统计海里数据的时候,Bitmap 能够有效地节省内存空间。

5.基数统计

基数统计就是指统计一个集合中不重复的元素个数。对应的就是统计网页的 UV。

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

我们来看一个例子,有一个用户 user1 访问 page1 时,你把这个信息添加到 Set 中:

SADD page1:uv user1

用户 1 再来访问时,Set 去重功能就能保证不会重复记录用户 1 的访问次数。当你需要统计 UV 时,可以直接用 SCARD 命令,这个命令会返回一个集合中的元素个数。

但是如果 page1 非常火爆,UV 达到了千万,这个时候,一个 Set 就要记录千万个用户 ID,而在搞促销活动时,这样的页面可能有成千上万个,如果每个页面都用这样的 Set ,就会消耗很大的空间。

当然,也可以使用 Hash 类型记录 UV。例如,可以把用户 ID 作为集合的 key,当用户访问页面时,就用 HSET 命令,对这个用户 ID 记录一个值 “1” ,表示一个独立访客:

HSET page1:uv user1 1

即使用户多次访问,重复执行这个命令,user1 的值仍为 1。当要统计 UV 时,我们可以用 HLEN 命令统计 Hash 集合中的所有元素个数即可。但是,和 Set 类型一样,当页面很多时,Hash 类型也会消耗很大的内存空间。

有没有什么办法既能完成统计,又能节省空间呢?

它就是 Redis 的 HyperLogLog 了。它是一种用于统计基数的数据集合类型,它最大的优势是当集合元素非常多时,它计算基数所需的空间总是固定的,而且还非常小。

在 Redis 中,每个 HyperLogLog 只需 12 KB 的内存,就可以计算接近 2 的 64 次方个元素的基数。

在统计 UV 时,你可以用 PFADD 命令把访问页面的每个用户都添加到 HyperLogLog 中。

PFADD page1:uv user1 user2 user3 user4

接下来,就可以用 PFCOUNT 命令直接获得 page1 的 UV 值了,这个命令的作用就是返回 HyperLogLog 的统计结果。

PFCOUNT page:uv

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/357451.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

12 数据仓库理论

数仓基本概述 数据仓库基本概念 数据仓库是一个为数据分析而设计的企业级数据管理系统。数据仓库可集中 、整合多个信息源的大量数据。 数仓核心架构 数据仓库建模概述 数据仓库建模意义 数据模型就是数据组织和存储方法,它强调从业务、数据存取和使用角度合理…

Django配置websocket时的错误解决

基于移动群智感知的网络图谱构建系统需要手机app不断上传数据到服务器并把数据推到前端标记在百度地图上,由于众多手机向同一服务器发送数据,如果使用长轮询,则实时性差、延迟高且服务器的负载过大,而使用websocket则有更好的性能…

链表与二叉树-数据结构

链表与二叉树-数据结构 创建叶子node节点建立二叉树三元组:只考虑稀疏矩阵中非0的元素,并且存储到一个类(三元组)的数组中。 创建叶子node节点 class Node{int no;Node next;public Node(int no){this.nono;} } public class Lb…

YOLOv8改进 | 可视化热力图 | 支持YOLOv8最新版本密度热力图,和视频热力图

一、本文介绍 本文给大家带来的机制是集成了YOLOv8最新版本的可视化热力图功能,热力图作为我们论文当中的必备一环,可以展示出我们呈现机制的有效性,本文的内容支持YOLOv8最新版本的根据密度呈现的热力图,同时支持视频检测,根据视频中的密度来绘画热力图。 在开始之前给…

薅运营商羊毛?封杀!

最近边小缘在蓝点网上看到一则消息 “浙江联通也开始严格排查PCDN和PT等大流量行为 被检测到可能会封停宽带”。 此前中国联通已经在四川和上海等多个省市严查家庭宽带 (部分企业宽带也被查) 使用 PCDN 或 PT,当用户的宽带账户存在大量上传数据的情况,中…

数据库管理-第141期 DG PDB - Oracle DB 23c(20240129)

数据库管理141期 2024-01-29 第141期 DG PDB - Oracle DB 23c(20240129)1 概念2 环境说明3 操作3.1 数据库配置3.2 配置tnsname3.3 配置强制日志3.4 DG配置3.5 DG配置建立联系3.6 启用所有DG配置3.7 启用DG PDB3.8 创建源PDB的DG配置3.9 拷贝pdbprod1文件…

【C++】I/O多路转接详解(一)

目录 1. 背景引入1.1 IO的过程1.2 五种IO模型1.2.1 阻塞IO1.2.2 非阻塞IO1.2.3 信号驱动IO1.2.4 IO多路转接1.2.5 异步IO 1.3 同步通信 与 异步通信1.4 阻塞 与 非阻塞1.4.1 阻塞与非阻塞区别1.4.2 设置非阻塞IO 2. select2.1 接口使用2.2 select执行过程2.3 select代码实践 3.…

<网络安全>《9 入侵防御系统IPS》

1 概念 IPS( Intrusion Prevention System)是电脑网络安全设施,是对防病毒软件(Antivirus Programs)和防火墙(Packet Filter, Application Gateway)的补充。 入侵预防系统(Intrusio…

JS第一课简单看看这是啥东西

1.什么是JavaScript JS是一门编程语言,是一种运行在客户端(浏览器)的编程语言,主要是让前端的画面动起来,注意HTML和CSS不是编程语言,他俩是一种标记语言。JS只要有浏览器就能运行不用跟Python或者Java一样上来装一个jdk或者Pyth…

2023年算法SAO-CNN-BiLSTM-ATTENTION回归预测(matlab)

2023年算法SAO-CNN-BiLSTM-ATTENTION回归预测(matlab) SAO-CNN-BiLSTM-Attention雪消融优化器优化卷积-长短期记忆神经网络结合注意力机制的数据回归预测 Matlab语言。 雪消融优化器( SAO) 是受自然界中雪的升华和融化行为的启发,开发了一种…

Docker入门篇(二)—— 命令

Docker入门篇(二)—— 命令 插播!插播!插播!亲爱的朋友们,我们的Cmake/Makefile/Shell这三个课程上线啦!感兴趣的小伙伴可以去下面的链接学习哦~ 构建工具大师-CSDN程序员研修院 一、引言 当…

二叉搜索树的后序遍历序列

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO 联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬 学习必须往深处挖&…

利用Knife4j注解实现Java生成接口文档

文章目录 1、简介2、生成文档3、系列注解3.1、Api3.2、ApiResponses和ApiResponse3.3、ApiOperation3.4、Pathyvariable⭐3.5、RequestBody3.6、ApiOperationSupport3.7、ApiImplicitParams 和 ApiImplicitParam3.8、ApiModel3.9、ApiModelProperty ​🍃作者介绍&am…

动手学RAG:汽车知识问答

原文:动手学RAG:汽车知识问答 - 知乎 Part1 内容介绍 在自然语言处理领域,大型语言模型(LLM)如GPT-3、BERT等已经取得了显著的进展,它们能够生成连贯、自然的文本,回答问题,并执行…

JUC并发编程-异步回调、JMM、volatile

15. 异步回调 Future 设计的初衷:对将来的某个事件结果进行建模! 其实就是前端 --> 发送ajax异步请求给后端 但是我们平时都使用CompletableFuture 1)异步调用:CompletableFuture 没有返回值的异步回调 public static void ma…

Microsoft Edge 浏览器报错 提示不安全

网站提示不安全 是因为 Microsoft Edge 开了安全过滤 我们需要把这个关掉 打开浏览器的设置,然后 找到隐私选项 找到下边的Microsoft Defender Smartscreen 关掉 Microsoft Edge 支持 Microsoft Defender SmartScreen | Microsoft Learn win10系统下打开网页提示…

【国产MCU】-认识CH32V307及开发环境搭建

认识CH32V307及开发环境搭建 文章目录 认识CH32V307及开发环境搭建1、CH32V307介绍2、开发环境搭建3、程序固件下载1、CH32V307介绍 CH32V307是沁恒推出的一款基于32位RISC-V设计的互联型微控制器,配备了硬件堆栈区、快速中断入口,在标准RISC-V基础上大大提高了中断响应速度…

第一节 分布式架构设计理论与Zookeeper环境搭建

目录 1. 分布式架构设计理论 1. 分布式架构介绍 1.1 什么是分布式 1.2 分布式与集群的区别 1.3 分布式系统特性 1.4 分布式系统面临的问题 2. 分布式理论 2.1 数据一致性 2.1.1 什么是分布式数据一致性 2.1.2 副本一致性 2.1.3 一致性分类 2.2 CAP定理 2.2.1 CAP定…

微服务-微服务Alibaba-Nacos 源码分析(上)

Nacos&Ribbon&Feign核心微服务架构图 架构原理 1、微服务系统在启动时将自己注册到服务注册中心,同时外发布 Http 接口供其它系统调用(一般都是基于Spring MVC) 2、服务消费者基于 Feign 调用服务提供者对外发布的接口,先对调用的本地接口加上…

c++程序的各阶段

c程序四个阶段 预处理阶段 预处理器&#xff08;cpp&#xff09;根据以字符#开头的命令&#xff0c;修改原始的C程序。比如hello.c中第一行的#include<stdio.h>命令告诉预处理器读取系统头文件stdio.h的内容&#xff0c;并把它直接插入程序文本中&#xff0c;结果就得到…