Elasticsearch搜索与排序经验小记

这篇具有很好参考价值的文章主要介绍了Elasticsearch搜索与排序经验小记。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

最近维护公司的APP搜索项目,在实际需求中,领导对搜索关心两方面,第一要搜出来,第二排序要符合人的搜索习惯,最近一段时间的搜索经验记录下来分享一下。

‘木瓜牛奶’ 是怎么搜出来的?

先来说说Elasticsearch基本的搜索,一段文字在es中能被搜索出来,抛开复杂的原理,简单理解成一句话:  搜索词的分词结果正好匹配上了内容的分词结果,这段内容就被搜索出来了。

这句话有两个核心,一个是分词,一个是匹配。

分词

对于第一个核心“分词”来说,它有两个需要分词,一个是搜索词的分词,一个是文档内容的分词(跟倒排索引有关,后面解释)。 先从搜索词说起,对于一个搜索词来说,它会被分词,根据分词器的不同,会有不同的分词结果。比如 “木瓜牛奶”,如果用 standard 分词,对于中文就比较呆板,一个字一个字被分词成 [“木”,“瓜”,“牛”,“奶”] 四个词,而如果用 ik_max_word 分词器,会被分词成 [“木瓜”,“牛奶”]。

下面json就展示了对于搜索词可以指定分词器,当然,只有match这种需要分词的行为才能指定分词器,如果你用term这种精确查询,是不让你用analyzer属性的。

{
  "query": {
    "bool": {
      "must": [

        {
          "match": {
            "channelSkuName": {
              "query": "木瓜牛奶",
              "analyzer": "ik_max_word"
            }
          }
        }
      ]
    }
  }
}

再看文档内容的分词,如果有一个商品名字段叫channelSkuName,值为 “好好吃的木瓜牛奶”,如果这个字段指定了ik_max_word 分词器,会被分词为[“好好”,“好吃”,“的”,“木瓜”,“牛奶”]。我们会发现,搜索词跟数据库内容被分词拆分之后,是有重合的内容的,[“木瓜”,“牛奶”] 是两个都具有的,这个是能被搜索出来的基础。

这个json截取了索引的mappering结构,展示了channelSkuName字段指定了"ik_max_word"分词器。然后下面还有fields字段,es是允许一个字段分别指定不同的字段类型和分词器的,只要搜索的时候对应好字段后缀就行了,比如"channelSkuName.standard"和"channelSkuName.pinyin"。

{
    "channelSkuName":{
        "analyzer":"ik_max_word",
        "type":"text",
        "fields":{
            "standard":{
                "analyzer":"standard",
                "type":"text"
            },
            "pinyin":{
                "analyzer":"pinyin",
                "type":"text"
            }
        }
    }
}

匹配

说完分词,再说匹配,在 Elasticsearch 中,有几种不同的查询类型可用于搜索文本数据。以下是 matchPhrase、match 和 term 查询的区别。

match 查询

match 查询用于在文本字段中查找与搜索词匹配的文档。

该查询会对搜索词进行分词,生成词项,并与文档中的词项进行匹配。默认情况下,match 查询使用 OR 操作符,即匹配任何一个词项,这个文档就会被搜索出来。

{
  "query": {
    "bool": {
      "must": [

        {
          "match": {
            "channelSkuName": {
              "query": "吃木瓜",
              "analyzer": "ik_max_word"
            }
          }
        }
      ]
    }
  }
}

上述示例在ik_max_word分词器下被拆分成[“吃”,“木瓜”],所以将匹配包含短语 “吃 or 木瓜” 的文档,如 “牛奶木瓜”会被搜索出来。why? 注意,上文说了,搜索词会被分词,文档内容同样会被分词,如果文档字段仍是使用ik_max_word分词器,“牛奶木瓜” 被分词为 [“牛奶”,“木瓜”],正因为和搜索词有一样的分词项 [“木瓜”],而且match 属于or匹配**,**所以会被搜索出来。

