空间存储压缩理论与实战

基本概念

Hash table 是根据关键字值key直接进行访问的数据结构,通过把关键字值映射到表中某个位置进行访问以加速查找key的速度。
bitmap 位图:数字在内存中1个byte用8个bit表示,即可以表示2^8 = 256个数。bitmap充分利用了每一行所有的位数,它将每个位置作为一个数,那么一行就可以模拟表示出8个数。
布隆过滤器可以简单理解为哈希+位图,它的基本思想是:通过一个Hash函数将一个元素映射成矩阵中的一个点,只需查看这个位置是1还是0就知道集合中是否存在它。但是由于哈希冲突:不同的元素经过散列函数后可能产生相同的哈希地址从而导致误判。为了降低误判的概率将一个元素经过多个散列函数映射到多个位上,如果这几个位都为1,我们认为它是存在的;如果有一位为0,我们认为它不存在。

热身

  • 题一:存储1亿的url的list,每个url最多占用64字节。现在想要实现一个过滤系统,判断任意url是否在这个list上。(允许万分之一的判断失误率,额外空间不超过30G)

    如果将所有url通过数据库或哈希表保存下来至少需要64G的空间显然不符合要求。在允许一定失误率的情况下可以使用布隆过滤器解决。布隆过滤器是基于Hash table哈希函数的位图表示:哈希函数将无限的输入值域映射到一个统一的区间上。简单的哈希例如:字符哈希等,读者也可以了解下基于哈希的经典实现:MD5和SHA1算法。
    接下来介绍下布隆过滤器。假设有一个长度为m的bit类型数组,数组的每个位置只占一个bit。再假设有k个 哈希函数对算出来的每个结果对长度m取余,然后在这个bit array上把相应的位置置为1,那么一个布隆过滤器生成完毕。
    在这里插入图片描述

    接下来是检查阶段,如何检查某个url是之前输入的呢?假设有个url :a,通过k个哈希函数算出k个值,然后把k个值取余。接下来就是看在bitmap上这些位置是不是都为“黑”。如果有一个不为黑,说明a一定不在list里。如果都为黑,说明在集合里但是可能有误判(假如bitmap长度过小所有的位置全部占满了)。

    • 题二:有十亿的整数集合{81,23,100,125…},判断给定一个整数是否在这个集合中。并比较空间使用情况。(JAVA)

    在这里插入图片描述
    int数组 1000000000 * 4 /(1024 * 1024 * 1024) ≈ \approx 3.73G
    byte数组 1000000000/(1024 * 1024 * 1024) ≈ \approx 0.93G
    bit数组 1000000000/8/(1024 * 1024 * 1024) ≈ \approx 0.12G
    bit Array是Bitmap的实现,其核心思想就是使用1bit表示元素的value,key为元素本身。Bitmap是元素到bit位的映射。
    我们来看如何将25映射到bitmap中:
    JAVA中int 是四个byte,32bit,如图所示:在这里插入图片描述
    先定位 Byte 25/8 =3,再计算bit位25%8=1在这里插入图片描述

Bitmap的使用场景

  • 网站黑名单系统、垃圾邮件过滤系统、爬虫网址去重
  • 数据库 Oracle的Bitmap Index
  • Redis(>2.2.0)支持setbit、getbit、bitcount,bitop and /or/xor/not destkey命令
  • LInux内核中FS中 Block Bitmap和inode Bitmap

基于Bitmap用户标签的构建

用户画像想必大家都不陌生,各类电商会把用户各个维度的数据抽象成标签用来进行数据分析、推荐算法及知识图谱的落地,因其数据量大、标签也经常分成好几级常常会消耗很多的存储资源。因此需要针对不同的场景来进行优化。
有一用户标签如下图示:

在这里插入图片描述

  • 场景1:在年龄上创建bitmap索引
    在这里插入图片描述
  • 场景2: 查找爱好游戏, 职业程序员的用户

爱好 = 游戏 AND 职业 = 程序员
在这里插入图片描述

  • 场景3:范围查找
    在这里插入图片描述

小于30岁的用户,即: 24|26|28 —>101011001101----->1,3,5,6,9,10,12
大于25岁且小于等于37的用户,即: 26|28|30|36 —>010001111111 ---->2,6,7,8,9,10,11,12

   存在的问题:
         计算次数随着查询的范围区间的扩大而增长。
   优化:通过非运算缩小查找范围
        大于25岁且小于等于37的用户:36 & ~24 --->010001111111 ---->2,6,7,8,9,10,11,12

小结:

  • Bitmap Index 的构建是在每个字段的每个值上构建的Bitmap
  • 通过AND/OR/NOT位运算逻辑,完成多维度交叉计算
  • 对于写少读多的范围查询,在创建索引时做变换处理以增加查询速度。

