基于 NGram 分词,优化 Es 搜索逻辑,并深入理解了 matchPhraseQuery 与 termQuery

这篇具有很好参考价值的文章主要介绍了基于 NGram 分词,优化 Es 搜索逻辑,并深入理解了 matchPhraseQuery 与 termQuery。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

之前不是写过一个全局搜索的功能吗,用户在使用的时候,搜(进出口)关键字,说搜不到数据,但是 Es 中确实是有一条标题为 (202009 进出口)的数据的,按道理来说,这确实要命中的,于是我开始回想我当时是如何写的这段搜索逻辑的代码!!!!

问题描述

之前所有检索的字段全是用的 matchPhraseQuery 查询,matchPhraseQuery 命中的条件其一就是,搜索字段所有的分词都要被 Es 词库命中,其二就是命中的分词在词库中的顺序要紧挨着的。不然就没法查出数据。接下来举例帮助大家理解。

  if (StringUtils.isNotEmpty(articleRequest.getKeyword())) {
        for (int i = 0; i < articleRequest.getKeys().length; i++) {
            boolQuery.should(QueryBuilders.matchPhraseQuery(articleRequest.getKeys()[i], articleRequest.getKeyword()));
        }
    }

使用 kibana 控制台,编写一条 DLS语句,由于 Es 默认使用的分词器是用的 standard,于是查看一下查(进出口)关键字,是被分词成了(进,出,口)

POST _analyze
{
  "analyzer": "standard",
  "text": "进出口"
}

termquery matchphrasequery,es,elasticsearch,大数据,搜索引擎
一开始建索引的时候,所有字段都没有指定分词器,都是用的默认的 standard 分词器,因此在使用 matchPhraseQuery 的时候,无论是 title 含有(进出口)还是 body 含有(进出口)关键字的数据都能够被正常检索出来,原因就是词库也是按照(进,出,口)存储的,查的关键字也是被分词成(进,出,口)进行匹配词库查询的,所有分词:位置紧挨着、顺序一致、且完全被包含。
termquery matchphrasequery,es,elasticsearch,大数据,搜索引擎
但是后来遇到一个问题就是,搜字母或者是数字,搜不到数据,例如:搜 20 ,但是明明有标题为 (202009 进出口数据 33)的数据,就搜不出来。到这里你会怎么去排查问题?接下来说下我的整个排查问题的流程。

排查索引库分词(发现问题)

基于默认的 standar 分词器查看一下, title 为 (202009 进出口数据33)是如何被分词存到词库中的

POST _analyze
{
  "analyzer": "standard",
  "text": "202009 进出口数据33"
}

termquery matchphrasequery,es,elasticsearch,大数据,搜索引擎
看了一下 202009 居然没有被分词,而是被当做了一个整体,当我们搜 20 的时候,是按照 20 的这个分词进行查询的,但是索引库中并没有 20 的分词,即不满足查询分词都要被词库包含的关系,更不满足分词顺序和词库保持一致,更不满足命中词库中的分词是紧挨着的条件,三大条件都不满足,能查到才怪呢?怎么去优化搜索逻辑?termquery matchphrasequery,es,elasticsearch,大数据,搜索引擎

如何去解决这个问题?

接下来肯定就是优化索引库中存储的分词结构了,让 title 为( 202009 进出口数据 33) 的这条数据,存储的分词包含 (20),而不是粗略的包含一个(202009),当然你也可以使用 Es 的 模糊查询 wildcard 或者 fuzzy ,考虑到数据量过大,查询性能不咋地,决定优化索引结构,用空间换时间!!!!为什么是空间换时间?存的分词粒度都变细了,意味着存的索引体积变大,这些数据都要硬件来存储的,可不是空间换时间嘛。接下来用主流的 IK 分词器去分下词看满不满足我们的需求

IK 分词器

编写 DLS 语句,对目标数据分词,看到还是没有(20)的分词出现,直接 Pass

POST _analyze
{
  "analyzer": "ik_max_word",
  "text": "202009进出口数据 33"
}

termquery matchphrasequery,es,elasticsearch,大数据,搜索引擎
对字母分词一样,粒度不满足我们的需求,直接 Pass
termquery matchphrasequery,es,elasticsearch,大数据,搜索引擎

NGram 分词器使用

接下来说本文的主角 NGram 分词器,分词的粒度可以由我们自己控制。在建索引的时候设置一下 Setting 代码都是固定的就好像你使用 Java Api一样,需要注意的是里面的 min_gram 指定最小分词粒度,max_gram 指定最大分词粒度。自定义分词器名字为:my_ngram_analyzer 接来举例说明,这个自定义分词器是干啥的!!!

