Bootstrap

Elasticsearch 原理解析(介绍)

介绍

Elasticsearch 是一个分布式可扩展的实时搜索和分析引擎,一个建立在全文搜索引擎 Apache Lucene(TM) 基础上的搜索引擎.当然 Elasticsearch 并不仅仅是 Lucene 那么简单,它不仅包括了全文搜索功能,还可以进行以下工作:

分布式实时文件存储,并将每一个字段都编入索引,使其可以被搜索。

实时分析的分布式搜索引擎。

可以扩展到上百台服务器,处理PB级别的结构化或非结构化数据。

基本概念

对应关系

一个文档的数据,通常是json格式

{
    "name" :     "John",
    "sex" :      "Male",
    "age" :      25,
    "birthDate": "1990/05/01",
    "about" :    "Hello world",
    "interests": [ "sports", "game" ]
}

名词解析

  • 集群(cluster):由一个或多个节点组成, 并通过集群名称与其他集群进行区分

  • 节点(node):单个ElasticSearch实例

  • 索引(index):在ES中, 索引是一组文档的集合

  • 分片(shard):因为ES是个分布式的搜索引擎, 所以索引通常都会分解成不同部分, 而这些分布在不同节点的数据就是分片. ES自动管理和组织分片, 并在必要的时候对分片数据进行再平衡分配, 所以用户基本上不用担心分片的处理细节,一个分片默认最大文档数量是20亿.

  • 副本(replica):ES默认为一个索引创建5个主分片, 并分别为其创建一个副本分片. 也就是说每个索引都由5个主分片成本, 而每个主分片都相应的有一个copy.

分片及副本的分配是高可用及快速搜索响应的设计核心。主分片与副本都能处理查询请求, 它们的唯一区别在于只有主分片才能处理索引请求。

架构

集群

节点架构图:

分片

每一个 shard 就是一个 Lucene Index,包含多个 segment 文件,和一个 commit point 文件。

在es配置好索引后,集群运行中是无法调整分片配置的。如果要调整分片数量,只能新建索引对数据进重新索引(reindex),该操作很耗时,但是不用停机。

分片时主要考虑数据集的增长趋势,不要做过度分片。

每个分片都有额外成本:

  • 每个分片本质上就是一个Lucene索引, 因此会消耗相应的文件句柄, 内存和CPU资源

  • 每个搜索请求会调度到索引的每个分片中. 如果分片分散在不同的节点倒是问题不太. 但当分片开始竞争相同的硬件资源时, 性能便会逐步下降

  • 每个搜索请求会遍历这个索引下的所有分片

  • ES使用词频统计来计算相关性. 当然这些统计也会分配到各个分片上. 如果在大量分片上只维护了很少的数据, 则将导致最终的文档相关性较差

es推荐的最大JVM堆空间时30~32G,所以如果分片最大容量限制为30G,假如数据量达到200GB,那么最多分配7个分片就足够了。过早的优化是万恶之源,过早的分片也是。

 

 

流程

写入数据

其中步骤3中primary直接落盘IO效率低,所以参考操作系统的异步落盘机制:

 

 

读取数据

搜索过程

 

 

 

 

索引

一切设计都是为了提高搜索的性能

 

为了提高搜索的性能,难免会牺牲某些其他方面,比如插入/更新,否则其他数据库不用混了。前面看到往Elasticsearch里插入一条记录,其实就是直接PUT一个json的对象,这个对象有多个fields,比如上面例子中的name, sex, age, about, interests,那么在插入这些数据到Elasticsearch的同时,Elasticsearch还默默的为这些字段建立索引--倒排索引,因为Elasticsearch最核心功能是搜索。

倒排索引

假如数据如下

 

那么建立的索引如下

Posting List

es分别为每个field都建立了一个倒排索引,Kate, John, 24, Female这些叫term,而[1,2]就是Posting List。Posting list就是一个int的数组,存储了所有符合某个term的文档id。

 

