简述

业务开发中经常会遇到这样一种情况,用户在搜索框输入时要实时展示搜索相关的结果。要实现这个场景常用的方案有Completion Suggester、search_as_you_type。那么这两种方式有什么区别呢?一起来了解下。

环境说明:

数据量:9000w+

es版本:7.10.1

脚本执行工具:kibana

Completion Suggester和search_as_you_type的区别

1.Completion Suggester是基于前缀匹配、且数据结构存储在内存中,超级快,缺点是耗内存

2.search_as_you_type可以是前缀、中缀匹配,可以很快,但是要选好查询方式

3.Api调用方式不同,Completion Suggester是通过Suggest语句查询,search_as_you_type和常规查询方式一致

举个栗子

如何实现前缀匹配需求

使用Completion Suggester,示例如下:

  1. 创建索引
PUT /es_demo
{
"mappings": {
"properties": {
"title_comp": {
"type": "completion",
"analyzer": "standard"
}
}
}
}
  1. 初始化数据
POST _bulk
{"index":{"_index":"es_demo","_id":"1"}}
{"title_comp": "愤怒的小鸟"}
{"index":{"_index":"es_demo","_id":"2"}}
{"title_comp": "最后一只渡渡鸟"}
{"index":{"_index":"es_demo","_id":"3"}}
{"title_comp": "今天不加班啊"}
{"index":{"_index":"es_demo","_id":"4"}}
{"title_comp": "愤怒的青年"}
{"index":{"_index":"es_demo","_id":"5"}}
{"title_comp": "最后一只996程序猿"}
{"index":{"_index":"es_demo","_id":"6"}}
{"title_comp": "今日无事,勾栏听曲"}
  1. 查询DSL

    通过前缀查询,查找以“愤怒”开头的字符串
GET /es_demo/_search
{
"suggest": {
"title_suggest": {
"prefix": "愤怒",
"completion": {
"field": "title_comp"
}
}
}
}
  1. 查询代码demo
@SpringBootTest
public class SuggestTest { @Autowired
private RestHighLevelClient restHighLevelClient; @Test
public void testComp() {
List<Map<String, Object>> list = suggestComplete("愤怒");
list.forEach(m -> System.out.println("[" + m.get("title_comp") + "]"));
} public List<Map<String, Object>> suggestComplete(String keyword) {
CompletionSuggestionBuilder completionSuggestionBuilder = SuggestBuilders.completionSuggestion("title_comp");
completionSuggestionBuilder.size(5)
//跳过重复的
.skipDuplicates(true); SuggestBuilder suggestBuilder = new SuggestBuilder();
suggestBuilder.addSuggestion("suggest_title", completionSuggestionBuilder)
.setGlobalText(keyword); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.suggest(suggestBuilder); SearchRequest searchRequest = new SearchRequest("es_demo").source(searchSourceBuilder); try {
SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
CompletionSuggestion completionSuggestion = response.getSuggest().getSuggestion("suggest_title"); List<Map<String, Object>> suggestList = new LinkedList<>();
for (CompletionSuggestion.Entry.Option option : completionSuggestion.getOptions()) {
Map<String, Object> map = new HashMap<>();
map.put("title_comp", option.getHit().getSourceAsMap().get("title_comp"));
suggestList.add(map);
} return suggestList;
} catch (IOException e) {
throw new RuntimeException("ES查询出错");
}
}
}

查询结果:

[愤怒的小鸟]
[愤怒的青年]

如何实现中缀匹配需求

使用search_as_you_type,此处提供了hanlp_index和standard两种分词器的字段示例。示例如下:

  1. 创建索引
PUT /es_search_as_you_type
{
"mappings": {
"properties": {
"title": {
"type": "text",
"fields": {
"han": {
"type": "search_as_you_type",
"analyzer": "hanlp_index"
},
"stan": {
"type": "search_as_you_type",
"analyzer": "standard"
}
}
}
}
}
}
  1. 初始化数据
