你将花费10分钟读完本篇文章,读完本文你将:
- 熟悉索引再Elasticsearch的作用
- 熟悉正排索引和倒排索引的原理区别
- 掌握索引的压缩算法原理
引言
这是一个安静的晚上,我在峡谷里打团,突然微信弹出一串消息,我一看,原来是女神又找我聊天。
女神:在么?
我:在在在,怎么了?男朋友又惹你生气了吗?
女神:没呢,我现在是两耳不闻窗外事,一心只学Elasticsearch😊
我:说吧,这次又是什么问题?
女神:听了上次学长的介绍,还是没有搞懂为什么Elasticsearch的搜索会更快呢?比如我在一篇文章里面进行查找,那我还是需要对文章全文检索啊?
我:很棒的问题,已经会
全文检索
这个词了。是的Elasticsearch或者说任何一个查找在最坏的情况下都需要对文章进行全部的检索,Elasticsearch快速的原因之一,是他采用了倒排索引的数据结构。
正排索引
我:首先,我想跟你强调一下索引的概念。索引相当于图书的目录,可以在目录中找到对应的页码,快速定位到对应的数据,这时候有两种思考的方向。
女神:我知道,我知道,肯定有一种,就像我说的这种查找方式,把文章都摆在那,然后一个一个去匹配。
我:是的。你的这种查找的方式符合
正排索引forward index
的概念。一般我们会给文章一个标识,再对文章进行分析,比如文章有哪些词语啊,这些词语出现了多少次,这篇文章在什么文件中的位置,这样更方便我们去查找。
女神:这样啊,那我们怎么去查找一篇包含某个词语的文章呢?
我:就像这样的结构,当我们需要查找某个信息的时候,我们可以去文档的列表里面遍历查找。首先需要找到文档(1),之后对文档下存储的词语进行遍历匹配,提取出需要的信息,再到下一个文档,重复查找操作。
女神:这不是傻子查找嘛,果然是我想到的方式😭
我:正排索引虽然在查找检索上很傻,但是它在别的地方表现却很好哦,这个地方我先卖个关子,我跟你说一下第二种思路吧。
倒排索引
举个栗子
假设八佾有个朋友,但这个朋友,不是八佾本人。在农历新年给领导写信,但是有两个领导是八佾的朋友很想骂sillybee的,于是他大胆的在信中这么写。
doc1:"dear leader ,you sillybee"
doc2:"dear leader ,i have to thank a happy silybe new year "
doc3:"dear leader ,i have to say happy new year"
三封已经封好的信, 八佾的朋友是个聪明的IT从业工作者(但他远没你想的那么聪明),担心自己分不清三封信,于是 他记录下了这么一张表。
我想帮他向你们解释一下这张表,第一列是出现的单词,第二列是单词出现的文档位置。例如,dear这个单词出现在文档1,文档2和文档3。sillybee这个单词出现在文档1中,这样就能快速找到文档出现的位置了。
下个定义
我:所以倒排索引的数据结构相比于正排索引就变成了这个样子。从词语去查找文档出现的位置,这样是不是更加快捷,就像八佾的朋友一样,立即就能找到关键词的文档在什么位置。
女神:你的这个朋友可真厉害!果然技术改变命运。后来呢,他又出任CEO,当上总经理吗?
我:他可没有你想的那么厉害。在贤者时间的时候冷静下来,毕竟还要为五斗米折腰,骂人的信不能寄出去,想要剔除出来,于是自信的通过上面的表,找到了sillybee这个词项,找到了对应的记录,第一 封信中有,于是快速的找到了骂人的信,后来他兴高采烈的把信寄了出去。
我:但是粗心的他把sillybe,写成了silybee,但是领导可不会get不到这个单词是骂人的意思。看来朋友并没有用好这 个倒排索引表。
女神:啊~那他好惨,要是他能进行一些词语的处理,如果查找sillybe的时候也查下silybee或者sillyb,就能发现了。
我:是的,所以Elasticsearch想到了这里,提供了丰富的文档处理。比Analyzer分词器,会对文本中的词按一定规则进行切分。因为不同的语言分词的规则不一样,比如中文和英文就有着不同的切分规则,还有一些模糊匹配,停用词等的,这个后面有机会我们再详细的探究。
正排索引和倒排索引的应用场景
女神:既然倒排索引的检索性能这么高效,为什么还需要正排索引呢?
我:你还真是抓住这个问题不放呢?你试想一下这样一个场景,你现在想去博客里面搜一篇
Elasticsearch Index Module
的文章,那么你想在你的页面上出现的博客排序是什么样的呢?
女神:当然是相关度越高越好啦,要是我找我就给他们每个文档打个分。
我:是的呢,Elasticsearch也是这么做的,只是对于相关度的定义的评分有着复杂的公式,从词频,逆向文档频率等各个维度来判断,这时候的倒排索引就很难胜任这样的工作
女神:对对对,因为倒排索引是根据关键词去找文档,然后再对文档分析,这时候文档本身的一些排序因素确实好像不好存,那Elasticsearch是怎么做的呢?
我:Elasticsearch在整个搜索排序的过程中:①倒排索引利用检索的高性能找到关键词能匹配的结果集。②之后由正排索引完成排序、过滤和聚合的功能。
女神:还是Elasticsearch厉害!!!👍🏻
我:说到这里,你了解了倒排索引,就知道为什么这种查询方式会更快了吧,但是你有没有发现这是一种很大的浪费?
女神:啊,对对对,我刚刚就想问,如果我们对文档的关键字都提取出来,记录文档的位置,记录出现的频次,记录各种信息,那我是不是要更大的空间来存储。
我:对的,一些必要的信息,比如说频次,位置啊是必须要存储的,但是我们知道,一个关键词后面有着很多个doc列表,存储着DOC id,这样就有大量的docid重复,这里有一些编码压缩的方法。
倒排索引的压缩算法
FOR
FOR的全称是Frame Of Reference,主要思想是先对DOC ID列表进行排序,之后使用差值的方式进一步压缩,这样需要表示的数字更小,占的位数少,需要空间就更少。这么说可能太抽象了。
我:第①步:先对doc id的列表进行升序排列,众所周知一个整数在计算机中用4个字节表示,因此6个数占得字节是24个。
我:第②步:对doc id进行向前求差得出delta list,将大数化小
我:第③步:对delta list进行分块,使得更小的数占更小的字节,用一个字节来表示,该块中的每个数占多大的位数,如图左边的绿色表示每个数都是7位,也就是7位一个数。右侧绿色表示的是每个数是5位,这样在反编码的时候,我们可以再恢复到原先的doc id。
女神:这设计的也太巧妙了吧?那Elasticsearch采用的就是这种存储方式么?
我:其实这种方式总结来说就是:
拉链归并
只是这种编码方式还是有些弊端的。比如说对数据的处理上来说,编码越复杂,追求更高的压缩率,那么对于查询来说效率就会更加低下
RBM
由于拉链归并
的思想,Elasticsearch对一些常用的过滤器进行了缓存,用来加速一些过滤器的执行,因此带来了几个问题:
- 因为不能缓存所有的过滤器,所以压缩比例对于倒排索引的编码匹配来说不那么重要
- 我们需要加速一些重复执行的过滤器,因此一个好的数据结构来说很重要
- 过滤器存储在内存中,而正排表基本上是在磁盘上
面对此种情况,Elasticsearch列举了一些对应的方案,进行了均衡。
第一种方式:整型数组
最直接的选择就是数组,因为数据的遍历性,可以极大的提速。然而压缩性却很难保证。如果你需要存储100Mb的文档,那么你就至少需要400Mb的空间。
第二种方式:bitmap
位图是一个数组,其中每个条目只占用一位,因此它们只有两个可能的值:0 或 1。
为了知道 docID 是否包含在位图中,需要读取索引 docID 处的值。 0 表示该集合不包含此 docID,而 1 表示该集合包含此 docID。迭代需要计算连续的零,这实际上非常快,因为 CPU 有专门的指令。如果我们与整型 相比,密集过滤器的内存使用率要好得多,因为我们现在只需要 100M 位 = 12.5MB。
但是这种情况下会有稀疏集的问题:虽然我们的第一个选项每次匹配需要 4 个字节,但现在无论有多少匹配,我们都需要 12.5MB 的内存。
第三种方式: roaring bitmaps
RBM是为了兼顾数组的遍历性和位图的低存储空间。具体的过程是这样的:
我:首先根据docId的高16位进行分块,每个块最多包含65535个,例如第一个块存储的是docId(0-65535),第二个块表示的docId(65536-131071)。这样用来解决bitmap的稀疏集的问题,使得每个块里的docId更加的
稠密
。这样我们就可以使用 bitmap的特性,但是此时数组的特性如何使用呢?
我:第二步,对docid进行分配,使得数据稠密,范围在集中的块里
我:第三步,上面部分解决了数据稀疏性的问题,但是如何用上数组的易于遍历呢?此时Elasticsearch做了一个实验。
我:根据图中可以看到当4096之后的数组内存使用是高于bitmap的,所以如果docId的个数小于4096个的时候就使用数组,如果高于4096个的时候就使用bitma,这样做出了均衡。
女神:哇,简简单单的压缩两个字,里面竟然包含了这么多的知识。了解了这么多,我还是不知道Elasticsearch怎么使用?你应该还不困吧?
我:哈哈哈,你可真是个机灵鬼。明天你把电脑带过来,我们实操一次,把你的电脑打造成Elasticsearch的学习机😎😎😎
女神:嘻嘻嘻😁
八佾Talk
冉求曰:”非不说子之道,力不足也。“
子曰:”力不足者,中道而废。今女画。“