1 query和filter区别

在进行query的时候,除了完成匹配的过程,我们实际上在问"这个结果到底有多匹配我们的搜索关键词"。在所有的返回结果的后面都会有一个_score字段表示这个结果的匹配程度,也就是相关性。相关性越高的结果就越排在前面,相关性越低就越靠后。当两个文档的相关性相同的时候,会根据lucene内部的doc_id字段来排序,这个字段对于用户是不可见的也不能控制。

而在进行filter的时候,仅仅是在问"这个文档符是否符合要求",这仅仅是一个过滤的操作判断文档是否满足我们的筛选要求,不会计算任何的相关性。比如timestamp的范围是否在2019和2020之间,status状态是否是1等等。

在一个查询语句里面可以同时存在queryfilter,只不过只有query的查询字段会进行相关性_score的计算,而filter仅仅用来筛选。比如在下面的查询语句里面,只有title字段会进行相关性的计算,而下面的status只是为了筛选并不会计算相关性。

GET /_search
{
  "query": {
    "bool": {
      "must": [
        {"match": {"title": "Search"}}
      ],
      "filter": [
        {"term": {"state": 1}}
      ]
    }
  }
}

对于在实际应用中应该用query还是用filter需要根据实际的业务场景来看。如果你的产品的搜索只是需要筛选得到最后的搜索结果并不需要Elasticsearch的相关性排序(你可能自定义了其他的排序规则),那么使用filter就完全能够满足要求并且能够有更好的性能(filter不需要计算相关性而且会缓存结果);如果需要考虑文档和搜索词的相关性,那么使用query就是最好的选择。

2 相关性

Elasticsearch的相似度计算主要是利用了全文检索领域的计算标准——TF/IDF(Term Frequency/Inverted Document Frequency)也就是检索词频率反向文档频率

  1. TF(检索词频率):检索词在这个字段里面出现的频率越高,相关性越高。比如搜索词出现5次肯定比出现1次的文档相关性更高。
  2. IDF(反向文档频率):包含检索词的文档的频率越高,这个检索词的相关性比重越低。如果一个检索词在所有的文档里面都出现了,比如中文的,那么这个检索词肯定就不重要,相对应的根据这个检索词匹配的文档的相关性权重应该下降。
  3. 字段长度:注意这个字段是文档的里面被搜索的字段,不是检索词。如果这个字段的长度越长,相关性就越低。这个主要是因为这个检索词在字段内的重要性降低了,文档就相对来说不那么匹配了。

在复合查询里面,比如bool查询,每个子查询计算出来的评分会根据特定的公式合并到综合评分里面,最后根据这个综合评分来排序。当我们想要修改不同的查询语句的在综合评分里面的比重的时候,可以在查询字段里面添加boost参数,这个值是相对于1来说的。如果大于1则这个查询参数的权重会提高;如果小于1,权重就下降。

这个评分系统一般是系统默认的,我们可以根据需要定制化我们自己的相关性计算方法,比如通过脚本自定义评分。

3 分词器

分析器是针对text字段进行文本分析的工具。文本分析是把非结构化的数据(比如产品描述或者邮件内容)转化成结构化的格式从而提高搜索效率的过程,通常在搜索引擎里面应用的比较多。

text格式的数据和keyword格式的数据在存储和索引的时候差别比较大keyword会直接被当成整个字符串保存在文档里面,而text格式数据,需要经过分析器解析之后,转化成结构化的文档再保存起来。比如对于the quick fox字符串,如果使用keyword类型,保存直接就是the quick fox,使用the quick fox作为关键词可以直接匹配,但是使用the或者quick就不能匹配;但是如果使用text保存,那么分析器会把这句话解析成thequickfox三个token进行保存,使用the quick fox就无法匹配,但是单独用thequickfox三个字符串就可以匹配。所以对于text类型的数据的搜索需要格外注意,如果你的搜索词得不到想要的结果,很有可能是你的搜索语句有问题。

分析器的工作过程大概分成两步:

  1. 分词(Tokenization):根据停止词把文本分割成很多的小的token,比如the quick fox会被分成thequickfox,其中的停止词就是空格,还有很多其他的停止词比如&或者#,大多数的标点符号都是停止词
  2. 归一化 (Normalization):把分隔的token变成统一的形式方便匹配,比如下面几种
  • 把单词变成小写,Quick会变成quick
  • 提取词干,foxes变成fox
  • 合并同义词,jumpleap是同义词,会被统一索引成jump

Elasticsearch自带了一个分析器,是系统默认的标准分析器,使用标准分析器, 大多数情况下都能够有不错的分析效果。用户也可以定义自己的分析器,用于满足不同的业务需求。
想要知道某个解析器的分析结果,可以直接在ES里面进行分析,执行下面的语句就行了:

POST /_analyze
{
  "analyzer": "standard",
  "text": "1 Fire's foxes"
}

返回的结果是:

{
  "tokens" : [
    {
      "token" : "1",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "<NUM>",
      "position" : 0
    },
    {
      "token" : "fire's",
      "start_offset" : 2,
      "end_offset" : 8,
      "type" : "<ALPHANUM>",
      "position" : 1
    },
    {
      "token" : "fox",
      "start_offset" : 9,
      "end_offset" : 12,
      "type" : "<ALPHANUM>",
      "position" : 2
    }
  ]
}

返回的tokens内部就是所有的解析结果,token表示解析的词语部分,start_offsetend_offset分别表示token在原text内的起始和终止位置,type表示类型,position表示这个token在整个tokens列表里面的位置。