北川建设局网站,舆情分析网站免费,整体vi设计,程序员40岁以后出路作者 | 码哥字节来源 | 码哥字节在移动应用的业务场景中#xff0c;我们需要保存这样的信息#xff1a;一个 key 关联了一个数据集合。常见的场景如下#xff1a;给一个 userId #xff0c;判断用户登陆状态#xff1b;显示用户某个月的签到次数和首次签到时间#xff1b… 作者 | 码哥字节来源 | 码哥字节在移动应用的业务场景中我们需要保存这样的信息一个 key 关联了一个数据集合。常见的场景如下给一个 userId 判断用户登陆状态显示用户某个月的签到次数和首次签到时间两亿用户最近 7 天的签到情况统计 7 天内连续签到的用户总数通常情况下我们面临的用户数量以及访问量都是巨大的比如百万、千万级别的用户数量或者千万级别、甚至亿级别的访问信息。所以我们必须要选择能够非常高效地统计大量数据例如亿级的集合类型。如何选择合适的数据集合我们首先要了解常用的统计模式并运用合理的数据类型来解决实际问题。四种统计类型二值状态统计聚合统计排序统计基数统计。本文将由二值状态统计类型作为实战篇系列的开篇文中将用到 String、Set、Zset、List、hash 以外的拓展数据类型 Bitmap 来实现。文章涉及到的指令可以通过在线 Redis 客户端运行调试地址https://try.redis.io/超方便的说。二值状态统计❝ 码哥什么是二值状态统计呀也就是集合中的元素的值只有 0 和 1 两种在签到打卡和用户是否登陆的场景中只需记录签到(1)或 未签到(0)已登录(1)或未登陆(0)。假如我们在判断用户是否登陆的场景中使用 Redis 的 String 类型实现key - userIdvalue - 0 表示下线1 - 登陆假如存储 100 万个用户的登陆状态如果以字符串的形式存储就需要存储 100 万个字符串了内存开销太大。❝ 码哥为什么 String 类型内存开销大String 类型除了记录实际数据以外还需要额外的内存记录数据长度、空间使用等信息。当保存的数据包含字符串String 类型就使用简单动态字符串SDS结构体来保存如下图所示SDSlen占 4 个字节表示 buf 的已用长度。alloc占 4 个字节表示 buf 实际分配的长度通常 len。buf字节数组保存实际的数据Redis 自动在数组最后加上一个 “\0”额外占用一个字节的开销。所以在 SDS 中除了 buf 保存实际的数据 len 与 alloc 就是额外的开销。另外还有一个 RedisObject 结构的开销因为 Redis 的数据类型有很多而且不同数据类型都有些相同的元数据要记录比如最后一次访问的时间、被引用的次数等。所以Redis 会用一个 RedisObject 结构体来统一记录这些元数据同时指向实际数据。对于二值状态场景我们就可以利用 Bitmap 来实现。比如登陆状态我们用一个 bit 位表示一亿个用户也只占用 一亿 个 bit 位内存 ≈ 100000000 / 8/ 1024/102412 MB。大概的空间占用计算公式是($offset/8/1024/1024) MB❝ 什么是 Bitmap 呢Bitmap 的底层数据结构用的是 String 类型的 SDS 数据结构来保存位数组Redis 把每个字节数组的 8 个 bit 位利用起来每个 bit 位 表示一个元素的二值状态不是 0 就是 1。可以将 Bitmap 看成是一个 bit 为单位的数组数组的每个单元只能存储 0 或者 1数组的下标在 Bitmap 中叫做 offset 偏移量。为了直观展示我们可以理解成 buf 数组的每个字节用一行表示每一行有 8 个 bit 位8 个格子分别表示这个字节中的 8 个 bit 位如下图所示Bitmap8 个 bit 组成一个 Byte所以 Bitmap 会极大地节省存储空间。 这就是 Bitmap 的优势。判断用户登陆态❝ 怎么用 Bitmap 来判断海量用户中某个用户是否在线呢Bitmap 提供了 GETBIT、SETBIT 操作通过一个偏移值 offset 对 bit 数组的 offset 位置的 bit 位进行读写操作需要注意的是 offset 从 0 开始。只需要一个 key login_status 表示存储用户登陆状态集合数据 将用户 ID 作为 offset在线就设置为 1下线设置 0。通过 GETBIT判断对应的用户是否在线。50000 万 用户只需要 6 MB 的空间。SETBIT 命令SETBIT key offset value设置或者清空 key 的 value 在 offset 处的 bit 值只能是 0 或者 1。GETBIT 命令GETBIT key offset获取 key 的 value 在 offset 处的 bit 位的值当 key 不存在时返回 0。假如我们要判断 ID 10086 的用户的登陆情况第一步执行以下指令表示用户已登录。SETBIT login_status 10086 1第二步检查该用户是否登陆返回值 1 表示已登录。GETBIT login_status 10086第三步登出将 offset 对应的 value 设置成 0。SETBIT login_status 10086 0用户每个月的签到情况在签到统计中每个用户每天的签到用 1 个 bit 位表示一年的签到只需要 365 个 bit 位。一个月最多只有 31 天只需要 31 个 bit 位即可。❝ 比如统计编号 89757 的用户在 2021 年 5 月份的打卡情况要如何进行key 可以设计成 uid:sign:{userId}:{yyyyMM}月份的每一天的值 - 1 可以作为 offset因为 offset 从 0 开始所以 offset 日期 - 1。第一步执行下面指令表示记录用户在 2021 年 5 月 16 号打卡。SETBIT uid:sign:89757:202105 15 1第二步判断编号 89757 用户在 2021 年 5 月 16 号是否打卡。GETBIT uid:sign:89757:202105 15第三步统计该用户在 5 月份的打卡次数使用 BITCOUNT 指令。该指令用于统计给定的 bit 数组中值 1 的 bit 位的数量。BITCOUNT uid:sign:89757:202105这样我们就可以实现用户每个月的打卡情况了是不是很赞。 ❝ 如何统计这个月首次打卡时间呢Redis 提供了 BITPOS key bitValue [start] [end]指令返回数据表示 Bitmap 中第一个值为 bitValue 的 offset 位置。在默认情况下 命令将检测整个位图 用户可以通过可选的 start 参数和 end 参数指定要检测的范围。所以我们可以通过执行以下指令来获取 userID 89757 在 2021 年 5 月份首次打卡日期BITPOS uid:sign:89757:202105 1需要注意的是我们需要将返回的 value 1 因为 offset 从 0 开始。连续签到用户总数❝ 在记录了一个亿的用户连续 7 天的打卡数据如何统计出这连续 7 天连续打卡用户总数呢我们把每天的日期作为 Bitmap 的 keyuserId 作为 offset若是打卡则将 offset 位置的 bit 设置成 1。key 对应的集合的每个 bit 位的数据则是一个用户在该日期的打卡记录。一共有 7 个这样的 Bitmap如果我们能对这 7 个 Bitmap 的对应的 bit 位做 『与』运算。同样的 UserID offset 都是一样的当一个 userID 在 7 个 Bitmap 对应对应的 offset 位置的 bit 1 就说明该用户 7 天连续打卡。结果保存到一个新 Bitmap 中我们再通过 BITCOUNT 统计 bit 1 的个数便得到了连续打卡 7 天的用户总数了。Redis 提供了 BITOP operation destkey key [key ...]这个指令用于对一个或者多个 键 key 的 Bitmap 进行位元操作。opration 可以是 and、OR、NOT、XOR。当 BITOP 处理不同长度的字符串时较短的那个字符串所缺少的部分会被看作 0 。空的 key 也被看作是包含 0 的字符串序列。便于理解如下图所示BITOP3 个 Bitmap对应的 bit 位做「与」操作结果保存到新的 Bitmap 中。操作指令表示将 三个 bitmap 进行 AND 操作并将结果保存到 destmap 中。接着对 destmap 执行 BITCOUNT 统计。// 与操作
BITOP AND destmap bitmap:01 bitmap:02 bitmap:03
// 统计 bit 位 1 的个数
BITCOUNT destmap简单计算下 一个一亿个位的 Bitmap占用的内存开销大约占 12 MB 的内存10^8/8/1024/10247 天的 Bitmap 的内存开销约为 84 MB。同时我们最好给 Bitmap 设置过期时间让 Redis 删除过期的打卡数据节省内存。小结思路才是最重要当我们遇到的统计场景只需要统计数据的二值状态比如用户是否存在、 ip 是否是黑名单、以及签到打卡统计等场景就可以考虑使用 Bitmap。只需要一个 bit 位就能表示 0 和 1。在统计海量数据的时候将大大减少内存占用。往期推荐read 文件一个字节实际会发生多大的磁盘IO如何优雅保护 Kubernetes 中的 SecretsRedis 内存满了怎么办这样置才正确云原生的本手、妙手和俗手点分享点收藏点点赞点在看