写点什么

【Redis】位图以及位图的使用场景 (统计在线人数和用户在线状态)

  • 2022 年 8 月 08 日
  • 本文字数:2984 字

    阅读完需:约 10 分钟

【Redis】位图以及位图的使用场景(统计在线人数和用户在线状态)

1、位图

位图的最大优点之一是,它们在存储信息时通常可以节省大量空间位图不是一个真实的数据类型,而是定义在字符串类型上的面向位的操作的集合。由于字符串类型是二进制安全的二进制大对象,并且最大长度是 512MB,适合于设置 2^32^ 个不同的位。位操作分为两组:常量时间单个位的操作,像设置一个位为 1 或者 0,或者获取该位的值。对一组位的操作,例如计算指定范围位的置位数量。

1 字节=1B=2^3^ b=8 位 1KB=2^10^ B1MB=2^10^KB512MB=2^9^ X 2^10^KB X 2^10^B X 2^3^b = 2^32^b ;

基本使用

我们上面知道了 位图其实是一个字符串; 那么其实我们也可以用 get set来进行操作的;位图操作的是二进制;

SETBIT key 索引 值 0/1

SETBIT 是设置二进制索引上的某个值为 0 或者还是 1; 如果设置了高索引位,则其余位置自动填充为 0;

刚刚设置的 key 为 f 在 第 1、2、7 号为设置了 1 其余的都是 0;二进制表现形式是 0110 0001当然我们知道位图是用字符串来存的; 可以用get命令来看看 输出来的是a; 因为对于的 ASCII 码就是 a


注意:一般我们看二进制的时候可能习惯的是从右边往左边看, 但是索引的话还是从左边往右边数的; 最左边的是以 0 号索引开始;

GETBIT key 索引

这里索引是位数索引

127.0.0.1:6379> GETBIT f 1(integer) 1127.0.0.1:6379> GETBIT f 0(integer) 0
复制代码

通过 SET 一次设置单个位图的所有位

例如我们上面设置的位图 f; 我们设置的时候只需了 3 次命令; 如果我们知道这个值是多少 可以通过SET来直接设置;比如

127.0.0.1:6379> set g aOK127.0.0.1:6379> get g"a"127.0.0.1:6379> GETBIT g 1(integer) 1127.0.0.1:6379> GETBIT g 0(integer) 0
复制代码

BITFIELD 设置多个位

bitfield 有三个子指令,分别是 get/set/incrby,它们都可以对指定位片段进行读写,但是最多只能处理 64 个连续的位,如果超过 64 位,就得使用多个子指令,bitfield 可以一次执行多个子指令

BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment] [OVERFLOW WRAP|SAT|FAIL]

127.0.0.1:6379> BITFIELD mykey2 set u1 1 1 set u1 2 1 set u1 7 11) (integer) 02) (integer) 03) (integer) 0127.0.0.1:6379> get mykey2"a"
复制代码

BITCOUNT

计算字符串中的设置位数(填充计数) 报告设置为 1 的位数。

时间复杂度 O(N)BITCOUNT key [start end] #start和end参数指的是字节的索引 不是位的索引

例如设置一个字符串 abcde

127.0.0.1:6379> set mykey abcdeOK127.0.0.1:6379> get mykey"abcde"
复制代码


127.0.0.1:6379> BITCOUNT mykey 0 0  //是计算第一个字符 a的位数(integer) 3127.0.0.1:6379> BITCOUNT mykey 0 1  //是计算前两个个字符 ab的位数(integer) 6127.0.0.1:6379> BITCOUNT mykey 0 2(integer) 10127.0.0.1:6379> BITCOUNT mykey 1 1 //是计算第2个字符 b的位数(integer) 3
复制代码

BITPOS 查找指定值为 0 或 1 的第一位。

BITPOS key bit [start] [end] #start和end参数指的是字节的索引 不是位的索引

还是 value=abcde 的值他们的二进制分别为a=0110 0001 b=0110 0010BITPOS mykey 1 0 0 表示找第一个字符 a 的第一个位是 1 的索引;那么 a 的第一个为是 1 的所以是 1

127.0.0.1:6379> BITPOS mykey 1 0 0(integer) 1
复制代码

BITPOS mykey 1 1 1 表示找第二个字符 b 的第一个位是 1 的索引;a=0110 0001 b=0110 0010 我们自己数一下也就值得索引在 9 位

127.0.0.1:6379> BITPOS mykey 1 1 1(integer) 9
复制代码

counter(counterh2)位图的使用场景

记录用户一年的签到情况

假如有这么一个需求

  1. 记录每个用户的一年中每天的签到情况

  2. 统计某个时间段 用户的签到天数

  3. 可以查询某个时间段的签到情况

