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

news2024/11/27 4:27:43

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

    • 前言
    • 问题描述
    • 排查索引库分词(发现问题)
    • 如何去解决这个问题?
    • IK 分词器
    • NGram 分词器使用
    • 替换 NGram 分词器后进行测试
    • 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": "进出口"
}

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

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

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

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

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

如何去解决这个问题?

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

IK 分词器

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

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

在这里插入图片描述
对字母分词一样,粒度不满足我们的需求,直接 Pass
在这里插入图片描述

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 还是其他问题。

在这里插入图片描述
于是我就这个 DLS 语句运行了一下,其实不是见鬼了,是我们需要理解一下 termQuery 与 matchPhraseQuery 的查询原理!!!

matchPhraseQuery 查询原理

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

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

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

termQuery 查询原理

搜索的关键字不会进行分词去匹配词库,搜 20 就会以 20 去匹配,命中词库中的一个分词即可,例如;现在有一条 title 为(202009 进出口数据 33)的数据,搜关键字 20 即可查出数据,满足现有的业务需求。
在这里插入图片描述

因此最后还改造了一下业务代码逻辑大概是这样,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())

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1170422.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vivado布局分析

1、高亮显示布局 1. 在“ Netlist ” &#xff08; 网表 &#xff09; 窗口中 &#xff0c; 选择要分析的层级。 2. 从弹出菜单中 &#xff0c; 选择“ Highlight Leaf Cells ” → “ Select a color ” &#xff08; 高亮叶节点单元 > 选择颜色 &#xff09; 。 3. …

【C语法学习】9 - fputs()函数

文章目录 1 函数原型2 参数3 返回值4 输出机制5 比较6 示例6.1 示例16.2 示例2 1 函数原型 fputs()&#xff1a;将str指向的内存空间中存储的字符串发送至指定流stream&#xff0c;函数原型如下&#xff1a; int fputs(const char *str, FILE *stream)2 参数 fputs()函数有两…

初谈Linux-Linux环境搭建(阿里云免费服务器+xshell)

文章目录 前言Linux环境搭建结尾 前言 Linux is not unix 本篇文章小编初谈Linux并搭建Linux环境&#xff08;阿里云免费服务器shell&#xff09; Linux Linux是一个开源的操作系统 环境搭建 1.点击阿里云ECS免费学生服务器 2.注册后完成学生认证 3.购买云服务器&#xf…

超详细shell学习笔记

shell笔记 一、Linux下的shell与内核1 什么是Shell?2 linux启动过程3 shell编程 二、变量与变量应用1 什么是变量&#xff1f;2 变量分类3 变量赋值格式4.变量命名方式5.变量声明6.变量引用7.变量清除8.变量只读9 内部参数变量10 常见环境变量11 变量应用14 字符串长度与截取1…

重生奇迹mu下载后仅仅只是挂机吗?

挂挂机、聊聊天&#xff0c;打打怪&#xff0c;如此简单、轻松的游戏或许有&#xff0c;但绝对不是重生奇迹mu&#xff01;因为重生奇迹mu挂机也不是那么容易&#xff0c;即便是多名高端玩家组队挂机&#xff0c;也有可能是全队惨灭&#xff0c;这样的情况时常发生在游戏中。 …

echarts 简单渐变折线图

渐变颜色多个折线图 import * as echarts from echarts/core // 看个人项目echart引入方式 options: {tooltip: {trigger: axis},yAxis: {type: value,name: 能耗&#xff1a;标准煤kg},legend: {icon: circle,itemHeight: 8,itemWidth: 8,top: 1%,left: center// selectedMo…

什么是机器学习中的正则化?

1. 引言 在机器学习领域中&#xff0c;相关模型可能会在训练过程中变得过拟合和欠拟合。为了防止这种情况的发生&#xff0c;我们在机器学习中使用正则化操作来适当地让模型拟合在我们的测试集上。一般来说&#xff0c;正则化操作通过降低过拟合和欠拟合的可能性来帮助大家获得…

基于jsp的采购管理系统的分析与实现

物资采购管理系统是针对内部而设计的&#xff0c;应用于的局域网&#xff0c;这样可以使得内部管理更有效的联系起来。企业采购管理系统是将IT技术用于企业采购信息的管理, 它能够收集与存储企业采购的档案信息&#xff0c;提供更新与检索企业采购信息档案的接口&#xff1b;提…

记录离线安装xlwings

