redis之位图

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: redis之位图

位图

位图,即大量bit组成的一个数据结构(每个bit只能是0和1),主要适合在一些场景下,进行空间的节省,并有意义的记录数据,

例如一些大量的bool类型的存取,一个用户365天的签到记录,签到了是1,没签到是0,如果用普通的key/value进行存储,当用户量很大的时候,需要的存储空间是很大的。

如果使用位图进行存储,一年365天,用365个bit就可以存储,365个bit换算成46个字节(一个稍长的字符串),如此就节省了很多的存储空间,

image.png

位图的本质其实是一个普通的字符串,也就是byte数组,可以使用get/set直接获取和设置整个位图的内容,也可以使用 getbit/setbit 将byte数组看成bit数组来处理。

使用位操作设置字符串

正常设置字符串都使用set命令,下面我们使用setbit设置一下位数组,最后以获取字符串的形式获取,

首先我们获取一下h、e两个ASCII码使用二进制的表示如下,

image.png

可以看到h的二进制码是 01101000 , e的二进制码是 01100101,我们只需要注意bit是1的位置,然后进行setbit,

image.png

需要注意的是,位数组的顺序和字符的位顺序是反的,根据这个原则,我们算出 h字符 每个1的位置分别是1/2/4, e字符的则是 9/10/13/15,

image.png

所以我们将使用setbit设置一个位数组,并在每个位置上(1/2/4/9/10/13/15)设置对应的1,

setbit data 1 1
setbit data 2 1
setbit data 4 1
setbit data 9 1
setbit data 10 1
setbit data 13 1
setbit data 15 1

零存整取

image.png

最后直接 get data这个key,会发现正好得到he,

image.png

setbit + get 的组合称为 零存整取,零存就是一个bit一个bit的设置,整取就是通过key名字,直接get出来所有的数据,

同样,我们还可以进行 零存零取,整存零取,整存就是直接使用字符串设置整个位数组,零取则是通过bit的位置,进行bit的获取。

零存零取

可以看到,我们根据setbit,对key叫做w的位数组进行bit设置,只设置了1/2/4这3个位置的值为1,下图中有getbit w 3, 获取第三个位置的值,此时默认是0,如果从业务角度触发,可以理解为,一共签到4天,第三天没有进行签到,

image.png

整存零取

下午所示,我们对w的这个key,直接set了一个h字符,随后通过getbit获取w的位数组里的每个bit,可以看到获取出来的内容和上面h字符的二进制内容相同 1/2/4的位置是1,其余是0

image.png

注意

  1. redis的位数组是自动扩充的,如果设置的某个偏移位置超出了现有的内容范围,就会自动将位数组进行零扩充,即扩容的位默认都是0值。
  2. 如果对应位的字节是不可打印字符,redis-cli将会显示该字符的十六进制形式。
  3. 一个字节是8个bit(位),要区分字节和位。

统计和查找 (bitcount/bitpos)

redis提供了 统计指令 bitcount 和 位图查找指令 bitpos ,

bitcount用来统计指定位置范围内1的个数,bitpos用来查找指定范围内出现的第一个0或1。

我们可以通过bitcount统计用户一共签到了多少天,通过bitpos指令查找用户从哪一天开始第一次签到,

如果指定了范围参数[start, end],就可以统计在某个时间范围内用户签到了多少天以及用户自某天以后的哪天开始签到,

但是需要注意的是,start和end参数是字节索引,也就是说,指定的位范围必须是8的倍数,

而不能任意指定,所以我们无法直接计算某个月内用户签到了多少天,如果需要计算的话,

可以使用getrange命令取出该月覆盖的字节内容,然后在内存中进行统计,例如2月覆盖了10-12个字节,就使用 getrange w 8 12 。

127.0.0.1:6379> set w hello    

OK

127.0.0.1:6379> bitcount w      # 所有字符中有多少个1

(integer) 21

127.0.0.1:6379> bitcount w 0 0   # 第一个字符中 1 的位数

(integer) 3

127.0.0.1:6379> bitcount w 0 1   # 前两个字符中 1 的位数

(integer) 7

127.0.0.1:6379> bitpos w 0       # 第一个 0 位

(integer) 0

127.0.0.1:6379> bitpos w 1       # 第一个 1 位

(integer) 1

127.0.0.1:6379> bitpos w 1 1 1       # 从第二个字符算起,第一个1位

(integer) 9

127.0.0.1:6379> bitpos w 1 2 2       # 从第三个字符算起,第一个1位

(integer) 17

bitfield

之前介绍的 setbit / getbit 指定位的值都是单个位,如果要一次操作多个位,就必须使用管道来处理,

