redis系列之——数据类型bitmaps:今天你签到了吗?

Redis系列目录

redis系列之——分布式锁
redis系列之——缓存穿透、缓存击穿、缓存雪崩
redis系列之——Redis为什么这么快?
redis系列之——数据持久化(RDB和AOF)
redis系列之——一致性hash算法
redis系列之——高可用(主从、哨兵、集群)
redis系列之——事物及乐观锁
redis系列之——数据类型geospatial:你隔壁有没有老王?
redis系列之——数据类型bitmaps:今天你签到了吗?
布隆过滤器是个啥!

平台日活跃100,000,000!!!今天有80,000,000人签到!!!

image-20200701211426310

这个是怎么统计出来的?如果让你设计,你会如何设计?是不是使用mysql存储用户活跃的信息,同时使用select count(*)统计总数?数据量大,是不是准备做离线数据处理?实时性如何保证?有没有更简单的方式?

image-20200701211602914

你也许已经知道Redis并不是简单的key-value存储,还有Hash、List、Set、Zset。

实际上Redis是一个数据结构服务器,支持不同类型的值。

今天给大家介绍redis中一种90%的程序员都不知道但是非常有用的数据类型,Bitmaps。

顺便回顾一下之前的两篇文章《redis系列之——缓存穿透、缓存击穿、缓存雪崩》、《布隆过滤器是个啥!》提到的bloom filter。

什么是 Bitmaps

来看看官方对Bitmaps的说明:

  • Bitmaps 并不是实际的数据类型,而是定义在String类型上的一个面向字节操作的集合。因为字符串是二进制安全的块,他们的最大长度是512M,最适合设置成2^32个不同字节。

  • bitmaps的位操作分成两类:1.固定时间的单个位操作,比如把String的某个位设置为1或者0,或者获取某个位上的值 2.对于一组位的操作,对给定的bit范围内,统计设定值为1的数目(比如人口统计)。

  • bitmaps最大的优势是在存储数据时可以极大的节省空间,比如在一个项目中采用自增长的id来标识用户,就可以仅用512M的内存来记录40亿用户的信息(比如用户是否希望收到新的通知,用1和0标识)

简单来说bitmaps就是一个长度可变的bit数组。每个位只能存储0或1。我们先来看看bitmap的具体表示,当我们使用命令 setbit key (0,2,4,6) 1后,这个bit数组的具体表示为:

bit0bit1bit2bit3bit4bit5bit6bit7
10101010

一天的1亿人的登录情况(登录、未登录)就可以使用一个长度为1亿的bit数组存储,数组的索引就是用户的userId(假设userId是自增的)。

如何使用

这里介绍几个常用的bitmaps的操作命令。

SETBIT key offset value

该命令起始版本:2.2.0;时间复杂度:O(1);返回值:在offset处原来的bit值;该命令的作用:设置或者清空key的value(字符串)在offset处的bit值。

那个位置的bit要么被设置,要么被清空,这个由value(只能是0或者1)来决定。当key不存在的时候,就创建一个新的字符串value。要确保这个字符串大到在offset处有bit值。参数offset需要大于等于0,并且小于2^32(限制bitmap大小为512MB)。当key对应的字符串增大的时候,新增的部分bit值都是设置为0。

警告:对于一个不存在的key,首次使用setbit命令在offset=2^32-1的位置(最后一个bit位)设置value时,或者对于一个存在的key,并非首次使用setbit命令在 offset=2^32-1 的位置设置value但之前设置offset的位置比较小时, Redis需要立即分配所有内存,这有可能会导致服务阻塞一会。在一台2010MacBook Pro上,offset为2^ 32-1(分配512MB)需要~300ms,offset为2^ 30-1(分配128MB)需要~80ms,offset为2^ 28-1(分配32MB)需要~30ms,offset为2^26-1(分配8MB)需要8ms。注意,一旦第一次内存分配完,后面对同一个key调用SETBIT就不会预先得到内存分配。

比如,某个平台需要统计每天用户的活跃情况(一个用户当天登了就算是一个当天活跃用户),每天都需要新生成一个bitmaps存储当天用户的登录情况。

这里的设计是: setbit active:{date} {userId} value ;比如存储2020-07-01该平台的用户活跃情况,key=active:2020-07-01;