有场景需要离线安装xlwings。 环境&#xff1a;win7 64位&#xff0c;python3.8.10-amd64。 首先安装python。 安装需要准备&#xff1a; 其中pywinn32解压如下&#xff1a; 安装python3.8.10&#xff0c;解压各类文件夹。 &#xff08;1&#xff09;首先安装pywin32-306.…

将python项目打包成exe可执行文件

auto-py-to-exe 有时候吧&#xff0c;想把自己的python程序打包成一个可执行文件。查询了一下可以使用auto-py-to-exe 来解决。 简介 Auto-py-to-exe是一个用于将Python脚本打包为可执行文件的开源工具。它提供了一个用户友好的图形界面&#xff0c;使用户能够轻松选择和配置…

AC修炼计划(AtCoder Regular Contest 163)

传送门&#xff1a;AtCoder Regular Contest 163 - AtCoder 第一题我们只需要将字符串分成两段&#xff0c;如果存在前面一段比后面一段大就成立。 #include<bits/stdc.h> #define int long long using namespace std; typedef long long ll; typedef pair<int,int&g…

自注意力机制理解

1、精密输入 输入是一个向量&#xff0c;输出表里或类 输入是一组向量&#xff08;变化的长度&#xff09;&#xff0c;输出表里或类&#xff1f;&#xff1f;&#xff1f; 例子1 句子生成 独热编码&#xff0c;不能包含语义信息 词嵌入&#xff0c;每个词是一个词向量 例…

【深度学习基础】Pytorch框架CV开发(1)基础铺垫

&#x1f4e2;&#xff1a;如果你也对机器人、人工智能感兴趣&#xff0c;看来我们志同道合✨ &#x1f4e2;&#xff1a;不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸对你有帮助&#xff0c;可点赞 &#x1f44d;…

BEM:css命名规范

BEM BEM(Block-Element-Modifier)&#xff0c;块、元素、修饰符&#xff0c;是一种CSS命名规范&#xff0c;旨在前端开发中创建可重用组件和代码共享的方法&#xff0c;使样式易于扩展&#xff0c;易于维护&#xff0c;易于理解 规范&#xff1a; 1、块&#xff08;Block&am…

Python|Pyppeteer获取威科先行文章链接(21)

前言 本文是该专栏的第21篇,结合优质项目案例持续分享Pyppeteer的干货知识,记得关注。 本文以“威科先行”的信息库为例,笔者将详细介绍使用pyppeteer“自动滑动页面并翻页”获取威科先行的文章链接。如果对pyppeteer的使用以及知识点不太熟悉的同学,可往前查看本专栏前面…

(参考写法)Transformer-Based Visual Segmentation:A Survey

基于Transformer的视觉分割综述 南洋理工大学NTU、上海人工智能实验室AI Lab整理300+论文 论文地址:https://arxiv.org/pdf/2304.09854.pdf 代码地址:https://github.com/lxtGH/Awesome-Segmentation-With-Transformer 前言 SAM (Segment Anything )作为一个视觉的分割…

python创建一个简单的flask应用

下面用python在本地和服务器上分别创建一个简单的flask应用&#xff1a; 1.在pc本地 1&#xff09;pip flask后创建一个简单的脚本flask_demo.py from flask import Flaskapp Flask(__name__)app.route(/) def hello_world():return Hello, World!winR进入命令行&#xff0c;…

https网站加载http资源问题

https网站加载http资源问题 前言&#xff1a;最近项目对接了一个第三方的平台、我们需要展示第三方平台返回来的图片资源、由于我们的服务器设置为了https、但是第三方平台返回的图片链接是 http 资源。所以就出现了图片无法加载出来的问题&#xff0c;在此记录一下问题的解决…

数据结构与算法(Java版) | 排序算法的介绍与分类

各位朋友&#xff0c;现在我们即将要进入数据结构与算法&#xff08;Java版&#xff09;这一系列教程中的排序算法这一章节内容的学习中了&#xff0c;所以还请大家系好安全带&#xff0c;跟随我准备出发吧&#xff01; 相信诸位应该都知道排序算法有很多种吧&#xff01;就算没…

旋转链表(C++解法)

题目 给你一个链表的头节点 head &#xff0c;旋转链表&#xff0c;将链表每个节点向右移动 k 个位置。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], k 2 输出&#xff1a;[4,5,1,2,3]示例 2&#xff1a; 输入&#xff1a;head [0,1,2], k 4 输出&#xff1a;[…