Elasticsearch 之 基础介绍及索引原理分析 【转】

基本概念

Elasticsearch是面向文档型数据库,一条数据就是一个文档,用JSO作为文档序列化的格式,比如下面的用户数据。

  1. {
  2. "name" : "John",
  3. "sex" : "Male",
  4. "age" : 25,
  5. "birthDate": "1990/05/01",
  6. "about" : "I love to go rock climbing",
  7. "interests": [ "sports", "music" ]
  8. }

在MySql中数据存储想到建立一个Use表,key代表为数据库中的字段,在Elasticsearch中这里就表示一个文档,当然这个文档会属于一个User类型,各式各样的类型存储于一个索引中。一个简易的Elasticsearch和Mysql的对照表。

  1. 关系数据库=》数据库=》表=》行=》列
  2.  
  3. Elasticsearch=》索引=》类型=》文档=》字段。
     

Elasticsearch是如何做到快速查询的

        Elasticsearch使用的倒排索引比MySql的B+Tree的索引更快,为什么?   

         关系型数据库的B+Tree索引,二叉树的查询效率是LogN,插入新节点不用移动全部节点,兼顾插入和查询性能。具体的查询性能优化后续用单独的篇章来描述。

         倒排索引:是通过字段中的内容来进行来建立倒排索引,假如对下面的数据进行索引

  1. | ID | Name | Age | Sex |
  2. | -- |:------------:| -----:| -----:|
  3. | 1 | Kate | 24 | Female
  4. | 2 | John | 24 | Male
  5. | 3 | Bill | 29 | Male

   对三个字段分别做倒排索引,结果看到为

  1. Name:
  2.  
  3. | Term | Posting List |
  4. | -- |:----:|
  5. | Kate | 1 |
  6. | John | 2 |
  7. | Bill | 3 |
  8. Age:
  9.  
  10. | Term | Posting List |
  11. | -- |:----:|
  12. | 24 | [1,2] |
  13. | 29 | 3 |
  14. Sex:
  15.  
  16. | Term | Posting List |
  17. | -- |:----:|
  18. | Female | 1 |
  19. | Male | [2,3] |

  Posting List 就是一个Int的数组,存储了所有符合term的文档ID。当随着数据量的上涨,Term的数据量也会上涨,如何来提高查询效率呢?

  Term Dictionary与Term Index  Elasticsearch采用B数相同的思路,直接通过内存查找Term,但是如果Term太多,Term Dictionary也会很大,放在内存不现实,所以有了Term Index,类似字段的索引页。Term Index存储的是term的前缀,然后结合FST(Finite sTATE Transducers)压缩技术,可以是term Index缓存到内存中,然后通过term index查询到term Dictionar的位置,然后再去磁盘上找Postion List。FST的原理请参照:FST原理 

假设我们现在要将mop, moth, pop, star, stop and top(term index里的term前缀)映射到序号:0,1,2,3,4,5(term dictionary的block位置)。最简单的做法就是定义个Map<string, integer="">,大家找到自己的位置对应入座就好了,但从内存占用少的角度想想,有没有更优的办法呢?答案就是:FST(理论依据在此,但我相信99%的人不会认真看完的)

Alt text

⭕️表示一种状态

-->表示状态的变化过程,上面的字母/数字表示状态变化和权重

将单词分成单个字母通过⭕️和-->表示出来,0权重不显示。如果⭕️后面出现分支,就标记权重,最后整条路径上的权重加起来就是这个单词对应的序号。

这里我还没有完全搞懂,如何在创建这个FST的时候,标记权重。

压缩的技巧

Elasticsearch里除了上面说到用FST压缩term index外,对posting list也有压缩技巧。 如果索引列的的字段内容区分度很小,就会造成posting list的数组非常大,比如性别只有男、女。如果有上千万个学生,那Postint list 至少有5百万左右的文档id。Elasticsearch采用Frame Of Reference(增量编码压缩,将大数变小数,按字节存储),首先要求Posting list是有序的,方便进行压缩。例如delta encoding,他是将顺序的数值进行压缩,只保存一个增量数字,1000,1001,1002,1003保存成 1000,1,2,3 压缩数值大小,结合变长压缩等讲小数字占用位数降下来,达到减小占用空间的效果,详细的解释清看这里:Elasticsearch 的性能优化

  • 第一步: 增量编码就是从第二个数开始每个数存储与前一个id的差值,即300-73=227302-300=2,...,一直到最后一个数;
  • 第二步: 就是将这些差值放到不同的区块,Lucene使用256个区块,下面示例为了方便展示使用了3个区块,即每3个数一组;
  • 第三步: 位压缩,计算每组3个数中最大的那个数需要占用bit位数,比如30、11、29中最大数30最小需要5个bit位存储,这样11、29也用5个bit位存储,这样才占用15个bit,不到2个字节,压缩效果很好。

