• 1. ES的并发处理
  • 2. 请求是如何路由到分片的
  • 2.1 主分片与副本分片的交互
  • 2.2 新建、索引和删除文档
  • 2.3 检索文档
  • 2.4 更新文档
  • 2.5 批量文档操作
  • 3. 分布式检索过程
  • 3.1 查询阶段
  • 3.2 取回阶段
  • 4 游标查询Scroll
  • 5. 分片内部原理
  • 5.1 ES的全文检索
  • 5.2 动态更新索引
  • 5.3 近实时搜索
  • 5.4 持久化变更
  • 5.5 段合并
  • 5.6 总结



这里主要记录一下es内部工作的原理,ES入门很简单,但想用好它我一直认为不是件简单的事情。本文的大部分内部是参考官方文档,内容如有错误欢迎指正;

内容持续更新中…

1. ES的并发处理

在做商品库存扣减时,难免会出现库存并的情况,比如:

es运行 es运行原理_客户端


web-1对库存count的数据量做了修改之后,并没有办法告知web-2库存的真实情况,就导致了商品超卖的情况;

在数据库领域处理此问题通常有两种机制来保证:

  1. 悲观并发控制:对并发的资源加锁控制,一个典型的例子是读取一行数据之前先将其锁住,确保只有拿到锁的线程能够对数据进行修改;
  2. 乐观并发控制:首先假设这种情况不可能发生,如果源数据在读写当中被修改,那么更新操作将会失败。应用程序接下来决定该如何解决冲突。例如:可以重试或将情况报告给用户。

ES是如何来处理并发的呢?
ES是分布式的,当文档创建、更新或删除时,新版本的文档必须复制到其它节点。ES也是异步和并发的,这意味着复制请求被并行发送,并且到达目的地也是乱序的。ES需要一种保证文档的旧版本不会覆盖新的版本。

Es中每个文档都会有一个版本号的字段,该字段每当文档被修改时递增。ES使用这个_version号来确保变更得到顺序的执行。如果旧版本的文档在新版本文档之后到达,它可以被简单的忽略。

// 修改文档时可以指定version,如果当前的doc文档的version!=1时,修改将失败;
PUT /website/blog/1?version=1 
{
  "title": "My first blog entry",
  "text":  "Starting to get the hang of this..."
}

思考:如何保证每个分片内的版本号不会出现乱序呢?

es运行 es运行原理_es运行_02


这主要是因为ES的所有写操作是都是由主分片来完成的,因为这些操作是需要同步到其它的副本分片上去。所以实现了并发更新的统一入口,如果是任意一个节点都有更新能力,那可能会造成分片与分片之间的某些doc版本冲突的问题;

2. 请求是如何路由到分片的

当创建一个文档的时候,文档会被存储到一个主分片中,ES如何知道一个文档应该存放到哪个分片呢?(假设我们有2个分片的情况下)
首先这肯定不是随机的,否则将来要获取文档的时候我们就无处可寻了。实际上,这个过程是根据下面这个公式计算的:

shard = hash(routing) % number_of_primary_shards

number_of_primary_shards:你指定索引的分片数;
routing:默认是文档的_id,也可以设置成一个自定义的值;
这也诠释了我们在创建索引的时候就需要确定好主分片的数量并且永远不会改变这个数量:如果数量变了,那么之前路由的值都会无效,文档再也找不到了。

所有的文档API( get 、 index 、 delete 、 bulk 、 update 以及 mget )都接受一个叫做routing的路由参数,通过这个参数我们可以自定义文档到分片的映射。

2.1 主分片与副本分片的交互

假设有一个集群由3个节点组成,它包含一个叫blogs的索引,有2个主分片,每个主分片有2个副本分片。相同分片的副本不会放在同一节点中;

es运行 es运行原理_客户端_03


我们可以发送请求到集群中的任意节点,每个节点都能力处理任意请求。每个节点都知道集群中任一文档的位置,所以可以直接将请求转发到需要的节点上面。比如:将请求发送到node1,我们可以将其称为“协调节点”;

注:当发送请求的时候, 为了扩展负载,更好的做法是轮询集群中所有的节点。

2.2 新建、索引和删除文档

新建、索引和删除 请求都是“写”操作, 必须在主分片上面完成之后才能被复制到相关的副本分片;

es运行 es运行原理_es运行_04


