ElasticSearch7.6.x 快速入门到实战案例

一. 前提

1. 版本选择:ElasticSearch7.6.1;

2. 6.x 和 7.x 的区别很大,6.x 是原生API;

3. 以前做搜索都是通过 mysql 进行搜索:like %java%,当大数据量的时候,速度十分慢,可以通过添加索引进行解决,添加索引后速度相对比较快点,但是本质还是达不到大数据的要求,此时就需要去使用一些分布式的全文搜索引擎

4. ElasticSearch:做搜索,百度、淘宝、github等都使用的它。

二. Doug Cutting 介绍

1. Google是一个做搜索引擎起家的公司,同期 Doug Cutting 做了一个基于文本搜索的函数库,命名为 Lucene

2. Lucene由 Java 编写,目的是为各种中小型应用软件加入全文检索,并且是开源的; 

3. 2001年底,成为 Apache 软件基金会 jakarta 项目的一个子项目;

4. 2004年,Doug Cutting 在 Lucene 的基础上和 Apache 开源伙伴 Mike Cafarella 合作开发一款开源的搜索引擎,命名为 Nutch;

5. 由于大数据时代的来临,不论是 Google 还是 Lutch 都面临搜索对象"体积"不断增加的问题;

6. 2003年 Google 介绍了自己的文件系统 GFS(Google File System),这是为了存储海量搜索数据而设计的专用文件系统,2004年 Doug Cutting 基于 Google 的 GFS论文,实现了分布式文件存储系统,命名为 NDFS(Nutch Distributed File System);

7. 2004年  Google 介绍了自己的 MapReduce 编程模型,用于大规模数据集(大于1TB)的并行分析运算,2005年 Doug Cutting 又基础MapReduce,在 Nutch 搜索引擎实现了该功能,到此大数据的两个问题:存储(NDFS) + 计算(MapReduce ) 就得到了解决;

8. 2006年 Yahoo 聘请了 Doug Cutting,之后 Doug Cutting 将 NDFS 和 MapReduce 进行了升级改造,并重新命令为 Hadoop(NDFS也改名为HDFS,Hadoop Distributed File System),这也是 Hadoop的由来;

9. 2006年 Google 介绍了自己的BigTable,这是一种分布式数据存储系统,用来处理海量数据的非关系型数据库,Doug Cutting在 Hadoop中也引入了BigTable,命令为 HBase,所有 Hadoop的核心部分,基本都有 Google的影子;

10. Lucene介绍:

(1) 是一套信息检索工具包,是一个jar包,不包含搜索引擎系统;

(2) 包含:索引结构(理解为数据库),读写索引的工具、排序功能、搜索规则......工具类;

11. Lucene 和 ElasticSearch 的关系:

ElasticSearch 是基于 Lucene 做了一些封装和增强,上手十分简单。

三. ElasticSearch 概述

1. Elaticsearch,简称为 ES,ES 是一个开源的高扩展分布式全文检索引擎,它可以近乎实时的存储、检索数据本身扩展性很好,可以扩展到上百台服务器,处理PB级别(大数据时代)的数据, ES 也使用 Java 开发并使用 Lucene 作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的 RESTful API 来隐藏 Lucene 的复杂性,从而让全文搜索变得简单;

2. 谁在使用:

(1) 维基百科,类似百度百科(百度的权重),全文检索,高亮,搜索推荐;

(2) The Guardian(国外新闻网站),类似搜狐新闻,用户行为日志(点击,浏览,收藏,评论) + 社交网络数据(对某某新闻的相 关看法),数据分析,给到每篇新闻文章的作者,让他知道他的文章的公众反馈(好,坏,热门,垃圾,鄙视,崇拜);

(3) Stack Overflow(国外的程序异常讨论论坛),IT问题,程序的报错,提交上去,有人会跟你讨论和回答,全文检索,搜索相关问题和答案,程序报错了,就会将报错信息粘贴到里面去,搜索有没有对应的答案;

(4) GitHub (开源代码管理),搜索上千亿行代码;

(5) 电商网站,检索商品;

(6) 日志数据分析,logstash采集日志,ES进行复杂的数据分析,ELK技术,elasticsearch + logstash+kibana

(7) 商品价格监控网站,用户设定某商品的价格阈值,当低于该阈值的时候,发送通知消息给用户,比如说订阅牙膏的监控,如果高露洁牙膏的家庭套装低于50块钱,就通知我,我就去买;

(8) BI系统,商业智能,Business Intelligence,比如说有个大型商场集团,BI分析一下某某区域最近3年的用户消费金额的趋势以及用户群体的组成构成,产出相关的数张报表,**区,最近3年,每年消费金额呈现100%的增长,而且用户群体85%是高级白领,开一个新商场,ES执行数据分析和挖掘,Kibana进行数据可视化;