127.0.0.1:6379> setbit active:2020-07-01 666 1 #userId=666的用户登陆,这是今天登陆的第一个用户。
(integer) 0
127.0.0.1:6379> setbit active:2020-07-01 100000000 1 #userId=100000000的用户登陆,这是今天第二个登陆的用户。
(integer) 0
127.0.0.1:6379> setbit active:2020-07-01 33 1
(integer) 0
127.0.0.1:6379> setbit active:2020-07-01 666 1 
(integer) 0
127.0.0.1:6379> setbit active:2020-07-01 100000 1
(integer) 0

第一个用户登陆时,会新建一个String,这个String被分配的空间时667bit,这是offset为0到665的用户未登录,值为0;

第二个用户登陆时,这个String已经存在,这时会被分配666到100000000的字节空间,这个时候就会出现短时间阻塞。

GETBIT key offset

该命令起始版本:2.2.0;时间复杂度:O(1);返回值:在offset处的bit值;该命令的作用:返回key对应的string在offset处的bit值。

当offset超出了字符串长度的时候,这个字符串就被假定为由0比特填充的连续空间。当key不存在的时候,它就认为是一个空字符串,所以offset总是超出范围,然后value也被认为是由0比特填充的连续空间。

比如,查询userId=666的用户在2020-07-01是否登陆:

127.0.0.1:6379> setbit active:2020-07-01 666
(integer) 1

返回为1,表示登陆了。

BITCOUNT key [start end]

该命令起始版本:2.6.0;时间复杂度:O(N);该命令的作用:统计字符串被设置为1的bit数;返回值:被设置为 1 的位的数量。

一般情况下,给定的整个字符串都会被进行计数,通过指定额外的 start 或 end 参数,可以让计数只在特定的位上进行。start 和 end 参数的设置和 GETRANGE 命令类似,都可以使用负数值:比如 -1 表示最后一个位,而 -2 表示倒数第二个位,以此类推。不存在的 key 被当成是空字符串来处理,因此对一个不存在的 key 进行 BITCOUNT 操作,结果为 0 。

比如,统计2020-07-01该平台有多少用户活跃(登陆):

127.0.0.1:6379> bitcount active:2020-07-01 0 -1
(integer) 342342326

有342342326个用户登陆过。

处理上面介绍的三个命令,对bitmaps操作的命令还有很多,因为bitmaps的本质是String,相关的操作命令和string一样,可以参考官网:http://www.redis.cn/commands.html#string

除了上面的命令,还可以通过相关的命令比较两个string的差异,对两个string做与、或、非运算;有很多实际的场景都可以通过这些简单的命令实现。

使用场景

由于bit数组的每个位置只能存储0或者1这两个状态;所以对于实际生活中,处理两个状态的业务场景就可以考虑使用bitmaps。如用户登陆/未登录,签到/未签到,关注/未关注,打卡/未打卡等。同时bitmap还通过了相关的统计方法进行快速统计。

内存占用比较

假如一个平台有8亿用户,平均日活跃用户有1亿,,分别使用List和Bitmap存储平台某一天是否活跃(登陆)用户时内存占用情况(1KB=1000bit):

数据类型每个userId占用空间需要存储的用户量内存使用总量
List4 * 8bit=32bit(假设userId用的是int存储)100,000,00032bit * 100,000,000= 400MB
Bitmaps1bit800,000,0001bit * 800,000,000=100MB

假如一个平台有8亿用户,平均日活跃用户有100万,,分别使用List和Bitmap存储平台某一天是否活跃(登陆)用户时内存占用情况(1KB=1000bit):

数据类型每个userId占用空间需要存储的用户量内存使用总量
List4 * 8bit=32bit(假设userId用的是int存储)1,000,00032bit * 1,000,000= 4MB
Bitmaps1bit800,000,0001bit * 800,000,000=100MB

所以并不是在所有的情况下,使用bitmap都是最好的选择。平台虽然有8亿用户,但是活跃的用户很少,这是使用Bitmaps,如果只有一个用户登录(加入是userId=800,000,000-1这个用户登录),也需要分配100MB的空间。

注意

bitmaps类型(string)最大长度为512M。
setbit时的偏移量很大时,可能会有较大耗时。
bitmaps不是绝对的好,有时可能更浪费空间。

Bloot Filter该怎么做,你是不是已经知道了?

完成,收工!!

传播知识,共享价值】,感谢小伙伴们的关注和支持,我是【诸葛小猿】,一个彷徨中奋斗的互联网民工。

xiabanle

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值