位图排序 大数据_「Redis」Redis Bitmap 「 位图 还债篇 」

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

码字不易,点赞再看。

前言

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

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

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

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

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

61e95fc655f68115dd86c93964a0e947.png

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

正文

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> setbit s 1 1(integer) 0127.0.0.1:6379> setbit s 2 1(integer) 0127.0.0.1:6379> setbit s 4 1(integer) 0127.0.0.1:6379> get s"h"

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

零存零取

127.0.0.1:6379> setbit a 1 1(integer) 0127.0.0.1:6379> setbit a 2 1(integer) 0127.0.0.1:6379> setbit a 4 1(integer) 0127.0.0.1:6379> getbit a 1  //获取某个具体位置的值 0/1(integer) 1

整存零取

127.0.0.1:6379> set b hOK127.0.0.1:6379> getbit b 1(integer) 1127.0.0.1:6379> getbit b 2(integer) 1127.0.0.1:6379> getbit b 3(integer) 0127.0.0.1:6379> getbit b 4(integer) 1

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

127.0.0.1:6379> setbit x 0 1(integer) 0127.0.0.1:6379> setbit x 1 1(integer) 0127.0.0.1:6379> get x"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> set w helloOK127.0.0.1:6379> bitcount w(integer) 21127.0.0.1:6379> bitcount w 0 0  // 第一个字符中 1 的位数(integer) 3127.0.0.1:6379> bitcount w 0 1  // 前两个字符中 1 的位数(integer) 7127.0.0.1:6379> bitpos w 0  // 第一个 0 位(integer) 0127.0.0.1:6379> bitpos w 1  // 第一个 1 位(integer) 1127.0.0.1:6379> bitpos w 1 1 1  // 从第二个字符算起,第一个 1 位(integer) 9127.0.0.1:6379> bitpos w 1 2 2  // 从第三个字符算起,第一个 1 位(integer) 17

点关注 不迷路

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

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


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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值