Elasticsearch的索引,单field索引和多field的联合索引

  作为一个分布式可扩展的实时搜索和分析引擎,一个建立在全文搜索引擎 Apache Lucene基础上的搜索引擎。Elasticsearch 可以用于:分布式实时文件存储,并将每一个字段都编入索引,使其可以被搜索;实时分析的分布式搜索引擎;可以扩展到上百台服务器,处理PB级别的结构化或非结构化数据。
Elasticsearch的文件存储
  Elasticsearch是面向文档型数据库,一条数据在这里就是一个文档,用JSON作为文档序列化的格式,比如下面这条用户数据:

{
    "name" :     "XiaoMing",
    "sex" :      "Male",
    "age" :      18,
    "birthDate": "2000/05/01",
    "about" :    "I love to go rock climbing",
    "interests": [ "sports", "music" ]
}

在Mysql数据库存储会建立一张User表,有姓名、性别等字段,在Elasticsearch里就是一个文档,当然这个文档会属于一个User的类型,各种各样的类型存在于一个索引当中。以下是将Elasticsearch和关系型数据术语对照表:

关系数据库     ⇒ 数据库 ⇒ 表    ⇒ 行    ⇒ (Columns)
Elasticsearch  ⇒ 索引(Index)类型(type)文档(Docments)字段(Fields)  

一个 Elasticsearch 集群可以包含多个索引(数据库),即其中包含了很多类型(表)。这些类型中包含了很多的文档(行),然后每个文档中又包含了很多的字段(列)。Elasticsearch的交互,可以使用Java API,也可以直接使用HTTP的Restful API方式,比如插入一条记录,可以简单发送一个HTTP的请求:

PUT /megacorp/employee/1  
{
    "name" :     "XiaoMing",
    "sex" :      "Male",
    "age" :      18,
    "birthDate": "2000/05/01",
    "about" :    "I love to go rock climbing",
    "interests": [ "sports", "music" ]
}

索引
  Elasticsearch提供强大的索引能力了。往Elasticsearch里插入一条记录,就是直接PUT一个json的对象,这个对象有多个fields,比如上面例子中的name, sex, age, about, interests,那么在插入这些数据到Elasticsearch的同时,Elasticsearch还为这些字段建立索引–倒排索引,因为Elasticsearch最核心功能是搜索。

Elasticsearch如何做到快速索引的?
  Elasticsearch使用的倒排索引比关系型数据库的B-Tree索引快。
  B-tree为了提高查询的效率,减少磁盘寻道次数,将多个值作为一个数组通过连续区间存放,一次寻道读取多个数据,同时也降低树的高度。对于倒排索引:
在这里插入图片描述
假设有以下数据:

ID Name Age Sex
1 Kate 24 Female
2 John 24 Male
3 Bill 29 Male

ID是Elasticsearch自建的文档id,那么Elasticsearch建立的索引如下:
Name:

Term Posting List
Kate 1
John 2
Bill 3

Age:

Term Posting List
24 [1,2]
29 3

Sex:

Term Posting List
Female 1
Male [2,3]

Elasticsearch分别为每个field都建立了一个倒排索引,Kate, John, 24, Female这些叫term,而[1,2]就是Posting List。Posting list就是一个int的数组,存储了所有符合某个term的文档id。
  通过posting list这种索引方式似乎可以很快进行查找,比如要找age=24的同学,即:id为1,2的同学。但如果有上千万的记录呢?如果想通过name来查找呢?

Term Dictionary
  Elasticsearch为了能快速找到某个term,将所有的term排个序,二分法查找term,时间复杂度logN,即Term Dictionary。这似乎和传统数据库通过B-Tree的方式类似,为何比B-Tree的查询快?

Term Index
  B-Tree通过减少磁盘寻道次数来提高查询性能,Elasticsearch也采用同样的思路,直接通过内存查找term,不读磁盘,但是如果term太多,term dictionary也会很大,放内存不现实,于是有了Term Index,就像字典里的索引页一样,A开头的有哪些term,分别在哪页,可以理解term index是一颗树。这棵树不会包含所有的term,它包含的是term的一些前缀。通过term index可以快速地定位到term dictionary的某个offset,然后从这个位置再往后顺序查找。
Elasticsearch的索引,单field索引和多field的联合索引_第1张图片
所以term index不需要存下所有的term,而仅仅是一些前缀与Term Dictionary的block之间的映射关系,再结合FST(Finite State Transducers)的压缩技术,可以使term index缓存到内存中。从term index查到对应的term dictionary的block位置之后,再去磁盘上找term,大大减少了磁盘随机读的次数。FSTs are finite-state machines that map a term (byte sequence) to an arbitrary output. FST以字节的方式存储所有的term,这种压缩方式可以有效的缩减存储空间,使得term index足以放进内存,但这种方式也会导致查找时需要更多的CPU资源。