在redis3.2以后,提供了bitfield指令,可以一次对多个位进行操作,bitfield有三个子指令,分别是get/set/incrby, 都可以对指定位片段进行读写,

但是最多只能处理64个连续的位,如果超过64位,就需要使用多个子指令,bitfield可以一次执行多个子指令。

示例

下面对下图的位数组使用 bitfield 做一些操作

image.png

127.0.0.1:6379> bitfield w get u4 0   # 从第1个位开始取4个位,结果是无符号数(u)

1) (integer) 6

127.0.0.1:6379> bitfield w get u3 2   # 从第3个位开始取3个位,结果是无符号数(u)

1) (integer) 5

127.0.0.1:6379> bitfield w get i4 0   # 从第1个位开始取4个位,结果是有符号数(i)

1) (integer) 6

127.0.0.1:6379> bitfield w get i3 2   # 从第3个位开始取3个位,结果是有符号数(i)

1) (integer) -3

有符号数是指获取的位数组中的第一个位是符号位,剩下的才是值,如果第一位是1,就是负数,

无符号数表示非负数,没有符号位,获取的位数组全部都是值,有符号数最多可以获取64位,

无符号数只能获取63位,因为redis协议中的integer是有符号数,最大64位,不能传递64位的无符号值,

如果超出位数限制,redis就会告诉你参数错误。

上面的指令可以合并成一条指令,可以看到得到的结果是一样的,

bitfield w get u4 0 get u3 2 get i4 0 get i3 2

image.png

set修改

我们从第9个位开始,用8个无符号数替换已经存在的8个位,其实就是把第二个字符替换了,由e变成a(它的ASCII码是97),可以看到结果也变成了 hallo

127.0.0.1:6379> bitfield w set u8 8 97

1) (integer) 101

127.0.0.1:6379> get w

"hallo"

incrby

incrby对指定范围的位进行自增操作,即++,这可能会发生溢出,如果增加了正数,会出现上溢出,如果增加的是负数,会出现下溢出,

redis默认的处理是折返,即如果出现了溢出,就将溢出的符号位丢掉,例如,如果是8位无符号数255,加1后就全部变成0,如果是8位有符号数127,加1后就溢出变成-128。

image.png

依然根据hello字符,来演示一下 incrby

127.0.0.1:6379> set w hello

OK

127.0.0.1:6379> bitfield w get u4 2     # 从第3位开始取4个无符号整数,第一次是10

1) (integer) 10

127.0.0.1:6379> bitfield w incrby u4 2 1

1) (integer) 11

127.0.0.1:6379> bitfield w incrby u4 2 1

1) (integer) 12

127.0.0.1:6379> bitfield w incrby u4 2 1

1) (integer) 13

127.0.0.1:6379> bitfield w incrby u4 2 1

1) (integer) 14

127.0.0.1:6379> bitfield w incrby u4 2 1

1) (integer) 15
127.0.0.1:6379> bitfield w incrby u4 2 1   #到这里的时候,已经溢出折返成0了

1) (integer) 0

bitfield指令提供溢出策略子指令 overflow,用户可以选择溢出行为,默认是折返(wrap),还可以选择失败(fail) 即报错不执行,还有饱和截断(sat) 即超过范围就停留在最大或最小值,

overflow指令只影响接下来的第一条指令,这条指令执行完以后,溢出策略就会变成默认值 折返(wrap)。

饱和截断

127.0.0.1:6379> set w hello

OK

127.0.0.1:6379> bitfield w overflow sat incrby u4 2 1

1) (integer) 11

127.0.0.1:6379> bitfield w overflow sat incrby u4 2 1

1) (integer) 12

127.0.0.1:6379> bitfield w overflow sat incrby u4 2 1

1) (integer) 13

127.0.0.1:6379> bitfield w overflow sat incrby u4 2 1

1) (integer) 14

127.0.0.1:6379> bitfield w overflow sat incrby u4 2 1

1) (integer) 15

127.0.0.1:6379> bitfield w overflow sat incrby u4 2 1 # 接下来的都将是保持最大值

1) (integer) 15

127.0.0.1:6379> bitfield w overflow sat incrby u4 2 1

1) (integer) 15

127.0.0.1:6379> bitfield w overflow sat incrby u4 2 1

1) (integer) 15

失败不执行

127.0.0.1:6379> set w hello

OK

127.0.0.1:6379> bitfield w overflow fail incrby u4 2 1

1) (integer) 11

127.0.0.1:6379> bitfield w overflow fail incrby u4 2 1

1) (integer) 12

127.0.0.1:6379> bitfield w overflow fail incrby u4 2 1

1) (integer) 13

127.0.0.1:6379> bitfield w overflow fail incrby u4 2 1

1) (integer) 14

127.0.0.1:6379> bitfield w overflow fail incrby u4 2 1