(9) 国内:站内搜索(电商,招聘,门户,等等),IT系统搜索(0A , CRM , ERP ,等等),数据分析(ES热门的一个使用场景)。

 四. ES 和 solr 的差别

1. Elaticsearch 简介:

(1) Elasticsearch 是一个实时分布式搜索和分析引擎,它让你以前所未有的速度处理大数据成为可能;

(2) 它用于全文搜索结构化搜索分析以及将这三者混合使用;

(3) 维基百科使用 Elasticsearch 提供全文搜索并高亮关键字,以及输入实时搜索(search-asyou-type)和搜索纠错(did-you-mean)等搜索
建议功能;

(4) 英国卫报使用 Elasticsearch 结合用户日志和社交网络数据提供给他们的编辑以实时的反馈,以便及时了解公众对新发表的文章的回
应;

(5) Stackoverflow 结合全文搜索与地理位置直询,以及 more-like-this 功能来找到相关的问题和答案;

(6) Github使用 Elasticslearch 检索1300亿行的代码;

(7) 但是 Elasticsearch 不仅用于大型企业,它还让像 DataDog 以及 Klout 这样的创业公司将最初的想法变成可扩展的解决方案;

(8) Elasticsearch 可以在你的笔记本上运行,也可以在数以百计的服务器上处理PB级别的数据;

(9) Elasticsearch 是一个基于 Apache Lucene(TM) 的开源搜索引擎,无论在开源还是专有领域,Lucene可以被认为是迄今为止最先进、性能最好的、功能最全的搜索引擎库,但是,Lucene只是一个库,想要使用它,你必须使用Java来作为开发语言并将其直接集成到你的应用中,更糟糕的是,Lucene非常复杂,你需要深入了解检索的相关知识来理解它是如何工作的;

(10) Elasticsearch 也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的 RESTful API 来隐藏 Lucene的复杂性,从而让全文搜索变得简单。

2. Solr 简介:

(1) Solr 是 Apache 下的一个顶级开源项目,采用 Java 开发,它是基于Lucene的全文搜索服务器,Solr 提供了比 Lucene 更为丰富的直询语言,同时实现了可配置、可扩展,并对索引、搜索性能进行了优化;

(2) Solr 可以独立运行、运行在 Jetty、Tomcat 等这些 Servlet 容器中,Solr 索引的实现方法很简单,用 POST 方法向 Solr 服务器发送一个描述 Field 及其内容的 XML 文档,Solr 根据 xml 文档添加、删除、更新索引,Solr 搜索只需要发送 HTTP GET 请求,然后对 Solr 返回Xml、json等格式的查询结果进行解析,组织页面布局,Solr 不提供构建 UI 的功能,Solr 提供了一个管理界面,通过管理界面可以查询 Solr 的配置和运行情况;

(3) Solr 是基于 lucene 开发企业级搜索服务器,实际上就是封装了lucene;

(4) Solr 是一个独立的企业级搜索应用服务器,它对外提供类似于 Web-service 的API接口,用户可以通过 http 请求,向搜索引擎服务器提交一定格式的文件,生成索引,也可以通过提出查找请求,并得到返回结果。

3. Lucene 简介:

(1) Lucene 是 apache 软件基金会4 jakarta 项目组的一个子项目,是一个开放源代码的全文检索引擎工具包,但它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擘(英文与德文两种西方语言);

(2) Lucene 的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎;

(3) Lucene 是一套用于全文检索和搜寻的开源程式库,由 Apache 软件基金会支持和提供;

(4) Lucene提供了一个简单却强大的应用程式接口,能够做全文索引和搜寻,在 Java 开发环境里 Lucene 是一个成熟的免费开源工具,就其本身而言,Lucene是当前以及最近几年最受欢迎的免费 Java 信息检索程序库,人们经常提到信息检索程序库,虽然与搜索引擎有关,但不应该将信息检索程序库与搜索引擎相混淆

(5) Lucene是一个全文检索引擎的架构,那什么是全文搜索引擎?

1) 全文搜索引擎是名副其实的搜索引擎,国外具代表性的有 Google、Fast/AIITheWebx、AltaVista、Inktomi、Teomax WiseNut 等,国内著名的有百度(Baidu),它们都是通过从互联网上提取的各个网站的信息(以网页文字为主)而建立的数据库,检索与用户查询条件匹配的相关记录,然后按一定的排列顺序将结果返回给用户,因此他们是真正的搜索引擎;

