视频教程:SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式,体系详解springcloud微服务技术栈课程|黑马步伐员Java微服务
阅读本文前可以先阅读以下文章:
- ElasticSearch快速入门——上篇(认识ElasticSearch、安装ElasticSearch、安装kibana、IK分词器、ElasticSearch中的基本概念、索引库操作、文档操作)
- ElasticSearch快速入门——下篇(在Java代码中操作ElasticSearch、JavaRestClient、操作索引库、操作文档、DSL查询、JavaRestClient查询、数据聚合)
- 通过docker启动ElasticSearch后为ElasticSearch设置用户和暗码
1. 什么是自动补全
ElasticSearch 中的自动补全跟我们明白的自动补全不太一样,为了各人明白,我们来看一个案例
当我们在搜索框输入 sj 时,搜索框下方会表现以 sj 拼音首字母开头的词条(如手机、湿巾、数据线、史记、书架等),这个功能被称为自动补全
自动补全功能可以让用户尽大概地搜索到想要的东西,而不必要打出完整的内容
2. 拼音分词器
要想实现自动补全功能,我们必要先学习一下拼音分词器,由于自动补全功能是基于拼音分词器实现的
2.1 初识拼音分词器
拼音分词器的官网:analysis-pinyin
拼音分词器跟我们学过的 IK 分词器相似,都是 ElasticSearch 的一个插件
2.2 下载拼音分词器
下载地点:v7.17.18
本次演示使用的 ElasticSearch 版本为 7.17.18
其它 ElasticSearch 版本对应的拼音分词器的下载地点:Tags
2.3 安装拼音分词器
解压完成之后,将拼音分词器上传到 ElasticSearch 的 plugin 目次下(本次演示是通过 docker 安装 ElasticSearch 的)
先将拼音分词器上传到服务器,一样平常是当前用户的目次
接着将拼音分词器复制到 ElasticSearch 的 plugin 的目次下
- sudo cp elasticsearch-analysis-pinyin-7.17.18 -r /var/lib/docker/volumes/elasticsearch-plugins/_data
复制代码 末了重启 ElasticSearch 容器
- sudo docker restart elasticsearch
复制代码 2.4 测试拼音分词器
我们在 Kibana 提供的控制台中测试拼音分词器是否生效
在欣赏器打开 Kibana 提供的控制台
- http://127.0.0.1:5601/app/dev_tools#/console
复制代码 输入以下内容测试拼音分词器是否生效
- POST /_analyze
- {
- "text": [
- "练习时长两年半"
- ],
- "analyzer": "pinyin"
- }
复制代码 测试结果如下,主要包含两部分内容:
- {
- "tokens" : [
- {
- "token" : "lian",
- "start_offset" : 0,
- "end_offset" : 0,
- "type" : "word",
- "position" : 0
- },
- {
- "token" : "lxsclnb",
- "start_offset" : 0,
- "end_offset" : 0,
- "type" : "word",
- "position" : 0
- },
- {
- "token" : "xi",
- "start_offset" : 0,
- "end_offset" : 0,
- "type" : "word",
- "position" : 1
- },
- {
- "token" : "shi",
- "start_offset" : 0,
- "end_offset" : 0,
- "type" : "word",
- "position" : 2
- },
- {
- "token" : "chang",
- "start_offset" : 0,
- "end_offset" : 0,
- "type" : "word",
- "position" : 3
- },
- {
- "token" : "liang",
- "start_offset" : 0,
- "end_offset" : 0,
- "type" : "word",
- "position" : 4
- },
- {
- "token" : "nian",
- "start_offset" : 0,
- "end_offset" : 0,
- "type" : "word",
- "position" : 5
- },
- {
- "token" : "ban",
- "start_offset" : 0,
- "end_offset" : 0,
- "type" : "word",
- "position" : 6
- }
- ]
- }
复制代码 3. 自定义分词器
3.1 拼音分词器存在的题目
拼音分词器还无法正常用于生产环境,由于拼音分词器存在一些题目
以 “训练时长两年半” 这句话为例,拼音分词器存在以下题目:
- “训练时长两年半” 这句话没有被分词,而是作为一个整体出现
- 把 “训练时长两年半” 这句话中的每一个字都形成了一个拼音(用处不大)
- 分词后的结果只剩下拼音,没有汉字
其实我们很少使用拼音搜索,大多数情况下我们都是使用中文去搜索的,分词后有拼音只是锦上添花,分词后的结果中汉字是必须保留的,所以我们必要对拼音分词器做一些配置,也就是自定义分词器
3.2 分词器(analyzer)的组成
ElasticSearch 中分词器(analyzer)的组成有三部分:
- character filters:在 tokenizer 之前对文本进行处理,比方删除字符、替换字符
- tokenizer:将文本按照一定的规则切割成词条(term),比方 keyword(不分词)、ik_smart 等
- tokenizer filter:将 tokenizer 输出的词条做进一步处理,比方巨细写转换、同义词处理、拼音处理等
3.3 如何自定义分词器
要想自定义分词器,一定要在创建索引库的时候去设置
我们可以在创建索引库时,通过 settings 来配置自定义的 analyzer(分词器)
自定义分词器时可以只设置分词器(analyzer)的某个部分
- PUT /test
- {
- "settings": {
- "analysis": {
- "analyzer": {
- "my_analyzer": {
- "tokenizer": "ik_max_word",
- "filter": "pinyin"
- }
- }
- }
- }
- }
复制代码 tokenizer 我们使用 ik_max_word,先分词,分好词后再将词条交给拼音分词器处理,如许做可以解决拼音分词器没有分词的题目
但是拼音分词器还存在两个题目:分词后的每一个字都形成了一个拼音、分词后的结果只剩下拼音,没有汉字
3.4 拼音分词器的可选参数
我们必要对拼音分词器做进一步的定制
在拼音分词器的官网上,给出了很多的可选参数(Optional Parameters)
参数名称寄义keep_first_letter启用后,只保留每个汉字的第一个字母。比方,刘德华变为ldh。默认:true。keep_separate_first_letter启用后,保留每个汉字的第一个字母,并分别表现。比方,刘德华变为l,d,h。默认:false。留意:这大概会因词频增长查询的含糊度。limit_first_letter_length设置第一个字母结果的最大长度。默认:16。keep_full_pinyin启用后,保留每个汉字的完整拼音。比方,刘德华变为[liu,de,hua]。默认:true。keep_joined_full_pinyin启用后,将每个汉字的完整拼音连接起来。比方,刘德华变为[liudehua]。默认:false。keep_none_chinese保留结果中的非汉字字母或数字。默认:true。keep_none_chinese_together保留非汉字字母在一起。默认:true。比方,DJ音乐家变为DJ,yin,yue,jia。当设置为false时,DJ音乐家变为D,J,yin,yue,jia。留意:必要先启用keep_none_chinese。keep_none_chinese_in_first_letter在首字母中保留非汉字字母。比方,刘德华AT2016变为ldhat2016。默认:true。keep_none_chinese_in_joined_full_pinyin在连接的完整拼音中保留非汉字字母。比方,刘德华2016变为liudehua2016。默认:false。none_chinese_pinyin_tokenize如果非汉字字母是拼音,将其拆分为单独的拼音词。默认:true。比方,liudehuaalibaba13zhuanghan变为liu,de,hua,a,li,ba,ba,13,zhuang,han。留意:必要先启用keep_none_chinese和keep_none_chinese_together。keep_original启用后,保留原始输入。默认:false。lowercase将非汉字字母转换为小写。默认:true。trim_whitespace默认:true。remove_duplicated_term启用后,移除重复的词以节省索引空间。比方,de的变为de。默认:false。留意:大概与位置相干的查询受到影响。ignore_pinyin_offset在6.0版本之后,偏移量受到严格限制,不允许重叠的词。通过此参数,将允许重叠的词,忽略偏移量。请留意,全部与位置相干的查询或高亮将变得不精确。如果必要偏移量,请设置为false。默认:true。 3.5 配置自定义分词器的tokenizer和filter
- PUT /test
- {
- "settings": {
- "analysis": {
- "analyzer": {
- "my_analyzer": {
- "tokenizer": "ik_max_word",
- "filter": "py"
- }
- },
- "filter": {
- "py": {
- "type": "pinyin",
- "keep_full_pinyin": false,
- "keep_joined_full_pinyin": true,
- "keep_original": true,
- "limit_first_letter_length": 16,
- "remove_duplicated_term": true,
- "none_chinese_pinyin_tokenize": false
- }
- }
- }
- }
- }
复制代码 创建一个自定义的分词器my_analyzer,使用ik_max_word分词器进行中文分词,并通过pinyin过滤器将中文词条转换为拼音,保留了原始中文词条和连接起来的全拼,同时限制了首字母长度并移除重复的词条
3.6 如何使用自定义分词器
自定义分词器创建好了之后,该怎么使用呢
要使用自定义分词器,我们必要在定义索引库字段(Mapping)的时候使用
- PUT /test
- {
- "settings": {
- "analysis": {
- "analyzer": {
- "my_analyzer": {
- "tokenizer": "ik_max_word",
- "filter": "py"
- }
- },
- "filter": {
- "py": {
- "type": "pinyin",
- "keep_full_pinyin": false,
- "keep_joined_full_pinyin": true,
- "keep_original": true,
- "limit_first_letter_length": 16,
- "remove_duplicated_term": true,
- "none_chinese_pinyin_tokenize": false
- }
- }
- }
- },
- "mappings": {
- "properties": {
- "name": {
- "type": "text",
- "analyzer": "my_analyzer"
- }
- }
- }
- }
复制代码 3.7 测试自定义分词器
3.7.1 直接测试
- POST /test/_analyze
- {
- "text": [
- "练习时长两年半"
- ],
- "analyzer": "my_analyzer"
- }
复制代码 测试结果

