首页 > 科技 > 「Redis」Redis Bitmap 「 位图 还债篇 」

「Redis」Redis Bitmap 「 位图 还债篇 」

不积跬步,无以至千里;不积小流,无以成江海。

码字不易,点赞再看。

前言

欠下的债总规要还,上篇介绍基础数据结构中有提到 bitmap。移步至: 传送门

本篇先从一道面试题开始,有这么一个场景:拥有 数亿 用户的电商平台,如何统计月活??

你用 set 记录活跃用户的id,没毛病,确实也可以达到效果,但是使用这种数据结构真的妥当吗??

针对如此宝贵的内存,能省点,不香吗??

1亿 用户为例: 用户id 用int存储, 做个比对,引出今天的主角 bitmap


不用我说,已经看出来优势了吧,没错这就是位图牛逼的地方。

正文

Redis官方文档对于位图的介绍如下:

位图不是一个真实的数据类型,而是定义在字符串类型上的面向 位 的操作集合。由于字符串类型是二进制安全的二进制大对象,并且最大长度是 512MB,适合于设置 2^32个不同的位。


位操作分为两组:常量时间单个位的操作,像设置一个位为 1 或者 0,或者获取该位的值。对一组位的操作,例如:在 给定的位 范围内计算 设置位 的数目。


位图的最大优势是:是一种非常显著的节省空间来存储信息的方式。例如,在一个系统中,不同用户由递增的用户 ID 来表示,可以使用 512MB 的内存来表示 40亿 用户的单个位信息(例如他们是否需要接收信件)。

简而言之,位图 是操作 比特位 的,其优点是节省 内存空间

位图的常用操作如下:
  • setbit 设置特定key对应的比特位的值。
  • getbit 获取特定key对应的比特位的值。
  • bitcount统计给定key对应的字符串比特位为1的数量。

Redis 的位数组是 自动扩充,如果设置了某个偏移位置超出了现有的内容范围,就会 自动 将位数组进行零扩充。

接下来我们使用 redis-cli 设置第一个字符,也就是 位数组 的前 8 位,我们只需要设置值为 1 的位,如 h (二进制:0 1 1 0 1 0 0 0) 字符只有 1/2/4 位需要设置。

127.0.0.1:6379>setbits11(integer)0127.0.0.1:6379>setbits21(integer)0127.0.0.1:6379>setbits41(integer)0127.0.0.1:6379>gets"h"

上面这个例子可以理解为「零存整取」,同样我们还也可以「零存零取」,「整存零取」。「零存」就是使用 setbit 对位值进行逐个设置,「整存」就是使用字符串一次性填充所有位数组,覆盖掉旧值。

零存零取

127.0.0.1:6379>setbita11(integer)0127.0.0.1:6379>setbita21(integer)0127.0.0.1:6379>setbita41(integer)0127.0.0.1:6379> getbita1//获取某个具体位置的值0/1(integer)1

整存零取

127.0.0.1:6379>setbhOK127.0.0.1:6379>getbitb1(integer)1127.0.0.1:6379>getbitb2(integer)1127.0.0.1:6379>getbitb3(integer)0127.0.0.1:6379>getbitb4(integer)1

如果对应位的字节是不可打印字符,redis-cli 会显示该字符的 16 进制形式。

127.0.0.1:6379>setbitx01(integer)0127.0.0.1:6379>setbitx11(integer)0127.0.0.1:6379>getx"\\xc0"
统计和查找

Redis 提供了位图统计指令 bitcount 和位图查找指令 bitpos,bitcount 用来统计指定位置范围内 1 的个数,bitpos 用来查找指定范围内出现的第一个 0 或 1。

比如我们可以通过 bitcount 统计用户一共签到了多少天,通过 bitpos 指令查找用户从哪一天开始第一次签到。如果指定了范围参数[start, end],就可以统计在某个时间范围内用户签到了多少天,用户自某天以后的哪天开始签到。

遗憾的是, start 和 end 参数是 字节索引,也就是说指定的位范围必须是 8 的倍数,这点比较坑,而不能任意指定。这很奇怪,不是很能理解 Antirez 为什么要这样设计。因为这个设计,我们无法直接计算某个月内用户签到了多少天,而必须要将这个月所覆盖的字节内容全部取出来 (getrange 可以取出字符串的子串) 然后在内存里进行统计,这个非常繁琐。

还可以将放入位图的offset统一乘以8(一个字节占8比特),这样一来就可以直接用redis的bitcount来统计对应索引范围内的比特值为1的数量了,当然这种方案的缺点也相当明显,就是浪费内存,因为原先只需要1比特存储的数据,现在需要8比特存储,所以这种方案不能很好地利用位图索引节省存储空间的特点。

接下来我们简单试用一下 bitcount 指令和 bitpos 指令:

127.0.0.1:6379>setwhelloOK127.0.0.1:6379>bitcountw(integer)21127.0.0.1:6379>bitcountw00//第一个字符中1的位数(integer)3127.0.0.1:6379>bitcountw01//前两个字符中1的位数(integer)7127.0.0.1:6379>bitposw0//第一个0位(integer)0127.0.0.1:6379>bitposw1//第一个1位(integer)1127.0.0.1:6379>bitposw111//从第二个字符算起,第一个1位(integer)9127.0.0.1:6379>bitposw122//从第三个字符算起,第一个1位(integer)17


点关注 不迷路

以上就是这篇的全部内容,能看到这里的都是 人才

如果你从本篇内容有收获,求 点赞,求 关注,求 转发 ,让更多的人学习到。


如果本文有任何错误,请批评指教,不胜感激 !

本文来自投稿,不代表本人立场,如若转载,请注明出处:http://www.souzhinan.com/kj/295074.html