2) 从搜索结果来源的角度,全文搜索引擎又可细分为两种,一种是拥有自己的检索程序(Indexer),俗称"蜘蛛"(Spider) 程序或“机人"(Robot) 程序,并自建网页数据库,搜索结果直接从自身的数据库中调用,如上面提到的7家引擎;另一种则是租用其他引擎的数据库,并按自定的格式排列搜索结果,如Lycos引擎。

4. Elasticsearch 和 Solr的比较:

(1) 仅仅做搜索时:

 (2) 需要建立索引时:

(3) 大数据时代: 

(4) 传统项目从 Solr 变为 ElasticSearch 的时候,效率可以提高50倍:

5.  Elasticsearch 和 Solr的总结:

(1) ES 基本是开箱即用(解压就可以用),非常简单,Solr安装略微复杂一丢丢;

(2) Solr 利用 Zookeeper 进行分布式管理,而 Elasticsearch 自身带有分布式协调管理功能;

(3) Solr 支持更多格式的数据,比如JSON、XML CSV,而 Elasticsearch 仅支持json文件格式

(4) Solr 官方提供的功能更多,而 Elasticsearch 本身更注重于核心功能,高级功能多有第三方插件提供,例如图形化界面需要kibana友好支撑;

(5) Solr查询快,但更新索引时慢(即插入删除慢),用于电商等查询多的应用:

1) ES 建立索引快(即查询慢),即实时性查询快,用于facebook新浪等搜索;

2) Solr 是传统搜索应用的有力解决方案,但 Elasticsearch 更适用于新兴的实时搜索应用;

(6) Solr比较成熟,有一个更大,更成熟的用户、开发和贡献者社区,而 Elasticsearch  相对开发维护者较少,更新太快,学习使用成本较高。

五. Elasticsearch的安装

1. 声明:JDK的最低版本为1.8,Java开发的时候,Elasticsearch 的版本和之后对应的 Java 的核心 jar 包需要版本对应,JDK环境正常;

2. 下载:https://www.elastic.co/cn/​

 程序安装包解压即用,是一个web项目,需要前端环境!

3. Windos下安装 Elasticsearch:

(1) 解压即可使用:

(2) 熟悉目录:

1) bin---启动文件

2) config---配置文件

a. jvm.options  java虚拟机相关配置

b. elasticsearch.yml  elasticsearch配置文件,默认端口9200,需要解决跨域问题

c. log4j2  日志配置文件 

3) lib---相关 jar 包

4) modules---功能模块 

5) plugins---插件(ik分词器)

6) log---日志

(3) 启动:

访问测试:

Elasticsearch自成集群,一个也是一个集群。

4. 安装可视化界面---head插件的安装:

(1) 依赖于node.js;

(2) 下载地址:https://github.com/mobz/elasticsearch-head​

(3) 运行方式: 

 (4) 安装依赖:

(5) 启动: 

页面访问:

连接出现跨域问题,无法访问,解决方式是在 elasticsearch.yml 文件中配置跨域:  

重启服务,出现正常页面:

初学,可以把 ES 理解成一个数据库,可以建立索引(库),文档(库中的数据);

 (6) 建立索引:

(7) 数据存放的地方:

(8) 查询数据的地方:

(9) 建议这个head就把它当做数据展示工具,后续的数据查询用kibana来操作

5. Windows下安装 kibana:

(1) 了解ELK:

1) ELK 是 Elasticsearch、Logstash、Kibana三大开源框架首字母大写简称,市面上也被成为 Elastic Stack,其中 Elasticsearch是一个基于Lucene、分布式、通过 Restful 方式进行交互的近实时搜索平台框架,像类似百度、谷歌这种大数据全文搜索引擎的场景都可以使用 Elasticsearch 作为底层支持框架,可见 Elasticsearch 提供的搜索能力确实强大,市面上很多时候我们简称 Elasticsearch 为 ES。Logstash 是 ELK 的中央数据流引擎,用于从不同目标(文件/数据存储/MQ)收集的不同格式数据,经过过滤后支持输出到不同目的地(文件/MQ/redis/elasticsearch/kafka等),Kibana可以讲  Elasticsearch 的数据通过友好的页面展示出来,提供实时分析的功能;

2) 市面上很多开发只要提到 ELK 能够一致说出它是一个日志分析架构技术栈总称,但实际上 ELK 不仅仅适用于日志分析,它还可以支持其它任何数据分析和收集的场景,日志分析和收集只是更具有代表性,并非唯一性。

(2) 下载地址:https://www.elastic.co/cn/kibana/​

(3) kibana 要和 ES 的版本一直才行