换句话说,如果文档字段使用的是standard分词器,"牛奶木瓜"会被分词成[“牛”,“奶”,“木”,“瓜”]四个词,就无法匹配[“吃”,“木瓜”]中的任何一个,也就没法匹配搜索到。

从这个例子可以看出,一个文档要能被搜索出来,一看分词(搜索词和文档内容),二看匹配规则(比如match),就能理解es搜索的大致方式。

matchPhrase 查询

matchPhrase 查询用于在文本字段中查找包含指定短语的文档。该查询要求文档中的字段与搜索词语完全匹配,包括相对的顺序和位置。什么是相对的顺序和位置?就是分词结果的排序,它并不是随意排序的,每个分词项都有自己的位置。下面举例说明:

{
    "query":{
        "match_phrase":{
            "channelSkuName":{
                "query":"木瓜牛奶",
                "analyzer":"ik_max_word"
            }
        }
    }
}

这个json跟第一个稍稍不一样, ‘match’替换成了’match_phrase’,我们知道"木瓜牛奶"的分词结果是[“木瓜”,“牛奶”],然后我们希望搜索 “皇麦世家木瓜牛奶燕麦片 350g*1袋”,我们先看下这个文本的分词结构:

入参:

GET http://ip:9200/任意index/_analyze
Content-Type: application/json


{
 "analyzer": "ik_max_word",
 "text": [
   "皇麦世家木瓜牛奶燕麦片 350g*1袋"
 ]
}

出参:

{
  "tokens": [
    {
      "token": "皇",
      "start_offset": 0,
      "end_offset": 1,
      "type": "CN_CHAR",
      "position": 0
    },
    {
      "token": "麦",
      "start_offset": 1,
      "end_offset": 2,
      "type": "CN_CHAR",
      "position": 1
    },
    {
      "token": "世家",
      "start_offset": 2,
      "end_offset": 4,
      "type": "CN_WORD",
      "position": 2
    },
    {
      "token": "世",
      "start_offset": 2,
      "end_offset": 3,
      "type": "CN_WORD",
      "position": 3
    },
    {
      "token": "家",
      "start_offset": 3,
      "end_offset": 4,
      "type": "CN_CHAR",
      "position": 4
    },
    {
      "token": "木瓜",
      "start_offset": 4,
      "end_offset": 6,
      "type": "CN_WORD",
      "position": 5
    },
    {
      "token": "牛奶",
      "start_offset": 6,
      "end_offset": 8,
      "type": "CN_WORD",
      "position": 6
    },
    {
      "token": "牛",
      "start_offset": 6,
      "end_offset": 7,
      "type": "CN_WORD",
      "position": 7
    },
    {
      "token": "奶",
      "start_offset": 7,
      "end_offset": 8,
      "type": "CN_CHAR",
      "position": 8
    },
    {
      "token": "燕麦片",
      "start_offset": 8,
      "end_offset": 11,
      "type": "CN_WORD",
      "position": 9
    },
    {
      "token": "燕麦",
      "start_offset": 8,
      "end_offset": 10,
      "type": "CN_WORD",
      "position": 10
    },
    {
      "token": "麦片",
      "start_offset": 9,
      "end_offset": 11,
      "type": "CN_WORD",
      "position": 11
    },
    {
      "token": "350g",
      "start_offset": 12,
      "end_offset": 16,
      "type": "LETTER",
      "position": 12
    },
    {
      "token": "350",
      "start_offset": 12,
      "end_offset": 15,
      "type": "ARABIC",
      "position": 13
    },
    {
      "token": "g",
      "start_offset": 15,
      "end_offset": 16,
      "type": "ENGLISH",
      "position": 14
    },
    {
      "token": "1",
      "start_offset": 17,
      "end_offset": 18,
      "type": "ARABIC",
      "position": 15
    },
    {
      "token": "袋",
      "start_offset": 18,
      "end_offset": 19,
      "type": "COUNT",
      "position": 16
    }
  ]
}