空间存储压缩

  • 痛点
    稀疏的Bitmap存在空间浪费
    举例: 在一个空的Bitmap 只存储一个整数:1000,则Bitmap空间前999个bit全被浪费。

  • 压缩算法

    • RLE(Run Length Encoding):行程长度编码是一种无损的压缩技术,其算法思路是把数据按线性序列分成两种情况:连续重复数据块和连续不重复数据块。对连续重复数据块进行压缩,压缩方法是用一个表示块数的属性加上一个数据块代表原先连续的若干数据;对于连续不重复数据块,RLE有两种处理方法:一是按照连续重复数据块进行压缩,块数为1;其二是不处理,将原始值当作压缩后的数据。

      举例: 原始数据:AAAAABBBBCCCDDEFFFFF
      编码后:5A4B3C2D1E5F or 5A4B3C2DE5F

    • WAH(Word Aligned Hybrid) : 词对齐混合编码充分利用了现代CPU的特性 。操作Word要比Byte效率高,所以WAH按照字对齐的方式对bitmap进行分组压缩;Hybrid针对bitmap中既存的大量连续的0或1序列,又存在01混合序列的情况,对这两种情况采用不同的压缩端码策路。

    WAH将Word分为两类literal words和fill words。
    literal words:用于存储01混合序列,最高位bit为0,其余bit 直接存储混合序列
    fill words用于存储0或1连续的序列,最高位bit位1,次高位为0是表示连续的0,次高位为1是表示连续的1,剩余bit存连续0或1的个数。
    在这里插入图片描述

    • Roaring Bitmap是一个高效压缩的Bitmap ,压缩率比RLE更高。Roaring Bitmap内部有一个 RoaringArray类型的属性highLowContainer, highLowContainer存储了Roaring。Bitmap的全部数据,它会将32位的整数拆分成高16位和低16位两部分来处理,RoaringArray有三个核心属性:
    short[] keys = null;
    Container[] values = null;
    int size = 0;
    

    一个32位的整数,高16位会作为key存在keys数组里,低16位被当做value存在values中的某个Container中,Keys和values通过下标对应,并且keys数组是有序的,方便二分查找; size记录了key-value对的数量。

Roaring Bitmaps 在 ES中的运用

成功的产品背后总有强大的算法作为支撑,ES作为一款优秀的搜索引擎其最重要的组成部分之一是能够有效地压缩以及快速解码有序整数列表。
众所周知,ES shard 底层是采用Lucene存储,shard内是定期合并的segment(段)。每个段中存着介于0到该段文档总数的标识符doc ID(最多231-1),标识符的概念可以类比于数组下标。段是顺序存储有关文档的数据,而doc ID是段内文档的索引。因此,段中的第一个文档的doc ID为0,第二个文档为1以此类推,直到最后一个文档,其doc ID为段内文档总数减一。存这些doc ID的目的是为了快速定位terms所在的文档列表(postings list)而这些由doc IDs组成的有序整数列表 可以通过算法来进行高效压缩。

  • 增量压缩(delta-encoding)

举例: postings list 为 [73, 300, 302, 332, 343, 372],增量列表为[73, 227, 2, 30, 11, 29],增量列表的值均在0-255之间意味着可以用1个byte来表示。在这里插入图片描述

以上的例子就是Lucene用来对磁盘上的倒排索引进行编码的技术,即postings list被分成256 doc ID 的block块,然后使用增量编码和位打包分别压缩每个块:Lucene计算最大位数将增量存储在块中,接着将此信息添加到头节点,最后使用此位数编码该块的所有增量。
这种模式同样也应用在es 查询场景:queries 和 filters也会返回排序后的document ID list :缓存过滤器(filter cache)。缓存过滤是一种加速常用过滤器查询的技术,可以将过滤器和段映射到与之匹配的doc IDs,但是和磁盘存储倒排索引的场景需求不一样的是对于压缩率要求不是非常严格但对于缓存过滤执行速度要求极高。为了满足这一点,es filter cache采用了roaring bitmaps技术,对排序后的整数list进行哈希,然后对第一个块将对0到65535之间的值进行编码,第二个块将在65536和131071之间进行编码,等等。然后在每个块中又分别对16个最低位进行编码:如果它的值小于4096,则数组将直接使用,否则使用位图。
在这里插入图片描述
使用4096作为阈值的原因是更省内存:
在这里插入图片描述

  • 总结
    ES对磁盘存储倒排索引使用增量压缩技术而对于查询场景在缓存中使用roaring bitmaps压缩,精准如手术刀般榨干每个bit的价值。

Bloomfilter在Redis 和 HBase中应用

在进行分布式爬取任务时候经常会用到Scrapy-Redis。其中为避免采集重复的url,scrapy_redis中的dupefilter实现了多个spider访问同一个Redis后如果获取到其他spider的Request 在同一个位置则进行去重。这里其实就是Bloomfilter在redis中去重的应用。Bloomfilter在内存中只需要占用极小的空间便可以给出is or not的判断。
HBase在进行Get操作时也是通过Bloomfilter技术过滤大量无效数据块以节省大量磁盘IO资源。

场景1: 营销场景连续登陆app5天送十元红包

原始做法:通过向数据库中插入[uid, timestamp]这两个字段并对时间戳索引,统计同一个uid 五天内的数据条数(T5day<Ts<Tnow),(Ts-T5day)/86400取整后判断是否1到5这五个整数都出现过。
简单优化做法:对每天做聚合因此只需要保存五条记录。缺点:需要删除五天前的数据。
使用bitmap保存64天:新增一个long状态类型代表8byte也就是64bit状态。如果当天该用户登陆则该bit位置位1,当天重读登陆则都是1。然后每次都计算:Ts-T5day)/86400取整得到N,然后这个bitmap左移N位。如果5天全部登陆则是0X1F。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值