private static String defaultIndexSetting = "{\n" +
        "        \"index.max_ngram_diff\":10,\n" +
        "        \"analysis\": {\n" +
        "          \"analyzer\": {\n" +
        "            \"my_ngram_analyzer\": {\n" +
        "              \"tokenizer\": \"my_ngram_tokenizer\"\n" +
        "            }\n" +
        "          },\n" +
        "          \"tokenizer\": {\n" +
        "            \"my_ngram_tokenizer\": {\n" +
        "              \"type\": \"ngram\",\n" +
        "              \"min_gram\": 1,\n" +
        "              \"max_gram\": 10,\n" +
        "              \"token_chars\": [\n" +
        "                \"letter\",\n" +
        "                \"digit\"\n" +
        "              ]\n" +
        "            }\n" +
        "          }\n" +
        "        }\n" +
        "      }";

由于我只对 title 字段设置了自定义分词器,mapping 如下。

 private static String defaultIndexMapping = "{\n" +
            "\t\"properties\": {\n" +
            "\t\t\"author\": {\n" +
            "\t\t\t\"type\": \"text\",\n" +
            "\t\t\t\"boost\": \"3\",\n" +
            "\t\t\t\"fields\": {\n" +
            "\t\t\t\t\"keyword\": {\n" +
            "\t\t\t\t\t\"type\": \"keyword\",\n" +
            "\t\t\t\t\t\"ignore_above\": 256\n" +
            "\t\t\t\t}\n" +
            "\t\t\t}\n" +
            "\t\t},\n" +
            "\t\t\"body\": {\n" +
            "\t\t\t\"type\": \"text\",\n" +
            "\t\t\t\"fields\": {\n" +
            "\t\t\t\t\"keyword\": {\n" +
            "\t\t\t\t\t\"type\": \"keyword\",\n" +
            "\t\t\t\t\t\"ignore_above\": 256\n" +
            "\t\t\t\t}\n" +
            "\t\t\t}\n" +
            "\t\t},\n" +
            "\t\t\"title\": {\n" +
            "\t\t\t\"boost\": \"10000\",\n" +
            "\t\t\t\"type\": \"text\",\n" +
            "\t\t\t\t\t\t        \"analyzer\": \"my_ngram_analyzer\",\n" +
            "\t\t\t\"fields\": {\n" +
            "\t\t\t\t\"keyword\": {\n" +
            "\t\t\t\t\t\"type\": \"keyword\",\n" +
            "\t\t\t\t\t\"ignore_above\": 256\n" +
            "\t\t\t\t}\n" +
            "\t\t\t}\n" +
            "\t\t},\n" +
            "\t\t\"createtime\": {\n" +
            "\t\t\t\"type\": \"date\",\n" +
            "\t\t\t\"format\": \"yyyy-MM-dd HH:mm:ss||yyyy-MM-dd\"\n" +
            "\t\t}\n" +
            "\t}\n" +
            "}\n";

接下来根据最新的 Setting、Mapping 配置替换之前的旧的索引,然后进行测试

log.info("create index mapping: " + tabIndex.getMapping());
    CreateIndexRequest indexRequest = new CreateIndexRequest(tabIndex.getIndexName().trim())
            .settings(tabIndex.getSetting(), XContentType.JSON)
            .mapping("_doc", tabIndex.getMapping(), XContentType.JSON);
    CreateIndexResponse response = null;
    try {
        response = restHighLevelClient.indices().create(indexRequest, RequestOptions.DEFAULT);
    } catch (IOException e) {
        e.printStackTrace();
        tabIndexService.delete(new EntityWrapper<TabIndex>().eq("index_name", tabIndex.getIndexName()));
        return JsonData.buildError("失败" + e.getMessage());
    }
    if (response != null) return JsonData.buildSuccess(response.isAcknowledged());
    else return JsonData.buildError("失败");

替换 NGram 分词器后进行测试

输入关键字:20,发现 title 为 (202009 进出口数据 33) 的这条数据还是查不到???????what fa,再次检查索引库分词,编写 DLS 语句看看,由于创建的新索引的名称是 zza,这里对 zza 索引下面标题包含 (202009 进出口数据 33)的数据进行分词,看看 Es 是如何存的!!!

POST /zza/_analyze
{
  "field": "title",
  "text": "202009 进出口数据 33"
}

