聊聊ElasticSearch的倒排索引

[toc]

为什么需要倒排索引

倒排索引也是索引。索引初衷都是为了快速检索到你要的数据。

每种数据库有自己需要解决的问题(或者说擅长的领域),对应的就有自己的数据结构,而不同的使用场景和数据结构,需要用不同的索引,才能起到最大化加快查询的目的

对MySQL来说,是B+树,对于ES/Lucene来说是倒排索引。

  • ES是建立在全文搜索引擎库Lucene基础上的搜索引擎,他隐藏了Lucene的复杂性,取而代之的提供一套简单一致的RESTful API,不过掩盖不了他底层也是Lucene的事实,Es的倒排索引,其实就是Lucene的倒排索引。

为什么叫倒排索引

在没有搜索引擎时,我们是直接输入网址,然后获取网站内容,这时我们的行文是:document->to->words,通过文章,获取里面的单词,此为倒排索引,forward index。

后来,我们希望能够输入一个单词,找到含有这个单词,或者和单词有关系的文章:word->to->documennts

于是我们把这种索引,称为inverted index,直译过来,应该叫反向索引,国内翻译成倒排索引

倒排索引的内部结构

首先,在数据生成的时候,比如爬虫爬到一篇文章,这里我们需要对这篇文章进行分析,将文本拆解成一个个单词。

这个过程很复杂,比如“生成还是死亡”,要如何让分词器自动将他分解为“生存”,“还是”,“死亡”三个词语,然后把“还是”,这个无意义的词语干掉。接着,把这两个词语已经它对应的文档id存下来:

word document Id
生存 1
死亡 1

接着爬虫继续,又爬到一个含有“生存”的文档,于是索引变成:

word document Id
生存 1,2
死亡 1

下次搜索“生存”,就会返回文档ID是1、2的两份文档。

上面这套索引的实现,只是最简单,想想看,这个世界上那么多单词,,每次搜索一个单词,都要全局遍历一遍,很明显不行。于是有了排序,我们需要对单词进行排序,像B+树一样,可以在页面实现二分查找。光排序还不行,单词都放在磁盘呢,磁盘IO非常慢,所以MySQL特意把索引缓存到了内存,但是ES不行,因为单词太多,所以是下面的结构:

image

ES的倒排索引,增加了最左边的一层字典树term index,他不存存储所有单词,只存储单词的前缀,通过字典树找到单词所在的块,也就是单词大概的位置,再在块里进行二分查找,找到对应的单词,在找到对应的文档列表。

当然内存,能省则省,所以ES还用了FST(Finite State Transducers)对他进一步压缩。

这里重点说一下最右边的Postinng List,别看只是存一个文档ID数组,但是他的设计,遇到的问题可不少。

Frame Of Reference

原生的Posting List有两个痛点:

  • 如何压缩以节省磁盘你内存
  • 如何快速求交并集(intersections and unionns)

压缩数据

我们简化一个ES要面对的问题,假设有这样的一个数组:[73, 300, 302, 332, 343, 372],如何把他进行压缩?

Es里,数据是按Segment存储的,每个Segment最多存65536个文档ID,所以文档ID的范围,从0到2^32 - 1,所以如果不进行任何处理,那么每个元素都会占用2 bytes,对应上面的数组就是6*2=12 bytes。

怎么压缩?

压缩,就是尽可能降低每个数据占用的空间,同时又能让信息不失真,能够还原回来

Step 1:Delta-encode 增量编码

我们只需要记录元素与元素之间的增量,于是数组变成了[73,227,2,30,11,29]

Step 2:Split into blockes 分隔成块

ES里面每个块是256个文档ID,这样可以保证每个块,增量编码后,每个元素都不会超过256(1 byte)。

为了方便演示,我们假设每个块是3个文档ID:
[73,227,2],[30,11,29]

Step 3:Bit packing 按需分配空间

