zoukankan      html  css  js  c++  java
  • Elasticsearch系列---深入全文搜索

    概要

    本篇介绍怎样在全文字段中搜索到最相关的文档,包含手动控制搜索的精准度,搜索条件权重控制。

    手动控制搜索的精准度

    搜索的两个重要维度:相关性(Relevance)和分析(Analysis)。

    相关性是评价查询条件与结果的相关程度,并对相关程度进行排序,一般使用TF/IDF方法。

    分析是指将索引文档与查询条件规范化的一个过程,目的是建立倒排索引时,尽可能地提升召回率。

    match查询原理

    匹配查询match是核心查询语法,它的主要应用场景就是全文搜索,我们举一个示例:

    GET /music/children/_search
    {
      "query": {
        "match": {
          "name": "wake"
        }
      }
    }
    

    Elasticsearch执行的步骤:

    1. 检索字段类型:match的字段name为text类型,是一个analyzed的字段,那么查询条件的字符串也应该被analyzed。
    2. 分析查询字符串:将查询字符串"wake"传入分词器中(与mapping的分词器一致),因为只有一个单词,所以match最终执行的是单个底层的term查询。
    3. 查找匹配文档:用term倒排索引中查找wake然后获取一组包含该词的文档。
    4. 为每个文档评分:用term查询计算每个文档相关度评分,即TF、IDF、length norm算法。

    得到的结果如下:

    {
      "took": 1,
      "timed_out": false,
      "_shards": {
        "total": 5,
        "successful": 5,
        "skipped": 0,
        "failed": 0
      },
      "hits": {
        "total": 1,
        "max_score": 0.2876821,
        "hits": [
          {
            "_index": "music",
            "_type": "children",
            "_id": "2",
            "_score": 0.2876821,
            "_source": {
              "id": "a810fad4-54cb-59a1-9b7a-82adb46fa58d",
              "author": "John Smith",
              "name": "wake me, shark me",
              "content": "don't let me sleep too late, gonna get up brightly early in the morning",
              "language": "english",
              "tags": "enlighten",
              "length": 55,
              "isRelease": true,
              "releaseDate": "2019-12-21"
            }
          }
        ]
      }
    }
    

    因为样本数据的问题,暂时只有一条文档匹配。

    搜索name中包含"you"或"sunshine"的文档

    GET /music/children/_search
    {
      "query": {
        "match": {
          "name": "you sunshine"
        }
      }
    }
    

    搜索name中包含"you"和"sunshine"的文档

    GET /music/children/_search
    {
      "query": {
        "match": {
          "name": {
            "query": "you sunshine",
            "operator": "and"
          }
        }
      }
    }
    

    搜索精准度控制的第一步:使用and关键字。如果希望所有搜索关键字都要匹配,可以用and来实现。

    搜索"you"、"my"、"sunshine"、"teeth" 4个关键字中,至少包含3个的文档

    GET /music/children/_search
    {
      "query": {
        "match": {
          "name": {
            "query": "you my sunshine teeth",
            "minimum_should_match": "75%"
          }
        }
      }
    }
    

    搜索精准度控制的第二步:指定至少匹配其中的多少个关键字,才能作为结果返回

    bool组合多个搜索条件

    用bool组合,可以完成更加个性化的搜索需求,例如我们查找名称包含"sunshine",但不包含"teeth",允许出现"you"、"my"关键字,示例如下:

    GET /music/children/_search
    {
      "query": {
        "bool": {
          "must":     { "match": { "name": "sunshine" }},
          "must_not": { "match": { "name": "teeth"  }},
          "should": [
                      { "match": { "name": "my" }},
                      { "match": { "name": "you"   }}
          ]
        }
      }
    }
    

    should对相关度评分计算的影响

    以上面的bool为例子,我们只讨论匹配的文档,按自己的理解,对文档进行粗略排名:

    1. 最符合搜索条件的

    文档中同时包含should中的"my"、"you"两个关键字。

    1. 很符合搜索条件的

    文档中包含should中的"my"或"you"两个关键字的其中一个。

    1. 符合搜索条件的

    文档中不包含should中的"my"和"you"。

    像must not这种硬性条件,不匹配都不会出现在结果集里,主要起到排除文档作用,不参与评分计算。但should也能影响相关度评分,匹配得越多,评分就越高。

    bool查询会为每个文档计算相关度评分_score ,再将所有匹配的 must 和 should 语句的分数 _score 求和,最后除以 must 和 should 语句的总数。

    这里不详细讲解评分计算的具体细节和分数,了解should对其有影响即可。

    should匹配原则

    如果查询条件中有must存在,那么should匹配的数量不作要求;如果没有must,则should必须要匹配一个,要不然全是should条件的,所有文档都能匹配,就失去了搜索的意义。

    如果带上minimum_should_match,那么就能做更精细的控制,可以指定必须要匹配几个should,才能返回结果集,如下两个示例是等同的:

    GET /music/children/_search
    {
      "query": {
        "match": {
          "name": {
            "query": "you my sunshine teeth",
            "minimum_should_match": "75%"
          }
        }
      }
    }
    
    GET /music/children/_search
    {
      "query": {
        "bool": {
          "should": [
            { "match": { "name": "you" }},
            { "match": { "name": "my"   }},
            { "match": { "name": "sunshine"   }},
    	    { "match": { "name": "teeth"   }}
          ],
          "minimum_should_match": 3 
        }
      }
    }
    

    多词查询的底层原理

    上一节当中我们提到的多词match的查询,Elasticsearch会将多词的term查询转换为bool查询,or查询使用should替代, and查询使用must,我们回顾一下上一节的示例:

    or查询

    下面两个查询是等价的

    GET /music/children/_search
    {
      "query": {
        "match": {
          "name": "you sunshine"
        }
      }
    }
    
    GET /music/children/_search
    {
      "query": {
        "bool": {
          "should": [
            {"term": {"name": "you"}},
            {"term": {"name": "sunshine"}}
          ]
        }
      }
    }
    

    and查询

    下面两个查询也是等价的

    GET /music/children/_search
    {
      "query": {
        "match": {
          "name": {
            "query": "you sunshine",
            "operator": "and"
          }
        }
      }
    }
    
    GET /music/children/_search
    {
      "query": {
        "bool": {
          "must": [
            {"term": {"name": "you"}},
            {"term": {"name": "sunshine"}}
          ]
        }
      }
    }
    

    minimum_should_match语法

    下面两个查询仍然是等价的

    GET /music/children/_search
    {
      "query": {
        "match": {
          "name": {
            "query": "you my sunshine teeth",
            "minimum_should_match": "75%"
          }
        }
      }
    }
    
    GET /music/children/_search
    {
      "query": {
        "bool": {
          "should": [
            { "match": { "name": "you" }},
            { "match": { "name": "my"   }},
            { "match": { "name": "sunshine"   }},
    	    { "match": { "name": "teeth"   }}
          ],
          "minimum_should_match": 3 
        }
      }
    }
    

    minimum_should_match的值可以改,也会根据实际的条件来换算,比如我写个75%,但实际的搜索词条就只有3个,那么minimum_should_match的值就会变成66.6%,即至少需要匹配2条。

    查询语句权重控制

    bool查询里的多条件并列,默认权重是一样的,但实际的搜索当中,我们可能会特别某一些关键词给予特殊的关注,希望匹配关注度高的文档排序能靠前一些,boost语法可以帮助我们实现这一需求。

    boost默认是1,可以在查询语句中自行设置,如下示例,我希望sunshine的权重要大一些:

    GET /music/children/_search
    {
      "query": {
        "bool": {
          "must": {
            "match": {
              "name": {
                "query": "sunshine",
                "boost": 2
              }
            }
          },
          "should": [
            {
              "match": {
                "name": "my"
              }
            },
            {
              "match": {
                "name": "you"
              }
            }
          ]
        }
      }
    }
    

    在查询结果里可以看到,符合条件的文档的_score值变得更高一些。boost值的设置不是简单的线性增长,boost设置为2不表示_score也会简单的翻一倍,boost值在计算时有归一化处理,但具体的计算数值及过程不在此篇作详细的解释。

    评分计算的小问题

    我们的演示环境是单node多shard模式的,有些示例发现评分会出现特别高的现象,给人感觉不是很准确,这是为什么呢?

    不准确的原因

    我们简单回顾一下评分计算的几个算法:TF、IDF、Length Norm,其中IDF本意上是在所有的document中,搜索关键词的出现次数,实际上IDF默认在本地shard执行的次数统计,一个shard只包含部分数据,不能代表所有数据,这样做比较高效,但会带来误差,也是造成结果不准确的原因。

    演示环境中出现较大偏差,还有一个原因是演示数据相对较少,如果只有一个document,并且该document符合查询条件,那么IDF的分数就会变得很高。

    解决办法

    1. 生产环境数据量相对较大,默认使用_id进行路由,在概率分布下,其实每个shard的数据基本上比较均匀,不用太担心演示环境的这种情况。
    2. 演示环境可以将primary shard设置为1,只有一个shard,那IDF的值肯定是对的。
    3. 演示环境进行搜索时,带上search_type=dfs_query_then_fetch参数,会将local IDF取出来计算global IDF。注意性能问题,生产上禁用。

    小结

    本篇主要介绍了全文搜索几种手动控制精度的方式:逻辑操作符变换、should命中率设置、权重调整等手段,最后对评分计算的小问题进行的简单描述,谢谢。

    专注Java高并发、分布式架构,更多技术干货分享与心得,请关注公众号:Java架构社区
    可以扫左边二维码添加好友,邀请你加入Java架构社区微信群共同探讨技术
    Java架构社区

  • 相关阅读:
    设计模式——简单工厂模式
    异常信息ASM ClassReader failed to parse class file的问题解决
    freemarker学习笔记
    java实现邮箱验证的功能
    Quartz学习——Quartz简单入门Demo(二)
    Quartz大致介绍(一)
    深入理解Java线程池:ScheduledThreadPoolExecutor
    抢火车票引发的思考
    CGLib动态代理引起的空指针异常
    从原理上搞定编码(四)-- Base64编码
  • 原文地址:https://www.cnblogs.com/huangying2124/p/12400345.html
Copyright © 2011-2022 走看看