可以看到此时的分词存储了 (2,20,202…)按道理来说查 2 或者 20 或者 202 等等都可以查到这条数据的。难道见鬼啦?于是我决定将代码的生成的 DLS 语句直接 Copy 到 kibana 中跑一下,看到底是代码 Api 的 Bug 还是其他问题。

termquery matchphrasequery,es,elasticsearch,大数据,搜索引擎
于是我就这个 DLS 语句运行了一下,其实不是见鬼了,是我们需要理解一下 termQuery 与 matchPhraseQuery 的查询原理!!!

matchPhraseQuery 查询原理

会将搜索关键字进行分词(这个根据索引用到的分词器一致),然后与词库中的分词进行匹配。例如,现在有一条 title 为(202009 进出口数据 33)的数据,当我们搜 20 的时候,会根据(2,20,0)去匹配词库termquery matchphrasequery,es,elasticsearch,大数据,搜索引擎
但是此时词库是按照(2,20,202…0)这个顺序存的。termquery matchphrasequery,es,elasticsearch,大数据,搜索引擎
再来回顾一下 matchPhraseQuery 命中索引的三大条件

  1. 搜索关键字分词要被词库存的分词完全包含
  2. 在点一的基础上,搜索分词顺序要和词库保持一致
  3. 在前俩点都满足的情况下,词库中匹配到的分词顺序要紧挨着

我们搜关键字 20 时,满足了上述点 1,2。但是不满足点 3,因此使用 matchPhraseQuery 搜不到 title 为(202009 进出口数据 33)的这条数据。那么有什么办法解决吗?答案是有的。就是指定 slop 参数。指定分词紧挨着的最大单位,默认是 1,通过调大这个参数也可以查出来指定数据
termquery matchphrasequery,es,elasticsearch,大数据,搜索引擎
不指定 slop 的情况下查不到数据,但是我现在的需求只要是关键字中包含 20 的数据都要被查到,调 slop 也不是办法,因此 title 字段的搜索不用 matchPhraseQuery,改用 termQuery
termquery matchphrasequery,es,elasticsearch,大数据,搜索引擎

termQuery 查询原理

搜索的关键字不会进行分词去匹配词库,搜 20 就会以 20 去匹配,命中词库中的一个分词即可,例如;现在有一条 title 为(202009 进出口数据 33)的数据,搜关键字 20 即可查出数据,满足现有的业务需求。
termquery matchphrasequery,es,elasticsearch,大数据,搜索引擎

因此最后还改造了一下业务代码逻辑大概是这样,title 字段用 termQuery,其他字段用 matchPhraseQuery。就可以了。

if (StringUtils.isNotEmpty(articleRequest.getKeyword())) {
    for (int i = 0; i < articleRequest.getKeys().length; i++) {
        if ("title".equals(articleRequest.getKeys()[i]))
            boolQuery.should(QueryBuilders.termQuery(articleRequest.getKeys()[i], articleRequest.getKeyword()));
        else
            boolQuery.should(QueryBuilders.matchPhraseQuery(articleRequest.getKeys()[i], articleRequest.getKeyword()));
    }
    boolQuery.minimumShouldMatch(1);
}

总结

matchPhraseQuery 命中条件

  1. 搜索关键字分词要被词库存的分词完全包含
  2. 在点一的基础上,搜索分词顺序要和词库保持一致
  3. 在前俩点都满足的情况下,词库中匹配到的分词顺序要紧挨着
    matchPhraseQuery 在查询前会对关键字进行分词,用到的分词器和索引中该字段指定的分词器一致,例如本文的 title 用到了 NGram 分词器,那么使用如下代码,检索 title 字段时,用到的分词器也是用的 Ngram
QueryBuilders.termQuery("title", articleRequest.getKeyword())

termquery matchphrasequery,es,elasticsearch,大数据,搜索引擎文章来源地址https://www.toymoban.com/news/detail-765394.html