Alt text

  Frame Of Reference 压缩算法对于倒排表来说效果很好,但对于需要存储在内存中的Filter缓存等不太合适,两者之间有很多不同之处:倒排表存储在磁盘,针对每个词都需要进行编码,而Filter等内存缓存只会存储那些经常使用的数据,而且针对Filter数据的缓存就是为了加速处理效率,对压缩算法要求更高;这就产生了下面针对内存缓存数据可以进行高效压缩解压和逻辑运算的roaring bitmaps算法。   

Roaring bitmaps,先从bitmap说起。Bitmap是一种数据结构,假设有某个posting list:

[3,1,4,7,8]

对应的Bitmap就是:

[0,1,0,1,1,0,0,1,1]

非常直观,用0/1表示某个值是否存在,比如8这个值就对应第8位,对应的bit值是1,这样用一个字节就可以代表8个文档id(1B = 8bit),旧版本(5.0之前)的Lucene就是用这样的方式来压缩的。但这样的压缩方式仍然不够高效,Bitmap自身就有压缩的特点,其用一个byte就可以代表8个文档,所以100万个文档只需要12.5万个byte。但是考虑到文档可能有数十亿之多,在内存里保存Bitmap仍然是很奢侈的事情。而且对于个每一个filter都要消耗一个Bitmap,比如age=18缓存起来的话是一个Bitmap,18<=age<25是另外一个filter缓存起来也要一个Bitmap。

Bitmap的缺点是存储空间随着文档个数线性增长,所以秘诀就在于需要有一个数据结构打破这个魔咒,那么就一定要用到某些指数特性:

  • 可以很压缩地保存上亿个bit代表对应的文档是否匹配filter;
  • 这个压缩的Bitmap仍然可以很快地进行AND和 OR的逻辑操作。

Lucene使用的这个数据结构叫做 Roaring Bitmap

其压缩的思路其实很简单。与其保存100个0,占用100个bit。还不如保存0一次,然后声明这个0重复了100遍。

这两种合并使用索引的方式都有其用途。Elasticsearch 对其性能有详细的对比,可阅读 Frame of Reference and Roaring Bitmaps

 

联合索引:

上面说了半天都是单字段索引,如果多个字段联合查询,如何快速查询,有下面两种方案。

1)利用跳表(skip list)的数据结构快速做与预算

2)利用上面提到的bitset按位与。

Alt text

   如果使用跳表方案,对最短的posting list的每个id在另外的两个多的posting list中查找是否存在,最后得到交集。

如果使用bitset就更加直观了,直接按位与,得到的结果就是交集。

 

最后总结与思考:

     1、Elasticsearch的索引思路:尽量讲磁盘中的东西搬进内存,减少磁盘IO,结合各种压缩技巧,用苛刻的态度使用内存。

      2、使用Elasticsearch建立索引的注意事情:1)不需要索引的字段,一定要明确定义,因为默认是建立索引的 2)对于String字段,不需要analysis的需要明确定义,因为默认是analysis 3)选择有规律的ID很重要,随机性太大的ID不利于查询。

      3、(存有疑惑待研究)可能是最影响查询性能的,应该是最后通过Posting list里的ID到磁盘中查找Document信息的那步,因为Elasticsearch是分Segment存储的,根据ID这个大范围的Term定位到Segment的效率直接影响了最后查询的性能,如果ID是有规律的,可以快速跳过不包含该ID的Segment,从而减少不必要的磁盘读次数,具体可以参考这篇如何选择一个高效的全局ID方案(评论也很精彩)

 

文章参考转载:

1)https://www.520mwx.com/view/44635  Elasticsearch 的性能优化

2)https://www.cnblogs.com/dreamroute/p/8484457.html  Elasticsearch-基础介绍及索引原理分析

3)https://blog.csdn.net/qq_31828515/article/details/56853478  数据结构位图

4)https://blog.csdn.net/yizishou/article/details/78342499 RoaringBitmap数据结构及原理

posted @ 2020-11-17 17:36  桑中子衿  阅读(142)  评论(0编辑  收藏  举报