Redis Bitmap类型应用场景

Bitmap(位图)以占用空间下极小,查询速度快的优势,主要应用于各种“是非”类二值情况的统计或判断。

Bitmap类型的常用操作

SETBIT 添加Bitmap
127.0.0.1:6379> SETBIT bitmap 10 1
(integer) 0
127.0.0.1:6379> SETBIT bitmap 8 1
(integer) 0
127.0.0.1:6379> SETBIT bitmap 3 1
(integer) 0

这里返回值 是偏移位的原始值(不是修改之后的值)

127.0.0.1:6379> SETBIT bitmap 3 1 # 再次设置,返回的之前的 1
(integer) 1
127.0.0.1:6379> SETBIT bitmap 3 0 # 再次修改值为0,返回的是之前设置的 1
(integer) 1
GETBIT 查询Bitmap
127.0.0.1:6379> GETBIT bitmap 3
(integer) 0
127.0.0.1:6379> GETBIT bitmap 8
(integer) 1
127.0.0.1:6379> GETBIT bitmap 100 # 之前没有设置过的位,默认值都是0
(integer) 0
BITCOUNT 统计bitmap中为1的个数

这里重新设置之前的位图,满足下面这样的结构

0123456789101112131415
0001000010100000
127.0.0.1:6379> DEL bitmap # 删除key
(integer) 1
127.0.0.1:6379> SETBIT bitmap 3 1
(integer) 0
127.0.0.1:6379> SETBIT bitmap 8 1
(integer) 0
127.0.0.1:6379> SETBIT bitmap 10 1
(integer) 0

127.0.0.1:6379> BITCOUNT bitmap # 统计所有范围
(integer) 3
127.0.0.1:6379> BITCOUNT bitmap 0 10 # 统计0字节到10字节 1的个数
(integer) 3
127.0.0.1:6379> BITCOUNT bitmap 1 10 # 统计1字节到10字节 1的个数
(integer) 2
127.0.0.1:6379> BITCOUNT bitmap 2 10 # 统计2字节到10字节 1的个数
(integer) 0
127.0.0.1:6379> BITCOUNT bitmap 0 0 # 统计2字节到0字节 1的个数
(integer) 1

可以看出局部范围统计的参数是以字节为单位(8bit)

BITPOS 指定范围内第一个0或1的偏移位

还是以这个结构为初始值

0123456789101112131415
0001000010100000
127.0.0.1:6379> BITPOS bitmap 1 # 全部范围下第一个为1的偏移位
(integer) 3
127.0.0.1:6379> BITPOS bitmap 0
(integer) 0
127.0.0.1:6379> BITPOS bitmap 1 1 2 # 1字节到2字节中 第一个为1的偏移位
(integer) 8
BITOP 对多个Bitmap 进行位操作,并保存到新的Bitmap中

操作可以有AND、OR、XOR 或者 NOT
设置3bitmap如下结构

bit1:

0123
1000

bit2:

0123
0100

bit3:

0123
0010
127.0.0.1:6379> SETBIT bit1 0 1
(integer) 0
127.0.0.1:6379> SETBIT bit2 1 1
(integer) 0
127.0.0.1:6379> SETBIT bit3 2 1
(integer) 0

127.0.0.1:6379> BITOP AND andbit bit1 bit2 bit3 # 对3个bitmap执行AND操作 并保存到andbit
(integer) 1
127.0.0.1:6379> GETBIT andbit 0
(integer) 0
127.0.0.1:6379> GETBIT andbit 1
(integer) 0
127.0.0.1:6379> GETBIT andbit 2
(integer) 0
127.0.0.1:6379> BITCOUNT andbit
(integer) 0

可以看出操作后andbit 最后的结构应该是

0123
0000

实际应用场景

1.用户当天是否签到/打卡/登录

这类基本只需要记录二值的功能都可以用bitmap实现,以用户id(数字类型)作为offset。
(比如用户ID从1开始,即使最大用户id到1个亿,那么记录1个bitmap 最多也只需要12mb的内存)

每天记录一个打卡的key,每个用户打卡就设置一个bit

127.0.0.1:6379> SETBIT mark:20240525 100 1 # 5月25号 用户100打卡
(integer) 0
127.0.0.1:6379> SETBIT mark:20240525 130 1
(integer) 0
127.0.0.1:6379> SETBIT mark:20240525 135 1
(integer) 0

如果需要判断用户某天是否打过卡这个状态
只需要查询一下某天对应的bitmap是否有对应位

127.0.0.1:6379> GETBIT mark:20240525 135
(integer) 1
127.0.0.1:6379> GETBIT mark:20240525 134
(integer) 0

SETBIT和GETBIT 执行的时间复杂度都是O(1) 所以速度是很快的
当然对于登出这种反向操作的也可以设置为0

还可以继续扩展 判断某个用户是否在一段时间内连续打卡

127.0.0.1:6379> SETBIT mark:20240526 100 1 # 5月26号 用户100再次打卡
(integer) 0
127.0.0.1:6379> BITOP AND and:mark mark:20240525 mark:20240526
(integer) 17
127.0.0.1:6379> GETBIT and:mark 100 # 用户100 在25和26号都打了卡
(integer) 1
127.0.0.1:6379> GETBIT and:mark 130 # 用户130 没有连续打卡
(integer) 0