(4) 启动:

 (5) 测试:

该页面是做过汉化,汉化方式是修改 kibana.yml 文件:

六. ES 的核心概念

集群、节点、索引、类型、文档、分片、映射是什么?

1. Elasticsearch 是面向文档(一切都是Json),关系行数据库和 Elasticsearch 客观的对比如下:

(1) Elasticsearch(集群)中可以包含多个索引(数据库),每个索引中可以包含多个类型(表),每个类型下又包含多个文档(行),每个中又包含多个字段(列)。

(2) 物理设计:Elasticsearch 在后台把每个索引划分成多个分片,每分分片可以在集群中的不同服务器间迁移,一个人就是一个集群,默认的集群名字就是 elasticsearch:

(3) 逻辑设计:一个索引类型中,包含多个文档,比如说文档1,文档2,当我们索引一篇文档时,可以通过这样的一种顺序找到它:索引>类型>文档ID,通过这个组合我们就能索引到某个具体的文档(注意:ID不必是整数,实际上它是个字符串)。 

2. 文档:就是一条条数据

(1) 之前说 Elasticsearch 是面向文档的,那么就意味着索引和搜索数据的最小单位是文档 Elasticsearch中,文档有几个重要属性:
1) 自我包含,一篇文档同时包含字段和对应的值,也就是同时包含key:value;
2) 可以是层次型的,一个文档中包含自文档,复杂的逻辑实体就是这么来的;
3) 灵活的结构,文档不依赖预先定义的模式,我们知道关系型数据库中,要提前定义字段才能使用,在 Elasticsearch 中,对于字段是非常灵活的,有时候,我们可以忽略该字段,或者动态的添加一个新的字段。

(2) 尽管我们可以随意的新增或者忽略某个字段,但是,每个字段的类型非常重要,比如一个年龄字段类型,可以是字符串也可以是整形,因为 Elasticsearch  会保存字段和类型之间的映射及其他的设置,这种映射具体到每个映射的每种类型,这也是为什么在 Elasticsearch  中,类型有时候也称为映射类型。

3. 类型:慢慢被废弃

类型是文档的逻辑容器,就像关系型数据库一样,表格是行的容器,类型中对于字段的定义称为映射,比如 name 映射为字符串类型,我们说文档是无模式的,它们不需要拥有映射中所定义的所有字段,比如新增一个字段,那么 Elasticsearch 是怎么做的呢? Elasticsearch 会自动的将新字段加入映射,但是这个字段的不确定它是什么类型,Elasticsearch 就开始猜,如果这个值是18 ,那么Elasticsearch 会认为它是整形,但是 Elasticsearch 也可能猜不对,所以最安全的方式就是提前定义好所需要的映射,这点跟关系型数据库殊途同归了,先定义好字段,然后再使用。

4. 索引:就是数据库

索引是映射类型的容器,Elasticsearch 中的索引是一个非常大的文档集合,索引存储了映射类型的字段和其他设置,然后它们被存储到了各个分片上了。

我们来研究下分片是如何工作的:

物理设计:节点和分片如何工作

一个集群至少有一个节点,而一个节点就是一个 Elasticsearch 进程,节点可以有多个索引默认的,如果你创建索引,那么索引将会有个5个分片(primary shard,又称主分片)构成的,每一个主分片会有一个副本(replica shard,又称复制分片)。

上图是一个有3个节点的集群,可以看到主分片和对应的复制分片都不会在同一个节点内,这样有利于某个节点挂掉了,数据也不至于丢失。实际上,一个分片是一个 Lucene 索引,一个包含倒排索引的文件目录,倒排索引的结构使得 Elasticsearch 在不扫描全部文档的情况下,就能告诉你哪些文档包含特定的关键字。

5. 倒排索引

Elasticsearch 使用的是一种称为倒排索引的结构,采用 Lucene 倒排索作为底层,这种结构适用于快速的全文搜索,一个索引由文档中所有不重复的列表构成,对于每一个词,都有一个包含它的文档列表,例如,现在有两个文档,每个文档包含如下内容:

Study every day, good good up to forever  # 文档 1 包含的内容
to forever, study every day, good good up # 文档2包含的内容

为了创建倒排索引,我们首先要将每个文档拆分成独立的词(或称为词条或者tokens) ,然后创建一个包含所有不重复的词条的排序列表,然后列出每个词条出现在哪个文档:

 现在试图搜索 to forever,只需要查看包含每个词条的文档:

两个文档都匹配,但是第一个文档比第二个匹配程度更高,如果没有别的条件,这两个包含关键字的文档都将返回。

再来看一个示例,比如我们通过博客标签来搜索文章,那么倒排索引列表就是这样的一个结构:

 