以下是在主副分片和任何副本分片上面成功新建,索引和删除文档所需要的步骤顺序:

  1. 客户端向Node1发送新建、索引或者删除请求。
  2. 节点使用文档的 _id 确定文档属于分片 0 。请求会被转发到 Node 3,因为分片 0 的主分片目前被分配在 Node 3 上。
  3. Node 3 在主分片上面执行请求。如果成功了,它将请求并行转发到 Node 1 和 Node 2 的副本分片上。一旦所有的副本分片都报告成功, Node 3 将向协调节点报告成功,协调节点向客户端报告成功。

在客户端收到成功响应时,文档变更已经在主分片和所有副本分片执行完成,变更是安全的。

在上面这个过程中会用到两个参数(只做了解):

  • consistency,即一致性,主分片在执行一个写操作之前要求必须有多少个副本分片处于活跃可用状态。它有三个值:one:只要主分状态ok就允许执行写操作,all:主分片和副本分片状态没问题才允许执行写操作,quorum:大多数的分片副本状态没问题(大多数是指:int( (primary + number_of_replicas) / 2 ) + 1);
  • timeout,即超时时间,如果没有足够的副本ES将会最多等待1分钟,也可以将参数改为100(100毫秒)或者是30s(30秒);
2.3 检索文档

可以从主分片或者从其它任意副本分片检索文档;

es运行 es运行原理_客户端_05


以下是从主分片或者副本分片检索文档的步骤顺序:

1、客户端向 Node 1 发送获取请求。
2、节点使用文档的 _id 来确定文档属于分片 0 。分片 0 的副本分片存在于所有的三个节点上。 在这种情况下,它将请求转发到 Node 2 。
3、Node 2 将文档返回给 Node 1 ,然后将文档返回给客户端。

在处理读取请求时,协调结点在每次请求的时候都会通过轮询所有的副本分片来达到负载均衡。

2.4 更新文档

ES更新文档的底层是标识旧文档为删除状态,然后新增一条文档;

es运行 es运行原理_数据_06


以下是部分更新一个文档的步骤:

  1. 客户端向 Node 1 发送更新请求。
  2. 它将请求转发到主分片所在的 Node 3 。
  3. Node 3 从主分片检索文档,修改 _source 字段中的 JSON ,并且尝试重新索引主分片的文档。 如果文档已经被另一个进程修改,它会重试步骤 3 ,超过 retry_on_conflict 次后放弃。
  4. 如果 Node 3 成功地更新文档,它将新版本的文档并行转发到 Node 1 和 Node 2 上的副本分片,重新建立索引。 一旦所有副本分片都返回成功, Node 3 向协调节点也返回成功,协调节点向客户端返回成功。

基于文档的复制
当主分片把更改转发到副本分片时, 它不会转发更新请求。 相反,它转发完整文档的新版本。请记住,这些更改将会异步转发到副本分片,并且不能保证它们以发送它们相同的顺序到达。 如果Elasticsearch仅转发更改请求,则可能以错误的顺序应用更改,导致得到损坏的文档。

2.5 批量文档操作

mget 和 bulk API 的模式类似于单文档模式。区别在于协调节点知道每个文档存在于哪个分片中。 它将整个多文档请求分解成 每个分片 的多文档请求,并且将这些请求并行转发到每个参与节点。

2.5.1 mget

es运行 es运行原理_分布式_07


以下是使用单个 mget 请求取回多个文档所需的步骤顺序:

  1. 客户端向 Node 1 发送 mget 请求。
  2. Node 1 为每个分片构建多文档获取请求,然后并行转发这些请求到托管在每个所需的主分片或者副本分片的节点上。一旦收到所有答复, Node 1 构建响应并将其返回给客户端。

2.5.2 bulk

es运行 es运行原理_数据_08


bulk API 按如下步骤顺序执行:

  1. 客户端向 Node 1 发送 bulk 请求。
  2. Node 1 为每个节点创建一个批量请求,并将这些请求并行转发到每个包含主分片的节点主机。
  3. 主分片一个接一个按顺序执行每个操作。当每个操作成功时,主分片并行转发新文档(或删除)到副本分片,然后执行下一个操作。 一旦所有的副本分片报告所有操作成功,该节点将向协调节点报告成功,协调节点将这些响应收集整理并返回给客户端。

bulk API 为什么是这种奇怪的格式:

在批量请求中引用的每个文档可能属于不同的主分片, 每个文档可能被分配给集群中的任何节点。这意味着批量请求 bulk 中的每个 操作 都需要被转发到正确节点上的正确分片。