从出参我们看到"木瓜"和"牛奶"的position是5和6,这就是上面我们说的位置,不过这里是绝对位置。我们再看看搜索词"木瓜牛奶"的位置。

入参:

GET http://ip:9200/任意index/_analyze
Content-Type: application/json

入参:
{
 "analyzer": "ik_max_word",
 "text": [
   "木瓜牛奶"
 ]
}

出参:

{
    "tokens":[
        {
            "token":"木瓜",
            "start_offset":0,
            "end_offset":2,
            "type":"CN_WORD",
            "position":0
        },
        {
            "token":"牛奶",
            "start_offset":2,
            "end_offset":4,
            "type":"CN_WORD",
            "position":1
        }
    ]
}

搜索词 "木瓜"和"牛奶"的position是0和1,虽然搜索词的position跟搜索内容的position绝对值不一样,但是他们相对位置是相邻的,matchPhrase能匹配上的要求有两个:

  1. 要求文档中的分词结果与搜索词分词完全匹配。
  2. 相对的顺序和位置符合要求

这两个都能满足,所以 “皇麦世家木瓜牛奶燕麦片 350g*1袋” 能被搜索出来。说到这里,我们能看到matchPhrase比match更能符合人类的搜索预期,matchPhrase相当于全文搜索,match相当于模糊搜索,但是我们再举一个相对顺序不一致的情况。

比如搜索词是"皇麦木瓜燕麦片",想搜索的商品名为"皇麦世家木瓜牛奶燕麦片 350g*1袋”, 从人的视觉习惯看起来跟商品名差不多,但是对搜索引擎分词结果来说,“皇麦"到"木瓜"到"燕麦片"之间,没有了"世家”,“牛奶”两个分词,在相对顺序上,它们已经匹配不上搜索内容分词的相对顺序了,所以无法搜索到。但是我们希望有个容错的机制可以容忍一些位置错乱,幸运的是,在使用matchPhrase的情况下,的确有个参数可以兼容顺序不一致的情况,非常实用。

slop 参数

slop 是一个参数,用于指定 matchPhrase 查询中允许的最大位置偏移量。它用于控制短语查询中单词的相对位置。默认情况下,slop 的值为 0,表示单词必须按照给定的顺序连续出现。如果设置了一个正整数的 slop 值,那么在指定范围内,单词可以以任意顺序出现,且允许有一些其他单词插入其中。

还是以上面无法搜索出来的例子来看,比如我们的搜索词是 “皇麦木瓜燕麦片”,通过分词分析,相比 "皇麦世家木瓜牛奶燕麦片 350g*1袋"的分词,少了[“世家”,“世”,“家”,“牛奶”,“牛”,“奶”]6个分词,我们设置slop为6,表示允许的中间不匹配位置的最大数目为6,这时候,"皇麦世家木瓜牛奶燕麦片 350g*1袋"就可以被搜出来。如果设置成5,通过我的实际检验,都没办法搜出来。

