关于电商搜索中Elasticsearch的正确使用姿势--检索篇

前言

书接上文,我们为电商项目做了个性化的索引配置之后,加下来就是正式的使用了。再ES的检索方面,也有一些值得注意的小技巧。本篇将会着重讲解笔者在使用ElasticSearch(下面简称ES)进行检索时的一些心得体会。

如果没有看过配置篇,可以移步这里: 电商项目中使用ES–配置篇

检索的前一步

其实在电商项目的中,真正走到ES检索之前,还是需要对用户的检索条件进行处理的,我们称这一步为预处理操作,这里的预处理并没有统一的范式,都是根据各自项目的需要对用户检索信息进行再加工。

拿笔者所经历的电商项目来说,预处理就包含了检索信息的纠错、敏感词过滤、同义词转换、意图识别、中文/英文/拼音识别等等,这些不在本篇所涉及的话题范围内,有兴趣的同学可以自行谷歌学习。

检索

到这里,我们需要调用SQL在ES中检索信息了,其实说白了就是CURD中的R(Retrieve)了,那么这一步,有哪些值得注意的点呢。

这里对ES的检索语法不做过多的介绍,但是作为使用ES的基本技能,如果小伙伴们还不熟悉的话,需要自行谷歌学习。

分数

相较于“精确查询“的Mysql而言,ES几乎90%的时间执行的都是匹配度查询,它除了我们需要的查询结果之外,每条文档还会返回一个_score,这个_score是ES对检索结果的打分,分数越高,我们就可以认为这条文档与检索条件的相关性更高。很显然,分数越高的,再返回结果中就越靠前。

sort

值得一提的是,当我们显式的制定排序字段时候,_score将会失效(返回值为null)。其实这很容易理解,人为干预的排序与ES所给出的匹配度分数是互相冲突的。这时候_score有值反而容易让人产生迷惑,返回null就显得比较合理。

operator

我们在查询字段时,ES给我们提供了基于match查询的API operator。比如下面这样

{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "title": "超级索尼子",
            "operator": "or"
          }
        }
      ]
    }
  },
  "from": 0,
  "size": 10
}

当不配置operator是ES默认是or,另外一个选项是and。那么orand有什么区别呢。

假设ES的对“超级索尼子“的分词结果是:“超级“,“索尼子“,“索尼“,“子“

operatoror时,文档title中包含上述分词中任何一个分词,该条文档就会被检索到。
operatorand时,文档title中必须包含上述分词中所有分词,这条文档才能够被检索到。

从字面解释上可以看出,and的检索精度是比or要高的。那么在电商项目中,我们是怎么运用其在不同的检索环节当中的呢。

二次召回

这里就不得不提到我们检索的两个环节:首次召回,二次召回。

当我们进行首次召回时,目标是力求精准匹配用户的检索条件。此时在operator中,我们理所当然的使用and进行召回。

但是当首次召回没有返回结果时,我们就需要考虑进行二次召回,此时的目标就从精准匹配变为尽可能匹配多的结果,所以我们需要使用or(这里其实比较复杂,还涉及到同义词,意图词,扩展词,拼音等,这些都属于预处理的范畴,与ES无关,这里省略掉)

改变权重

有时候我们对搜索结果的打分有一些额外的需求,这很合理,比如说我们在搜索“超级索尼子“的时候,再匹配到的若干文档中,有的再ipname中包含"超级索尼子“字符,有的则在title中包含。此时我们希望title中包含该字符的文档优先被检索并展示出来。

如果什么都不做的话,ES总是会对每个字段按照相同的权重进行打分。此时匹配度更高,但是匹配字段在ipname中的文档将会排在前面,这显然不是我们想看到的。

通常我们使用 boost API来改变匹配字段的权重,像这样

{
    "query": {
        "match" : {
            "title": {
                "query": "超级索尼子",
                "boost": 2
            },
            "ipname": {
                "query": "超级索尼子"
            }
        }
    }
}
'

上述查询语句在执行过程中,源自title字段的单词将具有比源自ipname字段的单词更高的分数(可以简单的理解为title的权重系数相比ipname提升了{boost}倍)。

boost不设置时默认为1

组合查询

我们在上篇中提到过full_name,在实际的查询过程中,查询DSL可能长这样