如果单个请求被包装在 JSON 数组中,那就意味着我们需要执行以下操作:

  1. 将 JSON 解析为数组(包括文档数据,可以非常大)
  2. 查看每个请求以确定应该去哪个分片
  3. 为每个分片创建一个请求数组
  4. 将这些数组序列化为内部传输格式
  5. 将请求发送到每个分片

这是可行的,但需要大量的 RAM 来存储原本相同的数据的副本,并将创建更多的数据结构,Java虚拟机(JVM)将不得不花费时间进行垃圾回收。

相反,Elasticsearch可以直接读取被网络缓冲区接收的原始数据。 它使用换行符字符来识别和解析小的 action/metadata 行来决定哪个分片应该处理每个请求。

这些原始请求会被直接转发到正确的分片。没有冗余的数据复制,没有浪费的数据结构。整个请求尽可能在最小的内存中处理。

3. 分布式检索过程

一个CRUD操作只对单个文档进行处理,可以通过index/type/id来表示文档在集群中哪个分片上,但是,搜索需要一种更加复杂的执行模型因为我们不知道查询会命中哪些文档,这些文档有可能在集群的任何分片上。

找到所有的匹配文档仅仅完成事情的一半,在search接口返回一个page结果之前,多分片中的结果必须组合成单个排序列表。为此,搜索是包含两个过程,我们称为query then fetch(查询 获取);

3.1 查询阶段

在初始查询阶段时,查询会广播到索引中的每一个分片(主分片或者副本分片)。每个分片在本地执行搜索并构建一个匹配文档的“优先队列”(可以理解为存放结果的队列);
3.1.1 优化队列

一个优先队列仅仅是一个存放匹配结果的有序列表。队列的大小取决于分页参数from和size。比如:
GET /_search
{
“from”: 90, // es中的from表示:从第多少条之后开始匹配;
“size”: 10
}
那将会创建一个大小为100的队列;

3.1.2 查询过程分布式搜索

es运行 es运行原理_搜索_09


查询阶段包含以下三个步骤:

  1. 客户端发送一个 search 请求到 Node 3 , Node 3 会创建一个大小为 from + size 的空优先队列。
  2. Node 3 将查询请求转发到索引的每个主分片或副本分片中。每个分片在本地执行查询并添加结果到大小为 from + size 的本地有序优先队列中。
  3. 每个分片返回各自优先队列中所有文档的 ID 和排序值给协调节点,也就是 Node 3 ,它合并这些值到自己的优先队列中来产生一个全局排序后的结果列表。
  • 当一个搜索请求被发送到某个节点时,这个节点就变成了协调节点。 这个节点的任务是广播查询请求到所有相关分片并将它们的响应整合成全局排序后的结果集合,这个结果集合会返回给客户端。
  • 第一步是广播请求到索引中每一个节点的分片及副本。查询请求可以被某个主分片或某个副本分片处理, 这就是为什么更多的副本(当结合更多的硬件)能够增加搜索吞吐率。 协调节点将在之后的请求中轮询所有的分片拷贝来分摊负载。
  • 每个分片在本地执行查询请求并且创建一个长度为 from + size 的优先队列—也就是说,每个分片创建的结果集足够大,均可以满足全局的搜索请求。 分片返回一个轻量级的结果列表到协调节点,它仅包含文档 ID 集合以及任何排序需要用到的值,例如 _score 。
  • 协调节点将这些分片级的结果合并到自己的有序优先队列里,它代表了全局排序结果集合。至此查询过程结束。

一个索引可以由一个或几个主分片组成, 所以一个针对单个索引的搜索请求需要能够把来自多个分片的结果组合起来。 针对 multiple 或者 all 索引的搜索工作方式也是完全一致的—仅仅是包含了更多的分片而已。

3.2 取回阶段

查询阶段标识哪些文档满足搜索请求,但是我们仍然需要取回这些文档。这是取回阶段的任务;

es运行 es运行原理_数据_10


分布式阶段由以下步骤构成:

  1. 协调节点辨别出哪些文档需要被取回并向相关的分片提交多个 GET 请求。
  2. 每个分片加载并 丰富 文档,如果有需要的话,接着返回文档给协调节点。
  3. 一旦所有的文档都被取回了,协调节点返回结果给客户端。
  • 协调节点首先决定哪些文档 确实 需要被取回。例如,如果我们的查询指定了 { “from”: 90, “size”: 10 } ,最初的90个结果会被丢弃,只有从第91个开始的10个结果需要被取回。这些文档可能来自和最初搜索请求有关的一个、多个甚至全部分片。
  • 协调节点给持有相关文档的每个分片创建一个 multi-get request ,并发送请求给同样处理查询阶段的分片副本。
  • 分片加载文档体-- _source 字段,一旦协调节点接收到所有的结果文档,它就组装这些结果为单个响应返回给客户端。

3.2.1 深分布问题
先查后取的过程支持用 from 和 size 参数分页,但是这是 有限制的 。 要记住需要传递信息给协调节点的每个分片必须先创建一个 from + size 长度的队列,协调节点需要根据 number_of_shards * (from + size) 排序文档,来找到被包含在 size 里的文档。

所以使用from和size时,需要尽量业务层面避免深分页问题;

4 游标查询Scroll

scroll 查询 可以用来对 Elasticsearch 有效地执行大批量的文档查询,而又不用付出深度分页那种代价。
先来说明Scroll的原理:

  1. 根据查询条件取某个时间点生成数据快照,数据快照生成之后,索引上的其它新/改/删,它将无法感知;
  2. 对生成的数据快照进行排序,注意,它只排序这一次,而对于上面说from和size它是会对每次查询的结果都进行排序,如果是深度分析,它会进行大数据量的排序,所以说这里只对数据进行了一次排序,虽然排序的数据可能有点多有点慢;

3.3.1 使用方式

GET /old_index/_search?scroll=1m    (1)
{
    "query": { "match_all": {}},
    "sort" : ["_doc"],              (2)               
    "size":  1000                   (3)
}

(1) 保持游标查询窗口一分钟
(2) 如果你对scroll取出的数据顺序没有要求的话,则可以对“_doc”进行排序,es对这种排序做了优化。
(3) 尽管我们指定字段 size 的值为1000,我们有可能取到超过这个值数量的文档。 当查询的时候, 字段 size 作用于单个分片,所以每个批次实际返回的文档数量最大为 size * number_of_primary_shards

这个查询的返回结果包括一个字段_scroll_id,它是一个base64编码的长字符串。我们再查询的时候传递_scroll_id字段;

GET /_search/scroll
{
    "scroll": "1m",             (1)  
    "scroll_id" : "xxxxxxxid"   (2)
}

(1)每次都尽量去刷新它的过期时间;
(2)上一次请求返回的id;

5. 分片内部原理

本节将会了解到:

  1. ES是如何实现全文检索的?
  2. ES为什么是近实时搜索的?
  3. 为什么文档的 CRUD (创建-读取-更新-删除) 操作是实时的?
  4. ES 是怎样保证更新被持久化在断电时也不丢失数据?
  5. 为什么删除文档不会立刻释放空间?
  6. refresh, flush, 和 optimize API 都做了什么, 你什么情况下应该使用他们?
5.1 ES的全文检索

ES的全文检索是通过一个叫做倒排索引来实现的,什么是倒排索引呢?
比如有两段文本:
1 | How are you?
2 | are you ok?
倒排索引包含一个有序列表,列表包含所有文档出现过的不重复个体,或称为 词项 ,对于每一个词项,包含了它所有曾出现过文档的列表:

Term

Doc1

Doc2

are

1

2

how

1

you

1

2

ok

2

当我们检索you的时候,可以看到会查出Doc1和Doc2的id;

不变性
倒排索引被写入磁盘后是不可改变的,它永远不会修改。不变性有重要的价值:

  • 不需要锁 如果你从来不更新索引,你就不需要担心多进程同时修改数据的问题。
  • 一旦索引被读入内核的文件系统缓存,便会留在哪里,由于其不变性。只要文件系统缓存中还有足够的空间,那么大部分读请求会直接请求内存,而不会命中磁盘。这提供了很大的性能提升。
  • 其它缓存(像filter缓存),在索引的生命周期内始终有效。它们不需要在每次数据改变时被重建,因为数据不会变化。
  • 写入单个大的倒排索引允许数据被压缩,减少磁盘 I/O 和 需要被缓存到内存的索引的使用量。

当然,一个不变的索引也有不好的地方。主要事实是它是不可变的! 你不能修改它。如果你需要让一个新的文档 可被搜索,你需要重建整个索引。这要么对一个索引所能包含的数据量造成了很大的限制,要么对索引可被更新的频率造成了很大的限制。

5.2 动态更新索引

上面提到了索引的不可变性,那ES又是如何来更新索引的呢?

段和提交点