如果要搜索含有 python 标签的文章,那相对于查找所有原始数据而言,查找倒排索引后的数据将会快的多,只需要查看标签这一栏,然后获取相关的文章ID即可。

Elasticsearch 的索引和 Lucene 的索引对比:

在 Elasticsearch 中,索引这个词被频繁使用,这就是术语的使用,在 Elasticsearch 中,索引被分为多个分片,每份分片是一个 Lucene 的索引,所以一个 Elasticsearch  索引是由多个 Lucene 索引组成的,别问为什么,谁让 Elasticsearch  使用 Lucene 作为底层呢!如无特指,说起索引都是指 Elasticsearch  的索引。

接下来的一切操作都在 kibana 中 Dev Tools 下的 Console 里完成。

 七. IK分词器插件

1. 什么是 IK 分词器?

分词:即把一段中文或者别的划分成一个个的关键字,我们在搜索时候会把自己的信息进行分词,会把数据库中或者索引库中的数据进行分词,然后进行一个匹配操作,默认的中文分词是将每个字看成一个词,比如“我爰编程”会被分为“我”,“爱”,“编”,“程”,这显然是不符合要求的,所以我们需要安装中文分词器 IK 来解决这个问题。

IK 提供了两个分词算法:ik_smart 和 ik_max_word,其中 ik_smart 为最少切分,ik_max_word为最细粒度划分,如果要使用中文,建议使用 IK 分词器。

2. 安装:https://github.com/medcl/elasticsearch-analysis-ik

3. 下载后,放入 Elasticsearch 插件即可:

4. 重启观察ES:

5. elasticsearch-plugin 可以通过这个命令来查看加载进来的插件:

6. 使用 kibana 测试:

(1) ik_smart为最小切分(不可能有重复的数据):

 (2) ik_max_word为最细粒度划分: 

(3) 测试:我们输入“超级喜欢狂神说java” 

发现问题:“狂神说”被拆开了;

解决问题:这种自己需要的词,需要加到我们的分词器的字典中,再IK分词器增加自己的配置

1) 编写分词器:

2) 重启观察ES: 

3) 测试:

以后需要自定义词语的时候,只需要在自定义dic中配置即可。

八. Rest风格说明 

 一种软件架构风格,而不是标准,只是提供了一组设计原则和约束条件,它主要用于客户端和服务器交互类的软件,基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。

基本Rest命令说明:

九. 关于索引的基本操作

1. 创建一个索引:

PUT  /索引名/~类型名(未来类型可以不用创建)~/文档id

{请球体}

2. 字段也是需要指定类型,常见类型如下:

(1) 字符串类型:text,keyword

(2) 数字类型:long,integer,short,byte,double,half_float,scaled_float

(3) 日期类型:date

(4) 布尔类型:boolean

(5) 二进制类型:binary

等等......

3. 指定字段类型:

4. 通过GET请求获取索引(文档同理)的具体信息:

5. 查看默认的信息:

ES的7.X的后续版本和8.X会弃用文档类型,默认使用_doc,可写可不写

如果在自己的文档字段没有指定,那么ES就会给我们默认配置字段类型。 

扩展:通过get _cat/可以获得ES的当前的很多信息

6. 修改数据:

(1) 还是PUT提交,覆盖原始数据即可:

1) _version 的数字代表是数据被改动的次数,是修改的话版本号会增加;

2) _result 为 updated 表示该PUT请求是修改数据;

3) 但是该方式修改,提交的数据如果有一个字段被删除了,那数据就没有该字段了:

(2) 通过POST提交:

推荐使用这个更新方法,doc中可以写所有的字段,也可以只写需要修改的字段

7. 删除索引: 

通过DELETE命令来实现删除,根绝你的请求来判断是删除索引还是删除文档记录,所有 RESTFUL 风格是 ES 推荐使用的方法。

十. 关于文档的基本操作

1. 简单操作:

(1) 添加数据:

(2) 获取数据 GET:

简单搜索:

简单的条件查询,可以根据默认的映射规则,产生基本的查询:

该条数据出现了_source字段,该字段未来如果存在多条查询出来的结果,该字段代表匹配度,匹配度越高分值越高。

2. 复杂搜索(排序、分页、高亮、模糊查询、精准查询):

(1) 匹配查询:

结果过滤(相当于mysql中的select name desc from....):

通过 _source 进行过滤;

以后使用Java操作ES时候,所有的方法和对象就是这里面的key。

(2) 排序:

 通过 sort 进行排序。

(3) 分页查询:

(4) 布尔值查询:

must,相当于mysql中的and,所有的条件都要符合 where age = 9 and name = XXX:

 should,相当于mysql中的or,所有的条件都要符合 where age = 9 or name = XXX:

must_not,相当于mysql中的not:

filter,过滤器:

 gt(大于),gte(大约等于),lt(小于),lte(小于等于)

(5) 匹配多个条件:

(6) 精准查询:

term查询是直接通过倒排索引指定的词条进度查询精确查找的;

关于分词:

1) term:直接查询精确的;

2) match:会使用分词器解析(先分析文档,在通过分析的文档进行查询)。

两个类型:text,keyword

1) 首先看一下两个不同分词器的效果:

2) 测试两个不同类型的查询效果:

a. 创建测试数据:

b. 测试text类型:

text会被当做普通分词器进行解析,所以数据全部可以查询到。

c. 测试keyword类型:

 keyword不会被分词器解析,所以只能精准查询。

(7) 多个值匹配精准查询:

 (8) 高亮查询:

 

PS:Mysql也可以实现上述功能,但是效率没有ES高。

十一. 集成SpringBoot

1. 查看官网文档:https://www.elastic.co/cn/

2. 找到原生的依赖:

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.6.2</version>
</dependency>

3. 找对象:

4. 配置基本的项目:

(1) idea创建springboot项目勾选:

(2)  查看maven坐标:

 (3) 检查版本号是否一致,不一致需要自定义版本号:

5. 具体API测试:

(1) 编写 ES 配置文件:

@Configuration
public class ElasticsearchClientConfig {

    @Bean
    public RestHighLevelClient restHighLevelClient(){
        RestHighLevelClient client = new RestHighLevelClient(
                RestClient.builder(
                        new HttpHost("127.0.0.1", 9200, "http")));
        return client;
    }
}

(2) 创建索引:

@SpringBootTest
class LearnEsApiApplicationTests {

	@Autowired
	@Qualifier("restHighLevelClient")
	private RestHighLevelClient client;

	// 测试创建索引
	@Test
	void testCreateIndex() throws IOException {
		// 1.创建索引请求,通过 new Create...("索引")创建请求
		CreateIndexRequest request = new CreateIndexRequest("es_index");
		// 2.客户端执行请求,通过 IndicesClient.create(request, RequestOptions.DEFAULT)执行请求
		CreateIndexResponse createIndexResponse = client.indices()
				.create(request, RequestOptions.DEFAULT);
		System.out.println(createIndexResponse);// org.elasticsearch.client.indices.CreateIndexResponse@676e6ca0
	}

}

(3) 判断索引是否存在:

    // 测试获取索引,判断是否存在
	@Test
	void testGetIndex() throws IOException {
		// 获取通过Get...方式
		GetIndexRequest request = new GetIndexRequest("es_index");
		boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
		System.out.println(exists);
	}

(4) 删除索引:

	// 测试删除索引
    @Test
	void testDeleteIndex() throws IOException {
		// 获取通过Delete...方式
		DeleteIndexRequest request = new DeleteIndexRequest("es_index");
		AcknowledgedResponse delete = client.indices().delete(request, RequestOptions.DEFAULT);
		System.out.println(delete.isAcknowledged());
	}

该AcknowledgedResponse是删除后的相应:

(5) CRUD文档:

1) 创建文档:

	// 测试添加文档 PUT /es_index/_doc/1
	@Test
	void testAddDocument() throws IOException {
		// 1.创建对象
		User user = new User("张三", 3);
		// 2.创建请求
		IndexRequest request = new IndexRequest("es_index");
		// 3.配置规则
		request.id("1");
		request.timeout(TimeValue.timeValueSeconds(1));
		// 4.通过 request.source(json数据,XContentType.JSON) 将数据放入json请求
		request.source(JSON.toJSONString(user), XContentType.JSON);
		// 5.客户端发送请求,获取相应的结果
		IndexResponse indexResponse = client.index(request, RequestOptions.DEFAULT); // IndexResponse[index=es_index,type=_doc,id=1,version=1,result=created,seqNo=0,primaryTerm=1,shards={"total":2,"successful":1,"failed":0}]

		System.out.println(indexResponse.toString());
		System.out.println(indexResponse.status()); // CREATED,对于命令返回的状态
	}