{
  "query": {
    "function_score": {
      "boost_mode": "sum",
      "score_mode": "sum",
      "functions": [
        {
          "filter": {
            "bool": {
              "should": {
                "prefix": {
                  "title": "超级索尼子"
                }
              }
            }
          },
          "weight": 640
        },
        {
          "filter": {
            "bool": {
              "should": {
                "prefix": {
                  "ipname": "超级索尼子"
                }
              }
            }
          },
          "weight": 320
        },
        {
          "filter": {
            "bool": {
              "should": {
                "prefix": {
                  "keyword.pinyin": "suonizi"
                }
              }
            }
          },
          "weight": 160
        }
      ],
      "query": {
        "bool": {
          "minimum_should_match": "1",
          "must": {
            "term": {
              "brandname": "世嘉"
            }
          },
          "should": [
            {
              "prefix": {
                "title": "超级索尼子"
              }
            },
            {
              "prefix": {
                "ipname": "超级索尼子"
              }
            },
            {
              "prefix": {
                "keyword.pinyin": "超级索尼子"
              }
            },
            {
              "match": {
                "full_name": {
                  "analyzer": "ik_smart_t2s",
                  "operator": "and",
                  "query": "超级索尼子"
                }
              }
            }
          ]
        }
      }
    }
  }
}

我们来解析一下上面的语句的含义,这依旧是一个查询“超级索尼子“的语句,它分位以下几部分:

  1. 包含一个must查询,brandname必须为“世嘉“
  2. 包含一个 should查询以及minimum_should_match,表示当至少匹配{minimum_should_match}should中的查询条件,该文档可以被检索到(minimum_should_match至少为1)
  3. should查询中包含3个前缀查询prefix以及一个匹配查询match
  4. 包含一个functions权重条件
  5. functions条件中包含对3个前缀查询的加权系数(wieght为加权因子)
  6. boost_modescore_mode均为sum,表示加权方式和分数计算方式均为求和

我们先看看3个前缀查询prefix以及一个匹配查询match,前缀查询,顾名思义,当检索关键词匹配对应Field的前缀时(包含以“超级索尼子“开头的文档)即算作命中查询条件

match查询中则用到了full_name,我们在上篇中提到过,这是一个copy_to字段,忘记了的话,可以倒回去回忆一下。由于有众多字段被拷贝到了full_name,这样可以避免我们的检索返回的结果过少的问题(前面的查询条件相对比较严格一些,可能导致返回结果少甚至没有)

归因问题(functionScore)

上篇我们提到了使用full_name查询会导致一个问题,这个问题就是归因,通俗点说,就是我们无法判断用户的检索信息命中的是哪一个Field(已本文为例,到底是哪个字段上查到了“超级索尼子“呢,titleipname或者是其他某个字段?)

笔者的项目中解决这个问题的方式依赖两点,除了full_name之外,我们要对需要进行归因的字段进行查询,就像上文的查询语句那样;

其次,就是使用functionScore来改变命中文档的分数权重(参见示例语句中的functionsweight),在本文示例中具体表现为,如果查询在match之前命中了某个prefix查询(比如说命中了title),那么针对命中的文档,ES会根据function在该文档score的基础上加上一个较大的wieght分数(title的话是640,可以翻上去查看示例DSL进行对照)

我们在拿到这个分数之后通过位运算进行处理,就可以知道用户检索信息命中了title字段(其他Fields同理),这样我们就实现了对查询结果的归因操作。

关于functionScore的用法有很多,限于篇幅原因不可能完全展开所有细节,感兴趣的小伙伴可以自行谷歌学习。

聚合

ES也支持聚合,复杂的聚合本身不是ES的强项,在电商搜索中运用范围有限,本文就不详细描述了 : P,同样,需要小伙伴们自行查阅学习。

结语

关于ES在电商中的应用,根据场景,使用方式也会有较大的差异。ES本身提供了非常灵活的查询机制,我们可以自由组合,配置出符合我们需要的功能。本文目的更多在于通过抛砖引玉的方式让小伙伴了解,并发掘更多ES在电商项目中的使用技巧。从而解决问题,更好实现并完善搜索功能。

明天就要上班了,让我们打起精神,迎接春节后第一个工作日吧~(还在休假的小伙伴自动忽略这句话~)
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值