对Elacticsearch 原理/数据结构/面试经典问题整理的文章;
映射 | Elasticsearch: 权威指南 | Elastic
Elasticsearch,这里简称ES。ES是一个开源的高可用高扩展的分布式全文搜索与分析引擎,可以提供PB级近实时的数据存储和检索能力,底层基于Lucene作为其核心处理组件并在此之上进行封装提供了多语言API以及Resutful API来隐藏Lucene的复杂性,是目前最流行的企业级搜索引擎;是一个分布式、Restful的搜索及分析引擎,设计用于分布式计算;能够达到实时搜索,稳定,可靠,快速。和Apache Solr一样,它也是基于Lucence的索引服务器,而ElasticSearch对比Solr的优点在于:
整体架构:
关键概念:
一、索引(Index)
ES将数据存储于一个或多个索引中,索引是具有类似特性的文档的集合。类比传统的关系型数据库领域来说,索引相当于SQL中的一个数据库,或者一个数据存储方案(schema)。索引由其名称(必须为全小写字符)进行标识,并通过引用此名称完成文档的创建、搜索、更新及删除操作。一个ES集群中可以按需创建任意数目的索引。
二、类型(Type)
类型是索引内部的逻辑分区(category/partition),然而其意义完全取决于用户需求。因此,一个索引内部可定义一个或多个类型(type)。一般来说,类型就是为那些拥有相同的域的文档做的预定义。例如,在索引中,可以定义一个用于存储用户数据的类型,一个存储日志数据的类型,以及一个存储评论数据的类型。类比传统的关系型数据库领域来说,类型相当于“表”。
三、文档(Document)
文档是Lucene索引和搜索的原子单位,它是包含了一个或多个域的容器,基于JSON格式进行表示。文档由一个或多个域组成,每个域拥有一个名字及一个或多个值,有多个值的域通常称为“多值域”。每个文档可以存储不同的域集,但同一类型下的文档至应该有某种程度上的相似之处。
四、分片(Shard)和副本(Replica)
ES的“分片(shard)”机制可将一个索引内部的数据分布地存储于多个节点,它通过将一个索引切分为多个底层物理的Lucene索引完成索引数据的分割存储功能,这每一个物理的Lucene索引称为一个分片(shard)。每个分片其内部都是一个全功能且独立的索引,因此可由集群中的任何主机存储。创建索引时,用户可指定其分片的数量,默认数量为5个。
Shard有两种类型:primary和replica,即主shard及副本shard。Primary shard用于文档存储,每个新的索引会自动创建5个Primary shard,当然此数量可在索引创建之前通过配置自行定义,不过,一旦创建完成,其Primary shard的数量将不可更改。Replica shard是Primary Shard的副本,用于冗余数据及提高搜索性能。每个Primary shard默认配置了一个Replica shard,但也可以配置多个,且其数量可动态更改。ES会根据需要自动增加或减少这些Replica shard的数量。
ES集群可由多个节点组成,各Shard分布式地存储于这些节点上。
分布式架构
多台独立的机器上分别存在es进程,每个es进程中存在多个shard。shard分为primary和replica,replica是primary的从备份,每个primary的replica一般都分布在其他机器上,保证可用性。
应用层
协议层
脚本/发现
数据处理
核心架构
Lucene是一个功能最强大的搜索库, ES底层的Engine模块通过封装lucene来实现索引写入和查询操作。
数据存储
Gateway 是 Elasticsearch 索引的持久化存储方式,ES 默认是先把索引存放到内存中,提高搜索的时效性,再持久化到硬盘里,保障数据的持久可靠。
如果创建一个索引product_idx,那么该索引的数据就会保存在多个shard中。es 集群多个节点,会自动选举一个节点为 master 节点,这个 master 节点其实就是干一些管理的工作的,比如维护索引元数据、负责切换 primary shard 和 replica shard 身份等。要是 master 节点宕机了,那么会重新选举一个节点为 master 节点。如果某个非 master 节点宕机了。那么此节点上的 primary shard 不就没了。那好,master 会让 primary shard 对应的 replica shard(在其他机器上)切换为 primary shard。如果宕机的机器修复了,修复后的节点也不再是 primary shard,而是 replica shard。
1、index包含多个shard。
2、每个shard都是一个最小工作单元,承载部分数据,lucene实例,完整的建立索引和处理请求的能力。增减节点时,shard会自动在nodes中负载均衡。
3 、primary shard和replica shard,每个document肯定只存在于某一个primary shard以及其对应的。replica shard中,不可能存在于多个primary shard。
4、replica shard是primary shard的副本,负责容错,以及承担读请求负载。副本中的数据保证强一致或最终一致。
5、primary shard的数量在创建索引的时候就固定了,因为索引时,需要按照primary shard的数量为文档做路由(默认使用文档的_id属性取哈希值做路由,也可以通过routing指定使用其他文档字段取哈希值做路由)。replica shard的数量可以随时修改。
6、primary shard的默认数量是5,replica默认是1,默认有10个shard,5个primary shard,5个replica shard。
7、primary shard不能和自己的replica shard放在同一个节点上(否则节点宕机,primary shard和副本都丢失,起不到容错的作用),但是可以和其他primary shard的replica shard放在同一个节点上;
字符串类型:是ES最常用的类型之一。其内部使用了倒排索引算法。目前有两类字符串类型:text、keyword;
数值类型:熟悉关系型数据库的应该都不难理解 。其中,独特的是half_float和scaled_float两个类型;
boolean
date
倒排索引:
普通keyword 数据采用倒排索引,其中根据不同使用场景用到了bitmap 。skipList 等数据结构;
地理位置等信息采用:BKD树;
会根据分词器对文字进行分词(也就是图上所看到的Ada/Allen/Sara..),这些分词汇总起来叫做Term Dictionary;
优化手段
该部分的词会非常非常多,所以es内部对其进行了排序,使用二分查找法来查,故而就不需要遍历整个词集
通过分词找到对应的记录,这些文档ID保存在PostingList;
优化手段
为节约磁盘空间和快速得出交并集结果 。使用FOR以及RBM编码技术对内容压缩
由于Term Dictionary
的词实在太多了,不可能把Term Dictionary
所有的词都放在内存中,于是elastic还抽了一层叫做Term Index
,这层只存储 部分 词的前缀,Term Index
会存在内存中(检索会特别快) 具体如下:
字典里的索引页一样,A开头的有哪些term,分别在哪页,可以理解term index是一颗树。通过term index可以快速地定位到term dictionary的某个offset,然后从这个位置再往后顺序查找。大大减少了磁盘随机读的次数
这里遗留一个问题,如果Term Index树还是很大怎么办?
优化手段
为节省内存 ,该部分在内存中是以FST(https://cs.nyu.edu/~mohri/pub/fla.pdf)的形式保存的
其他优化思路:
当对多个字段进行检索时,利用了bitmap按位与进行归并优化(本身也是用bitmap的方式进行了存储
注:在特定场景非bitmap存储时,使用跳表来进行联合查询;
倒排索引特点
倒排索引适用场景
Doc Values 为正排索引;
在 Elasticsearch 中,Doc Values 就是一种列式存储结构,默认情况下每个字段的 Doc Values 都是激活的(除了 text 类型),Doc Values 是在索引时创建的,当字段索引时,Elasticsearch 为了能够快速检索,会把字段的值加入倒排索引中,同时它也会存储该字段的 Doc Values。
Doc values 通过转置两者间的关系来解决适用倒排索引聚合效率低、难以扩展的问题。
倒排索引
将词项
映射到包含它们的文档
,doc values
将文档
映射到它们包含的词项
。
Doc Values 特点
Doc Values 适用场景
Elasticsearch 中的 Doc Values 常被应用到以下场景:
注意:
因为文档值被序列化到磁盘,我们可以依靠操作系统的帮助来快速访问。
当 工作集(working set) 远小于节点的可用内存,系统会自动将所有的文档值保存在内存中,使得其读写十分高速;
当其远大于可用内存,操作系统会自动把 Doc Values 加载到系统的页缓存中,从而避免了 jvm 堆内存溢出异常。
fielddata:
如前所述:
text 类型字段是不支持 Doc Values正排索引的,text字段使用的是:查询时创建的基于的内存数据结构(query-time in-memory data structure) fielddata。fielddata 将 text 字段用于聚合、排序或在脚本中使用时,将按需构建此数据结构。
实现机理:
它是通过从磁盘读取每个段的整个倒排索引,反转词项↔︎文档关系并将结果存储在JVM堆中的内存中来构建的。
fielddata 特点
fielddata 适用场景
fielddata 使用注意事项
联合索引
上面说的都是单field索引,如果多个field索引的联合查询,倒排索引如何满足快速查询的要求呢?
跳表的数据结构:
将一个有序链表level0,挑出其中几个元素到level1及level2,每个level越往上,选出来的指针元素越少,查找时依次从高level往低查找,比如55,先找到level2的31,再找到level1的47,最后找到55,一共3次查找,查找效率和2叉树的效率相当,但也是用了一定的空间冗余来换取的。
默认:倒排索引默认所有字段都启用,正排索引 Doc Values 非 text 类型默认启用, source (存储原始文档的 所有字段的 json 结构数据)和 store (存储指定字段的 json 数据) 的启用与否需要结合业务实际。假设:正排索引、倒排索引、_source 、store 都启用了,存储肯定会增加,但不是线性的 4倍。
索引优化思路?
ES索引优化主要从两个方面解决问题: 一、索引数据过程 大家可能会遇到索引数据比较慢的过程。其实明白索引的原理就可以有针对性的进行优化。ES索引的过程到相对Lucene的索引过程多了分布式数据的扩展,而这ES主要是用tranlog进行各节点之间的数据平衡。所以从上我可以通过索引的settings进行第一优化: 这两个参数第一是到tranlog数据达到多少条进行平衡,默认为5000,而这个过程相对而言是比较浪费时间和资源的。所以我们可以将这个值调大一些还是设为-1关闭,进而手动进行tranlog平衡。第二参数是刷新频率,默认为120s是指索引在生命周期内定时刷新,一但有数据进来能refresh像lucene里面commit,我们知道当数据addDoucment后,还不能检索到要commit之后才能行数据的检索,所以可以将其关闭,在最初索引完后手动refresh一之,然后将索引setting里面的index.refresh_interval参数按需求进行修改,从而可以提高索引过程效率。 另外的知道ES索引过程中如果有副本存在,数据也会马上同步到副本中去。我个人建议在索引过程中将副本数设为0,待索引完成后将副本数按需量改回来,这样也可以提高索引效率。 "number_of_replicas": 0;
如果Term Index树还是很大怎么办?
3.es 为什么搜索快?
先查后取的过程支持用 from
和 size
参数分页,但是这是 有限制的 。 要记住需要传递信息给协调节点的每个分片必须先创建一个 from + size
长度的队列,协调节点需要根据 number_of_shards * (from + size)
排序文档,来找到被包含在 size
里的文档。
取决于你的文档的大小,分片的数量和你使用的硬件,给 10,000 到 50,000 的结果文档深分页( 1,000 到 5,000 页)是完全可行的。但是使用足够大的 from
值,排序过程可能会变得非常沉重,使用大量的CPU、内存和带宽。因为这个原因,我们强烈建议你不要使用深分页。
实际上, “深分页” 很少符合人的行为。当2到3页过去以后,人会停止翻页,并且改变搜索标准。会不知疲倦地一页一页的获取网页直到你的服务崩溃的罪魁祸首一般是机器人或者web spider。
再有,使用scroll
查询 可以用来对 Elasticsearch 有效地执行大批量的文档查询,而又不用付出深度分页那种代价。
游标查询允许我们 先做查询初始化,然后再批量地拉取结果。 这有点儿像传统数据库中的 cursor 。
游标查询会取某个时间点的快照数据。 查询初始化之后索引上的任何变化会被它忽略。 它通过保存旧的数据文件来实现这个特性,结果就像保留初始化时的索引 视图 一样。
深度分页的代价根源是结果集全局排序,如果去掉全局排序的特性的话查询结果的成本就会很低。 游标查询用字段 _doc
来排序。 这个指令让 Elasticsearch 仅仅从还有结果的分片返回下一批结果。
启用游标查询可以通过在查询的时候设置参数 scroll
的值为我们期望的游标查询的过期时间。 游标查询的过期时间会在每次做查询的时候刷新,所以这个时间只需要足够处理当前批的结果就可以了,而不是处理查询结果的所有文档的所需时间。 这个过期时间的参数很重要,因为保持这个游标查询窗口需要消耗资源,所以我们期望如果不再需要维护这种资源就该早点儿释放掉。 设置这个超时能够让 Elasticsearch 在稍后空闲的时候自动释放这部分资源。