- {
- "tokens" : [
- {
- "token" : "练习",
- "start_offset" : 0,
- "end_offset" : 2,
- "type" : "CN_WORD",
- "position" : 0
- },
- {
- "token" : "lianxi",
- "start_offset" : 0,
- "end_offset" : 2,
- "type" : "CN_WORD",
- "position" : 0
- },
- {
- "token" : "lx",
- "start_offset" : 0,
- "end_offset" : 2,
- "type" : "CN_WORD",
- "position" : 0
- },
- {
- "token" : "时长",
- "start_offset" : 2,
- "end_offset" : 4,
- "type" : "CN_WORD",
- "position" : 1
- },
- {
- "token" : "shichang",
- "start_offset" : 2,
- "end_offset" : 4,
- "type" : "CN_WORD",
- "position" : 1
- },
- {
- "token" : "sc",
- "start_offset" : 2,
- "end_offset" : 4,
- "type" : "CN_WORD",
- "position" : 1
- },
- {
- "token" : "两年",
- "start_offset" : 4,
- "end_offset" : 6,
- "type" : "CN_WORD",
- "position" : 2
- },
- {
- "token" : "liangnian",
- "start_offset" : 4,
- "end_offset" : 6,
- "type" : "CN_WORD",
- "position" : 2
- },
- {
- "token" : "ln",
- "start_offset" : 4,
- "end_offset" : 6,
- "type" : "CN_WORD",
- "position" : 2
- },
- {
- "token" : "两",
- "start_offset" : 4,
- "end_offset" : 5,
- "type" : "COUNT",
- "position" : 3
- },
- {
- "token" : "liang",
- "start_offset" : 4,
- "end_offset" : 5,
- "type" : "COUNT",
- "position" : 3
- },
- {
- "token" : "l",
- "start_offset" : 4,
- "end_offset" : 5,
- "type" : "COUNT",
- "position" : 3
- },
- {
- "token" : "年半",
- "start_offset" : 5,
- "end_offset" : 7,
- "type" : "CN_WORD",
- "position" : 4
- },
- {
- "token" : "nianban",
- "start_offset" : 5,
- "end_offset" : 7,
- "type" : "CN_WORD",
- "position" : 4
- },
- {
- "token" : "nb",
- "start_offset" : 5,
- "end_offset" : 7,
- "type" : "CN_WORD",
- "position" : 4
- }
- ]
- }
复制代码 3.7.2 插入文档测试
测试数据如下(狮子和虱子的拼音是一样的)
- POST /test/_doc/1
- {
- "id": 1,
- "name": "狮子"
- }
- POST /test/_doc/2
- {
- "id": 2,
- "name": "虱子"
- }
复制代码 我们先通过拼音 shizi 来搜索
- GET /test/_search
- {
- "query": {
- "match": {
- "name": "shizi"
- }
- }
- }
复制代码 成功搜索出狮子和虱子
但如果我们搜索的内容是掉入狮子笼怎么办呢
- GET /test/_search
- {
- "query": {
- "match": {
- "name": "掉入狮子笼怎么办"
- }
- }
- }
复制代码
从搜索结果中我们可以发现,我们明明搜索的是狮子,怎么虱子也搜索出来了?
这阐明我们自定义的分词器有题目,在用拼音搜索时确实没题目,但是在用中文搜索时却搜出了同音词
3.8 使用自定义分词器要留意的事项
拼音分词器适合在创建倒排索引的时候使用,但不能在搜索的时候使用
创建倒排索引时
用户搜索狮子,搜索结果中居然出现了虱子
所以,我们在创建倒排索引时使用的分词器要和搜索时使用的分词器分开
怎么分开呢,在创建倒排索引时使用 my_analyzer 分词器,搜索时使用 ik_smart 分词器
- PUT /test
- {
- "settings": {
- "analysis": {
- "analyzer": {
- "my_analyzer": {
- "tokenizer": "ik_max_word",
- "filter": "py"
- }
- },
- "filter": {
- "py": {
- "type": "pinyin",
- "keep_full_pinyin": false,
- "keep_joined_full_pinyin": true,
- "keep_original": true,
- "limit_first_letter_length": 16,
- "remove_duplicated_term": true,
- "none_chinese_pinyin_tokenize": false
- }
- }
- }
- },
- "mappings": {
- "properties": {
- "name": {
- "type": "text",
- "analyzer": "my_analyzer",
- "search_analyzer": "ik_smart"
- }
- }
- }
- }
复制代码 我们删除 test 索引库之后,重写创建 test 索引库进行测试
- GET /test/_search
- {
- "query": {
- "match": {
- "name": "掉入狮子笼怎么办"
- }
- }
- }
复制代码 测试结果如下(可以看到,搜索结果中没有虱子了)
4. DSL实现自动补全查询
ElasticSearch 提供了 Completion suggester 查询来实现自动补全功能,这个查询会匹配以用户输入内容开头的词条并
返回
4.1 字段的范例的束缚
为了提高补全查询的效率,对于文档中字段的范例有一些束缚:
- 参与补全查询的字段必须是 completion 范例
- 字段的内容一样平常是用来补全的多个词条形成的数组
4.2 查询语法
索引库
- PUT test2
- {
- "mappings": {
- "properties": {
- "title":{
- "type": "completion"
- }
- }
- }
- }
复制代码 测试数据
- POST test2/_doc
- {
- "title": ["Sony", "WH-1000XM3"]
- }
- POST test2/_doc
- {
- "title": ["SK-II", "PITERA"]
- }
- POST test2/_doc
- {
- "title": ["Nintendo", "switch"]
- }
复制代码 实行查询操作
- POST /test2/_search
- {
- "suggest": {
- "title_suggest": {
- "text": "s",
- "completion": {
- "field": "title",
- "skip_duplicates": true,
- "size": 10
- }
- }
- }
- }
复制代码 查询结果(查询结果中包含了文档的原始信息)
5. 自动补全案例
我们来做一个关于旅店数据的自动补全案例
5.1 预备工作
5.1.1 创建hotel索引库
- PUT /hotel
- {
- "settings": {
- "analysis": {
- "analyzer": {
- "text_anlyzer": {
- "tokenizer": "ik_max_word",
- "filter": "py"
- },
- "completion_analyzer": {
- "tokenizer": "keyword",
- "filter": "py"
- }
- },
- "filter": {
- "py": {
- "type": "pinyin",
- "keep_full_pinyin": false,
- "keep_joined_full_pinyin": true,
- "keep_original": true,
- "limit_first_letter_length": 16,
- "remove_duplicated_term": true,
- "none_chinese_pinyin_tokenize": false
- }
- }
- }
- },
- "mappings": {
- "properties": {
- "id": {
- "type": "keyword"
- },
- "name": {
- "type": "text",
- "analyzer": "text_anlyzer",
- "search_analyzer": "ik_smart",
- "copy_to": "all"
- },
- "address": {
- "type": "keyword",
- "index": false
- },
- "price": {
- "type": "integer"
- },
- "score": {
- "type": "integer"
- },
- "brand": {
- "type": "keyword",
- "copy_to": "all"
- },
- "city": {
- "type": "keyword"
- },
- "starName": {
- "type": "keyword"
- },
- "business": {
- "type": "keyword",
- "copy_to": "all"
- },
- "location": {
- "type": "geo_point"
- },
- "pic": {
- "type": "keyword",
- "index": false
- },
- "all": {
- "type": "text",
- "analyzer": "text_anlyzer",
- "search_analyzer": "ik_smart"
- },
- "suggestion": {
- "type": "completion",
- "analyzer": "completion_analyzer",
- "search_analyzer": "ik_smart"
- }
- }
- }
- }
复制代码 5.1.2 导入测试工程
测试工程的 Gitee 地点:hotel-demo
5.1.3 导入旅店数据到数据库中
SQL 脚本在测试工程的 doc 目次下
5.1.4 将数据库中的数据导入到ElasticSearch
导入数据前,更改与连接 ElasticSearch 相干的信息(如果 ElasticSearch 没有设置暗码,可以去除 setHttpClientConfigCallback 代码)
运行 HotelDocumentTest 测试类中的 testBulkRequest 方法,将数据库中的数据导入到 ElasticSearch
在 Kibana 提供的控制台检查数据是否导入成功
- GET /hotel/_search
- {
- "query": {
- "match_all": {}
- }
- }
复制代码
5.2 测试自动补全功能
在 Kibana 提供的控制台测试自动补全功能
- GET /hotel/_search
- {
- "suggest": {
- "suggestions": {
- "text": "s",
- "completion": {
- "field": "suggestion",
- "skip_duplicates": true,
- "size": 10
- }
- }
- }
- }
复制代码 测试结果
6. RestAPI实现自动补全查询
构建请求参数的 API