2.当前在线人数

对于某个应用/游戏,或者某个视频当前在线观看人数的统计

针对这类场景也是同样以用户ID为offset

127.0.0.1:6379> SETBIT app:online 100 1 # 用户100在线 登记
(integer) 0
127.0.0.1:6379> SETBIT app:online 130 1
(integer) 0
127.0.0.1:6379> SETBIT app:online 135 1
(integer) 0
127.0.0.1:6379> BITCOUNT app:online # 这一时刻在线用户数
(integer) 3
127.0.0.1:6379> SETBIT app:online 130 0 # 130用户下线 登记
(integer) 1
127.0.0.1:6379> BITCOUNT app:online 
(integer) 2

当然上面是实现统计的基本原理,对于实际场景中还需要考虑一些情况,比如用户下线并不会通知系统(系统强制退出/断网/关机等),这样就无法感知到用户是否下线。
一般在线的统计是需要客户端定时上报是否在线的(比如每隔60s上报一次在线情况)
这样我们在统计某个应用在线用户数的时候,可以直接清空原来的bitmap, 让用户重新上报在线情况。
不过这样粗暴的解决也会存在一个问题,删除key后,由于每个客户端上报的周期不一样(最多要60s才能将所有真正的在线用户全部上报上来),这样在60s之内去获取在线人数 肯定会比实际的少。

时间节点1:

在线人数4个

01234567
01010101

时间节点2:

id:5异常退出后
在线人数还是4个

01234567
01010101

时间节点3:

删除key
用户:1 上报在线 用户:3 用户:7 还未到下个客户端上报周期
统计到的在线用户数是1,实际上应该是3

01234567
01000000

为了优化这个问题,有以下一些解决方案:

缩短客户端上报在线状态的时间间隔

这个方案可以降低统计数据与真实数据间的差距,但是会增加服务或者网络额外的开销

按时间段统计上报

比如客户端上报周期是60s,
10:00~10:01时间段,写入的key为app:online:1000

10:01~10:02时间段,写入的key为app:online:1001
但是查询在线人数时候,还是使用上一周期的key(app:online:1000)
对于主动上报离线的用户, 还是往上一周期的key(app:online:1000)去上报(当前时间段的key为默认位就是0)

这样做的一个前提是,我们需要牺牲数据的实时性(延迟60s左右,一般的业务也是允许的),同时也增加了空间的使用,不过我们可以增加清理策略,比如在第三个时间段10:02~10:03时,删除app:online:1000的key(当然为了后续统计方便,也可以同时在其他数据库记录这个聚合后的值再删除)。


补充

Bitmap底层是基于String类型存储的(string底层以二进制格式存储)

127.0.0.1:6379> SETBIT bit 0 1
(integer) 0
127.0.0.1:6379> get bit # 使用get命令
"\x80"

反过来也可以,比如“3”的底层存储是

01234567
00110011
127.0.0.1:6379> SET stringbit 3 # 写入string类型的3
OK
127.0.0.1:6379> GETBIT stringbit 0
(integer) 0
127.0.0.1:6379> GETBIT stringbit 1
(integer) 0
127.0.0.1:6379> GETBIT stringbit 2
(integer) 1
127.0.0.1:6379> GETBIT stringbit 3
(integer) 1
127.0.0.1:6379> GETBIT stringbit 4
(integer) 0
127.0.0.1:6379> GETBIT stringbit 5
(integer) 0
127.0.0.1:6379> GETBIT stringbit 6
(integer) 1
127.0.0.1:6379> GETBIT stringbit 7
(integer) 1
  • 31
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Redis的数据类型有String、Hash、List、Set、Zset、GEO、Stream、HyperLogLog和Bitmap。在实际应用中,不同的数据类型可以用于不同的场景。 1. String类型:主要用于缓存和存储单个的值,比如用户的登录信息、计数器等。 2. Hash类型:适用于存储和获取对象的多个字段,比如存储用户的信息、商品的属性等。 3. List类型:可以按照插入顺序存储多个值,并支持在列表的两端进行插入和删除操作,比如消息队列、实时聊天记录等。 4. Set类型:用于存储多个不重复的值,也可以进行交集、并集、差集等操作,比如存储用户的好友列表、标签等。 5. Zset类型:有序集合,每个元素都会关联一个分数,可以根据分数进行范围查找和排序,适用于排行榜、带权重的数据等。 6. GEO类型:用于地理位置信息的存储和查询,可以计算距离、查找附近的位置等。 7. Stream类型:适用于消息队列的场景,可以按照时间顺序存储和消费消息。 8. HyperLogLog类型:用于统计独立元素的个数,可以进行基数估算,适用于统计UV、PV等场景。 9. Bitmap类型:用于位图操作,可以进行位运算和统计,比如用户签到、在线状态等。 在实际应用中,根据具体的需求和数据特点,选择合适的Redis数据类型可以带来更好的性能和扩展性。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [最全总结Redis数据类型使用场景](https://blog.csdn.net/qq_27681741/article/details/125289210)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值