Code前端首页关于Code前端联系我们

Redis实战:巧妙利用Bitmap访问亿级海量数据统计

terry 2年前 (2023-09-26) 阅读数 55 #数据库

在移动应用业务场景中,我们需要保存这样的信息:为一个数据集合分配一个key。

常见场景包括:

  • 输入userId,判断用户的登录状态;
  • 显示当月登录次数及用户首次登录时间;
  • 近7天内2亿用户登录情况,统计7天内连续登录的用户数量;

通常情况下,用户和访问量都很大,比如几百万、几千万的用户,或者几千万甚至几十亿的访问信息。

因此我们需要选择一种可以非常高效地统计大量数据(例如数十亿)的集合类型。

要选择正确的数据集,首先要了解常用的统计模型,并应用对数据的合理理解来解决实际问题。

四种统计类型:

  1. 二进制状态统计;
  2. 聚合统计;
  3. 订单统计;
  4. 基本统计数据。

本文使用二进制状态统计类型作为练习系列的开始。 Wen 中将 使用除 String、Set、Zset、List 和 hash Bitmap 之外的扩展数据类型。

本文的说明可以通过在线Redis客户端(https://try.redis.io/)运行和调试,极其方便。

消息

分享更多,付出更多。在不考虑投资回报率的情况下,尽早为他人创造更多价值。从长远来看,这些努力将会放大地回报给你。

尤其是当您第一次开始与他人合作时,不要担心短期回报。这没有多大意义。相反,它是关于锻炼你自己的愿景、观点和解决问题的能力。

二进制状态统计

马哥,什么是二进制状态统计?

即集合的元素的值只有0和1。在登录场景以及用户是否登录的情况下,只记录login(1) 未登录 ( 0 ) 已登录 (1) 或 或 未登录。

如果我们使用Redis String类型实现来判断用户是否登录(key -> userId, value -> 0表示离线,1 - 登录),如果存储100万个用户如果将登录状态存储为字符串,则必须存储100万个字符串,这会消耗太多内存。

妈妈、兄弟,为什么String类型内存大呢?除了记录实际数据之外,

字符串类型还需要额外的内存来记录数据长度、空间使用情况等信息。

如果保存的数据包含字符串,则String类型以简单动态字符串(SDS)结构保存,如下图:

Redis 实战:巧用Bitmap 实现亿级海量数据统计

  • len:占用4个字节,表示buf已被使用。长度。
  • alloc:4个字节,表示buf实际分配的长度,通常>len。
  • buf:保存实际数据的字节数组。 Redis会自动在数组末尾添加一个“\0”,这会占用一个额外的字节。

所以在SDS中,除了buf保存实际数据之外,len和alloc代表了额外的额外成本。

此外,还有一个额外的RedisObject结构的头,因为Redis中有很多数据类型,相同的元数据附加到不同的数据类型上(例如,上次访问时间、引用) 。号等)。

所以Redis使用RedisObject结构来统一捕获这些元数据并指向实际数据。

Redis 实战:巧用Bitmap 实现亿级海量数据统计

对于二进制状态的场景,我们可以使用 Bitmap 来访问它们。比如我们用一位来表示登录状态,一亿个用户只占用一亿位内存≈(100000000 / 8/1024/1024) 12 MB。

大概的空间占用计算公式是:($offset/8/1024/1024) MB

什么是位图?

Bitmap底层数据结构采用String类型的SDS数据结构来保存位数组。 Redis 使用每个字节数组的 8 位。每个位表示一个元素的二进制状态(不是 0 就是 1)。

位图也可以想象为包含位单元的数组。数组的每个单元只能存储0或1。数组的下标称为位图偏移量。

为了直观直观,我们可以理解为buf数组的每个字节由一行表示,每行8位,8个格子分别代表这个字节中的8位,如图如下:

Redis 实战:巧用Bitmap 实现亿级海量数据统计

1 个字节创建 8 位,因此 Bitmap 节省了大量的存储空间。 这就是Bitmap的优点。

判断用户的登录状态

如何使用Bitmap判断大量用户中的某个用户是否在线?