{
 "query": {
   "match_phrase": {
     "channelSkuName": {
       "query": "皇麦木瓜燕麦片",
       "slop": 6,
       "analyzer": "ik_max_word"
     }
   }
 }
term 查询

matchPhrase 和 match 都建立在分词再查找的基础上,而 term 查询不会对查询词进行分词,而是直接与文档中的词项进行精确匹配。所以term不接受analyzer属性,term适合精确的编码查询等场景。

但是需要注意的是,term 适合查询 keyword 类型的字段,一般文本类型分为 text 和 keyword。下面json给了一个keyword示例,channelSkuName本身是text类型,但是channelSkuName.keyword就是keyword类型,keyword 类型不会做分词处理。


{
    "channelSkuName":{
        "type":"text",
        "fields":{
            "keyword":{
                "ignore_above":256,
                "type":"keyword"
            }
        }
    }
}

用"世家"是能搜索到 "皇麦世家木瓜牛奶燕麦片 350g*1袋"的内容的,因为"世家"不分词直接匹配上了"皇麦世家木瓜牛奶燕麦片 350g*1袋"的分词结果(匹配上了“世家”),如果是搜索的channelSkuName.keyword,那就肯定搜索不出来。

{
 "query": {
   "term": {
     "channelSkuName": "世家"
   }
 }
}

搜出来了再怎么排序?

搜出来之后,因为是个列表,我们需要根据人的搜索预期进行排序,产品给了如下需求:

  1. 搜索短语完全匹配的在前面
  2. 搜索短语也要支持模糊匹配
  3. 其它业务上的排序(依照其它字段排序)

其实前两个需求用 matchPhrase 和 match 搜索就行了,两者用should相连,不管是精确匹配还是模糊匹配都能满足要求,至于排序我们需要了解score机制。

score

在Elasticsearch中,每个搜索结果都会有一个分数(score),用于表示与查询的匹配程度。分数越高表示与查询的匹配度越高。es默认用score进行排序,看起来似乎满足我们的需求,因为完全匹配的score分数肯定更高,但是我们的排序规则还带上业务上规则的时候,就出现了一些麻烦。比如同样是完全匹配的商品中,自营的商品会排序更靠前,要实现这样的排序,你可能会想到完全匹配的商品作为第一优先级,自营作为第二优先级,很简单的问题。但是你少考虑了一点,es复杂的score计算机制,即使完全匹配的商品,score分数几乎都不可能相等(es有自己的匹配度计算),这样的话就没办法做“第二优先级-自营”的排序了,这时候你会想,如果能自己定义“完全匹配的商品”的score分数就好了。

Constant Score

常量化(Constant Score)是一种将某一搜索条件的分数设置为固定值的方法。有时候我们希望在搜索中不考虑复杂的匹配度,而是将某一搜索条件的分数统一设定为某个固定值。这可以通过使用常量分数查询(Constant Score Query)来实现。

{
 "query": {
   "constant_score": {
     "filter": {
       "match_phrase": {
         "channelSkuName": {
           "query": "皇木瓜牛奶燕麦片 350g*1袋",
           "slop": 2,
           "analyzer": "ik_max_word"
         }
       }
     },
     "boost": 5
   }
 }
}

通过新增 constant_score 和 boost ,可以指定通过当前条件搜出来的商品score分数会被固化成5分,这样就非常方便我们新增其它的业务排序,更好的符合产品需求

结语

这篇文章更多的是实践经验而非es原理解析,自己经验小记下来,抛砖引玉。文章来源地址https://www.toymoban.com/news/detail-491320.html

  • 2023-10-23  重新编排文章,预留下一篇文章介绍倒排索引

到了这里,关于Elasticsearch搜索与排序经验小记的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • elasticsearch[四]-数据聚合排序查询、搜索框自动补全、数据同步、集群

    elasticsearch[四]-数据聚合排序查询、搜索框自动补全、数据同步、集群

    **聚合(aggregations)**可以让我们极其方便的实现对数据的统计、分析、运算。例如: 什么品牌的手机最受欢迎? 这些手机的平均价格、最高价格、最低价格? 这些手机每月的销售情况如何? 实现这些统计功能的比数据库的 sql 要方便的多,而且查询速度非常快,可以实现近

    2024年01月19日
    浏览(10)
  • ElasticSearch系列 - SpringBoot整合ES:实现搜索结果排序 sort

    00. 数据准备 01. Elasticsearch 默认的排序方式是什么? ElasticSearch 默认的排序方式是相关性排序。相关性排序是根据查询条件与文档的匹配程度来计算每个文档的相关性得分,然后按照得分从高到低进行排序。相关性排序是 ElasticSearch 中最常用的排序方式,因为它可以根据查询

    2024年02月02日
    浏览(12)
  • Elasticsearch 核心技术(九):搜索结果处理(分页、排序、指定返回字段、去重、高亮显示)

    Elasticsearch 核心技术(九):搜索结果处理(分页、排序、指定返回字段、去重、高亮显示)

    ❤️ 博客主页:水滴技术 🚀 支持水滴: 点赞 👍 + 收藏 ⭐ + 留言 💬 🌸 订阅专栏:大数据核心技术从入门到精通

    2023年04月13日
    浏览(11)
  • 【ElasticSearch】使用 Java 客户端 RestClient 实现对文档的查询操作,以及对搜索结果的排序、分页、高亮处理

    【ElasticSearch】使用 Java 客户端 RestClient 实现对文档的查询操作,以及对搜索结果的排序、分页、高亮处理

    在 Elasticsearch 中,通过 RestAPI 进行 DSL 查询语句的构建通常是通过 HighLevelRestClient 中的 resource() 方法来实现的。该方法包含了查询、排序、分页、高亮等所有功能,为构建复杂的查询提供了便捷的接口。 RestAPI 中构建查询条件的核心部分是由一个名为 QueryBuilders 的工具类提供

    2024年01月16日
    浏览(17)
  • ElasticSearch进阶小记

    ElasticSearch进阶小记

      目录 一、ik分词器 1.1 指定ik分词器 1.2 使用ik分词器 二、批量操作文档 2.1 脚本实现 2.2 api实现 三、查询(重点) 3.1 matchAll 3.1.1 脚本使用 3.1.1 api使用 3.2 termQuery 3.2.1 脚本使用  3.2.2 api使用 3.3 matchQuery  3.3.1 脚本使用 3.3.2 api使用 3.4 模糊查询 3.4.1 脚本使用 3.4.1.1 wildcard查询

    2024年03月22日
    浏览(2)
  • DAY23:二叉树(十三)二叉树的最近公共祖先+二叉搜索树的最近公共祖先

    DAY23:二叉树(十三)二叉树的最近公共祖先+二叉搜索树的最近公共祖先

    一定要仔细看 提示 ,二叉树 数值不重复 ,意味着后序遍历不会存在两边找到了同个元素的情况 本题需要进一步理解后序遍历, 可以认为后序遍历在\\\"深入\\\"到每个子树的最深层之后,才开始\\\"回溯\\\"并访问节点 。 在某种意义上,这可以被视为从下往上的遍历方式 , 但需要注

    2024年02月09日
    浏览(8)
  • LeetCode235. 二叉搜索树的最近公共祖先

    LeetCode235. 二叉搜索树的最近公共祖先

    235. 二叉搜索树的最近公共祖先 一、题目 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大( 一个节点也可以是

    2024年02月12日
    浏览(8)
  • LeetCode_二叉搜索树_中等_236.二叉搜索树的最近公共祖先

    LeetCode_二叉搜索树_中等_236.二叉搜索树的最近公共祖先

    给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。” 例如,给定如下二叉搜

    2023年04月10日
    浏览(9)
  • ElasticSearch-学习笔记02【ElasticSearch索引库维护】

    ElasticSearch-学习笔记02【ElasticSearch索引库维护】

    Java后端-学习路线-笔记汇总表【黑马程序员】 ElasticSearch-学习笔记01【ElasticSearch基本介绍】 【day01】 ElasticSearch-学习笔记02【ElasticSearch索引库维护】 ElasticSearch-学习笔记03【ElasticSearch集群】 ElasticSearch-学习笔记04【Java客户端操作索引库】 【day02】 ElasticSearch-学习笔记05【Spri

    2024年02月04日
    浏览(25)
  • Java——二叉树的最近公共祖先及二叉搜索树介绍

    Java——二叉树的最近公共祖先及二叉搜索树介绍

    目录 二叉树的最近公共祖先 题目  思路一:如果给定的是一颗二叉搜索树, 思路二:假设是孩子双亲表示法  二叉搜索树 定义Node类 查找 删除 插入 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点

    2023年04月08日
    浏览(10)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包