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

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 【Redis】位图以及位图的使用场景(统计在线人数和用户在线状态)

目录

位图

基本使用

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;

image.png

刚刚设置的 key 为 f 在 第1、2、7 号为设置了1 其余的都是0;

二进制表现形式是 0110 0001

当然我们知道位图是用字符串来存的; 可以用get命令来看看 输出来的是a; 因为对于的ASCII码就是 a

image.png

注意:一般我们看二进制的时候可能习惯的是从右边往左边看, 但是索引的话还是从左边往右边数的; 最左边的是以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

位图的使用场景

记录用户一年的签到情况

假如有这么一个需求


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

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

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

想要实现上面的需求. 最笨最笨的方法是当用户签到了就在数据库中插入一条数据; 然后需要的时候再查询出来;那么上面的需求也能满足;

但是这种方式就太浪费内存了;一个用户一年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参数指的是字节的索引 不是位的索引

所以要做一些处理

image.png

如上图所示 如何统计上面位索引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内存大小

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

127.0.0.1:6379> SETBIT bigbit 1 1
(integer) 0

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

image.png


7.72->7.73 ;

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

127.0.0.1:6379> SETBIT bigbit 100000000 1
(integer) 0

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

image.png

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


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

在上面的例子中, 一亿位的数据量使用 BITCOUNT进行统计;

BITCOUNT 复杂度是O(N) ; 像get操作是O(1);

如果数据特别大的话可能会有性能问题; 官网是这样子说的:


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


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


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

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


image.png

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
6月前
|
存储 监控 NoSQL
Redis HyperLogLog: 高效统计大数据集的神秘利器
Redis HyperLogLog: 高效统计大数据集的神秘利器
125 1
|
6月前
|
存储 算法 NoSQL
探秘HyperLogLog:Redis中的基数统计黑科技
探秘HyperLogLog:Redis中的基数统计黑科技
341 0
|
6月前
|
存储 NoSQL BI
Redis 实战篇:巧用 Bitmap 实现亿级海量数据统计
Redis 实战篇:巧用 Bitmap 实现亿级海量数据统计
175 0
|
3月前
|
存储 NoSQL Java
Redis助力高并发网站:在线用户统计不再是难题!
小米带你了解如何使用Redis高效统计网站的在线与并发用户数。通过维护用户的活跃时间,利用Redis有序集合(Sorted Set)特性,可实时更新在线用户列表并统计数量。具体实现包括记录用户上线时间、定期清理离线用户及统计特定时间窗口内的活跃用户数。这种方法适用于高并发场景,保证统计结果的实时性和准确性。跟着小米一起探索Redis的强大功能吧!
94 2
|
7天前
|
NoSQL Java API
springboot项目Redis统计在线用户
通过本文的介绍,您可以在Spring Boot项目中使用Redis实现在线用户统计。通过合理配置Redis和实现用户登录、注销及统计逻辑,您可以高效地管理在线用户。希望本文的详细解释和代码示例能帮助您在实际项目中成功应用这一技术。
15 3
|
5月前
|
存储 NoSQL 算法
Redis系列学习文章分享---第十篇(Redis快速入门之附近商铺+用户签到+UV统计)
Redis系列学习文章分享---第十篇(Redis快速入门之附近商铺+用户签到+UV统计)
39 0
|
3月前
|
消息中间件 存储 NoSQL
redis实战——go-redis的使用与redis基础数据类型的使用场景(一)
本文档介绍了如何使用 Go 语言中的 `go-redis` 库操作 Redis 数据库
172 0
redis实战——go-redis的使用与redis基础数据类型的使用场景(一)
|
4月前
|
canal 缓存 NoSQL
Redis常见面试题(一):Redis使用场景,缓存、分布式锁;缓存穿透、缓存击穿、缓存雪崩;双写一致,Canal,Redis持久化,数据过期策略,数据淘汰策略
Redis使用场景,缓存、分布式锁;缓存穿透、缓存击穿、缓存雪崩;先删除缓存还是先修改数据库,双写一致,Canal,Redis持久化,数据过期策略,数据淘汰策略
Redis常见面试题(一):Redis使用场景,缓存、分布式锁;缓存穿透、缓存击穿、缓存雪崩;双写一致,Canal,Redis持久化,数据过期策略,数据淘汰策略
|
4月前
|
NoSQL Redis C++
c++开发redis module问题之在复杂的Redis模块中,特别是使用第三方库或C++开发时,接管内存统计有哪些困难
c++开发redis module问题之在复杂的Redis模块中,特别是使用第三方库或C++开发时,接管内存统计有哪些困难
|
3月前
|
NoSQL Java Redis
Redis字符串数据类型之INCR命令,通常用于统计网站访问量,文章访问量,实现分布式锁
这篇文章详细解释了Redis的INCR命令,它用于将键的值增加1,通常用于统计网站访问量、文章访问量,以及实现分布式锁,同时提供了Java代码示例和分布式锁的实现思路。
117 0