数据有两种情况,一种是term特别多,一种是posting list特别多。es对此分别进行优化,优化方式是添加索引(提升时间效率),压缩(提升空间效率)

Term dictionary

Elasticsearch为了能快速找到某个term,将所有的term排个序,二分法查找term,logN的查找效率,就像通过字典查找一样。

 

Term Index

B-Tree通过减少磁盘寻道次数来提高查询性能,Elasticsearch也是采用同样的思路,直接通过内存查找term,不读磁盘,但是如果term太多,term dictionary会很大,放内存不现实,于是有了Term Index,就像字典里的索引页一样,A开头的有哪些term,分别在哪页,可以理解term index是一颗树:

这棵树不会包含所有的term,它包含的是term的一些前缀。通过term index可以快速地定位到term dictionary的某个offset,然后从这个位置再往后顺序查找。

 

 

Term Index不需要存所有term,只是一个前缀数,再结合FST压缩技术,存放到内存中。通过TermIndex定位到TermDictionary在磁盘上的block后,在顺序查找磁盘,降低随机读磁盘的次数

压缩技巧

Posting List压缩

比如以性别作为倒排索引,es数据有成千上万条,那么Posting List数据量也会特别大。需要进行压缩存储

5。0版本之前Lucene直接使用bitMap的形式进行压缩。假设某个posting list:

[1,3,4,7,10]

对应的bitMap是:

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

单这样压缩方式仍然不够高效,如果有1亿个文档,那么需要12.5MB的存储空间,这仅仅是对应一个索引字段(我们往往会有很多个索引字段)。于是有人想出了Roaring bitmaps这样更高效的数据结构。

Roaring bitmap(RBM)

Bitmap的缺点是存储空间随着文档个数线性增长,RBM需要打破这个魔咒就一定要用到某些指数特性:

将posting list按照65535为界限分块,比如第一块所包含的文档id范围在0~65535之间,第二块的id范围是65536~131071,以此类推。再用<商,余数>的组合表示每一组id,这样每组里的id范围都在0~65535内了

 

举个栗子说明就好了。现在我们要将 821697800 这个 32 bit 的整数插入 RBM 中,整个算法流程是这样的:

联合索引

  • 利用跳表(Skip list)的数据结构快速做“与”运算,或者对最短的posting list中的每个id,逐个在另外两个posting list中查找看是否存在,最后得到交集的结果。

  • 利用上面提到的bitset按位“与”直接按位与,得到的结果就是最后的交集。

 

总结

将磁盘里的东西尽量搬进内存减少磁盘随机读取次数(同时也利用磁盘顺序读特性),结合各种奇技淫巧的压缩算法,用及其苛刻的态度使用内存。

注意事项

  • 不需要索引的字段,一定要明确定义出来,因为默认是自动建索引的

  • 同样的道理,对于String类型的字段,不需要analysis的也需要明确定义出来,因为默认也是会analysis的

  • 选择有规律的ID很重要,随机性太大的ID(比如java的UUID)不利于查询

  • 上面看到的压缩算法,都是对Posting list里的大量ID进行压缩的,那如果ID是顺序的,或者是有公共前缀等具有一定规律性的ID,压缩比会比较高;

 

注意事项2

  • 拒绝大聚合

  • 拒绝模糊查询

  • 拒绝深度分野ES获取数据时,每次默认最多获取10000条,获取更多需要分页,但存在深度分页问题,一定不要使用from/Size方式,建议使用scroll或者searchAfter方式。scroll会把上一次查询结果缓存一定时间(通过配置scroll=1m实现),所以在使用scroll时一定要保证search结果集不要太大。

  • 拒绝多层嵌套,不要超过2层,避免内存泄漏

  • 拒绝top》100的潮汛top查询是在聚合的基础上再进行排序,如果top太大,cpu的计算量和耗费的内存都会导致查询瓶颈

实战

es常见用法

全屏