2) 获取文档:

	// 测试获取文档,判断是否存在 GET /es_index/_doc/1
	@Test
	void testDocumentIsExists() throws IOException {
		GetRequest request = new GetRequest("es_index", "1");
		// 不获取返回的 _source的上下文
		request.fetchSourceContext(new FetchSourceContext(false));
		request.storedFields("_none_");
		// 客户端发送请求
		boolean exists = client.exists(request, RequestOptions.DEFAULT);
		System.out.println(exists);
	}

	// 测试获取文档信息
	@Test
	void testGetDocument() throws IOException {
		// 1. 通过 GetRequest 创建获取请求
		GetRequest request = new GetRequest("es_index", "1");
		// 2.get方法发送请求,获取相应结果
		GetResponse response = client.get(request, RequestOptions.DEFAULT);
		System.out.println(response.getSourceAsString()); // 打印文档内容,{"age":3,"name":"张三"} 
		System.out.println(response); // 返回全部内容,和命令查询是一样的,{"_index":"es_index","_type":"_doc","_id":"1","_version":1,"_seq_no":0,"_primary_term":1,"found":true,"_source":{"age":3,"name":"张三"}}
	}

3) 更新文档:

	// 测试更新文档信息 POST /es_index/_doc/1/_update
	@Test
	void testUpdateDocument() throws IOException {
		// 1.通过 UpdateRequest 创建更新请求
		UpdateRequest request = new UpdateRequest("es_index", "1");
		// 2.配置规则
		request.timeout(TimeValue.timeValueSeconds(1));
		// 3.创建更新对象
		User user = new User("张三yyds", 33);
		// 4.放入json数据
		request.doc(JSON.toJSONString(user),XContentType.JSON);
		// 5.客户端发送更新请求
		UpdateResponse response = client.update(request, RequestOptions.DEFAULT);
		System.out.println(response.status()); // true
	}

4) 删除文档:

	// 测试删除文档记录 DELETE /es_index/_doc/1
	@Test
	void testDeleteDocument() throws IOException{
		DeleteRequest request = new DeleteRequest("es_index", "1");
		request.timeout(TimeValue.timeValueSeconds(1));
		DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
		System.out.println(response.status());
	}

5) 批量插入数据:

	// 测试批量插入数据
	@Test
	void testBulkRequest() throws IOException {
		BulkRequest request = new BulkRequest();
		request.timeout(TimeValue.timeValueSeconds(10));

		ArrayList<User> list = new ArrayList<>();
		list.add(new User("张三",3));
		list.add(new User("李四",4));
		list.add(new User("王五",5));
		list.add(new User("赵六",6));
		list.add(new User("田七",7));
		list.add(new User("钱八",8));

		// 批处理请求
		for (int i = 0; i < list.size(); i++){
			// 批量删除和更新只需要在这做对应的修改即可
			request.add(
					new IndexRequest("es_index")
							.id(""+(i+1)) // 如果这不指定id,会生成随机字符串
							.source(JSON.toJSONString(list.get(i)),XContentType.JSON)
			);
		}
		BulkResponse response = client.bulk(request, RequestOptions.DEFAULT);
		System.out.println(response.hasFailures());
	}

6) 查询数据:

	// 测试查询数据
	// SearchRequest 搜索请求
	// SearchSourceBuilder 条件构造
	// HighlightBuilder 高亮构造
	// TermQueryBuilder 精确查询构造
	// MatchAllQueryBuilder 匹配所有构造
	// xxxQueryBuilder
	@Test
	void testSearch() throws IOException {
		// 1.创建搜索请求
		SearchRequest request = new SearchRequest("es_index");
		// 2.构建搜索条件
		SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
		// 3.构建查询条件,通过 QueryBuilders.XXX 来实现
		TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("age", "3");
		// MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
		sourceBuilder.query(termQueryBuilder);
		sourceBuilder.timeout(TimeValue.timeValueMinutes(1));

		request.source(sourceBuilder);
		SearchResponse response = client.search(request, RequestOptions.DEFAULT);
		System.out.println(JSON.toJSONString(response.getHits())); // 全部数据
		for (SearchHit hit : response.getHits().getHits()) {
			System.out.println(hit.getSourceAsMap()); // user数据
		}
	}

十二. 实战

1. 爬取数据:

(1) maven导入解析网页的工具包:

<dependency>
	<groupId>org.jsoup</groupId>
	<artifactId>jsoup</artifactId>
	<version>1.10.2</version>
</dependency>

(2) 编写实体类:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Content {
    private String img;
    private String price;
    private String title;
}

(3) 编写爬虫工具类:

@Component
public class HtmlParseUtils {

    public static void main(String[] args) throws Exception {
        new HtmlParseUtils().parseJD("java").forEach(System.out::println);
    }

