《ElasticStack从入门到实践》学习笔记6

六、ElasticSearch中Search的运行机制

    Search执行的时候,实际分为两个步骤执行:

        ---> Query阶段:搜索

        ---> Fetch阶段:获取

    1、Query—Then—Fetch:

        假设集群my_cluster中存在三个节点node1、node2、node3,其中master为node1,其余的为data节点。

        1)Query阶段:

            假设node3接收到Client发送的查询请求之后,先进行query:

            A、node3在6个主副分片上随机选择3个分片,发送search request;

            B、被选中的3个分片分别执行查询并排序,返回from+size个文档id和排序值;

            C、node3整合3个分片返回的from+size个文档id,根据排序值排序选取from到from+size的文档id。

        2)Fetch阶段:

            node3根据Query阶段获取的文档id列表去对应shard上获取详情数据:

            A、node3向相关分片发送multi_get请求;

            B、3个分片返回文档详细数据;

            C、node3拼接返回的结果返回给Client。

    2、相关性算分:

        相关性算分在shard和shard之间是相互独立的。也就意味着:同一个单词term在不同的shard上的TDF等值也可能是不同的。得分与shard有关。当文档数量不多是,会导致相关性算分严重不准的情况发生

        解决方案:

            1)设置分片数为1个,从根本上排除问题。(此方案只适用于百万/少千万级的少量数据)

            2)使用DFS Query-then-Fetch查询方式。

        DFS Query-then-Fecth:

            在拿到所有文档后,再重新进行完整的计算一次相关性得分,耗费更多的CPU和内存,执行性能也较低。所以也不推荐。

#使用DLS Query-then-Fetch进行查询:
GET my_index/_search?search_type=dfs_query_then_fetch
{
 "query":{
  "match":{
   ...
  }
 }
}

    3、排序相关:

        默认采用相关性算分结果进行排序。可通过sort参数自定义排序规则,如:

#使用sort关键词进行排序
GET my_index/_search
{
 "sort":{    #关键词
  "birth":"desc"
 }
}
#或使用数组形式定义多字段排序规则
GET my_index/_search
{
 "sort":[    #使用数组
  {
   "birth":{
    "order":"asc"
   }
  },
  {
   "age":{
    "order":"desc"
   }
  }
 ]
}

        1)直接按数字/日期排序,如上例中birth。

        2)按字符串进行排序:字符串排序较特殊,因为在ES中有keyword和text两种:

            A、针对text类型排序:

#直接对text类型进行排序
GET my_index/_search
{
 "sort":{
  "username":"desc"    #针对username字段进行倒序排序
 }
}

                返回结果:

《ElasticStack从入门到实践》学习笔记6_第1张图片

            B、针对keyword类型排序:                

#针对keyword进行排序
GET my_index/_search
{
 "sort":{
  "username.keyword":"desc"    #针对username的子类型keyword类型进行倒叙排序
 }
}

        3)关于fielddata和docvalues:

            排序的实质是对字段的原始内容排序的过程,此过程中倒排索引无法发挥作用,需要用到正排索引。即:通过文档ID和字段得到原始内容。ES提供2中实现方式:

            A、Fielddata。    默认禁用。

            B、DocValues。    默认启用,除了text类型。

            Fielddata  对比  DocValues。

对比 Fielddata DocValues
创建时机 搜索时即时创建 创建索引时创建,和倒排索引创建时间一致
创建位置 JVM Heap 磁盘
优点 不占用额外磁盘空间 不占用Heap内存
缺点 文档较多时,同时创建会花费过多时间,占用过多Heap内存 减慢索引的速度,占用额外的磁盘空间

        4)Fielddata的开启:

            Fielddata默认关闭,可通过如下api进行开启,且在后续使用时随时可以开启/关闭:

#开启字段的fielddata设置
PUT my_index/_mapping/doc
{
 "properties":{
  "username":{
   "type":"text",
   "fielddata":true    #关键词
  }
 }
}

            使用场景:一般在对分词做聚合分析的时候开启

        5)Docvalues的关闭:

            Docvalues默认开启,可在创建索引时关闭,且之后不能再打开,要打开只能做reindex操作。

#关闭字段的docvalues设置
PUT my_index
{
 "mappings":{
  "doc":{
   "properties":{
    "username":{
     "type":"keyword",
     "doc_values":false    #关键词
    }
   }
  }
 }
}

            使用场景:当明确知道,不会使用这个字段排序或者不做聚合分析的时候,可关闭doc_values,减少磁盘空间的占用。

    4、分页与遍历:

        ES提供了三种方式来解决分页和遍历的问题:

            from/size,scroll,search_after

        1)from/size:

            from:指明开始位置;

            size:指明获取总数

#使用from——size
GET my_index/_search
{
 "from":1,    #从第2个开始搜索
 "size":2     #获取2个长度
}

            A、此时产生了一个经典的问题,也是分布式文件系统必定面对的问题:深度分页

                问题:如何在数据分片存储的情况下, 获取前1000个文档?

                答案:先从每个分片上获取前1000个文档, 然后由处理节点聚合所有分片的结果之后,再排序获取前1000个文档。

                此时页数越深,处理的文档就越多,占用的内存就越大,耗时就越长。这就是深度分页问题。

                为了尽量避免深度分页为题,ES通过设定index.max_result_window限定最多到10000条数据。

            B、在设计分页系统时,有一个分页数十分重要:

                total_page=(total + page_size -1) / page_size

                总分页数= (文档总数+认为设定的文档大小-1)   / 人为设定的文档大小

                但是在搜索引擎中的意义并不大,因为如果排在前面的结果都不能让用户满意,那么越往后,越不能让用户满意。

        2)scroll:

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

            A、不能用来做实时搜索,因为数据不是实时的;

            B、尽量不用复杂的sort条件,使用_doc最高效;

            C、使用比较复杂。

            步骤:

                 a、发起一个scroll search:

#发起一个scroll search
GET my_index/_search?scroll=5m    #该快照的有效时间为5min
{
 "size"1    #指明每次scroll返回的文档数
}

                 会返回后续会用到的_scroll_id:

《ElasticStack从入门到实践》学习笔记6_第2张图片

            b、调用scroll search 的api,获取文档集合,不断迭代至返回hits数组为空时停止:

POST _search/scroll
{
 "scroll":"5m",    #指明有效时间
 "scroll_id":"xxxxxx"    #上一步返回的_scroll_id
}

            之后不断返回新的_scroll_id,使用新的_scroll_id进行查询,直到返回数组为空。

            当不断的进行迭代,会产生很多scroll,导致大量内存被占用,可以通过clear api进行删除:

#使用clear api对scroll进行删除
DELETE /_search/scroll
{
 "scroll_id":[
   "xxxxxx",    #_scroll_id
   "xxxxxx",    #_scroll_id
   ......
 ]
}

#删除所有的scroll
DELETE /_search/scroll/_all

        3)search_after:

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

            缺点:不能使用from参数,即:不能指定页数。且只能下一页,不能上一页。

            使用步骤:

               A、第一步:正常搜索,但是要指定sort值,并保证值唯一:

#第一步,正常搜索
GET my_index/_search
{
 "size":1,
 "sort":{
  "age":"desc",
  "_id":"desc"
 }
}

               B、第二步:使用上一步最后一个文档的sort值进行查询:

#第二步,使用sort值进行查询
GET my_index/_search
{
 "size":1,
 "search_after":[28,"2"],    #28,"2",是上一次搜索返回的sort值
 "sort":{
  "age":"desc",
  "_id":"desc"
 }
}

        4)如何避免深度分页问题:

            这个问题目前连google都没能解决,所以只能最大程度避免,通过唯一排序值定位每次要处理的文档数都控制在size内:

            应用场景:

            A、from/size:需实时获取顶部的部分文档,且需自由翻页(实时);

            B、scroll:需全部文档,如:导出所有数据的功能(非实时);

            C、search_after:需全部文档,不需自由翻页(实时)。

 

你可能感兴趣的:(大数据相关,ElasticStack,数据分析,搜索引擎)