ToB企服应用市场:ToB评测及商务社交产业平台

标题: 【主流技术】ElasticSearch 在 Spring 项目中的实践 [打印本页]

作者: 拉不拉稀肚拉稀    时间: 2022-6-23 14:12
标题: 【主流技术】ElasticSearch 在 Spring 项目中的实践
前言

ElasticSearch简称es,是一个开源的高扩展的分布式全文检索引擎。
它可以近乎实时的存储、检索数据,其扩展性很好,ElasticSearch是企业级应用中较为常见的技术。
下面和大家分享 ElasticSearch 集成在Spring Boot 项目的一些学习心得。
一、ElasticSearch概述

1.1基本认识

ElasticSearch 是基于 Lucene 实现的开源、分布式、RESTful接口的全文搜索引擎。
Elasticsearch 还是一个分布式文档数据库,其中每个字段均是被索引的数据且可被搜索,它能够扩展至数以百计的服务器存储以及处理PB级的数据。
Elasticsearch 可以通过简单的 RESTful 风格 API 来隐藏 Lucene  的复杂性,让搜索变得更加简单。
1.2核心概念

Elasticsearch 的核心概念是 Elasticsearch 搜索的过程,在搜索的过程中,Elasticsearch 的存储过程、数据结构都会有所涉及。
注:

1.3倒排索引

Elasticsearch 使用一种名为倒排索引的结构进行搜索,一个索引由文档中所有不重复的列表构成,对于每一个词,都有一个包含它的文档列表。
传统数据库的搜索结构一般以id为主,可以一一对应数据库中的所有内容,即key-value的形式。
而倒排索引则与之相反,以内容为主,将所有不重复的内容记录按照匹配的程度(阈值)进行展示,即value-key的形式。
以下举两个例子来进行说明。
1.4了解ELK

ELK 是 ElasticSearch、Logstash、Kibana这三大开源框架首字母大写简称。
其中 Logstash 是中央数据流引擎,用于从不同目标(文件/数据存储/MQ)中收集不同的数据格式,经过过滤后支持输送到不同的目的地(文件/MQ/Redis/elasticsearch/kafka等)。
而 Kibana 可以将 ElasticSearch 的数据通过友好的可视化界面展示出来,且提供实时分析的功能。
ELK一般来说是一个日志分析架构技术栈的总称,但实际上 ELK 不仅仅适用于日志分析,它还可以支持任何其它数据分析和收集的场景,日志的分析和收集只是更具有代表性,并非 ELK 的唯一用途。
二、ElasticSearch(插件)安装

2.1安装声明

2.2 ElasticSearch下载

官网地址:https://www.elastic.co
下载地址(7.6.1版本):https://www.elastic.co/downloads/past-releases/elasticsearch-7-6-1,推荐迅雷下载(速度较快)。
2.3安装ElasticSearch

将下载好的压缩包进行安装即可,解压后如下图所示:

解压安装
2.4启动ElasticSearch

打开bin文件夹下的elasticsearch.bat文件,双击启动后访问默认地址:localhost:9200,即可得到以下json格式的数据:
  1. {
  2.   "name" : "ZHUZQC",
  3.   "cluster_name" : "elasticsearch",
  4.   "cluster_uuid" : "AMdLpCANStmY8kvou9-OtQ",
  5.   "version" : {
  6.     "number" : "7.6.1",
  7.     "build_flavor" : "default",
  8.     "build_type" : "zip",
  9.     "build_hash" : "aa751e09be0a5072e8570670309b1f12348f023b",
  10.     "build_date" : "2020-02-29T00:15:25.529771Z",
  11.     "build_snapshot" : false,
  12.     "lucene_version" : "8.4.0",
  13.     "minimum_wire_compatibility_version" : "6.8.0",
  14.     "minimum_index_compatibility_version" : "6.0.0-beta1"
  15.   },
  16.   "tagline" : "You Know, for Search"
  17. }
复制代码
2.5可视化界面-head安装

