Elasticsearch-索引写入过程

Elasticsearch-索引写入过程

一、Lucene的写操作及问题

  1. 没有并发设计: lucene只是一个搜索引擎库,并没有涉及到分布式相关的设计,因此要想使用Lucene来处理海量数据,并利用分布式的能力,就必须在其之上进行分布式的相关设计;

  2. 非实时: 将文件写入lucence后并不能立即被检索,需要等待lucene生成一个完整的segment才能被检索;

  3. 数据存储不可靠: 写入lucene的数据不会立即被持久化到磁盘,如果服务器宕机,那存储在内存中的数据将会丢失;

  4. 不支持部分更新: lucene中提供的updateDocuments仅支持对文档的全量更新,对部分更新不支持。

二、ES写入方案

1. 分布式设计

  • 为了支持对海量数据的存储和查询,ES引入分片的概念,一个索引被分为多个分片,每个分片可以有一个主分片和多个副分片;

  • 每个分片副本都是一个完整的Lucene实例;

  • 分片number计算公式: shard_num = hash(\routing)%num_primary_shards。

2. 近实时性-refresh操作

当一个文档写入Lucene后不能被立即查询到的, ES提供了一个refresh操作,会定时地调用Lucene的reopen(新版的为openChanged)为内存中新写入的数据生成一个新的segment,此时被处理的 文档均可以被检索到。refresh的操作的间隔时间由 refresh_interval来设置,默认为 1s。

3. 数据存储可靠性

  • 引入transLog

    • 背景:首先一个文档写入Lucene后是存储在内存中的,即使执行了refresh操作,仍然是在文件系统缓存着,如果此时宕机,那么这部分数据就会丢失

    • 方案:为此ES增加了transLog,当进行文档写操作时,先写入Lucene中,然后写入一份到transLog,写入transLog是落盘的;(如果可靠性要求不高,

    • 可异步落盘,由配置 index.translog.durability和 index.translog.sync_interval控制)。transLog是追加,因此性能比随机写入要好。

    • 写入顺序是先写入Lucene,再写入transLog中。

  • flush操作

    • 另外每30分钟或者transLog达到一定大小(index.translog.flush_threshold_size控制,默认为512m),ES触发一次flush操作,此时ES会先执行 refresh操作将buffer中的数据生成segment,然后调用Lucene的commit方法将所有内存中的segment fsync到磁盘中,此时Lucene中的数据就完成 持久化了,会清空transLog中的数据(6.x不删除)
  • merge操作(额外的)

    • 背景:由于refresh默认是 1s刷新一次,所以会参数很多小的segment

    • 方案:因此ES会运行一个任务检测当前内存中的segment,对符合条件的segment进行合并,减少segment个数,提高查询速度,降低负载。还可清理已删除和更新的文档。

  • 多副本机制(额外的)

4. 部分更新

  • 背景:Lucene仅支持对文档的整体更新

  • 方案:ES为了支持局部更新,在Lucene的store索引中存储了一个_source字段,该字段的key值为文档ID,内容为文档的原文。 当进行更新操作时,先从_source中获取原文,与更新部分进行合并之后,再调用Lucene API进行全量更新。

  • 对于写入ES,还没有refresh的数据,从transLog中获取。

  • 为了防止多线程修改数据,采用乐观锁,增加了文档_version字段。

三、ES写入流程

1. 基本流程

  • ES的任意节点都可以作为协调节点(coordination node)接收请求,经过一系列后处理

  • 然后通过_routing确定primary shard,并将请求发送到primary shard上

  • primary shard完成写入后,将写入数据并发发送给replica

  • replica写入完成之后,告诉primary shard

  • primary shard再将请求返回协调节点。

2. coordinating 节点

ES集群中的任何节点,都可作为协调节点,接收和转发请求。当接收请求后,具体操作如下:

  • ingest pipeline:
*   判断请求是否符合某个ingest pipeline的pattern;
    
*   数据预处理,格式调整、添加字段等;
    
*   如果当前角色没有ingest角色,发送给有ingest的节点
  • 自动创建索引:判断索引是否存在,没有就创建索引

  • 设置routing:获取请求URL或mapping中的_routing,如果没有则使用_id,如果没有指定_id,则ES自动生成一个全局唯一ID

  • 构建shardBulkRequest:

由于Bulk Request中包含多种(Index/Update/Delete)请求,这些请求分别需要到不同的shard上执行,因此协调节点, 会将请求按照shard分开,同一个shard上的聚合在一起,构建BulkRequest

  • 将请求发送给primary shard(写操作)

  • 等待primary shard(返回信息)

3. primary shard

Primary的请求入口是PrimaryOperationTransportHandler的MessageReceived,当接收到请求后,具体操作如下:

  • 判断请求类型:

    • 遍历Bulk请求中的各个子请求,根据不同的操作类型跳转不同的处理逻辑
  • 将Update操作转换成Index/Delete请求:

    • 先获取当前文档内容,与Update请求内容合并生成新的文档,然后将Update请求变成Index请求,此处文档设置Version v1
  • Parse Doc:解析文档的各个字段,并添加如_uid等ES相关的系统字段

  • 更新mapping:

    • 对于新增字段会根据dynamic mapping或dynamic template生成对应的mapping, 如果mapping中有dynamic mapping相关设置则按设置处理,如忽略或抛出异常
  • 获取sequence ID和Version:

