深入探索Elasticsearch数据写入黑箱

        Elasticsearch 数据写入涉及多个步骤过程,以此来确保数据能够高效、安全地存储在集群中。本文将带你探索 Elasticsearch 对数据的操作流程。

一、Index 流程

        创建索引时,一个 Document 文档需要先经过路由规则定位到主 Shard,发送这个 Doc 到主 Shard 上建索引,成功后在发送这个 Doc 到这个 Shard 的副本上建索引,等副本上建索引成功后才返回成功。

        那 Doc 是经过什么样的路由规则定位到 Shard 的呢?下面看一下路由规则。

        路由规则

深入探索Elasticsearch数据写入黑箱_第1张图片

        写入数据,数据应该保存在哪个主分片呢?有个计算叫路由Hash:hash(id) % 主分片数量,对主键进行hash计算,在模主分片数。

        写入数据时,在连接集群前是没有路由规则的,集群的信息还不知道。连接时可能连接任意机器,然后经过路由计算,协调节点将数据发送到正确的机器上进行数据写入,主分片写完后,在写入副本分片,然后才给用户返回写入成功。这样会影响性能,可以设置参数来设置数据一致性的程度,如 consistency 参数设置为 one,只要主分片状态 OK 就允许执行写入成功;设置为 all,必须主分片和所有副本分片的状态都没问题才执行成功;设置为 quorum,即大多数的分片副本状态为 OK,即为执行成功,默认为 quorum。

        查询数据,不一定从主分片查,还能从副本查询,那查询时如何确定从哪查呢?上图中的分片和副本数量可以保证数据在任何机器上都有,所有查询时查询数据时,查询哪台机器都能查询的到。这叫做分片控制,什么叫分片控制:用户可以访问任何一个节点都能获取数据,这个节点称之为协调节点,这个节点可能会很忙,会把请求转发到另外的节点进行查询。一般的策略是轮询。

        写入流程

        在每一个Shard中,写入分为两部分,先写入Lucene,在写入TransLog。

深入探索Elasticsearch数据写入黑箱_第2张图片

        写入请求到达 Shard 后,先写 Lucene 文件,创建好索引,此时索引还在内存里面,接着去写 TransLog,写完 TransLog 后,刷新 TransLog 数据到磁盘上,写磁盘成功后,请求返回给用户。这里有几个关键点,一是和数据库不同,数据库是先写 CommitLog,然后再写内存,而Elasticsearch 是先写内存,最后才写 TransLog,这样的原因:

  1. Lucene 的内存写入会有很复杂的逻辑,很容易失败,比如分词,字段长度超过限制等,比较重,为了避免 TransLog 中有大量无效记录,减少 recover 的复杂度和提高速度,所以就把写 Lucene 放在了最前面。
  2. 写 Lucene 内存后,并不是可被搜索的,需要通过Refresh把内存的对象转成完整的Segment 后,然后再次 reopen 后才能被搜索,一般这个时间设置为1秒钟,导致写入Elasticsearch 的文档,最快要1秒钟才可被从搜索到,所以 Elasticsearch 在搜索方面是NRT(Near Real Time)近实时的系统。
  3. 当 Elasticsearch 作为 NoSQL 数据库时,查询方式是 GetById,这种查询可以直接从TransLog 中查询,这时候就成了 RT(Real Time)实时系统。
  4. 每隔一段比较长的时间,比如30分钟后,Lucene 会把内存中生成的新 Segment 刷新到磁盘上,刷新后索引文件已经持久化了,历史的 TransLog 就没用了,会清空掉旧的TransLog。

        在创建索引时,应考虑合适的主分片数(primary shards)和副本分片数(replicas)。一旦索引创建完成,其分片数量就不可更改。

        Elasticsearch提供了近实时搜索能力,但并非强一致性的系统。对于需要立即反映最新写入状态的应用场景,需要了解其最终一致性的特点。

二、更新流程

        更新数据时会有并发问题,同一条数据两请求同时更新,那到底以哪个为准呢?

        Elasticsearch 采用了乐观锁。Elasticsearch 是分布式的,当文档创建、更新或删除时,新版本的文档必须复制到集群中的其他节点。Elasticsearch 也是异步和并发的。这意味着这些复制请求被并行发送,并且到达目的地时也许顺序是乱序的。Elasticsearch 需要一种方法确保文档的旧版本不会覆盖新版本。

        每个文档都有一个 _version(版本号),当文档被修改时版本号递增。Elasticsearch 使用这个version 来确保变更正确的顺序执行。如果旧版本的数据在新版本之后到达,可以被简单的忽略。

        Lucene 不支持部分字段的 update,所以需要再 Elasticsearch 中实现该功能,流程如下:

  1. 客户端通过 Elasticsearch REST API 发送一个更新请求到集群中的任意节点,收到 update 请求后,从 Segment 或者 TransLog 中读取 id 完整的Doc,记录版本号V1。
  2. 将版本 V1 的全量 Doc 和请求中的部分字段 Doc 合并为一个完整的 Doc,同时更新内存中的 versionMap,获取到完整 Doc 后,update 请求就变成了 Index 请求。
  3. 加锁。
  4. 再次从 versionMap 中读取该 id 最大版本号 V2,如果 versionMap 中没有,则从 Segment或 TransLog 中读取,这里基本都会从 versionMap 中获取。
  5. 检查版本是否冲突(V1==V2),如果冲突则回退到开始”Update Doc“阶段,重新执行。如果不冲突,则执行罪行的add请求。
  6. 在 Index Doc 阶段,首先将 version + 1 得到 V3,再将 Doc 加入到 Lucene 中去,Lucene中会先删除同id下已经存在的 doc id,然后再增加 doc。写入 Lucene 成功后,将当前 V3更新到 versionMap中。
  7. 释放锁,部分更新流程结束。
  8. 更新操作完成后,主分片会将更新同步给相应的副本分片,副本分片同样完成更新并确认。

        更新时注意事项

  1. 不推荐直接替换整个文档来进行更新,而是尽可能地使用部分更新或脚本来修改特定字段。这有助于减少磁盘I/O和内存消耗,同时保持索引大小更小。
  2. 批量更新:如果需要更新大量文档,应考虑使用批量更新操作(如_bulk API),它能够显著提高性能并减少网络开销。不过要注意批量操作也有其限制,比如单次请求的大小以及系统资源消耗。
  3. 并发更新策略在高并发场景下,可能出现频繁的版本冲突。可以通过设置合理的重试策略(如retry_on_conflict参数)来处理此类问题。

        正确理解和运用Elasticsearch的数据更新机制对于保证数据的一致性和系统的稳定性至关重要。同时,针对不同场景采取合适的更新策略和最佳实践,有助于提升集群整体性能。

        总之,在Elasticsearch中操作数据时,要熟悉其 RESTful API,合理运用各种查询和更新机制,并根据实际需求采取合适的优化策略。

你可能感兴趣的:(elasticsearch,大数据,搜索引擎,es数据操作)