结果解析

- import cn.itcast.hotel.service.IHotelService;
- import org.apache.http.HttpHost;
- import org.apache.http.auth.AuthScope;
- import org.apache.http.auth.UsernamePasswordCredentials;
- import org.apache.http.impl.client.BasicCredentialsProvider;
- import org.elasticsearch.action.search.SearchRequest;
- import org.elasticsearch.action.search.SearchResponse;
- import org.elasticsearch.client.RequestOptions;
- import org.elasticsearch.client.RestClient;
- import org.elasticsearch.client.RestClientBuilder;
- import org.elasticsearch.client.RestHighLevelClient;
- import org.elasticsearch.search.suggest.Suggest;
- import org.elasticsearch.search.suggest.SuggestBuilder;
- import org.elasticsearch.search.suggest.SuggestBuilders;
- import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
- import org.junit.jupiter.api.AfterEach;
- import org.junit.jupiter.api.BeforeEach;
- import org.junit.jupiter.api.Test;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.boot.test.context.SpringBootTest;
- import java.io.IOException;
- import java.util.List;
- @SpringBootTest
- class HotelSuggestionTest {
- private RestHighLevelClient restHighLevelClient;
- @Autowired
- private IHotelService hotelService;
- @Test
- void testSuggestion() throws IOException {
- // 1.准备SearchRequest
- SearchRequest searchRequest = new SearchRequest("hotel");
- // 2.准备DSL
- searchRequest.source().suggest(new SuggestBuilder().addSuggestion(
- "suggestions",
- SuggestBuilders.completionSuggestion("suggestion")
- .prefix("h")
- .skipDuplicates(true)
- .size(10)
- ));
- // 3.发送请求
- SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
- // 4.解析结果
- // 4.1.获取suggest对象
- Suggest suggest = searchResponse.getSuggest();
- // 4.2.根据名称获取suggestion对象
- CompletionSuggestion suggestion = suggest.getSuggestion("suggestions");
- // 4.3.获取options
- List<CompletionSuggestion.Entry.Option> options = suggestion.getOptions();
- // 4.4.遍历
- for (CompletionSuggestion.Entry.Option option : options) {
- System.out.println("option.getText().string() = " + option.getText().string());
- }
- }
- @BeforeEach
- void setUp() {
- // 用户名和密码
- String username = "elastic";
- String password = "tF8RGg2vd0FAzgkK";
- final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
- credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));
- RestClientBuilder restClientBuilder = RestClient
- .builder(new HttpHost("127.0.0.1", 9200, "http"))
- .setHttpClientConfigCallback(httpClientBuilder -> httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider));
- restHighLevelClient = new RestHighLevelClient(restClientBuilder);
- }
- @AfterEach
- void tearDown() throws IOException {
- restHighLevelClient.close();
- }
- }
复制代码 7. 综合案例:实现搜索框自动补全
测试工程已实现搜索框自动补全,启动测试工程后,在欣赏器中查看搜索框的自动补全效果
前端源代码的 Gitee 地点:auto-complete
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |