bitmap原理和redis bitmap应用

bitmap原理

bitmap是什么?在计算机中一个字节(byte)=8位(bit),这里的bit就是位,数据的最小表示单位,map一般是表示地图或者映射。

简单回顾一下二进制的一些知识:

1byte=8bit

1个bit有二种状态:0或1

所以1个byte可以表示00000000->11111111,也就是十进制0-255

其中十进制和二进制的对应关系如下:

0 ---------> 0000 0000
 1 ---------> 0000 0001
 2 ---------> 0000 0010
 3 ---------> 0000 0011
 4 ---------> 0000 0100
 5 ---------> 0000 0101
 6 ---------> 0000 0110
 7 ---------> 0000 0111
 8 ---------> 0000 1000
 9 ---------> 0000 1001
10 ---------> 0000 1010
11 ---------> 0000 1011
12 ---------> 0000 1100
13 ---------> 0000 1101
14 ---------> 0000 1110
15 ---------> 0000 1111
.......................
.......................
255...........1111 1111

在大部分变成语言里,int类型一般的都是占4个byte,也就是32位,甭管你这个数字是1或者是21亿你都得栈32位,所以如果你现在有10亿数字需要存在内存里面,需要分配多少内存呢:

1000000000 * 4 / 1024 / 1024 = 3800MB,大概需要3800MB内存,这里计算出的数值只适合C,在PHP里面,一个整形变量占用的实际空间远远大于4byte,是好几倍。

为了解决这个问题,bitmap采用一种映射机制,举个例子,假如有1、3、7、2、5这5个数字需要存放,正常情况下需要5*4bye=20byte,但是bitmap只需要1byte,它是如何做到的?

假设下面是1byte,首先将所有位置为0:00000000

从第一个0开始数数,把对应数字的位置置位1,比如说第一个1那就是第2个位置置位1,第二个三就是把第4个位置置位1,以此类推

1 => 0 1 0 0 0 0 0 0
3 => 0 0 0 1 0 0 0 0
7 => 0 0 0 0 0 0 0 1
2 => 0 0 1 0 0 0 0 0
5 => 0 0 0 0 0 1 0 0

累加起来最终的串就是:0 1 1 1 0 1 0 1

其实最终的数字和二进制没什么关系,纯粹是数数,这个串就代表最大到7的数字,然后我们就开始数数,从0开始:

比如第1个位置是1,那就记个1
比如第2个位置是1,那就记个2
比如第3个位置是1,那就记个3
比如第5个位置是1,那就记个5
比如第7个位置是1,那就记个7

结果就是1 2 3 5 7,不仅仅排序,而且还排重了,如果按照这种转换机制,1个int类型,32位的话,可以表示0-31之间的数字

如果要表示最大一万的数,就需要1万个位的串,但是编程语言并没有这样的数据类型,但是可以用数组去模拟。举个例子,一个整形是32位,也就说我们大概需要314个数组元素来表示这个串:

  • 数组第1个元素 00 - 31
  • 数组第2个元素 32 - 63
  • 数组第3个元素 64 - 95
  • 数组第4个元素 96 - 127
  • ……

提到这个算法的好处,最大的好处就是节省内存,节省了好几十倍,适合处理大量数据,除了快速排序,还可以快速去重,快速查询是否存在,还有一个好听的应用Bloom Filter(布隆过滤器)

 

 redis bitmap应用

setBit

说明:给一个指定的key的值的第offset位赋值位value

参数:key offset value:bool or int (1 or 0)

返回值:long:0或1

 getBit

说明:返回一个指定key的二进制信息

参数:key offset

返回值:long

bitCount

说明:返回一个指定key中位的值为1的个数(是以byte为单位的bit)

参数:key start offset

返回值:long

bitOp

说明:对不同的二进制存储数据进行位运算(AND、OR、NOT、XOR)

参数:operation destkey key [key …]

 返回值:long

 

bitmap优势:

  1. 基于最小的单位bit进行存储,所以非常省空间
  2. 设置时候时间复杂度O(1),读取时候时间复杂度O(n),操作时非常快的
  3. 二进制数据的存储,进行相关计算的时候非常快
  4. 方便扩容

 bitmap限制:

redis中bit映射并限制在512MB之内,所以最大时2^32位。建议每个key的位数都控制下,因为读取时候时间复杂度O(n),越大的串读的时间花销越大。

 

bitmap使用场景:

1.视频属性的无限延伸:

需求分析:一个拥有亿级数据量的短视频app,视频存在各种属性(是否加锁、是否特效等等),需要做各种标记。

可能想到的解决方案:

  1. 存储在mysql中,肯定不行,一个是随着业务增长属性一直增加,并且存在有时间限制的属性,直接对数据库进行加减字段是非常不合理的做法。即使是存在一个字段中用json等压缩技术存储也存在读效率的问题,并且对于大几亿的数据来说,废弃的字段回收起来非常麻烦。
  2. 直接记录在redis中,根据业务属性+uid为key来存储。读写效率角度没毛病,但是存储的角度来说key的数据量都大于value了,太耗费空间了。即使是用json等压缩技术来存储。也存在问题,解压需要时间,并且大几亿的数据回收也是难题。

设计方案:

使用redis的bitmap进行存储。
key由属性id+视频分片id组成。value按照视频id对分片范围取模来决定偏移量offset。10亿视频一个属性约120m还是挺划算的。

伪代码:

 

 

2.用户在线状态

 需求分析:需要对子项目提供一个接口,来提供某用户是否在线?

设计方案:使用bitmap是一个节约空间效率又高的一种方法,只需要一个key,然后用户id为偏移量offset,如果在线就设置为1,不在线就设置为0,3亿用户只需要36MB的空间。

伪代码:

 

 

 3.统计活跃用户

需求分析:需要计算活跃用户的数据情况。

设计方案:使用时间作为缓存的key,然后用户id为offset,如果当日活跃过就设置为1。之后通过bitOp进行二进制计算算出在某段时间内用户的活跃情况。

伪代码:

 

 

4.用户签到

需求分析:用户需要进行签到,对于签到的数据需要进行分析与相应的运运营策略。

设计方案:使用redis的bitmap,由于是长尾的记录,所以key主要由uid组成,设定一个初始时间,往后没加一天即对应value中的offset的位置。

伪代码:

 

 

 

 

 

 

 原文出处:

https://blog.csdn.net/u011957758/article/details/74783347

https://zhuanlan.zhihu.com/p/67920410

posted @ 2021-09-25 21:28  事儿爸_董  阅读(1061)  评论(0编辑  收藏  举报