从SequcenceNumberService获取一个sequenceIDVersionSequcenID用于初始化LocalCheckPoint, version是根据当前Versoin+1用于防止并发写导致数据不一致。

  1. 写入Lucene:

这一步开始会对文档uid加锁,然后判断uid对应的version v2和之前update转换时的version v1是否一致, 不一致则返回第二步重新执行。 如果version一致,如果同id的doc已经存在,则调用lucene的updateDocument接口,

如果是新文档则调用lucene的addDoucument. 这里有个问题,如何保证Delete-Then-Add的原子性,

ES是通过在Delete之前加上已refresh锁,禁止被refresh,只有等待Add完成后释放了Refresh Lock, 这样就保证了这个操作的原子性。

写入transLog:

写入Lucene的Segment后,会以key value的形式写Translog, Key是Id, Value是Doc的内容。当查询的时候,如果请求的是GetDocById则可以直接根据_id从translog中获取。 满足nosql场景的实时性。

flush translog:

默认情况下,translog要在此处落盘完成,如果对可靠性要求不高,可以设置translog异步, 那么translog的fsync将会异步执行,但是落盘前的数据有丢失风险。

  • 发送请求给replicas:

    • 将构造好的bulkrequest并发发送给各replicas,等待replica返回,这里需要等待所有的replicas返回,响应请求给协调节点。如果某个shard执行失败, 则primary会给master发请求remove该shard。这里会同时把sequenceID, primaryTerm, GlobalCheckPoint等传递给replica。
  • 等待replicas响应:

    • 当所有的replica返回请求时,更新primary shard的LocalCheckPoint。

4. replicas shard

  • Replica 请求的入口是在ReplicaOperationTransportHandler的messageReceived,当replica shard接收到请求时执行如下流程:

  • 判断操作类型 replica收到的写入请求只会有add和delete,因update在primary shard上已经转换为add或delete了。根据不同的操作类型执行对应的操作

  • Parse Doc

  • 更新mapping

  • 获取sequenceId和Version 直接使用primary shard发送过来的请求中的内容即可

  • 写如lucene

  • write Translog

  • Flush translog

五、client客户端中BulkProcessor详细使用

  • BulkProcessor设计模式

BulkProcessor将创建bulkRequest对象的过程和时机以及批量执行请求的过程和时机封装了起来, 我们不必手动去调用client.bulk()来执行批量请求,只需要将请求add到BulkProcessor中(BulkProcessor中维护一个bulkRequest), BulkProcessor“满了”就自动执行请求然后重新创建一个bulkRequest,以此循环往复,最后手动调用awaitClose执行所有请求并释放资源。

整个执行请求的过程和时机对于用户来说是完全透明的,我们不必关心什么时候执行请求以及具体怎么执行。 BulkProcessor.builder使用了构建者模式,将consumer和listener作为“原料”投入后调用build来定制一个BulkProcessor。

BulkRequest使用了类似于模板方法的模式理念,将创建BulkRequest和客户端执行请求这两步封装了起来, 用户只需要将请求add到BulkProcessor中即可。

BulkRequest,BulkProcessor对比总结

BulkRequest整个使用过程“循规蹈矩”,从创建BulkRequest到add请求到客户端执行请求按顺序走。

BulkProcessor中维护了一个BulkRequest对象。总的来说,BulkProcessor将创建BulkRequest和客户端执行请求这两步封装了起来,

用户只需要将请求add到BulkProcessor中即可,最后手动调用关闭完成整个请求。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
您好,C知道在这里,我可以帮助您解决Elasticsearch数据无法写入的问题。以下是一些可能的原因和解决方法: 1. 确认索引是否存在:在写入数据之前,确保您要写入索引已经存在。如果不存在,您可以使用Elasticsearch索引API来创建索引。 2. 检查集群健康状态:使用Elasticsearch的健康检查API(/_cluster/health)来确认集群的健康状态。如果集群处于黄色或红色状态,可能会导致数据写入失败。您可以根据返回的错误信息来诊断和解决问题。 3. 检查文档ID:在写入数据时,确保指定了唯一的文档ID。如果指定的文档ID已经存在,将会更新该文档而不是创建新的文档。如果没有指定文档ID,Elasticsearch将自动生成一个唯一的ID。 4. 检查索引设置:确保您的索引设置允许写入操作。您可以使用Elasticsearch索引设置API(/_settings)来检查和修改索引的设置。 5. 检查文档映射:确保您的索引映射允许写入操作。如果映射中某个字段不允许写入,那么写入操作将会失败。您可以使用Elasticsearch索引映射API(/_mapping)来检查和修改索引的映射。 6. 检查存储空间:确保您的Elasticsearch集群有足够的存储空间来存储新的数据。如果磁盘空间已满,写入操作将会失败。 如果您仍然遇到问题,请提供更多的信息,例如错误消息、Elasticsearch版本以及相关的配置信息,以便更好地帮助您解决问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值