1) (integer) 15

127.0.0.1:6379> bitfield w overflow fail incrby u4 2 1 # 接下来的都是失败

1) (nil)

127.0.0.1:6379> bitfield w overflow fail incrby u4 2 1

1) (nil)

127.0.0.1:6379> bitfield w overflow fail incrby u4 2 1

1) (nil)

get/set/incrby一起执行

127.0.0.1:6379> bitfield w set u4 1 0 get u4 1 incrby u4 2 1

1) (integer) 0

2) (integer) 0

3) (integer) 1

127.0.0.1:6379> get w

"\x04ello"
相关实践学习
基于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
目录
相关文章
|
存储 NoSQL 算法
【Redis源码】位图SETBIT、BITCOUNT(九)
【Redis源码】位图SETBIT、BITCOUNT(九)
69 0
|
存储 NoSQL Redis
【Redis的那些事 · 续集】Redis的位图、HyperLogLog数据结构演示以及布隆过滤器
位图的最小单位是bit,每个bit的值只能是0和1,位图的应用场景一般用于一些签到记录,例如打卡等。场景举例: 例如某APP要存储用户的打卡记录,如果按照正常的思路来做,可能是用户每天是否打卡的记录都单独设置一个key-value键值对来存储,这样的话,每个用户每天都需要耗费一个键值对空间。而如果是位图,就可以很方便地通过位图来进行记录
194 0
【Redis的那些事 · 续集】Redis的位图、HyperLogLog数据结构演示以及布隆过滤器
|
存储 NoSQL 安全
【Redis】位图以及位图的使用场景(统计在线人数和用户在线状态)
【Redis】位图以及位图的使用场景(统计在线人数和用户在线状态)
【Redis】位图以及位图的使用场景(统计在线人数和用户在线状态)
|
存储 NoSQL Redis
基于Redis的bitmap位图实现用户签到功能(下)
由于 String 数据类型的最大长度是 512M,所以 String 支持的位数是 2^32 位。512M 表示字节 Byte 长度,换算成位 bit 需要乘以 8,即 512 2^10 2^10 * 8=2^32; Strings 的最大长度是 512M,还能存更大的数据?当然不能,但是我们可以换种实现思路,就是将大 key 换成小 key,这样存储的大小完全不受限。
341 0
基于Redis的bitmap位图实现用户签到功能(下)
|
存储 缓存 NoSQL
基于Redis的bitmap位图实现用户签到功能(上)
由于 String 数据类型的最大长度是 512M,所以 String 支持的位数是 2^32 位。512M 表示字节 Byte 长度,换算成位 bit 需要乘以 8,即 512 2^10 2^10 * 8=2^32; Strings 的最大长度是 512M,还能存更大的数据?当然不能,但是我们可以换种实现思路,就是将大 key 换成小 key,这样存储的大小完全不受限。
365 0
基于Redis的bitmap位图实现用户签到功能(上)
|
存储 NoSQL Redis
Redis-位图
关于位图,可能大家不太熟悉,
103 0
Redis-位图
|
存储 NoSQL Redis
第七章:Redis 位图bitmap&基数统计HyperLogLog
1. 什么是位图 redis可以直接对数据进行位操作。 获取hello二进制的第0位.png 2. 实例 setbit key offset value #给位图指定索引设置值 上面我们给hello赋值为world,那么我们现在把它的二进制第0位改成1,再进行get hello setbit .
1692 0
|
1月前
|
消息中间件 缓存 NoSQL
Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。
【10月更文挑战第4天】Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。随着数据增长,有时需要将 Redis 数据导出以进行分析、备份或迁移。本文详细介绍几种导出方法:1)使用 Redis 命令与重定向;2)利用 Redis 的 RDB 和 AOF 持久化功能;3)借助第三方工具如 `redis-dump`。每种方法均附有示例代码,帮助你轻松完成数据导出任务。无论数据量大小,总有一款适合你。
74 6
|
6天前
|
缓存 NoSQL 关系型数据库
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
本文详解缓存雪崩、缓存穿透、缓存并发及缓存预热等问题,提供高可用解决方案,帮助你在大厂面试和实际工作中应对这些常见并发场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
|
7天前
|
存储 缓存 NoSQL
【赵渝强老师】基于Redis的旁路缓存架构
本文介绍了引入缓存后的系统架构,通过缓存可以提升访问性能、降低网络拥堵、减轻服务负载和增强可扩展性。文中提供了相关图片和视频讲解,并讨论了数据库读写分离、分库分表等方法来减轻数据库压力。同时,文章也指出了缓存可能带来的复杂度增加、成本提高和数据一致性问题。
【赵渝强老师】基于Redis的旁路缓存架构