Elasticsearch 基于 Lucene, 这个 java 库引入了 按段搜索 的概念。 每一 段 本身都是一个倒排索引但 索引 在 Lucene 中除表示所有 段 的集合外, 还增加了 提交点 的概念;

es运行 es运行原理_分布式_11


新的文档首先被添加到内存索引缓存中,然后写入到一个基于磁盘的段;

逐段搜索会以如下流程进行工作:

  • 新文档被收集到内存索引缓存(一个在内存缓存中包含新文档的 Lucene 索引)不时地, 缓存被 提交,如下图;
  • 一个新的段被写入磁盘。
  • 一个新的包含新段名字的 提交点 被写入磁盘。
  • 磁盘进行 同步所有在文件系统缓存中等待的写入都刷新到磁盘,以确保它们被写入物理文件。
  • 新的段被开启,让它包含的文档可见以被搜索。
  • 内存缓存被清空,等待接收新的文档。

es运行 es运行原理_客户端_12


当一个查询被触发,所有已知的段按顺序被查询。词项统计会对所有段的结果进行聚合,以保证每个词和每个文档的关联都被准确计算。 这种方式可以用相对较低的成本将新文档添加到索引。

删除和更新

段是不可改变的,所以既不能从把文档从旧的段中移除,也不能修改旧的段来进行反映文档的更新。 取而代之的是,每个提交点会包含一个 .del 文件,文件中会列出这些被删除文档的段信息。

当一个文档被 “删除” 时,它实际上只是在 .del 文件中被 标记 删除。一个被标记删除的文档仍然可以被查询匹配到, 但它会在最终结果被返回前从结果集中移除。

文档更新也是类似的操作方式:当一个文档被更新时,旧版本文档被标记删除,文档的新版本被索引到一个新的段中。 可能两个版本的文档都会被一个查询匹配到,但被删除的那个旧版本文档在结果集返回前就已经被移除。

5.3 近实时搜索

上面提到的按段提交引来了另外一个问题,就是段被提交后(写入磁盘后)才允许开启被搜索的能力,这样延迟过高;

磁盘在这里成为了瓶颈。提交(Commiting)一个新的段到磁盘需要一个 fsync 来确保段被物理性地写入磁盘,这样在断电的时候就不会丢失数据。 但是 fsync 操作代价很大; 如果每次索引一个文档都去执行一次的话会造成很大的性能问题。

我们需要的是一个更轻量的方式来使一个文档可被搜索,这意味着 fsync 要从整个过程中被移除。

处理方案:在内存索引缓冲区中的文档会被写入到一个新的段中。但是这里新段会被先写入到文件系统缓存—这一步代价会比较低,稍后再被刷新到磁盘—这一步代价比较高。不过只要文件已经在缓存中, 就可以像其它文件一样被打开和读取了。

refresh API
在 Elasticsearch 中,写入和打开一个新段的轻量的过程叫做 refresh 。 默认情况下每个分片会每秒自动刷新一次。这就是为什么我们说 Elasticsearch 是 近 实时搜索: 文档的变化并不是立即对搜索可见,但会在一秒之内变为可见。

POST /_refresh 刷新(Refresh)所有的索引
POST /blogs/_refresh 只刷新(Refresh) blogs 索引

在生产环境中,如果你需要导入大批量数据,可以先将refresh功能关闭,待导入完成后再开启;

5.4 持久化变更

如果没有用 fsync 把数据从文件系统缓存刷(flush)到硬盘,我们不能保证数据在断电甚至是程序正常退出之后依然存在。为了保证 Elasticsearch 的可靠性,需要确保数据变化被持久化到磁盘。

在 动态更新索引,我们说一次完整的提交会将段刷到磁盘,并写入一个包含所有段列表的提交点。Elasticsearch 在启动或重新打开一个索引的过程中使用这个提交点来判断哪些段隶属于当前分片。

即使通过每秒刷新(refresh)实现了近实时搜索,我们仍然需要经常进行完整提交来确保能从失败中恢复。但在两次提交之间发生变化的文档怎么办?我们也不希望丢失掉这些数据。