下载地址:https://github.com/mobz/elasticsearch-head/
安装要求:先检查计算机是否安装node.js、npm
head启动2.6初步创建索引

可以把索引当作一个数据库来使用,具体的创建如下步骤所示:
创建索引注:head仅可以当作一个数据可视化的展示工具,对于查询语句推荐使用Kibana。
2.7安装Kibana工具

Kibana是一个针对 ElasticSearch 的开源分析、可视化平台,用于搜索、查看交互存储在ElasticSearch中的数据。
Kibana 操作简单,基于浏览器的的用户界面可以快速创建仪表板(dashboard)并实时显示数据。
官网下载:https://www.elastic.co/downloads/past-releases/kibana-7-6-1
注意事项:Kibana 版本需要和 ElasticSearch 的版本保持一致。
安装步骤如下:
2.8使用Kibana工具

在开发的过程中,可供数据测试的工具有很多,比如postman、head、Chrome浏览器等,这里推荐使用 Kibana 进行数据测试。
操作界面如下图所示:

kibana界面三、IK分词器

3.1基本介绍

在使用中文进行搜索时,我们会对要搜索的信息进行分词:将一段中文分成一个个的词语或者句子,然后将分出的词进行搜索。
默认的中文分词是一个汉字一个词,如:“你好世界”,会被分成:“你”,“好”,“世”,“界”。但这样的分词方式显然并不全面,比如还可以分成:“你好”,“世界”。
ik分词器就解决了默认分词不全面的问题,可以将中文进行不重复的分词。
ik分词器提供了两种2算法:ik_smart(最少切分)以及ik_max_word(最细颗粒度划分)。
github下载:https://github.com/medcl/elasticsearch-analysis-ik/releases/tag/v7.6.1
3.2使用Kibana测试


图3-23.3修改本地字典

ik分词的默认字典并不能完全涵盖所有的中文分词,当我们想自定义分词时,就需要修改ik分词器的字典配置。
具体效果如下图3-3所示:

图3-3四、Rest风格操作

ElasticSearch 使用 Rest 风格来进行一系列操作,具体的命令如图4-1所示:

图4-14.1创建索引
  1. PUT /test_1/type/1
  2. {
  3.   "name": "zhuzqc",
  4.   "age": 35364
  5. }
复制代码
4.2修改索引内容
  1. GET /test_1
  2. {
  3.   "mappings": {
  4.     "properties": {
  5.       "name": {
  6.         "type": "text"
  7.       },
  8.       "age": {
  9.         "type": "long"
  10.       },
  11.       "birthdy": {
  12.         "type": "date"
  13.       }
  14.     }
  15.   }
  16. }
复制代码
4.3更新索引内容
  1. POST /test_1/_doc/1/_update
  2. {
  3.   "doc": {
  4.   "name": "noone"  
  5.   }
  6. }
复制代码
4.4删除索引
  1. DELETE test_2
复制代码
4.5关于documents的操作

4.5.1基本操作

documents 可以看作是数据库中的行记录;
  1. PUT zhuzqc/user/3
  2. {
  3.   "name": "李四",
  4.   "age": 894,
  5.   "desc": "影流之主",
  6.   "tags": ["劫","刺客","中单"]
  7. }
复制代码
2.获取数据:
  1. GET zhuzqc/user/1
复制代码
3.更新数据
  1. // POST请求对指定内容进行更新
  2. POST zhuzqc/user/1/_update
  3. {
  4.   "doc": {
  5.   "name": "342rfd",
  6.   "age": 243234
  7.   }
  8. }
复制代码
4.简单的条件查询
  1. // 查询统一GET开头,_search后接?,q代表query,属性:内容
  2. GET zhuzqc/user/_search?q=name:李
  3. 如:查询zhuzqc索引中name为李四的信息,其中李四遵循默认的分词规则
  4. GET zhuzqc/user/_search?q=name:李四
复制代码
4.5.2复杂操作