POST _bulk
{"index":{"_index":"es_search_as_you_type","_id":"1"}}
{"title": "愤怒的小鸟"}
{"index":{"_index":"es_search_as_you_type","_id":"2"}}
{"title": "最后一只渡渡鸟"}
{"index":{"_index":"es_search_as_you_type","_id":"3"}}
{"title": "今天不加班啊"}
{"index":{"_index":"es_search_as_you_type","_id":"4"}}
{"title": "愤怒的青年"}
{"index":{"_index":"es_search_as_you_type","_id":"5"}}
{"title": "最后一只996程序猿"}
{"index":{"_index":"es_search_as_you_type","_id":"6"}}
{"title": "今日无事,勾栏听曲"}
  1. 查询DSL
GET /es_search_as_you_type/_search
{
"query": {
"match": {
"title.stan": {
"query": "的小",
"operator": "and"
}
}
}
}
  1. 查询代码demo
@SpringBootTest
public class SuggestTest { @Autowired
private RestHighLevelClient restHighLevelClient; @Test
public void testSearchAsYouType() {
List<Map<String, Object>> list = suggestSearchAsYouType("的小");
list.forEach(m -> System.out.println("[" + m.get("title") + "]"));
} public List<Map<String, Object>> suggestSearchAsYouType(String keyword) {
//这里使用了search_as_you_type的2gram字段,可以根据自己需求调整配置
MatchQueryBuilder matchQueryBuilder = matchQuery("title.stan._2gram", keyword).operator(Operator.AND); //需要返回的字段
String[] includeFields = new String[]{"title"};
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder()
.query(matchQueryBuilder).size(5)
.fetchSource(includeFields, null)
.trackTotalHits(false)
.trackScores(true)
.sort(SortBuilders.scoreSort()); SearchRequest searchRequest = new SearchRequest("es_search_as_you_type").source(searchSourceBuilder); try {
SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
org.elasticsearch.search.SearchHits hits = response.getHits(); List<Map<String, Object>> suggestList = new LinkedList<>();
for (org.elasticsearch.search.SearchHit hit : hits) {
Map<String, Object> map = new HashMap<>();
map.put("title", hit.getSourceAsMap().get("title").toString());
suggestList.add(map);
}
return suggestList;
} catch (IOException e) {
throw new RuntimeException("ES查询出错");
}
}
}

查询结果:

[愤怒的小鸟]

分词器说明

查看分词结果的方式

第一种

指定分词器

GET _analyze
{
"analyzer": "standard",
"text": [
"愤怒的小鸟"
]
}

第二种

指定使用某个字段的分词器

POST es_search_as_you_type/_analyze
{
"field": "title.stan",
"text": [
"愤怒的青年"
]
}

hanlp_index和standard分词器的区别

standard分词器

  • 默认会过滤掉符号
  • 中文以单个字为最小单位,英文则会以空格符或其他符号或中文分隔作为一个单词

例:

GET _analyze
{
"analyzer": "standard",
"text": [
"愤怒的小鸟"
]
}

分词结果:

{
"tokens" : [
{
"token" : "愤",
"start_offset" : 0,
"end_offset" : 1,
"type" : "<IDEOGRAPHIC>",
"position" : 0
},
{
"token" : "怒",
"start_offset" : 1,
"end_offset" : 2,
"type" : "<IDEOGRAPHIC>",
"position" : 1
},
{
"token" : "的",
"start_offset" : 2,
"end_offset" : 3,
"type" : "<IDEOGRAPHIC>",
"position" : 2
},
{
"token" : "小",
"start_offset" : 3,
"end_offset" : 4,
"type" : "<IDEOGRAPHIC>",
"position" : 3
},
{
"token" : "鸟",
"start_offset" : 4,
"end_offset" : 5,
"type" : "<IDEOGRAPHIC>",
"position" : 4
}
]
}

hanlp_index分词器

  • 默认不会过滤符号
  • 通过语义等对字符串进行分词,会分出词语

例:

GET _analyze
{
"analyzer": "hanlp_index",
"text": [
"愤怒的小鸟"
]
}

分词结果:

{
"tokens" : [
{
"token" : "愤怒",
"start_offset" : 0,
"end_offset" : 2,
"type" : "a",
"position" : 0
},
{
"token" : "的",
"start_offset" : 2,
"end_offset" : 3,
"type" : "ude1",
"position" : 1
},
{
"token" : "小鸟",
"start_offset" : 3,
"end_offset" : 5,
"type" : "n",
"position" : 2
}
]
}

生产实践中的查询情况

基本都是几百毫秒就解决。ps:如果一条数据字段很多,最好只返回几个需要的字段即可,否则数据传输就要占用较多时间。

总结

当然,无论是Completion Suggester还是search_as_you_type的查询配置方式都还有很多,例如Completion Suggester的Context Suggester,search_as_you_type的2gram、3gram,还有查询类型match_bool_prefix、match_phrase、match_phrase_prefix等等。各种组合起来都会产生不同的效果,笔者这里只是列举出一种还算可以的方式。关于其他的查询类型和配置如何使用以及分别是怎么工作的,下次有空再聊聊。

官方文档链接

https://www.elastic.co/guide/en/elasticsearch/reference/7.10/search-as-you-type.html

【ElasticSearch】大数据量情况下的前缀、中缀实时搜索方案的更多相关文章

  1. 大数据量情况下求top N的问题

    上周五的时候去参加了一个面试,被问到了这个问题.问题描述如下: 假如存在一个很大的文件,文件中的每一行是一个字符串.请问在内存有限的情况下(内存无法加载这个文件中的所有内容),如何计算出出现频率最高的 ...

  2. phpExcel导入大数据量情况下内存溢出解决方案

    PHPExcel版本:1.7.6+ 在不进行特殊设置的情况下,phpExcel将读取的单元格信息保存在内存中,我们可以通过 PHPExcel_Settings::setCacheStorageMeth ...

  3. phpExcel大数据量情况下内存溢出解决

    版本:1.7.6+ 在不进行特殊设置的情况下,phpExcel将读取的单元格信息保存在内存中,我们可以通过 PHPExcel_Settings::setCacheStorageMethod() 来设置 ...

  4. 大数据量情况下高效比较两个list

    比如,对两个list<object>进行去重,合并操作时,一般的写法为两个for循环删掉一个list中重复的,然后再合并. 如果数据量在千条级别,这个速度还是比较快的.但如果数据量超过20 ...

  5. MYSQL的大数据量情况下的分页查询优化

    最近做的项目需要实现一个分页查询功能,自己先看了别人写的方法: <!-- 查询 --> <select id="queryMonitorFolder" param ...

  6. C#拼接SQL语句,SQL Server 2005+,多行多列大数据量情况下,使用ROW_NUMBER实现的高效分页排序

    /// <summary>/// 单表(视图)获取分页SQL语句/// </summary>/// <param name="tableName"&g ...

  7. 大数据量冲击下Windows网卡异常分析定位

    背景 mqtt的服务端ActiveMQ在windows上,多台PC机客户端不停地向MQ发送消息. 现象 观察MQ自己的日志data/activemq.log里显示,TCP链接皆异常断开.此时尝试从服务 ...

  8. 大数据量场景下storm自定义分组与Hbase预分区完美结合大幅度节省内存空间

    前言:在系统中向hbase中插入数据时,常常通过设置region的预分区来防止大数据量插入的热点问题,提高数据插入的效率,同时可以减少当数据猛增时由于Region split带来的资源消耗.大量的预分 ...

  9. 一脸懵逼学习HBase---基于HDFS实现的。(Hadoop的数据库,分布式的,大数据量的,随机的,实时的,非关系型数据库)

    1:HBase官网网址:http://hbase.apache.org/ 2:HBase表结构:建表时,不需要指定表中的字段,只需要指定若干个列族,插入数据时,列族中可以存储任意多个列(即KEY-VA ...

  10. MySQL数据库如何解决大数据量存储问题

    利用MySQL数据库如何解决大数据量存储问题? 各位高手您们好,我最近接手公司里一个比较棘手的问题,关于如何利用MySQL存储大数据量的问题,主要是数据库中的两张历史数据表,一张模拟量历史数据和一张开 ...