Elasticsearch 增加了一个 translog ,或者叫事务日志,在每一次对 Elasticsearch 进行操作时均进行了日志记录。通过 translog ,整个流程看起来是下面这样:

  1. 一个文档被索引之后,就会被添加到内存缓冲区,并且 追加到了 translog;
  2. 刷新(refresh)分片每秒被刷新(refresh)一次:
  • 这些在内存缓冲区的文档被写入到一个新的段中,且没有进行 fsync 操作。
  • 这个段被打开,使其可被搜索。
  • 内存缓冲区被清空。
  1. 这个进程继续工作,更多的文档被添加到内存缓冲区和追加到事务日志;
  2. 每隔一段时间—例如 translog 变得越来越大—索引被刷新(flush);一个新的 translog 被创建,并且一个全量提交被执行 在刷新(flush)之后,段被全量提交,并且事务日志被清空” ):
  • 所有在内存缓冲区的文档都被写入一个新的段。
  • 缓冲区被清空。
  • 一个提交点被写入硬盘。
  • 文件系统缓存通过 fsync 被刷新(flush)。
  • 老的 translog 被删除。

translog 提供所有还没有被刷到磁盘的操作的一个持久化纪录。当 Elasticsearch 启动的时候, 它会从磁盘中使用最后一个提交点去恢复已知的段,并且会重放 translog 中所有在最后一次提交后发生的变更操作。

translog 也被用来提供实时 CRUD 。当你试着通过ID查询、更新、删除一个文档,它会在尝试从相应的段中检索之前, 首先检查 translog 任何最近的变更。这意味着它总是能够实时地获取到文档的最新版本。

flush API
这个执行一个提交并且截断 translog 的行为在 Elasticsearch 被称作一次 flush 。 分片每30分钟被自动刷新(flush),或者在 translog 太大的时候也会刷新。请查看 translog 文档 来设置,它可以用来 控制这些阈值:
flush API 可以被用来执行一个手工的刷新(flush):

POST /blogs/_flush
POST /_flush?wait_for_ongoing

Translog 有多安全?
translog 的目的是保证操作不会丢失。这引出了这个问题: Translog 有多安全?

在文件被 fsync 到磁盘前,被写入的文件在重启之后就会丢失。默认 translog 是每 5 秒被 fsync 刷新到硬盘, 或者在每次写请求完成之后执行(e.g. index, delete, update, bulk)。这个过程在主分片和复制分片都会发生。最终, 基本上,这意味着在整个请求被 fsync 到主分片和复制分片的translog之前,你的客户端不会得到一个 200 OK 响应。

在每次请求后都执行一个 fsync 会带来一些性能损失,尽管实践表明这种损失相对较小(特别是bulk导入,它在一次请求中平摊了大量文档的开销)。

但是对于一些大容量的偶尔丢失几秒数据问题也并不严重的集群,使用异步的 fsync 还是比较有益的。比如,写入的数据被缓存到内存中,再每5秒执行一次 fsync 。

这个行为可以通过设置 durability 参数为 async 来启用:

PUT /my_index/_settings
{
“index.translog.durability”: “async”,
“index.translog.sync_interval”: “5s”
}

这个选项可以针对索引单独设置,并且可以动态进行修改。如果你决定使用异步 translog 的话,你需要 保证 在发生crash时,丢失掉 sync_interval 时间段的数据也无所谓。请在决定前知晓这个特性。

如果你不确定这个行为的后果,最好是使用默认的参数( “index.translog.durability”: “request” )来避免数据丢失。

5.5 段合并

由于自动刷新流程每秒会创建一个新的段 ,这样会导致短时间内的段数量暴增。而段数目太多会带来较大的麻烦。 每一个段都会消耗文件句柄、内存和cpu运行周期。更重要的是,每个搜索请求都必须轮流检查每个段;所以段越多,搜索也就越慢。

Elasticsearch通过在后台进行段合并来解决这个问题。小的段被合并到大的段,然后这些大的段再被合并到更大的段。

段合并的时候会将那些旧的已删除文档从文件系统中清除。被删除的文档(或被更新文档的旧版本)不会被拷贝到新的大段中。

启动段合并不需要你做任何事。进行索引和搜索时会自动进行,它的流程:

1、 当索引的时候,刷新(refresh)操作会创建新的段并将段打开以供搜索使用。

2、 合并进程选择一小部分大小相似的段,并且在后台将它们合并到更大的段中。这并不会中断索引和搜索。

es运行 es运行原理_分布式_13


3、 新的段被刷新(flush)到了磁盘。 ** 写入一个包含新段且排除旧的和较小的段的新提交点。新的段被打开用来搜索。老的段被删除。

5.6 总结

默认1s将数据存入系统缓存中并开启一个新的段;
translog默认每5秒做一次持久化,如果说5s内段未被持久化一磁盘,一样会造成数据丢失;