压缩技巧
  Elasticsearch里除了用FST压缩term index外,对posting list也有压缩技巧。尽管posting list只存储文档id,如果Elasticsearch需要对同学的性别进行索引,如果有上千万个同学,而世界上只有男/女这样两个性别,每个posting list都会有至少百万个文档id。 Elasticsearch有效的对文档id进行压缩: Frame Of Reference 增量编码压缩,将大数变小数,按字节存储。首先,Elasticsearch要求posting list是有序的,方便压缩,如下图:
Elasticsearch的索引,单field索引和多field的联合索引_第2张图片
通过增量,将原来的大数变成小数仅存储增量值,再按bit排序,最后通过字节存储。

Roaring bitmaps
  Bitmap是一种数据结构,假设有某个posting list:[1,3,4,7,10]。对应的Bitmap就是:[1,0,1,1,0,0,1,0,0,1]。用0/1表示某个值是否存在,比如10这个值就对应第10位,对应的bit值是1,这样用一个字节就可以代表8个文档id,但这样的压缩方式仍然不够高效,如果有1亿个文档,就需要12.5MB的存储空间,这仅仅是对应一个索引字段(实际往往会有很多个索引字段)。于是采用Roaring bitmaps这样更高效的数据结构。
  Bitmap的缺点是存储空间随着文档个数线性增长,Roaring bitmaps将posting list按照65535为界限分块,比如第一块所包含的文档id范围在0-65535之间,第二块的id范围是65536-131071,以此类推。再用<商,余数>的组合表示每一组id,这样每组里的id范围都在0~65535内了。

Elasticsearch的索引,单field索引和多field的联合索引_第3张图片
除了1024外,65535也是一个经典值,因为65535=2^16-1,正好是用2个字节能表示的最大数,一个short的存储单位,注意到上图里的最后一行“If a block has more than 4096 values, encode as a bit set, and otherwise as a simple array using 2 bytes per value”,如果是大块,用节省点用bitset存,小块就豪爽点,2个字节也不计较了,用一个short[]存着方便。
  为何用4096来区分大块还是小块?由于二进制,4096*2bytes = 8192bytes < 1KB,磁盘一次寻道可以顺序把一个小块的内容都读出来,再大一位就超过1KB了,需要两次读。

联合索引
  相对于单field索引,如果多个field索引的联合查询,倒排索引满足快速查询的要求利用以下两点:

  • 利用跳表(Skip list)的数据结构快速做“与”运算
  • 利用上面提到的bitset按位“与”

跳表的数据结构如图所示:
Elasticsearch的索引,单field索引和多field的联合索引_第4张图片
将一个有序链表level0,挑出其中几个元素到level1及level2,每个level越往上,选出来的指针元素越少,查找时依次从高level往低查找,比如55,先找到level2的31,再找到level1的47,最后找到55,一共3次查找,查找效率和2叉树的效率相当,但也是用了一定的空间冗余来换取的。
假设有下面三个posting list需要联合索引:

Elasticsearch的索引,单field索引和多field的联合索引_第5张图片
如果使用跳表,对最短的posting list中的每个id,逐个在另外两个posting list中查找看是否存在,最后得到交集的结果。如果使用bitset,就很直观了,直接按位与,得到的结果就是最后的交集。

Elasticsearch的索引

  将磁盘里的东西尽量搬进内存,减少磁盘随机读取次数(同时也利用磁盘顺序读特性),结合压缩算法,严格使用内存。因此,对于使用Elasticsearch进行索引时需要注意:

  • 不需要索引的字段,一定要明确定义出来,因为默认是自动建索引的
  • 对于String类型的字段,不需要analysis的也需要明确定义出来,因为默认也是会analysis的
  • 选择有规律的ID很重要,随机性太大的ID(比如java的UUID)不利于查询

关于最后一点,有多个因素:

  1. 压缩算法,都是对Posting list里的大量ID进行压缩的,那如果ID是顺序的,或者是有公共前缀等具有一定规律性的ID,压缩比会比较高
  2. 另外一个因素:可能是最影响查询性能的,应该是最后通过Posting list里的ID到磁盘中查找Document信息的那步,因为Elasticsearch是分Segment存储的,根据ID这个大范围的Term定位到Segment的效率直接影响了最后查询的性能,如果ID是有规律的,可以快速跳过不包含该ID的Segment,从而减少不必要的磁盘读次数。

你可能感兴趣的:(Elastic,Search)