上述的一些简单查询操作在企业级应用开发中使用地较少,更多地还是使用查询实现复杂的业务。
随着业务的复杂程度增加,查询的语句也随之复杂起来,在使用复杂查询的过程中必然会涉及一些 elasticsearch 的进阶语法。
对于复杂查询的操作在下一章会详细介绍。
五、查询详解

ElasticSearch引擎首先分析需要查询的字符串,根据分词器规则对其进行分词。分词之后,才会根据查询条件进行结果返回。
5.1关键字介绍

  1. GET product_cloud/_search
  2. {
  3.   "query": {
  4.     "bool": {
  5.       "must": [
  6.         {
  7.           "bool": {
  8.             "should": [
  9.               {"match": {"product_comment":"持续交付 工程师"}}
  10.             ]
  11.           }
  12.         },
  13.         {
  14.           "bool": {
  15.             "should": [
  16.               {"terms": {"label_ids": [3]}}
  17.             ]
  18.           }
  19.         }
  20.       ],
  21.       "filter": {
  22.         "range": {
  23.           "label_ids": {
  24.             "gte": 0
  25.           }
  26.         }
  27.       }
  28.     }
  29.   }
复制代码
  1. "_source": ["product_comment","product_name","label_ids","product_solution","company_name"],
  2.   "sort": [
  3.     {
  4.       "label_ids": {
  5.         "order": "desc"
  6.       }
  7.     }
  8.   ],
  9.   "from": 0,
  10.   "size": 3
复制代码
5.2 highlight 高亮

使用 highlight  关键字可以在搜索结果中对需要高亮的字段进行高亮(可自定义样式)展示,具体代码如下:
  1. GET product_cloud/_search
  2. {
  3.   "query": {
  4.     "term": {
  5.       "product_comment": "世界"
  6.     }
  7.   },
  8.   "highlight": {
  9.     "pre_tags": "<p class='key' style='color:red'>",
  10.     "post_tags": "</p>",   
  11.     "fields": {
  12.       "product_comment": {}
  13.     }
  14.   }
  15. }
复制代码
六、Spring Boot集成ElasticSearch

在 Elasticsearch  的官方文档中有对 Elasticsearch 客户端使用的详细介绍: https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/8.0/installation.html
6.1添加依赖
  1. <properties>
  2.         <java.version>11</java.version>
  3.         
  4.         <elasticsearch.verson>7.6.1</elasticsearch.verson>
  5. </properties>
  6.         <dependency>
  7.             <groupId>org.springframework.boot</groupId>
  8.             <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
  9.         </dependency>
复制代码
6.2创建对象

定义一个客户端对象:
  1. @Configuration
  2. public class EsConfig {
  3.     @Bean
  4.     public RestHighLevelClient restHighLevelClient(){
  5.         RestHighLevelClient restHighLevelClient = new RestHighLevelClient(
  6.                 RestClient.builder(
  7.                         new HttpHost("127.0.0.1",9200,"http")
  8.                 )
  9.         );
  10.         return restHighLevelClient;
  11.     }
  12. }
复制代码
6.3分析类中的方法(索引相关API)

七、相关API操作

API 的操作主要是将Spring Boot项目与 Elasticsearch 的 indices 与 docs 相关联起来,这样可以做到在 Elasticsearch 中对项目数据进行一系列的操作。
7.1文档API

  1.     // 测试添加文档
  2.     @Test
  3.     void testAddDocument() throws IOException {
  4.         // 创建对象
  5.         User user = new User("zzz",3);
  6.         // 创建请求
  7.         IndexRequest zhu_index_request = new IndexRequest("zhu_index");
  8.         // 规则:put /zhu_index/_doc/1
  9.         zhu_index_request.id("1");
  10.         zhu_index_request.timeout(TimeValue.timeValueSeconds(1));
  11.         // 将数据放入 ElasticSearch 请求(JSON格式)
  12.         zhu_index_request.source(JSON.toJSONString(user), XContentType.JSON);
  13.         // 客户端发送请求
  14.         IndexResponse indexResponse = restHighLevelClient.index(zhu_index_request,            
  15.         RequestOptions.DEFAULT);
  16.     }
  17.     // 添加大批量的数据
  18.     @Test
  19.     void testBulkRequest() throws IOException {
  20.         BulkRequest bulkRequest = new BulkRequest();
  21.         bulkRequest.timeout("10s");
  22.         //创建数据集合
  23.         ArrayList<User> userList = new ArrayList<>();
  24.         userList.add(new User("zzz2",22));
  25.         userList.add(new User("zzz3",23));
  26.         userList.add(new User("zzz4",24));
  27.         userList.add(new User("zzz5",25));
  28.         userList.add(new User("zzz6",26));
  29.         //遍历数据:批量处理
  30.         for (int i = 0; i < userList.size(); i++) {
  31.             // 批量添加(或更新、或删除)
  32.             bulkRequest.add(
  33.                     new IndexRequest("zhu_index")
  34.                     //.id(""+(i+1))
  35.                     .source(JSON.toJSONString(userList.get(i)), XContentType.JSON));
  36.         }
  37.         BulkResponse bulkResponse = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
  38.     }
复制代码
  1. @Test
  2.     void testGetDocument() throws IOException {
  3.         GetRequest getRequest = new GetRequest("zhu_index","1");
  4.         GetResponse getResponse = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
  5.         // 返回_source的上下文
  6.         getRequest.fetchSourceContext(new FetchSourceContext(true));
  7.     }
复制代码
  1.    // 更新文档信息
  2.     @Test
  3.     void testUpdateDocument() throws IOException {
  4.         UpdateRequest updateRequest = new UpdateRequest("zhu_index","1");
  5.         updateRequest.timeout("1s");
  6.         User user = new User("ZhuZhuQC",18);
  7.         updateRequest.doc(JSON.toJSONString(user), XContentType.JSON);
  8.         UpdateResponse updateResponse = restHighLevelClient.update(updateRequest,
  9.         RequestOptions.DEFAULT);
  10.      
  11.     }
复制代码
  1.     // 查询数据
  2.     @Test
  3.     void testSearch() throws IOException {
  4.         // 创建查询对象
  5.         SearchRequest searchRequest = new SearchRequest(EsConst.ES_INDEX);
  6.         // 构建搜索条件(精确查询、全匹配查询)
  7.         TermQueryBuilder termQuery = QueryBuilders.termQuery("name","zzz2");
  8.         MatchAllQueryBuilder matchAllQuery = QueryBuilders.matchAllQuery();
  9.         // 执行构造器
  10.         SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
  11.         sourceBuilder.query(termQuery);
  12.         sourceBuilder.query(matchAllQuery);
  13.         // 设置查询时间,3秒内
  14.         sourceBuilder.timeout(new TimeValue(3, TimeUnit.SECONDS));
  15.         // 设置分页
  16.         sourceBuilder.from(0);
  17.         sourceBuilder.size(3);
  18.         // 最后执行搜索,并返回搜索结果
  19.         searchRequest.source(sourceBuilder);
  20.         SearchResponse searchResponse = restHighLevelClient.search(searchRequest,
  21.         RequestOptions.DEFAULT);
  22.         searchResponse.getHits();
  23.         // 打印结果
  24.         System.out.println(JSON.toJSONString(searchResponse.getHits()));
  25.         for (SearchHit documentFields : searchResponse.getHits().getHits()) {
  26.             System.out.println(documentFields.getSourceAsMap());
  27.         }
  28.     }
复制代码
八、实战分析

实战部分会模拟一个真实的 ElasticSearch 搜索过程:从创建项目开始,到使用爬虫爬取数据、编写业务,再到前后端分离交互,最后搜索结果高亮展示。
8.1创建项目

创建项目的步骤可如以下几步:
8.2爬取数据

在真实的项目中,数据可以从数据库获得,也可以从MQ(消息队列)中获得,也可以通过爬取数据(爬虫)获得,在这里介绍一下使用爬虫获取项目所需数据的过程。
1.首先导入网页解析依赖:
  1.         
  2.         <dependency>
  3.             <groupId>org.jsoup</groupId>
  4.             <artifactId>jsoup</artifactId>
  5.             <version>1.11.2</version>
  6.         </dependency>
复制代码
2.编写网页解析工具类(返回爬取到的数据):
  1. @Component
  2. public class HtmlParseUtil {
  3.     public static List<Content> parseJD(String keyword) throws IOException {
  4.         // 1、获取请求:https://search.jd.com/Search?keyword=java
  5.         String reqUrl = "https://search.jd.com/Search?keyword=" + keyword;
  6.         // 2、解析网页,返回的document对象就是页面的 js 对象
  7.         Document document = Jsoup.parse(new URL(reqUrl), 30000);
  8.         // 3、js 中使用的方法获取页面信息
  9.         Element j_goodList = document.getElementById("J_goodsList");
  10.         // 4、获取所有的 li 元素
  11.         Elements liElements = j_goodList.getElementsByTag("li");
  12.         //5、返回List封装对象
  13.         ArrayList<Content> goodsList = new ArrayList<>();
  14.         //5、获取元素中的内容,遍历的 li 对象就是每一个 li 标签
  15.         for (Element el : liElements) {
  16.             String price = el.getElementsByClass("p-price").eq(0).text();
  17.             String title = el.getElementsByClass("p-name").eq(0).text();
  18.             String img = el.getElementsByTag("img").eq(0).attr("data-lazy-img");
  19.             // 将爬取的信息放入 List 对象中
  20.             Content content = new Content();
  21.             content.setTitle(title);
  22.             content.setImg(img);
  23.             content.setPrice(price);
  24.             goodsList.add(content);
  25.         }
  26.         return goodsList;
  27.     }
  28. }
复制代码
8.3编写业务

要编写的业务只有两部分:1、将上述获取的数据放入 ElasticSearch 的索引中;2、实现 ElasticSearch 的搜索功能;
步骤一:

1.controller层:
  1.     @Autowired
  2.     private ContentService contentService;
  3.     @GetMapping("/parse/{keyword}")
  4.     public Boolean parse(@PathVariable("keyword") String keyword) throws IOException {
  5.         return contentService.parseContent(keyword);
  6.     }
复制代码
2.service层:
  1.     @Autowired
  2.     private RestHighLevelClient restHighLevelClient;
  3.     /**
  4.      *  1、将解析后的数据放入 ElasticSearch 的索引中
  5.      * */
  6.     public Boolean parseContent(String keyword) throws IOException {
  7.         List<Content> contents = new HtmlParseUtil().parseJD(keyword);
  8.         //批量插入 es
  9.         BulkRequest bulkRequest = new BulkRequest();
  10.         bulkRequest.timeout("2m");
  11.         for (int i = 0; i < contents.size(); i++) {
  12.             bulkRequest.add(
  13.                     new IndexRequest("jd_goods")
  14.                     .source(JSON.toJSONString(contents.get(i)), XContentType.JSON)
  15.             );
  16.         }
  17.         BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
  18.         return bulk.hasFailures();
  19.     }
复制代码
步骤二:

1.controller层:
  1.     @GetMapping("/search/{keyword}/{pageNo}/{pageSize}")
  2.     public List<Map<String, Object>> search(@PathVariable("keyword") String keyword,
  3.                                             @PathVariable("pageNo") Integer pageNo,
  4.                                             @PathVariable("pageSize") Integer pageSize) throws IOException {
  5.         return contentService.searchPage(keyword, pageNo, pageSize);
  6.     }
复制代码
2.service层:
[code]    /**     * 2、获取数据后实现搜索功能     * */    public List searchPage(String keyword, Integer pageNo, Integer pageSize) throws IOException {        if (pageNo




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4