到了这里,关于基于 NGram 分词,优化 Es 搜索逻辑,并深入理解了 matchPhraseQuery 与 termQuery的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • 03-ES核心概念理解&IK分词器详解

    文档 就是我们的一条条数据 user 1 zhangsan 18 2 lisi 20 3 wangwu 50 之前说elasticsearch是面向文档的,那么就意味着索引和搜索数据的最小单位是文档,elasticsearch中,文档有几个重要属性: 自我包含,一篇文档同时包含字段和对应的值,也就是同时包含key:value ! 可以是层次型的,一个

    2024年04月26日
    浏览(23)
  • Elasticsearch (ES) 搜索引擎: 文本搜索:分析器/分词器、同义词/停用词、拼音搜索、高亮显示、拼写纠错

    原文链接:https://xiets.blog.csdn.net/article/details/132349032 版权声明:原创文章禁止转载 专栏目录:Elasticsearch 专栏(总目录) 文本搜索主要指的就是全文搜索,全文搜索是搜索引擎的核心功能,与精确匹配的结构化数据不同,文本(text)数据在构建索引和搜索时都需要进行额外的处

    2024年02月03日
    浏览(44)
  • 搜索引擎elasticsearch :安装elasticsearch (包含安装组件kibana、IK分词器、部署es集群)

    kibana可以帮助我们方便地编写DSL语句,所以还要装kibana 因为我们还需要部署kibana容器,因此需要让es和kibana容器互联。这里先创建一个网络: 这里我们采用elasticsearch的7.12.1版本的镜像,这个镜像体积非常大,接近1G。不建议大家自己pull。 课前资料提供了镜像的tar包: 大家将

    2024年02月16日
    浏览(45)
  • 深入理解 Python and 逻辑运算符(踩坑)

    以上代码返回什么? 实际生产项目踩到的坑,也怪自己没理解到未,才疏学浅!!! 想当然的以为 python 自己会做真值判断了。其实真值判断是在 if 条件语句时 会生效,但在普通的 and 的运算符下有另外一个规则。 “The Boolean type is a subtype of the integer type, and Boolean values be

    2024年02月09日
    浏览(36)
  • ElasticSearch 中的中文分词器以及索引基本操作详解,Java高并发编程详解深入理解pdf

    PUT book/_settings { “number_of_replicas”: 2 } 修改成功后,如下: 更新分片数也是一样。 2.3 修改索引的读写权限 索引创建成功后,可以向索引中写入文档: PUT book/_doc/1 { “title”:“三国演义” } 写入成功后,可以在 head 插件中查看: 默认情况下,索引是具备读写权限的,当然这

    2024年04月09日
    浏览(39)
  • 【FPGA常见逻辑门:与、或、非、异或】——深入理解并掌握

    【FPGA常见逻辑门:与、或、非、异或】——深入理解并掌握 FPGA(现场可编程门阵列)是一种用于构建数字电路的集成电路,它具有可编程性和可重构性,可满足各种应用需求。而在FPGA中,逻辑门是实现数字电路的基本部件。本文将介绍FPGA中四种常见逻辑门:与门、或门、

    2024年02月03日
    浏览(26)
  • 《深入理解C语言中的逻辑运算符及其短路特性》

    在C语言中,除了关系运算符之外,我们还可以使用逻辑运算符。逻辑运算符主要包括与运算()、或运算(||)和非运算(!)三种。这些运算符可以用来进行复杂的条件判断,简化程序的执行流程。在进行逻辑运算时,C语言规定非0即真,0即假。本篇博客主要围绕这个特性展

    2024年02月05日
    浏览(37)
  • 深入理解Android音视频同步机制(一)ExoPlayer的avsync逻辑

    对于此前没有了解过ExoPlayer的朋友,我们在这里先用下面的时序图简单介绍一下ExoPlayer在音视频同步这块的基本流程: 图中 ExoPlayerImplInternal是Exoplayer的主loop所在处,这个大loop不停的循环运转,将下载、解封装的数据送给AudioTrack和MediaCodec去播放。 MediaCodecAudioRenderer和MediaC

    2023年04月12日
    浏览(39)
  • ElasticSearch - 基于 拼音分词器 和 IK分词器 模拟实现“百度”搜索框自动补全功能

    目录 一、自动补全 1.1、效果说明 1.2、安装拼音分词器 1.3、自定义分词器 1.3.1、为什么要自定义分词器 1.3.2、分词器的构成 1.3.3、自定义分词器 1.3.4、面临的问题和解决办法 问题 解决方案 1.4、completion suggester 查询 1.4.1、基本概念和语法 1.4.2、示例 1.4.3、示例(黑马旅游)

    2024年02月07日
    浏览(36)
  • 【算法证明 六】深入理解广度优先搜索

    看了算法导论,才知道自己理解的深搜、广搜有多肤浅。 搜索算法非常直观,容易理解。只要简单学过其思想,都能在做算法题时自己写出来,或者模板背出来。但是这些是图论算法的基础,如果不把搜索算法的方方面面研究透彻,很难再学习更难得图论算法。接下来两篇文

    2024年02月11日
    浏览(35)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包