基础知识

分片和分段

“分片”是Lucene的一个索引。 它本身就是一个功能齐全的搜索引擎。每个分片包含多个“分段”,其中分段是倒排索引。

分段内的doc数量上限是2的31次方。默认每秒都会生成一个segment文件。

在分片中搜索将依次搜索每个片段,然后将其结果合并到该分片的最终结果中。分段是不可变的。更新文档时,它实际上只是将旧文档标记为已删除,并为新文档编制索引。合并过程还会清除这些旧的已删除文档。

document路由算法

  • shard = hash(文档id) % number_of_primary_shards;即用 主分片数文档id的hash 取余。
  • 另外也可以手动指定routing value。可以保证某一类document一定被路由到一个shard上去,那么在后续进行应用级别的负载均衡,以及提升批量读取的性能的时候是很有帮助的。

primary shard 能随时改变吗

  • 不能,如果改变了主分片的数目。那么使用路由算法就无法计算出原来分片,间接导致了数据丢失。

replica shard从分片可以改变吗

  • 备份分片数是可以修改的,因为备份分片和路由算法无关。

写数据过程

  • 客户端选择一个 node 发送请求过去,这个 node 就是 coordinating node(协调节点)。
  • coordinating node 对 document 进行路由,将请求转发给对应的 node(有 primary shard)。
  • 实际的 node 上的 primary shard 处理请求,然后将数据同步到 replica node
  • coordinating node 如果发现 primary node 和所有 replica node 都搞定之后,就返回响应结果给客户端。

读数据过程

  • 可以通过 doc id 来查询,会根据 doc id 进行 hash,判断出来当时把 doc id 分配到了哪个 shard 上面去,从那个 shard 去查询。

  • 客户端发送请求到任意一个 node,成为 coordinate node
  • coordinate nodedoc id 进行哈希路由,将请求转发到对应的 node,此时会使用 round-robin轮询算法,在 primary shard 以及其所有 replica shard 中随机选择一个,让读请求负载均衡。
  • 接收请求的 node 返回 document 给 coordinate node
  • coordinate node 返回 document 给客户端。

搜索数据过程

你根据 java 关键词来搜索,将包含 javadocument 给搜索出来

  • 客户端发送请求到一个 coordinate node
  • 协调节点将搜索请求转发到所有的 shard 对应的 primary shardreplica shard,都可以。
  • query phase:每个 shard 将自己的搜索结果(其实就是一些 doc id)返回给协调节点,由协调节点进行数据的合并、排序、分页等操作,产出最终结果。
  • fetch phase:接着由协调节点根据 doc id 去各个节点上拉取实际的 document 数据,最终返回给客户端。

写请求是写入 primary shard,然后同步给所有的 replica shard;

读请求可以从 primary shard 或 replica shard 读取,采用的是随机轮询算法。

写数据底层原理

  • 文档先写入内存buffer,在 buffer 里的时候数据是搜索不到的;同时将数据写入 translog 日志文件。
  • 然后会有一个refresh的操作,默认每隔1秒钟就会将buffer中的数据刷入到操作系统缓存os cache中并清空buffer,这时候数据就能被搜索到了。所以,es是近实时的,数据写入到可以被搜索,默认是1秒。此外每隔5s还会将os cache中数据记录到translog中。
  • 一直重复以上步骤直到translog日志文件达到一定长度或者30分钟后就会执行commit操作。首先会在磁盘中记录一个commit point(保存了当前Shard成功写入磁盘的所有segment),然后os cache中的所有segment file缓存都会被fsync强制刷到磁盘上,最后清空并创建一个新的translog。
  • 另外因为每秒都会产生一个segment文件,导致每次搜索都要遍历所有segment。因此会在后台执行segment的merge操作,被标记为deleted的document也会被彻底物理删除。具体就是会选择一些相似大小的segment合并成一个大的segment,然后写一个新的commit point加入新的分段并移除旧的分段,最后将新的segment打开以供搜索并删除旧的分段。

image-20210909140141060

refresh Index会刷到所有从节点吗

  • 是刷在主节点的,然后refresh可以配置为true和wati_for和false
    • true表示更新数据之后,立刻对相关的分片(包括副本) 刷新,这个刷新操作保证了数据更新的结果可以立刻被搜索到。
    • 这个参数表示,刷新不会立刻进行,而是等待一段时间才刷新 ( index.refresh_interval),默认时间是 1 秒。
    • refresh 的默认值,更新数据之后不立刻刷新,在返回结果之后的某个时间点会自动刷新,也就是随机的,看es服务器的运行情况。

为什么 es 是准实时的

数据先写入内存 buffer,然后每隔 1s,将数据 refresh 到 os cache,到了 os cache 数据就能被搜索到(所以我们才说 es 从写入到能被搜索到,中间有 1s 的延迟)。每隔 5s,将数据写入 translog 文件(这样如果机器宕机,内存数据全没,最多会有 5s 的数据丢失),translog 大到一定程度,或者默认每隔 30mins,会触发 commit 操作,将缓冲区的数据都 flush 到 segment file 磁盘文件中。数据写入 segment file 之后,同时就建立好了倒排索引。

综上,其实 es 第一是准实时的,数据写入 1 秒后可以搜索到;可能会丢失数据的。有 5 秒的数据,停留在 buffer、translog os cache、segment file os cache 中,而不在磁盘上,此时如果宕机,会导致 5 秒的数据丢失。

删除/更新数据底层原理

  • 如果是删除操作,commit 的时候会生成一个 .del 文件,里面将某个 doc 标识为 deleted 状态,那么搜索的时候根据 .del 文件就知道这个 doc 是否被删除了。

  • 如果是更新操作,就是将原来的 doc 标识为 deleted 状态,然后新写入一条数据。

    • buffer 每 refresh 一次,就会产生一个 segment file,所以默认情况下是 1 秒钟一个 segment file,这样下来 segment file 会越来越多
    • 此时会定期执行 merge。每次 merge 的时候,会将多个 segment file 合并成一个。同时这里会将标识为 deleted 的 doc 给物理删除掉,然后将新的 segment file 写入磁盘。这里会写一个 commit point,标识所有新的 segment file,然后打开 segment file 供搜索使用,同时删除旧的 segment file