12 | 有一亿个keys要统计,应该用哪种集合?

2024-04-20 21:32

本文主要是介绍12 | 有一亿个keys要统计,应该用哪种集合?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • Redis核心技术与实战
    • 实践篇
      • 12 | 有一亿个keys要统计,应该用哪种集合?
        • 聚合统计
        • 排序统计
        • 二值状态统计
        • 基数统计
        • Redis 集合类型比较


Redis核心技术与实战

实践篇

12 | 有一亿个keys要统计,应该用哪种集合?

要想选择合适的集合,就得了解常用的集合统计模式。集合类型常见的四种统计模式,包括聚合统计、排序统计、二值状态统计和基数统计。

聚合统计

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

应用场景: 统计手机 App 每天的新增用户数和第二天的留存用户数

解决方案: 用一个集合记录所有登录过 App 的用户 ID,同时,用另一个集合记录每一天登录过 App 的用户 ID。然后,再对这两个集合做聚合统计。

记录所有登录过 App 的用户 ID 可以直接使用 Set 类型,把 key 设置为 user:id,表示记录的是用户 ID,value 就是一个 Set 集合,里面是所有登录过 App 的用户 ID,把这个 Set 叫作累计用户 Set,如下图所示:

在这里插入图片描述

接统计每天的新增用户,key 是 user:id 以及当天日期,例如 user : id :20200803,value 是 Set 集合,记录当天登录的用户 ID。如下图所示:

在这里插入图片描述

假设手机 App 在 2020 年 8 月 3 日上线,那么,8 月 3 日前是没有用户的。此时,累计用户 Set 是空集,当天登录的用户 ID 会被记录到 key 为 user : id: 20200803 的 Set 中。所以,user : id : 20200803 这个 Set 中的用户就是当天的新增用户。

然后,计算累计用户 Set 和 user : id : 20200803 Set 的并集(SUNIONSTORE) 结果,结果保存在 user:id 这个累计用户 Set 中,如下所示:

SUNIONSTORE  user:id  user:id  user:id:20200803 

等到 8 月 4 日再统计时,把 8 月 4 日登录的用户 ID 记录到 user : id: 20200804 的 Set 中。接下来,计算累计用户 Set 和 user : id : 20200804 Set 的差集(SDIFFSTORE),结果保存在 key 为 user:new 的 Set 中,如下所示:

SDIFFSTORE  user:new  user:id:20200804 user:id  

计算 8 月 4 日的留存用户时,只需要再计算 user : id : 20200803 和 user : id : 20200804 两个 Set 的交集(SINTERSTORE)

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

排序统计

应用场景: 电商网站上提供最新评论列表

解决方案: 要求集合类型能对元素保序

在 Redis 常用的 4 个集合类型中(List、Hash、Set、Sorted Set),List 和 Sorted Set 属于有序集合。
List 是按照元素进入 List 的顺序进行排序,而 Sorted Set 可以根据元素的权重来排序。

  • 采用 List 的情况。每个商品对应一个 List,这个 List 包含了对这个商品的所有评论,而且会按照评论时间保存这些评论,每来一个新评论,就用 LPUSH 命令把它插入 List 的队头。
    一旦涉及到分页操作,List 就可能会出现问题。 原因在于,List 是通过元素在 List 中的位置来排序的,当有一个新元素插入时,原先的元素在 List 中的位置都后移了一位。所以,对比新元素插入前后,List 相同位置上的元素就会发生变化,用 LRANGE 读取时,就会读到旧元素。
  • 采用 Sorted Set 的情况。可以按评论时间的先后给每条评论设置一个权重值,然后再把评论保存到 Sorted Set 中。Sorted Set 的 ZRANGEBYSCORE 命令就可以按权重排序后返回元素。这样的话,即使集合中的元素频繁更新,Sorted Set 也能通过 ZRANGEBYSCORE 命令准确地获取到按序排列的数据。

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

二值状态统计

二值状态就是指集合元素的取值就只有 0 和 1 两种。

应用场景: 用户签到

解决方案: 选择 Bitmap

Bitmap 本身是用 String 类型作为底层数据结构实现的一种统计二值状态的数据类型。 可以把 Bitmap 看作是一个 bit 数组。

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

统计 ID 3000 的用户在 2020 年 8 月份的签到情况

  1. 第一步,执行下面的命令,记录该用户 8 月 3 号已签到。
SETBIT uid:sign:3000:202008 2 1 

key = uid:sign:3000:202008 ;offset = 2(因为是3号); value = 1

  1. 第二步,检查该用户 8 月 3 日是否签到。
GETBIT uid:sign:3000:202008 2 
  1. 第三步,统计该用户在 8 月份的签到次数。
BITCOUNT uid:sign:3000:202008

统计出 10 天连续签到的用户总数
Bitmap 支持用 BITOP 命令对多个 Bitmap 按位做“与”“或”“异或”的操作,操作的结果会保存到一个新的 Bitmap 中。
三个 Bitmap bm1、bm2 和 bm3,对应 bit 位做“与”操作,结果保存到了一个新的 Bitmap 中(示例中,这个结果 Bitmap 的 key 被设为“resmap”)。

在这里插入图片描述

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

基数统计

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

应用场景: 网页 UV 统计

解决方案: Redis 的集合类型中,Set 类型默认支持去重。

有一个用户 user1 访问 page1 时,把这个信息加到 Set 中。

