Elasticsearch Search API

官方文档
实现对es中存储的数据进行查询分析,endpoint_search,查询主要有两种形式:

  • URI Search:操作简便,方便通过命令行测试,仅包含部分查询语法
  • Request Body Search:es提供完备查询语法Query DSL(Domain Specific Language)
GET /bank/_search?q=gender:M
GET /bank/account/_search
{
  "query": {
    "match": {
      "gender": "M"
    }
  }
}   

URI Search详解

通过url query参数来实现搜索,常用参数如下:

  • q: 指定查询语句,语法为 Query String Syntax
  • df: q中不指定字段时默认查询的字段,如果不指定,es会查询所有字段
  • sort:排序
  • timeout:指定超时时间,默认不超时
  • from,size:用于分页
GET /myindex/_search?q=alfred&df=user&sort=age:asc&from=4&size=10&timeout=1s
#查询user字段包含alfred的文档,结果按照age升序排列,返回第5-14个文档,如果超过1s没有结束,则以超时结束

Query String Syntax

1.term与phrase

alfred way 等效于 alfred OR way
"alfred way" 词语查询,要求先后顺序

2.泛查询

alfred等效于在所有字段去匹配该term

3.指定字段

 name:alfred

4.Group分组指定,使用括号指定匹配的规则

(quick OR brown) AND fox
status:(active OR pending) title:(full text search)

5.布尔操作符

  • AND(&&),OR(||),NOT(!)

    name:(tom NOT lee) 注意大写,不能小写
    
  • + - 分别对应must和must_not

    name:(tom +lee -alfred)
    name:((lee && !alfred)||(tom && lee && !alfred))
    

    +在url中会被解析为空格,要使用encode后的结果才可以,为%2B

6.范围查询,支持数值和日志

  • 区间写法,闭区间用[],开区间用{}

    age: [1 TO 10]意为 1<=age<=10
    age: [1 TO 10}意为 1<=age<10
    age: [1 TO ]意为 age>=1
    age: [* TO 10]意为 age<=10
    
  • 算数符号写法

    age:>=1
    age:(>=1&&<=10)或者age:(+>=1 +<=10)
    

7.通配符查询代表一个字符,*代表0或多个字符

name:t?m
name:tom*

通配符匹配执行效率低,且占用较多内存,不建议使用
如无特殊需求,不要将?/*放在最前面

8.正则表达式

name:/[mb]oat/

9.模糊匹配 fuzzy query

name:roam~1
匹配与roam差一个character的词,比如foam roams等

10.近似度查询 proximity search

"fox quick"~5
以term为单位进行差异比较,比如"quick fox" "quick brown fox"都会被匹配

说明:

GET /myindex/_search?q=username:alfred way
{
  "profile": "true"
}
#其中alfred为指定username字段查询,way为泛查询

如果想在指定字段中查询,可以改为如下语句:
GET /myindex/_search?q=username:"alfred way"
GET /myindex/_search?q=username:(alfred way)

GET /myindex/_search?q=username:(alfred +way) (包含alfred或者包含way)+被解析为空格,需要改为如下形式
GET /myindex/_search?q=username:(alfred %2Bway)(必须包含way,可以有alfred)


userna:alfred AND way 和userna:(alfred AND way)的查询语法完全不同,前者包含一个泛查询
可以通过{"profile": "true"}查看查询语法。      

将查询语句通过http request body 发送到es,主要包含如下参数:

  • query: 符合Query DSL语法的查询语句
  • from,size
  • timeout
  • sort

Query DSL

Query DSL: 基于json定义的查询语言,主要包含如下两种类型:

  • 字段类查询:如term、match、range等,只针对某一字段进行查询
  • 复合查询:如bool查询等,包含一个或多个字段类查询或者复合查询语句

字段类查询:
字段类查询主要包括以下两类:

  • 全文匹配:针对text类型的字段进行全文检索,会对查询语句先进行分词处理,如match,match_phrase等query类型
  • 单词匹配:不会对查询语句做分词处理,直接去匹配字段的倒排索引,如term,terms,range等query类型

match查询示例:

GET my_index/_search
{
  "query": {
    "match": {
      "username": "alfred way"
    }
  }
}

查询流程如下所示:
这里写图片描述
通过operator参数可以控制单词间的匹配关系,可选项为or和and(默认为or)
通过minimum_should_match参数可以控制需要匹配的单词数

复合查询:

  • Constant Score Quer:该查询将其内部的插叙结果文档得分都设定为1或者boost的值
  • Bool Query:布尔查询由一个或多个布尔子句组成,包含filter、must、must_not、should

关于Query DSL的用法可以参考:Elasticsearch Query DSL

相关性算分

相关性算分是指文档与查询语句间的相关度,英文为relevance
通过倒排索引可以获取与查询语句相匹配的文档列表,那么如何将最符合用户查询需求的文档放到前列呢?本质是一个排序问题,排序依据是相关性算分

相关性算分的几个重要概念:

  • Term Frequency(TF): 词频,即单词在该文档中出现的次数。词频越高,相关度越高
  • Document Frequency(DF): 文档频率,即单词出现的文档数
  • Inverse Document Frequency(IDF):逆向文档频率,与文档频率相反,简单理解为1/DF。即单词出现的文档数越少,相关度越高。
  • Field-length Norm: 文档越短,相关性越高

ES目前主要有两个相关性算分模型:

  • TF/IDF模型
  • BM25模型:5.x之后的默认模型

这里写图片描述
这里写图片描述

可以通过explain参数来查看具体的计算方法,但要注意:es的算分是按照shard进行的,即shard分数计算时互相独立的,所以在使用explain的时候注意分片数;可以通过设置索引的分片数为1来避免这个问题。

{
  "explain": true,
  "query": {
    "match": {
      "FIELD": "TEXT"
    }
  }
}

相关性算分问题
相关性算分在shard与shard间是相互独立的,也就意味着同一个Term的IDF等值在不同shard上是不同的。文档的相关性算分和它所处的shard相关,在文档数量不多时,会导致相关性算分严重不准的情况发生

解决思路有两个:

  1. 设置分片数为1个,从根本上排除问题,在文档数量不多的时候可以考虑该方案,比如百万到千万级别的文档数量
  2. 使用DFS Query-then-Fetch查询方式

DFS Query-then-Fetch是在拿到所有文档后再重新完整的计算一次相关性算分,耗费更多的cpu和内存,执行性能也比较低下,一般不建议使用。使用方式如下:

GET my_index/_search?search_type=dfs_query_and_fetch
{
  "query": {
    "match": {
      "FIELD": "TEXT"
    }
  }
}   

排序

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

GET /bank/account/_search
{
  "sort": [
    {
      "age": {
        "order": "desc"
      }
    }
  ]
}   

按照字符串比较比较特殊,因为es有textkeyword两种类型。针对text类型排序,如下所示:
这里写图片描述
正确用法:使用keyword类型的子字段”username.keyword

GET my_index/_search
{
  "sort": [
    {
      "username.keyword": {
        "order": "desc"
      }
    }
  ]
}

排序的过程实质是对字段原始内容排序的过程,这个过程中倒排索引无法发挥作用,需要用到正排索引,也就是通过ID和字段可以快速得到字段原始内容。es对此提供了两种实现方式:

  • fielddata:默认禁用
  • doc values:默认启用,除了text类型
    这里写图片描述
    Fieldata: 默认是关闭的,可以通过api开启:此时字符串是按照分词后的term排序,往往结果很难符合预期,一般是在对分词做聚合分析的时候开启
PUT my_index/_mapping/doc
{
  "properties": {
    "username":{
      "type": "text",
      "fielddata": true
    }
  }
}       
#fielddata可以随时开启和关闭,只对text类型有效

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

PUT my_index1
{
  "mappings": {
    "doc": {
      "properties": {
        "username":{
          "type":"keyword",
          "doc_values": false
        }
      }
    }
  }
}

可以通过docvalue_fields字段获取fielddata或者doc values中存储的内容:

GET my_index/_search
{
  "docvalue_fields": [
    "username",
    "username.keyword",
    "age"
    ]
}
#指明需要的字段

分页与遍历

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

  • from/size
  • scroll
  • search_after

(一)from/size: 最常用分页方案:from指明开始位置;size指明获取总数

GET /my_index/_search
{
  "from": 2,
  "size": 10
}       

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

(二)Scroll:遍历文档集的api,以快照的方式来避免深度分页的问题
不能用来做实时搜索,因为数据不是实时的;尽量不要使用复杂的sort条件,使用_doc最高效;使用稍嫌复杂

第一步需要发起一个scroll search:
es在收到该请求后会根据查询条件创建文档Id合集的快照
这里写图片描述
第二步调用scroll search的api,获取文档集合:
不断迭代调用直到返回hits.hits数组为空时停止

POST _search/scroll
{
  "scroll": "5m",
  "scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAvbQFnE1WDhDNlJYVDFLMGQ5UHJJWk9RN3cAAAAAAAL2zxZxNVg4QzZSWFQxSzBkOVBySVpPUTd3AAAAAAAC9tEWcTVYOEM2UlhUMUswZDlQcklaT1E3dwAAAAAAAvgAFmdOYXZrZGFsU2VDc0tlRnVGdThKR1EAAAAAAAL4ARZnTmF2a2RhbFNlQ3NLZUZ1RnU4SkdR"
}

这里写图片描述

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

DELETE _search/scroll/_all

(三)Search_After: 避免深度分页的性能问题,提供实时的下一页文档获取功能
缺点是不能使用from参数,即不能指定页数;只能下一页,不能上一页;使用简单

第一步为正常的搜索,但要指定sort值,并保证值唯一
第二步为使用上一步最后一个文档的sort值进行查询
这里写图片描述

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

三种分页方法应用场景比较:
这里写图片描述

猜你喜欢

转载自blog.csdn.net/wfs1994/article/details/80770320