    public List<Content> parseJD(String keyword) throws Exception {
        String url = "https://search.jd.com/Search?keyword="+keyword;
        Document document = Jsoup.parse(new URL(url), 30000);
        Element element = document.getElementById("J_goodsList");
        Elements elements = element.getElementsByTag("li");

        List<Content> goodsList = new ArrayList<>();
        for (Element el : elements) {
            String img = el.getElementsByTag("img").eq(0).attr("data-lazy-img");
            String price = el.getElementsByClass("p-price").eq(0).text();
            String title = el.getElementsByClass("p-name").eq(0).text();
            Content goods = new Content();
            goods.setImg(img);
            goods.setPrice(price);
            goods.setTitle(title);
            goodsList.add(goods);
        }
        return goodsList;
    }
}

2. 业务编写:

(1) Controller层:

@RestController
public class ContentController {

    @Autowired
    private ContentService contentService;

    @GetMapping("/parse/{keyword}")
    public boolean parse(@PathVariable("keyword") String keyword) throws Exception {
        return contentService.parseContent(keyword);
    }

    @GetMapping("/search/{keyword}/{pageNo}/{pageSize}")
    public List<Map<String,Object>> search(@PathVariable("keyword") String keyword,
                                           @PathVariable("pageNo") int pageNo,
                                           @PathVariable("pageSize") int pageSize) throws IOException {
        return contentService.search(keyword,pageNo,pageSize);
    }
    
}

(2) Service层:

@Service
public class ContentService {

    @Autowired
    private RestHighLevelClient restHighLevelClient;

    @Autowired
    private HtmlParseUtils htmlParseUtils;

    public boolean parseContent(String keyword) throws Exception {
        List<Content> jdInfo = htmlParseUtils.parseJD(keyword);

        BulkRequest request = new BulkRequest();
        request.timeout(TimeValue.timeValueMillis(2));

        for (int i = 0; i < jdInfo.size(); i++){
            request.add(
                    new IndexRequest("jd_goods")
                            .source(JSON.toJSONString(jdInfo.get(i)), XContentType.JSON)
            );
        }
        BulkResponse bulk = restHighLevelClient.bulk(request, RequestOptions.DEFAULT);
        return !bulk.hasFailures();
    }

    // 搜索数据
    public List<Map<String,Object>> search(String keyword,int pageNo,int pageSize) throws IOException {
        // 1.创建条件查询
        SearchRequest request = new SearchRequest("jd_goods");
        // 2.条件构造
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        sourceBuilder.timeout(TimeValue.timeValueMillis(2));
        sourceBuilder.from(pageNo);
        sourceBuilder.size(pageSize);
        // 3.精准匹配
        TermQueryBuilder termQuery = QueryBuilders.termQuery("title", keyword);
        sourceBuilder.query(termQuery);
        // 4.执行搜索
        request.source(sourceBuilder);
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);

        List<Map<String,Object>> list = new ArrayList<>();
        for (SearchHit hit : response.getHits().getHits()) {
            list.add(hit.getSourceAsMap());
        }
        return list;
    }
}

3. 高亮搜索:

    // 高亮搜索数据
    public List<Map<String,Object>> searchHighligther(String keyword,int pageNo,int pageSize) throws IOException {
        // 条件搜索
        SearchRequest request = new SearchRequest("jd_goods");

        // 条件构造器
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        sourceBuilder.timeout(TimeValue.timeValueMillis(2));

        // 分页
        sourceBuilder.from(pageNo);
        sourceBuilder.size(pageSize);

        // 精准匹配
        TermQueryBuilder termQuery = QueryBuilders.termQuery("title", keyword);
        sourceBuilder.query(termQuery);

        // 高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("title");
        highlightBuilder.requireFieldMatch(false); //多个高亮显示
        highlightBuilder.preTags("<span style='color:red'>");
        highlightBuilder.postTags("</span>");
        sourceBuilder.highlighter(highlightBuilder);

        // 执行搜索
        request.source(sourceBuilder);
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);

        List<Map<String,Object>> list = new ArrayList<>();
        for (SearchHit hit : response.getHits().getHits()) {
            // 获取高亮数据
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            HighlightField highlightField = highlightFields.get("title");
            // 原来的数据
            Map<String, Object> sourceAsMap = hit.getSourceAsMap();
            // 解析高亮字段,将原来的字段替换为高亮字段即可
            if (highlightField != null){
                Text[] texts = highlightField.fragments();
                String newTitle = "";
                for (Text text : texts) {
                    newTitle += text;
                }
                sourceAsMap.put("title",newTitle);
            }
            list.add(sourceAsMap);
        }
        return list;
    }

PS:该文档为【狂神说Java】ElasticSearch7.6.x最新完整教程通俗易懂_哔哩哔哩_bilibili 的学习笔记,推荐想快速入门ES的可以去B站学习。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值