SADD page1:uv user1

当需要统计 UV 时,直接用 SCARD 命令返回一个集合中的元素个数。

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

也可以用 Hash 类型记录 UV。

把用户 ID 作为 Hash 集合的 key,当用户访问页面时,就用 HSET 命令(用于设置 Hash 集合元素的值),对这个用户 ID 记录一个值“1”,表示一个独立访客,用户 1 访问 page1 后,就记录为 1 个独立访客,如下所示:

HSET page1:uv user1 1

即使用户 1 多次访问页面,重复执行这个 HSET 命令,也只会把 user1 的值设置为 1,仍然只记为 1 个独立访客。当要统计 UV 时,可以用 HLEN 命令统计 Hash 集合中的所有元素个数。

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

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

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

在 Redis 中,每个 HyperLogLog 只需要花费 12 KB 内存,就可以计算接近 2^64 个元素的基数。
在统计 UV 时,用 PFADD 命令(用于向 HyperLogLog 中添加新元素) 把访问页面的每个用户都添加到 HyperLogLog 中。

PFADD page1:uv user1 user2 user3 user4 user5

PFCOUNT 命令直接获得 page1 的 UV 值

PFCOUNT page1:uv

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

Redis 集合类型比较

在这里插入图片描述

这篇关于12 | 有一亿个keys要统计,应该用哪种集合?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/921368

相关文章

Java 的ArrayList集合底层实现与最佳实践

《Java的ArrayList集合底层实现与最佳实践》本文主要介绍了Java的ArrayList集合类的核心概念、底层实现、关键成员变量、初始化机制、容量演变、扩容机制、性能分析、核心方法源码解析、... 目录1. 核心概念与底层实现1.1 ArrayList 的本质1.1.1 底层数据结构JDK 1.7

C++中unordered_set哈希集合的实现

《C++中unordered_set哈希集合的实现》std::unordered_set是C++标准库中的无序关联容器,基于哈希表实现,具有元素唯一性和无序性特点,本文就来详细的介绍一下unorder... 目录一、概述二、头文件与命名空间三、常用方法与示例1. 构造与析构2. 迭代器与遍历3. 容量相关4

Redis中的有序集合zset从使用到原理分析

《Redis中的有序集合zset从使用到原理分析》Redis有序集合(zset)是字符串与分值的有序映射,通过跳跃表和哈希表结合实现高效有序性管理,适用于排行榜、延迟队列等场景,其时间复杂度低,内存占... 目录开篇:排行榜背后的秘密一、zset的基本使用1.1 常用命令1.2 Java客户端示例二、zse

Java集合之Iterator迭代器实现代码解析

《Java集合之Iterator迭代器实现代码解析》迭代器Iterator是Java集合框架中的一个核心接口,位于java.util包下,它定义了一种标准的元素访问机制,为各种集合类型提供了一种统一的... 目录一、什么是Iterator二、Iterator的核心方法三、基本使用示例四、Iterator的工

Java JUC并发集合详解之线程安全容器完全攻略

《JavaJUC并发集合详解之线程安全容器完全攻略》Java通过java.util.concurrent(JUC)包提供了一整套线程安全的并发容器,它们不仅是简单的同步包装,更是基于精妙并发算法构建... 目录一、为什么需要JUC并发集合?二、核心并发集合分类与详解三、选型指南:如何选择合适的并发容器?在多

python语言中的常用容器(集合)示例详解

《python语言中的常用容器(集合)示例详解》Python集合是一种无序且不重复的数据容器,它可以存储任意类型的对象,包括数字、字符串、元组等,下面:本文主要介绍python语言中常用容器(集合... 目录1.核心内置容器1. 列表2. 元组3. 集合4. 冻结集合5. 字典2.collections模块

SpringBoot分段处理List集合多线程批量插入数据方式

《SpringBoot分段处理List集合多线程批量插入数据方式》文章介绍如何处理大数据量List批量插入数据库的优化方案:通过拆分List并分配独立线程处理,结合Spring线程池与异步方法提升效率... 目录项目场景解决方案1.实体类2.Mapper3.spring容器注入线程池bejsan对象4.创建

C++统计函数执行时间的最佳实践

《C++统计函数执行时间的最佳实践》在软件开发过程中,性能分析是优化程序的重要环节,了解函数的执行时间分布对于识别性能瓶颈至关重要,本文将分享一个C++函数执行时间统计工具,希望对大家有所帮助... 目录前言工具特性核心设计1. 数据结构设计2. 单例模式管理器3. RAII自动计时使用方法基本用法高级用法

IDEA与MyEclipse代码量统计方式

《IDEA与MyEclipse代码量统计方式》文章介绍在项目中不安装第三方工具统计代码行数的方法,分别说明MyEclipse通过正则搜索(排除空行和注释)及IDEA使用Statistic插件或调整搜索... 目录项目场景MyEclipse代码量统计IDEA代码量统计总结项目场景在项目中,有时候我们需要统计

Java集合中的链表与结构详解

《Java集合中的链表与结构详解》链表是一种物理存储结构上非连续的存储结构,数据元素的逻辑顺序的通过链表中的引用链接次序实现,文章对比ArrayList与LinkedList的结构差异,详细讲解了链表... 目录一、链表概念与结构二、当向单链表的实现2.1 准备工作2.2 初始化链表2.3 打印数据、链表长