ElasticSearch 源码分析 一 基本原理

时间:2024-04-12 10:34:26

一、背景
Elasticsearch是一个实时分布式搜索和分析引擎。它让你以前所未有的速度处理大数据成为可能。本文主要介绍实现分布式搜索和分析的基础–存储,好的存储设计在根本上决定了查询的性能。

es的存储本质上是采用了lucene全文索引,在其基础上实现了分布式功能。

几个基本概念:

Cluster:集群,一个或多个节点的集合,通过名字唯一标识
Node:节点,组成集群的服务单元,保存数据并具有索引和搜索的能力,通过名字唯一标识
Shards: 分片 ,将索引分为多个块,每块叫做一个分片。索引定义时需要指定分片数且不能更改,每个分片都是一个功能完整的Index,分片带来规模上(数据水平切分)和性能上(并行执行)的提升
Replicas:复制分片,对主分片的复制称为复制分片,索引定义时需指定复制分片数(大于等于0)且可以更改,复制分片可以提供节点失败时的高可用能力,同时能够提升搜索时的并发性能(搜索可以在全部分片上并行执行)
Index:索引,具有相似特点的文档的集合,可以对应为关系型数据库中的数据库,通过名字在集群内唯一标识(必须全部小写)
Type:类别,索引内部的逻辑分类/分区,可以对应为关系型数据库中的表,通过名字在Index内唯一标识
Document:文档,能够被索引的最小单位,JSON格式,属于一个索引的某个类别中,从属关系为: Index -> Type -> Document,通过id 在Type 内唯一标识
 

二、分布式实现
2.1分布式算法
shard = hash(routing) % number_of_primary_shards
以上是路由文档到分片的算法,routing值是一个任意字符串,它默认是_id但也可以自定义。这个routing字符串通过哈希函数生成一个数字,然后除以主切片的数量得到一个余数(remainder),余数的范围永远是0到number_of_primary_shards - 1,这个数字就是特定文档所在的分片。

这也解释了为什么主分片的数量只能在创建索引时定义且不能修改:如果主分片的数量在未来改变了,所有先前的路由值就失效了,文档也就永远找不到了。

2.2写请求
新建、索引和删除请求都是写(write)操作,它们必须在主分片上成功完成才能复制到相关的复制分片上。

ElasticSearch 源码分析 一 基本原理

下面我们罗列在主分片和复制分片上成功新建、索引或删除一个文档必要的顺序步骤:

客户端给Node 1发送新建、索引或删除请求。
节点使用文档的_id确定文档属于分片0。它转发请求到Node 3,分片0位于这个节点上。
Node 3在主分片上执行请求,如果成功,它转发请求到相应的位于Node 1和Node 2的复制节点上。当所有的复制节点报告成功,Node 3报告成功到请求的节点,请求的节点再报告给客户端。
个人理解:此处每个节点都知道index_X-shard_N-node_M的元数据信息,才能进行相应的请求转发。

2.3读请求
文档能够从主分片或任意一个复制分片被检索。

ElasticSearch 源码分析 一 基本原理

下面我们罗列在主分片或复制分片上检索一个文档必要的顺序步骤:

客户端给Node 1发送get请求。
节点使用文档的_id确定文档属于分片0。分片0对应的复制分片在三个节点上都有。此时,它转发请求到Node 2。
Node 2返回endangered给Node 1然后返回给客户端。
2.4局部更新
update API 结合了之前提到的读和写的模式。

ElasticSearch 源码分析 一 基本原理

下面我们罗列执行局部更新必要的顺序步骤:

客户端给Node 1发送更新请求。
它转发请求到主分片所在节点Node 3。
Node 3从主分片检索出文档,修改_source字段的JSON,然后在主分片上重建索引。如果有其他进程修改了文档,它以retry_on_conflict设置的次数重复步骤3,都未成功则放弃。
如果Node 3成功更新文档,它同时转发文档的新版本到Node 1和Node 2上的复制节点以重建索引。当所有复制节点报告成功,Node 3返回成功给请求节点,然后返回给客户端
三、分片管理
3.1倒排索引不可变
写入磁盘的倒排索引是不可变的,它有如下好处:

不需要锁。如果从来不需要更新一个索引,就不必担心多个程序同时尝试修改。
一旦索引被读入文件系统的缓存(译者:在内存),它就一直在那儿,因为不会改变。只要文件系统缓存有足够的空间,大部分的读会直接访问内存而不是磁盘。这有助于性能提升。
在索引的声明周期内,所有的其他缓存都可用。它们不需要在每次数据变化了都重建,因为数据不会变。
写入单个大的倒排索引,可以压缩数据,较少磁盘IO和需要缓存索引的内存大小。
当然,不可变的索引有它的缺点,首先是它不可变!你不能改变它。如果想要搜索一个新文档,必须重见整个索引。这不仅严重限制了一个索引所能装下的数据,还有一个索引可以被更新的频次。