对于第一个块,[73,227,2],最大元素是227,需要8 bits,那么给这个块每个元素,都分配8 bits的空间。
但是对于第二个块,[30,11,29],最大的元素才30,只需要5 bits,那么给每个元素只分配5 bits的空间足够。

这一步,可以说是把吝啬发挥到极致。

以上三个步骤,共同组成了一项编码技术,Frame Of Reference(FOR):

image

Roaring bitmaps

现在来说Posting List的第二个痛点,如何快速求交并集(inteersections and unions)

在ES中查询,通常不只有一个查询条件,比如想搜索:

  • 含有“生存”相关词语的文档
  • 文档发布时间在最近一个月
  • 文档发布者是平台的特约作者
    这样就需要根据三个字段,去三个倒排索引里去查,当然磁盘里的数据,上一节提到了,用了FOR进行压缩,所以我们要吧数据进行反向处理,即解压,才能还原成原始文档ID,然后把这三个文档ID数组在内存中做一个交集。

注:即使没有多条件查询,ES也需要频繁求并集,因为ES是分片存储的

我么把ES遇到的问题,简化成一道算法题。

加入有下面三个数组:
[64,300,303,343]

[73,300,302,303,343,372]

[303,311,333,343]
求他们的交集

Integer 数组

直接用原始文档ID,可能会说,那就逐个遍历,遍历完就知道交集是什么了。

其实对于有序数组,用跳表(skip table)可以更搞笑,这里不展开了,因为不管是从性能,还是空间上考虑,Integer数组都不靠谱,假设有100M个文档ID,每个文档ID占2 bytes,那已经是200MB,而这些数据要放到内存中进行处理的,把这么大量的数据,从磁盘解压后丢到内存,内存肯定撑不住。

Bitmap

假设有这样的一个数组:

[3,6,7,10]

我们可以这样来表示:
[0,0,0,1,0,0,1,1,0,0,1]

我们用0表示角标对应的数组不存在,1表示存在
这样两个好处:

  • 节省空间:我们只需要0和1,那每个文档ID就只需要1 bit,还是假设有100M个文档,那只需要100M bits=100M * 1/8 bytes=12.5M,比之前用Integer数组的200MB,优秀太多
  • 运算更快:0和1,天然就适合进行为运算,求交集,&一下,求并集,|一下,一切都回归到计算机的起点。

Roaring Bitmaps

Bitmap有个硬伤,就是不管有多少个文档,占用的空间都是一样的,之前说过,Posting List的每个Segement最多放65536个文档ID,举一个极端的例子,有一个数组,里面只有两个文档ID:[0,65535],用Bitmap,要怎么表示[1,0,0,0...(超多个0)...,0,0,1],你需要65536个bit,也就是65536/8=8192 bytes,而用Integer数组,只需要2*2 bytes=4 bytes,可见文档数量不多的时候,使用Integer数组更加节省内存。

算一下临界值,很简单文档数量多少,bitmap都需要8192 bytes,而Integer数组则和文档数量成线性相关,每个文档ID占2 bytes,所以:8192/2=4096,当文档数量少于4096时,用Integer数组,否则,用bitmap

image

注:补充一下Roaring bitmaps和之前讲的Frame Of Referrence的关系。Frame Of Referrence是压缩数据,减少磁盘占用空间,所以我们从磁盘取数据时,也需要一个反向过程,即解压,解压后才有我们上面看到的这样子的文档ID数组:[73,300,302,303,343,372],接着我们需要对数据进行处理,求交集或者并集,这时候数据是需要放到内存进行处理的,我们三个这样的数组,这些数组可能很大,而内存空间比磁盘还宝贵,于是需要更强有力的压缩算法,同时还要有利于快速的求交并集,于是有了Roaring bitmaps处理后,缓存到内存中ES称之为filter cache。

总结

每一种数据库都有自己要解决的问题(或者说擅长的领域),对应的就有自己的数据结构,而不同的使用场景和数据结构,需要用不同的索引,才能起到最大化加快查询目的。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343