Bitmap 提供GETBIT 和 SETBIT 操作,通过偏移值 offset 读写位数组的偏移位置处的位。请注意,偏移量从 0 开始。

只需要一个key=login_status即可存储用户登录状态的聚合数据。使用用户ID作为偏移量,在线时设置为1,离线时设置为0。使用GETBIT判断相应用户是否在线。 5 亿用户仅需要 6 MB 空间。

SETBIT 命令

SETBIT <key> <offset> <value>

设置或清除偏移处键值的位值(只能为 0 或 1)。

GETBIT 命令

GETBIT <key> <offset>

获取键值偏移中的位的值。如果键不存在,则返回0。

假设我们要判断用户ID 10086的登录状态:

首先执行以下命令,表明该用户已登录。

SETBIT login_status 10086 1

第二步是验证用户是否已登录。统计中的返回值1记录用1位代表每个用户每天的登录情况,每年的登录情况只需要365位。一个月最多有31天,只需要31位。

比如2021年5月统计号为89757的用户如何登录?键

uid:sign:{userId}:{yyyyMM},该月每一天的值 - 1 可以用作偏移量(因为偏移量是从 0 开始的,所以 偏移量 = 日期 - 1 )。

第一步是执行以下命令来记录用户在2021年5月16日的登录情况。

SETBIT uid:sign:89757:202105 15 1

第二步,判断用户号89757是否在2021年5月16日登录。 ❀第三步,统计该用户5月份的登录次数,使用命令BITCOUNT。该指令用于计算给定位块中值为 1 的位的数量。

BITCOUNT uid:sign:89757:202105

这样我们就可以了解每个月用户的登录情况,是不是很棒?

本月首次登录时间如何计算?

Redis 提供了BITPOS key bitValue [start] [end] 语句。返回的数据是位图第一个值的偏移位置,即bitValue

默认情况下,该命令检测整个位图。用户可以使用可选参数startend指定要检测的范围。

因此,通过执行以下命令,我们得到2021年5月的userID = 89757 首次登录日期:

BITPOS uid:sign:89757:202105 1

我们必须返回偏移值+1。从0开始。

连续登录用户总数

连续7天记录了1亿用户的登录明细后,如何统计这连续7天的连续登录用户总数?

我们使用当天的日期作为 Bitmap 键,使用 userId 作为偏移量。如果登录,请将偏移位置中的位设置为 1。

对应的集合中的数据位是用户当天的登录记录。

这样的位图一共有7个。如果我们可以对这7个Bitmap的相应位进行“与”运算。

相同的用户 ID 偏移量相同。如果7位图像对应的偏移位置上的某个用户ID位=1,则表示该用户已连续登录7天。

结果保存在新的位图中。然后我们统计位数=1和BITCOUNT,得到连续7天登录的用户总数。

Redis 提供BITOP 操作 destkey key [key ...]该命令用于对一个或多个 key = key 的位图进行位操作。

操作可以是OR、、 异或。当BITOP处理不同长度的字符串时,较短字符串的缺失部分被视为0。空键 也被视为字符串序列 0

很容易理解,如下图所示:

Redis 实战:巧用Bitmap 实现亿级海量数据统计

3 Bitmap,将相应位进行“与”运算,并将结果保存到新的 Bitmap 中。

运算语句对三个位图进行AND运算,并将结果保存到destmap。然后对destmap进行BITCOUNT统计。

// 与操作
BITOP AND destmap bitmap:01 bitmap:02 bitmap:03
// 统计 bit 位 =  1 的个数
BITCOUNT destmap

简单计算100M位图占用的内存开销大约需要12MB内存(10^8/8/1024/1024)。 7 天位图的内存约为 84 MB。不过,最好设置 Bitmap 的过期时间,让 Redis 删除过期的登录,以节省内存。

总结

想法是最重要的。当我们遇到只需要统计数据的二进制状态的统计场景时,比如用户是否存在、IP地址是否被列入黑名单、登录统计等,我们可以考虑使用这个。位图。

只需一位即可表示0和1。这大大减少了计算大量数据时的内存使用量。

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门