【Redis】位图以及位图的使用场景(统计在线人数和用户在线状态) ゞ 浴缸里的玫瑰 2022-12-20 02:07 115阅读 0赞 ### 目录 ### * * 位图 * * 基本使用 * * SETBIT key 索引 值0/1 * GETBIT key 索引 * 通过SET 一次设置单个位图的所有位 * BITFIELD 设置多个位 * BITCOUNT * BITPOS 查找指定值为0或1的第一位。 * 位图的使用场景 * * 记录用户一年的签到情况 * 实时统计在线人数和某个用户的在线状态 * BITCOUNT统计大数据量的性能问题 ## 位图 ## **位图的最大优点之一是,它们在存储信息时通常可以节省大量空间** 位图不是一个真实的数据类型,而是定义在字符串类型上的面向位的操作的集合。由于字符串类型是二进制安全的二进制大对象,并且最大长度是 `512MB`,适合于设置 `2^32^`个不同的位。 位操作分为两组:常量时间单个位的操作,像设置一个位为 1 或者 0,或者获取该位的值。对一组位的操作,例如计算指定范围位的置位数量。 1字节=1B=2^3b=8位 1KB=2^10^B 1MB=2^10^KB 512MB=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; ![在这里插入图片描述][20201110145059667.png_pic_center] 刚刚设置的 key 为 f 在 第1、2、7 号为设置了1 其余的都是0; 二进制表现形式是 `0110 0001` 当然我们知道位图是用字符串来存的; 可以用`get`命令来看看 输出来的是`a`; 因为对于的ASCII码就是 a ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTA2MzQwNjY_size_16_color_FFFFFF_t_70_pic_center] 注意:一般我们看二进制的时候可能习惯的是从右边往左边看, 但是索引的话还是从左边往右边数的; 最左边的是以0号索引开始; #### GETBIT key 索引 #### 这里索引是位数索引 127.0.0.1:6379> GETBIT f 1 (integer) 1 127.0.0.1:6379> GETBIT f 0 (integer) 0 #### 通过SET 一次设置单个位图的所有位 #### 例如我们上面设置的位图 f; 我们设置的时候只需了3次命令; 如果我们知道这个值是多少 可以通过`SET`来直接设置;比如 127.0.0.1:6379> set g a OK 127.0.0.1:6379> get g "a" 127.0.0.1:6379> GETBIT g 1 (integer) 1 127.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 1 1) (integer) 0 2) (integer) 0 3) (integer) 0 127.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 abcde OK 127.0.0.1:6379> get mykey "abcde" 127.0.0.1:6379> BITCOUNT mykey 0 0 //是计算第一个字符 a的位数 (integer) 3 127.0.0.1:6379> BITCOUNT mykey 0 1 //是计算前两个个字符 ab的位数 (integer) 6 127.0.0.1:6379> BITCOUNT mykey 0 2 (integer) 10 127.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 0010` BITPOS 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 ## 位图的使用场景 ## ### 记录用户一年的签到情况 ### 假如有这么一个需求 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参数指的是字节的索引 不是位的索引` 所以要做一些处理 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTA2MzQwNjY_size_16_color_FFFFFF_t_70_pic_center 1] 如上图所示 如何统计上面位索引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_human used_memory_rss_human:7.72M [随时间监视RSS内存大小][RSS] 然后设置一下某个某个key位图 127.0.0.1:6379> SETBIT bigbit 1 1 (integer) 0 设置完了之后 可以看到内存有变化 ![在这里插入图片描述][20201110172355710.png_pic_center] 从`7.72->7.73` ; 接下来我们把第一亿位的索引也设置为1(这样做的目的是让这个key直接占用1亿个位) 127.0.0.1:6379> SETBIT bigbit 100000000 1 (integer) 0 设置完成之后可以看到内存马上就要变化了 ![在这里插入图片描述][20201110172553313.png_pic_center] 从`7.73->20.92` 跟我们计算的大概12MB左右; ### BITCOUNT统计大数据量的性能问题 ### 在上面的例子中, 一亿位的数据量使用 `BITCOUNT`进行统计; `BITCOUNT` 复杂度是O(N) ; 像`get`操作是O(1); 如果数据特别大的话可能会有性能问题; 官网是这样子说的: 在内存在456字节大小的时候,BITCOUNT仍然与任何其他O(1) Redis命令(如`GET`或`INCR` )一样快。 当位图很大时,有两种选择: * 取一个单独的密钥,该密钥在每次修改位图时都会递增。使用小的Redis Lua脚本可以非常高效和原子。 * 使用BITCOUNT 开始和结束 可选参数递增地运行位图,在客户端积累结果,并可选地将结果缓存到密钥中。 -------------------- ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTA2MzQwNjY_size_16_color_FFFFFF_t_70_pic_center 2] [20201110145059667.png_pic_center]: /images/20221120/ebca0ffebaaf424e9efc4e9b122089e4.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTA2MzQwNjY_size_16_color_FFFFFF_t_70_pic_center]: https://img-blog.csdnimg.cn/20201110145306680.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTA2MzQwNjY=,size_16,color_FFFFFF,t_70#pic_center [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTA2MzQwNjY_size_16_color_FFFFFF_t_70_pic_center 1]: https://img-blog.csdnimg.cn/20201110164302894.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTA2MzQwNjY=,size_16,color_FFFFFF,t_70#pic_center [RSS]: https://shirenchuang.blog.csdn.net/article/details/109576132#RSS_181 [20201110172355710.png_pic_center]: https://img-blog.csdnimg.cn/20201110172355710.png#pic_center [20201110172553313.png_pic_center]: https://img-blog.csdnimg.cn/20201110172553313.png#pic_center [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTA2MzQwNjY_size_16_color_FFFFFF_t_70_pic_center 2]: https://img-blog.csdnimg.cn/886b05d001a34c45819e251d44491604.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTA2MzQwNjY=,size_16,color_FFFFFF,t_70#pic_center
还没有评论,来说两句吧...