3.2动态索引
索引不可变首先需要解决的问题是如何在保持不可变好处的同时更新倒排索引。答案是,使用多个索引。不是重写整个倒排索引,而是增加额外的索引反映最近的变化。每个倒排索引都可以按顺序查询,从最老的开始,最后把结果聚合。

Elasticsearch底层依赖的Lucene,引入了per-segment search的概念。一个段(segment)是有完整功能的倒排索引,但是现在Lucene中的索引指的是段的集合,再加上提交点(commit point,包括所有段的文件),如图1所示。新的文档,在被写入磁盘的段之前,首先写入内存区的索引缓存,如图2、图3所示。

图1:一个提交点和三个索引的Lucene

ElasticSearch 源码分析 一 基本原理

一个per-segment search如下工作:

新的文档首先写入内存区的索引缓存。
不时,这些buffer被提交:
一个新的段——额外的倒排索引——写入磁盘。
新的提交点写入磁盘,包括新段的名称。
磁盘是fsync’ed(文件同步)——所有写操作等待文件系统缓存同步到磁盘,确保它们可以被物理写入。
新段被打开,它包含的文档可以被检索
内存的缓存被清除,等待接受新的文档。
图2:内存缓存区有即将提交文档的Lucene索引

ElasticSearch 源码分析 一 基本原理

图3:提交后,新的段加到了提交点,缓存被清空

ElasticSearch 源码分析 一 基本原理

当一个请求被接受,所有段依次查询。所有段上的Term统计信息被聚合,确保每个term和文档的相关性被正确计算。通过这种方式,新的文档以较小的代价加入索引。

3.3近实时搜索(refresh)
位于Elasticsearch和磁盘间的是文件系统缓存。如前所说,在内存索引缓存中的文档(图1)被写入新的段(图2),但是新的段首先写入文件系统缓存,这代价很低,之后会被同步到磁盘,这个代价很大。但是一旦一个文件被缓存,它也可以被打开和读取,就像其他文件一样。

 Lucene允许新段写入打开,好让它们包括的文档可搜索,而不用执行一次全量提交。这是比提交更轻量的过程,可以经常操作,而不会影响性能。

在Elesticsearch中,这种写入打开一个新段的轻量级过程,叫做refresh。默认情况下,每个分片每秒自动刷新一次。这就是为什么说Elasticsearch是近实时的搜索了:文档的改动不会立即被搜索,但是会在一秒内可见。

3.4持久化变更(flush)
没用fsync同步文件系统缓存到磁盘,我们不能确保电源失效,甚至正常退出应用后,数据的安全。为了ES的可靠性,需要确保变更持久化到磁盘。

我们说过一次全提交同步段到磁盘,写提交点,这会列出所有的已知的段。在重启,或重新打开索引时,ES使用这次提交点决定哪些段属于当前的分片。

当我们通过每秒的刷新获得近实时的搜索,我们依然需要定时地执行全提交确保能从失败中恢复。但是提交之间的文档怎么办?我们也不想丢失它们。

ES增加了事务日志(translog),来记录每次操作。有了事务日志,过程现在如下:

当一个文档被索引,它被加入到内存缓存,同时加到事务日志。

图1:新的文档加入到内存缓存,同时写入事务日志

refresh使得分片的进入如下图描述的状态。每秒分片都进行refeash:

内存缓冲区的文档写入到段中,但没有fsync。
段被打开,使得新的文档可以搜索。
缓存被清除

随着更多的文档加入到缓存区,写入日志,这个过程会继续

不时地,比如日志很大了,新的日志会创建,会进行一次全提交:

内存缓存区的所有文档会写入到新段中。
清除缓存
一个提交点写入硬盘
文件系统缓存通过fsync操作flush到硬盘
事务日志被清除
事务日志记录了没有flush到硬盘的所有操作。当故障重启后,ES会用最近一次提交点从硬盘恢复所有已知的段,并且从日志里恢复所有的操作。

3.5端合并(optimize)
通过每秒自动刷新创建新的段,用不了多久段的数量就爆炸了。有太多的段是一个问题。每个段消费文件句柄,内存,cpu资源。更重要的是,每次搜索请求都需要依次检查每个段。段越多,查询越慢。

ES通过后台合并段解决这个问题。小段被合并成大段,再合并成更大的段。

这是旧的文档从文件系统删除的时候。旧的段不会再复制到更大的新段中。

1
这个过程你不必做什么。当你在索引和搜索时ES会自动处理。这个过程如图:两个提交的段和一个未提交的段合并为了一个更大的段所示:

索引过程中,refresh会创建新的段,并打开它。
合并过程会在后台选择一些小的段合并成大的段,这个过程不会中断索引和搜索。

ElasticSearch 源码分析 一 基本原理

下图描述了合并后的操作:

新的段flush到了硬盘。
新的提交点写入新的段,排除旧的段。
新的段打开供搜索。
旧的段被删除。

ElasticSearch 源码分析 一 基本原理

合并大的段会消耗很多IO和CPU,如果不检查会影响到搜素性能。默认情况下,ES会限制合并过程,这样搜索就可以有足够的资源进行。