Skip to content

ES - Search的运行机制

Search执行的时候实际分两个步骤运作的

Query阶段

image-20221209131318475

  • node3在接收到用户的search请求后,会先进行Query阶段(此时是CoordinatingNode角色)
    • node3在6个主副分片中随机选择3个分片,发送search request
    • 被选中的3个分片会分别执行查询并排序,返回from+size个文档Id和排序值
    • node3 整合3个分片返回的from+size个文档id,根据排序值排序后选取from到from+size的文档id

Fetch 阶段

image-20221209132129072

  • node3根据Query阶段获取的文档Id列表去对应的shard 上获取文档详情数据
    • node3向相关的分片发送 multi_get请求
    • 3个分片返回文档详细数据
    • node3拼接返回的结果并返回给客户

相关性算分问题

  • 相关性算分在 shard 与 shard 间是相互独立的,也就意味着同一个 Term 的 IDF 等值在不同 shard 上是不同的。文档的相关性算分和它所处的 shard 相关
  • 在文档数量不多时,会导致相关性算分严重不准的情况发生(在不同的shard上面)
  • 解决思路有两个:
    • 设置分片数为1个,从根本上排除问题,在文档数量不多的时候可以考虑该方案,比如百万到千万级别的文档数量
    • 使用DFS Query-then-Fetch查询方式
      • DFSQuery-then-Fetch 是在拿到所有文档后再重新完整的计算一次相关性算分,耗费更多的 cpu 和内存,执行性能也比较低下,一般不建议使用。使用方式如下
      • image-20221209184301806

排序

es默认会采用相关性算分排序,用户可以通过设定sorting参数来自行设定排序规则

image-20221209184501893

按照字符串排序比较特殊,因为es有 text和 keyword 两种类型,针对 text 类型排序,如下所示:

image-20221209184652070

排序的过程实质是对字段原始内容排序的过程,这个过程中倒排索引无法发挥作用,需要用到正排索引,也就是通过文档id 和字段可以快速得到字段原始内容

  • es对此提供了2种实现方式
    • fielddata 默认禁用
    • doc values 默认启用,除了 text 类型
对比FieldDataDocValues
创建时机搜索时即时创建索引时创建,与倒排索引创建时机一致
创建位置JVM Heap磁盘
优点不会占用额外的磁盘资源不会占用 Heap 内存
缺点文档过多时,即时创建会花过多时间,占用过多Heap内存减慢索引的速度,占用额外的磁盘资源

Fielddata默认是关闭的,可以通过如下api开启︰

  • 此时字符串是按照分词后的term排序,往往结果很难符合预期·一般是在对分词做聚合分析的时候开启

image-20221209185423679

Doc Values 默认是启用的,可以在创建索引的时候关闭如果后面要再开启 doc values,需要做 reindex 操作作

image-20221209230535584

可以通过 docvalues_fields 获取fielddata 或者doc values中存储的内容

分页与遍历

es提供了3种方式来解决分页与遍历的问题

  • from/size
  • scroll
  • search_after

最常用的分页方案

  • from指明开始位置
  • size指明获取总数

image-20221209190312263

深度分页
  • 深度分页是一个经典的问题:在数据分片存储的情况下如何获取前1000个文档?
    • 获取从990~1000的文档时,会在每个分片上都先获取1000个文档,然后再由 Coordinating Node 聚合所有分片的结果后再排序选取前1000个文档
    • 页数越深,处理文档越多,占用内存越多,耗时越长。尽量避免深度分页,es 通过 index.max_result_window 限定最多到 10000条数据

image-20221209190714969

Scroll

  • 遍历文档集的 api,以快照的方式来避免深度分页的问题

    • 不能用来做实时搜索,因为数据不是实时的
    • 尽量不要使用复杂的 sort 条件,使用_doc 最高效
    • 使用稍嫌复杂
  • 第一步需要发起1个 scroll search,如下所示

    • es 在收到该请求后会根据查询条件创建文档 Id 合集的快照

image-20221209192235227

  • 第二步调用 scroll search 的 api,获取文档集合,如下所示不断迭代调用直到返回 hits.hits 数组为空时停止

image-20221209192510342

  • 过多的 scroll 调用会占用大量内存,可以通过 clear api 删除过多的 scroll 快照

image-20221209192814728

Search After

  • 避免深度分页的性能问题,提供实时的下一页文档获取功能

    • 缺点是不能使用from参数,即不能指定页数

    • 只能下一页,不能上一页

    • 使用简单

  • 第一步为正常的搜索,但要指定 sort 值,并保证值唯一

  • 第二步为使用上一步最后一个文档的 sort 值进行查询

image-20221209193152633

如何避免深度分页问题 ?
  • 通过唯一排序值定位将每次要处理的文档数都控制在 size 内

image-20221209193600739

类型场景
From/Size需要实时获取顶部的部分文档,且需要自由翻页
Scroll需要全部文档,如导出所有数据的功能
Search_After需要全部文档,不需要自由翻页

文章来源于自己总结和网络转载,内容如有任何问题,请大佬斧正!联系我