随机推荐

  1. 简单实用Ecplise常用快捷键

    简单实用Eclipse常用快捷键 用了Eclipse两年了,简单总结下目前我经常使用的快捷键!!! 1. Ctrl+Shift+R 功能:打开资源,这组快捷键可以让你打开你的工程中的任何一个文件 操作 ...

  2. 阿里云 AIGC 白嫖 FC 搭建 stable diffusion

    下午瞎逛在 V 站看到阿里在做推广,正好这几天在研究 stable-diffusion,就进去看了看,活动地址: https://developer.aliyun.com/topic/aigc . 主 ...

  3. 为HttpClient开启HTTP/2

    .Net Core在调用其他服务时,调用通常使用HttpClient,而HttpClient默认使用HTTP/1.1 . 配置 HttpClient 以使用 HTTP/2 h2 连接 自 .NET C ...

  4. day09-达人探店

    功能04-达人探店 5.功能04-达人探店 5.1发布&查看探店笔记 5.1.1发布探店笔记 探店笔记类似点评网站的评价,往往是图文结合.对应的表有两个: tb_blog:探店笔记表,包含笔记 ...

  5. 「微服务」这10道Consul面试题值得一看

    前言 Consul 是一种非常强大的分布式服务发现和配置管理工具,它可以帮助开发人员和运维人员更好地管理和维护分布式系统. 但是,使用 Consul 也需要投入一定的人力和物力,需要根据实际情况进行选 ...

  6. 2022-11-22:小美将要期中考试,有n道题,对于第i道题, 小美有pi的几率做对,获得ai的分值,还有(1-pi)的概率做错,得0分。 小美总分是每道题获得的分数。 小美不甘于此,决定突击复习,

    2022-11-22:小美将要期中考试,有n道题,对于第i道题, 小美有pi的几率做对,获得ai的分值,还有(1-pi)的概率做错,得0分. 小美总分是每道题获得的分数. 小美不甘于此,决定突击复习, ...

  7. 2020-08-19:TCP是通过什么机制保障可靠性的?

    福哥答案2020-08-19: 福哥口诀法:校(jiao)序确重拥流连(tcp可靠性保障机制:校验.序号.确认.重传.拥塞.流量.连接)校验:数据是否正确.序号:对数据编号seq.确认:ACK.重传: ...

  8. 2022-03-04:爱吃香蕉的珂珂。 珂珂喜欢吃香蕉。这里有 N 堆香蕉,第 i 堆中有 piles[i] 根香蕉。警卫已经离开了,将在 H 小时后回来。 珂珂可以决定她吃香蕉的速度 K (单位:根

    2022-03-04:爱吃香蕉的珂珂. 珂珂喜欢吃香蕉.这里有 N 堆香蕉,第 i 堆中有 piles[i] 根香蕉.警卫已经离开了,将在 H 小时后回来. 珂珂可以决定她吃香蕉的速度 K (单位:根 ...

  9. 2021-05-04:给定一个非负整数c,你要判断是否存在两个整数a和b,使得a*a+b*b=c。【举例】c=5时,返回true。c=4时,返回true。c=3时,返回false。

    2021-05-04:给定一个非负整数c,你要判断是否存在两个整数a和b,使得aa+bb=c.[举例]c=5时,返回true.c=4时,返回true.c=3时,返回false. 福大大 答案2021- ...

  10. Vuex modules 中active相互调用

    大中型项目中使用vuex进行状态管理时,经常会按模块分割到不同的module中去,而操作中难免有模块中的active相互调用的情况,然而有时也会出现一些问题,这里顺便记录下 store目录结构 在us ...