想要实现上面的需求. 最笨最笨的方法是当用户签到了就在数据库中插入一条数据; 然后需要的时候再查询出来;那么上面的需求也能满足;但是这种方式就太浪费内存了;一个用户一年 365 天就有最多 365 条数据;那么假如有 1 亿个用户 这数据是很庞大的; 当然我们还是有很多聪明的方式来解决这个问题;这里就不讨论了;我们直接讨论如何用 redis 中的位图来实现;

一年 365 天的签到情况;只有 签到了或者没签到两种情况;很适合用位图 0/1 来做;一年只需要 365 位就足够记录一个用户的签到情况了; 365 位,只需要 46 个字节 (一个字节有 8 位) 就可以完全容纳下,这就大大节约了存储空间。

可以设置功能上线当天比如 2020-1-1为索引 0; 后面签到的时候日期做一个差值就可以算出来位数了;

查询某个时间段的签到情况 redis 中并没有批量查询的位图的命令;只有单个查询getbit ,所以只能一个个执行; 为了减少网络开销; 可以通过管道 或者写lua脚本来批量查询

统计 用户的签到总天数BITCOUNT uidkey 0 0

BITCOUNT 统计区间范围

BITCOUNT key [start end] #start和end参数指的是字节的索引 不是位的索引

像这种统计区间范围的还真不是很好统计; 因为start和end参数指的是字节的索引 不是位的索引所以要做一些处理

在这里插入图片描述

如上图所示 如何统计上面位索引 5-25 中的数据呢?那么我们首先把最大和最小所以计算出来 他们分别在哪个字节索引中;因为一个字节索引包含了 8 个位索引所以很好计算出来;5%8 取模运算 = 0;25%8 取模运算 = 3 位索引为 5 的在 字节索引为 0 的位图中位索引为 25 的在字节索引为 3 的位图中先去掉这首位字节 然后统计中间的位图BITCOUNT key 1 2 得到结果 4

再单独计算首尾的位数位索引 5 占用后面的 5 6 7 三个位 用getbit一个个查询出来为 1 位索引 25 只占用 24 25 两个位 用getbit一个个查询出来为 2 三个一起加起来就行了 4+1+2 = 7;

实时统计在线人数和某个用户的在线状态

如果只是实时统计在线人数我们可能直接用 redis 中的 incr 就可以很方便的统计;但是如果我们还需要记录每个用户是否在线呢?那么一般情况可能 每个用户 id 作为 key 是否在线作为 value 存储; 那么这样也不是不可以但是就是比较占用内存也没有什么必要

那么通过位图来做就很方便和节约空间了

每个用户占用一位; 就算用一亿个用户 那么占用的内存大概在 100000000/8b/1024B/1024MB 约等于 12MB ;查询某个用户在线状态用getbit key 索引就行了统计在线人数就更简单了 BITCOUNT

那么我们来检测一下占用的内存是不是这样的;我们开启实时检测内存使用状态

[root@t]# /usr/local/bin/redis-cli -r -1 -i 1 INFO |grep rss_humanused_memory_rss_human:7.72M
复制代码

随时间监视RSS内存大小

然后设置一下某个某个 key 位图

127.0.0.1:6379> SETBIT bigbit 1 1(integer) 0
复制代码

设置完了之后 可以看到内存有变化

7.72->7.73 ;接下来我们把第一亿位的索引也设置为 1(这样做的目的是让这个 key 直接占用 1 亿个位)

127.0.0.1:6379> SETBIT bigbit 100000000 1(integer) 0
复制代码

设置完成之后可以看到内存马上就要变化了


7.73->20.92 跟我们计算的大概 12MB 左右;

BITCOUNT 统计大数据量的性能问题

在上面的例子中, 一亿位的数据量使用 BITCOUNT进行统计;BITCOUNT 复杂度是 O(N) ; 像get操作是 O(1);如果数据特别大的话可能会有性能问题; 官网是这样子说的:

在内存在 456 字节大小的时候,BITCOUNT 仍然与任何其他 O(1) Redis 命令(如GETINCR )一样快。

当位图很大时,有两种选择:

  • 取一个单独的密钥,该密钥在每次修改位图时都会递增。使用小的 Redis Lua 脚本可以非常高效和原子。

  • 使用 BITCOUNT 开始和结束 可选参数递增地运行位图,在客户端积累结果,并可选地将结果缓存到密钥中。



在这里插入图片描述

发布于: 刚刚阅读数: 3
用户头像

关注公众号: 石臻臻的杂货铺 获取最新文章 2019.09.06 加入

进高质量滴滴技术交流群,只交流技术不闲聊 加 szzdzhp001 进群 也可获取《kafka运维大全》

评论

发布
暂无评论
【Redis】位图以及位图的使用场景(统计在线人数和用户在线状态)_redis'_石臻臻的杂货铺_InfoQ写作社区