ElasticSearch 入门教程笔记

视频教程:【狂神说Java】ElasticSearch7.6.x最新完整教程通俗易懂
视频地址:https://www.bilibili.com/video/BV17a4y1x7zq
拒绝白嫖,感谢狂神分享的视频教程

ElasticSearch 概述

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

据国际权威的数据库产品测评机构 DB Engines 的统计,在2016年1月,ElasticSearch 已经超过 Solr 等,成为排名第一的搜索引擎类应用。

谁在使用:

  1. *

  2. The Guardian(国外新闻网站)

  3. Stack Overflow(国外程序异常讨论论坛)

  4. GitHub(开源代码管理)

  5. 电商网站

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

    … …

ES 与 solr

ElasticSearch 简介

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

它用于全文搜索、结构化搜索、分析以及将这三者混合使用:

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

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

*结合全文搜索与地理位置查询,以及more-like-this功能来找到相关的问题和答案。

Github使用Elasticsearch检索1300亿行的代码。

但是Elasticsearch不仅用于大型企业,它还让像DataDog以及Klout这样的创业公司将最初的想法变成可扩展的解决方案。Elasticsearch可以在你的笔记本上运行,也可以在数以百计的服务器上处理PB级别的数据。Elasticsearch是一个基于Apache Lucene™的开源搜索引擎。无论在开源还是专有领域, Lucene可以被认为是迄今为止最先进、性能最好的、功能最全的搜索引擎库。

但是, Lucene只是一个库。想要使用它,你必须使用Java来作为开发语言并将其直接集成到你的应用中,更糟糕的是, Lucene非常复杂,你需要深入了解检索的相关知识来理解它是如何工作的。

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

Solr 简介

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

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

solr是基于lucene开发企业级搜索服务器,实际上就是封装了lucene。

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

Lucene 简介

Lucene是apache软件基金会4 jakarta项目组的一个子项目,是一个开放源代码的全文检索引擎工具包,但它不是-一个完整的全文
检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎(英文与德文两种西方语言)。
Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立
起完整的全文检索引擎。Lucene是一 套用于全文检索和搜 寻的开源程式库,由Apache软件基金会支持和提供。Lucene提供了一个
简单却强大的应用程式接口,能够做全文索引和搜寻。在Java开发环境里Lucene是一个成熟的免费开源工具。 就其本身而言,
Lucene是当前以及最近几年最受欢迎的免费Java信息检索程序库。人们经常提到信息检索程序库,虽然与搜索引擎有关,但不应该
将信息检索程序库与搜索引擎相混淆。

Lucene是一个全文检索引擎的架构。那什么是全文搜索引擎?

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

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

ElasticSearch 和 Solr 比较

  • 当单纯地对已有数据进行搜索时,Solr 更快
    ElasticSearch 入门教程笔记

  • 当实时建立索引时,Solr 会产生 I/O 阻塞,查询性能较差,ElasticSearch 具有明显优势
    ElasticSearch 入门教程笔记

  • 随着数据量的增加,Solr 的搜索效率会变得更低,而 ElasticSearch 却没有明显的变化
    ElasticSearch 入门教程笔记

  • 将搜索基础设施由 Solr 转向 ElasticSearch 后,将提高将近 50× 的搜索性能
    ElasticSearch 入门教程笔记

ElasticSearch vs Solr总结

  1. es基本是开箱即用(解压就可以用! ) , 非常简单。Solr安装略微复杂
  2. Solr 利用Zookeeper进行分布式管理,而Elasticsearch自身带有分布式协调管理功能。
  3. Solr 支持更多格式的数据,比如JSON、XML、 CSV ,而Elasticsearch仅支持json文件格式。
  4. Solr 官方提供的功能更多,而Elasticsearch本身更注重于核心功能,高级功能多有第三方插件提供,例如图形化界面需要kibana友好支撑。
  5. Solr 查询快,但更新索引时慢(即插入删除慢) , 用于电商等查询多的应用;
    • ES建立索引快(即查询慢) ,即实时性查询快,用于facebook新浪等搜索。
    • Solr是传统搜索应用的有力解决方案,但Elasticsearch 更适用于新兴的实时搜索应用。
  6. Solr比较成熟,有一个更大,更成熟的用户、开发和贡献者社区,而Elasticsearch相对开发维护者较少,更新太快,学习使用
    成本较高。

ElasticSearch 安装

  • 环境要求:JDK 1.8 以上,Nodejs

  • 下载:官网 https://www.elastic.co/

  • Windows安装包:elasticsearch-7.15.2-windows-x86_64.zip,解压即用

  • 目录结构:

    + bin						启动文件
    + config					配置文件
    	+ log4j2.properties		日志配置文件
    	+ jvm.options			虚拟机配置文件,建议把内存调小,
    	+ elasticsearch.yml		elasticsearch配置文件,默认9200端口,会有跨域问题
    + lib						相关jar包
    + logs						日志
    + modules					模块
    + plugins					插件
    
  • 启动,双击 /elasticsearch-7.15.2/bin/elasticsearch.bat

    访问 127.0.0.1:9200,得到页面

    {
      "name" : "CRATER-PC",
      "cluster_name" : "elasticsearch",				// 集群名称,一个服务也是集群
      "cluster_uuid" : "8AWbPdNgRymiY6VXtuiAuA",
      "version" : {
        "number" : "7.15.2",
        "build_flavor" : "default",
        "build_type" : "zip",
        "build_hash" : "93d5a7f6192e8a1a12e154a2b81bf6fa7309da0c",
        "build_date" : "2021-11-04T14:04:42.515624022Z",
        "build_snapshot" : false,
        "lucene_version" : "8.9.0",
        "minimum_wire_compatibility_version" : "6.8.0",
        "minimum_index_compatibility_version" : "6.0.0-beta1"
      },
      "tagline" : "You Know, for Search"
    }
    

ElasticSearch-head 安装

  • 下载地址:https://github.com/mobz/elasticsearch-head

  • 解压后,是一个前端 webpackage 项目

  • cd elasticsearch-head

  • npm install

  • npm run start

  • open http://localhost:9100/

进入页面后,产生了跨域问题(跨ip,跨端口),因为在 9100端口 连接 9200端口,解决:

  • 停止 ElasticSearch,修改 elasticsearch.yml,添加以下内容。

    http.cors.enabled: true
    http.cors.allow-origin: "*"
    

    再次重启 ElasticSearch,head 连接器成功。
    ElasticSearch 入门教程笔记

    可以随便新建一个索引,当前可以理解为数据库

    关于符合查询,head 作为一个数据展示工具,不建议在这里面写命令。以后在 Kibana 里查询

Kibana 安装

了解 ELK

ELK是Elasticsearch、Logstash、 Kibana三大开源框架首字母大写简称。市面上也被成为Elastic Stack(Elastic 技术栈)。

其中Elasticsearch是一 个基于Lucene、分布式、通过Restful方式进行交互的近实时搜索平台框架。像类似百度、谷歌这种大数据全文搜索引擎的场景都可以使用Elasticsearch作为底层支持框架,可见Elasticsearch提供的搜索能力确实强大,市面上很多时候我们简称Elasticsearch为es。

Logstash是ELK的*数据流引擎,用于从不同目标(文件/数据存储/MQ )收集的不同格式数据,经过过滤后支持输出到不同目的
地(文件/MQ/redis/elasticsearch/kafka等)。

Kibana可以将elasticsearch的数据通过友好的页面展示出来,提供实时分析的功能。

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

安装 Kibana

Kibana是一个针对 Elasticsearch 的开源分析及可视化平台,用来搜索、查看交互存储在 Elasticsearch 索引中的数据。使用Kibana,可以通过各种图表进行高级数据分析及展示。Kibana让海量数据更容易理解。它操作简单,基于浏览器的用户界面可以快速创建仪表板( dashboard )实时显示 Elasticsearch 查询动态。设置Kibana非常简单。无需编码或者额外的基础架构,几分钟内就可以完成Kibana安装并启动 Elasticsearch 索引监测。

官网: https://www.elastic.co/cn/kibana

  • 下载 kibana-7.15.2-windows-x86_64.zip。注意:Kibana 版本必须和 ES 版本一致。

  • 解压,是一个标准的工程

  • 启动:双击 /bin/kibana.bat

  • 访问 localhost:5601
    ElasticSearch 入门教程笔记

  • 在左侧菜单栏找到 Dev Tools 使用 Kibana 上使用开发工具
    ElasticSearch 入门教程笔记

  • 汉化(国际化),在 /x-pack/plugins/translations/translations 下有一个 zh-CN.json 文件,用于翻译。

    编辑 config/kibana.yml 文件,做如下修改:

    #i18n.locale: "en"
    i18n.locale: "zh-CN"
    

    重启 Kibana,语言变成了中文
    ElasticSearch 入门教程笔记

ElasticSearch 核心概念

ElasticSearch 是面向文档的。关系型数据库和 ElasticSearch 的对比

Relational DB ElasticSearch
数据库(database) 索引(indices)
表(tables) type
行(rows) documents
字段(columns) fields

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

物理设计:

ElasticSearch 在后台把每个 索引划分成多个分片 ,每片分片可以在集群中的不同服务器间迁移

逻辑设计:

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

文档

之前说elasticsearch是面向文档的,那么就意味着索引和搜索数据的最小单位是文档,Elasticsearch中,文档有几个重要属性:

  • 自我包含, 一篇文档同时包含字段和对应的值,也就是同时包含 key : value
  • 可以是层次型的,一个文档中包含自文档,复杂的逻辑实体就是这么来的。就是一 个json对象,fastjson进行自动转换。
  • 灵活的结构,文档不依赖预先定义的模式,我们知道关系型数据库中,要提前定义字段才能使用,在elasticsearch中,对于字段是非常灵活的,有时候,我们可以忽略该字段,或者动态的添加一个新的字段。
  • 尽管我们可以随意的新增或者忽略某个字段,但是,每个字段的类型非常重要,比如一个年龄字段类型,可以是字符串也可以是整型。因为elasticsearch会保存字段和类型之间的映射及其他的设置。这种映射具体到每个映射的每种类型,这也是为什么在Elasticsearch中,类型有时候也称为映射类型。

类型

类型是文档的逻辑容器,就像关系型数据库一样,表格是行的容器。类型中对于字段的定义称为映射,比如name映射为字符串类型。

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

索引

就是数据库。

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

物理设计:节点和分片如何工作
ElasticSearch 入门教程笔记

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

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

倒排索引

ElasticSearch 使用的是一种被成为倒排索引的结构,其采用 Lucene 倒排索引作为底层。这种结构适用于快速的全文搜索,一个索引由文档中所有不重复的列表构成,对于每一个词,都有一个包含它的文档列表。

  • 例如,现在有两个文档,每个文档包含如下内容:

    Study every day, good good up to forever		# 文档1的内容
    To forever, study every day, good good up		# 文档2的内容
    
  • 为了创建倒排索引,首先要将每个文档拆分成独立的词(或称为词条、tokens),然后创建一个包含所有不重复词条的排序列表,然后列出每个词条出现在哪个文档:

    term doc_1 doc_2
    Study ×
    To ×
    every
    forever
    day
    study ×
    good
    to ×
    up
  • 现在,尝试检索 to forever,只需要查看每个词条的文档

    term doc_1 doc_2
    to ×
    forever
  • 两个文档都匹配,但是第一个文档比第二个文档的匹配程度(权重/分数)更高,如果没有其他条件,这两个包含关键字的文档都将被返回。

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

    博客文章(原始数据) 索引列表(倒排索引)
    博客文章ID 标签 标签 博客文章ID
    1 python python 1,2,3
    2 python linux 3,4
    3 linux,python
    4 linux

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

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

在 Elasticsearch 中,索引这个词被频繁使用,这就是术语的使用。在 Elasticsearch 中,索引被分为多个分片,每份分片是一个Lucene的索引。所以一个 Elasticsearch 索引是由多 个Lucene索引组成的。如无特指,说起索弓|都是指elasticsearch的索引。

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

IK 分词器插件

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

IK提供了两个分词算法: ik _smart 和 ik_max_word,其中 ik_smart 为最少切分, ik_max _word 为最细粒度划分。

安装 IK 分词器

  1. 下载: https://github.com/medcl/elasticsearch-analysis-ik/releases

    注意:版本需要对应

    elasticsearch-analysis-ik-7.15.2.zip

  2. 进入 elasticsearch-7.15.2/plugins 目录下,新建一个 ik 目录,解压
    ElasticSearch 入门教程笔记

  3. 重启 ElasticSearch 并观察,可以看到插件被加载了
    ElasticSearch 入门教程笔记
    也可以使用 elasticsearch-plugin 命令查看插件
    ElasticSearch 入门教程笔记

  4. 使用 Kibana 测试

    ik_smart 最少切分:
    ElasticSearch 入门教程笔记
    ik_max_word 最细粒度划分,穷尽词库的所有可能:
    ElasticSearch 入门教程笔记

  5. 测试中发现,如下测试案例,使用哪种分词算法,都不能拆出想要的 “混元形意”、“耗子尾汁”,对于这种词,需要添加到自定义的字典中。
    ElasticSearch 入门教程笔记

  6. 自定义字典,在 ik 分词器插件的 config 目录下,新建一个字典文件 crater.dic ,内容如下

    混元形意
    耗子尾汁
    

    修改同目录下的 IKAnalyzer.cfg.xml 文件,配置上自定义的字典

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
    <properties>
    	<comment>IK Analyzer 扩展配置</comment>
    	<!--用户可以在这里配置自己的扩展字典 -->
    	<entry key="ext_dict">crater.dic</entry>
    	 <!--用户可以在这里配置自己的扩展停止词字典-->
    	<entry key="ext_stopwords"></entry>
    	<!--用户可以在这里配置远程扩展字典 -->
    	<!-- <entry key="remote_ext_dict">words_location</entry> -->
    	<!--用户可以在这里配置远程扩展停止词字典-->
    	<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
    </properties>
    

    重启后观察日志,加载了自定义的字典
    ElasticSearch 入门教程笔记

  7. 再次测试,自定义的词都被拆分出来了
    ElasticSearch 入门教程笔记ElasticSearch 入门教程笔记

Rest 风格说明

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

基本命令说明

method url 地址 描述
PUT 127.0.0.1:9200/索引名称/类型名称/文档ID 创建文档(指定文档ID)
POST 127.0.0.1:9200/索引名称/类型名称 创建文档(随机文档ID)
POST 127.0.0.1:9200/索引名称/类型名称/文档ID/_update 修改文档
DELETE 127.0.0.1:9200/索引名称/类型名称/文档ID 删除文档
GET 127.0.0.1:9200/索引名称/类型名称/文档ID 通过文档ID查询文档
POST 127.0.0.1:9200/索引名称/类型名称/_search 查询所有数据

索引的基本操作

新增索引

  1. 创建一个索引

    # 其中类型名以后的版本就去掉了
    PUT /索引名/类型名/文档ID
    {请求体}
    
    # 返回值包括
    "_index": 	当前索引名
    "_type"		当前类型
    "_version"	当前版本(1表示还没有修改过)
    

    ElasticSearch 入门教程笔记

    在 elasticsearch-head 中的概览和索引页面,也可以看到新建的索引
    ElasticSearch 入门教程笔记ElasticSearch 入门教程笔记

    在数据索引页面可以看到数据的详情信息,完成了自动增加索引。这也是初期可以把 ES 当做数据库来学习的原因。
    ElasticSearch 入门教程笔记

  2. 字段的数据类型

    • 字符串类型

      text、keyword

    • 数值型

      long、Integer、short、byte、double、float、half float、scaled float

    • 日期类型

      date

    • te布尔类型

      boolean

    • 二进制类型

      binary

    • 等等…

  3. 指定字段的类型

    类似于建库(建立索引和字段对应类型),也可看做规则的建立
    ElasticSearch 入门教程笔记

  4. 获取 test2 索引的信息
    ElasticSearch 入门教程笔记

  5. 默认的数据类型

    _doc 默认类型(default type),type 在未来的版本中会逐渐弃用,可以不指定,也可以显式的指定
    ElasticSearch 入门教程笔记ElasticSearch 入门教程笔记

    如果文档字段没有指定类型,那么 ElasticSearch 会默认配置字段类型。

    扩展: 通过 GET _cat 命令查看 ElasticSearch 的信息
    ElasticSearch 入门教程笔记

    其他命令:

    GET _cat/indices
    GET _cat/aliases
    GET _cat/allocation
    GET _cat/count
    GET _cat/fielddata
    GET _cat/health
    GET _cat/indices
    GET _cat/master
    GET _cat/nodeattrs
    GET _cat/nodes
    GET _cat/pending_tasks
    GET _cat/plugins
    GET _cat/recovery
    GET _cat/repositories
    GET _cat/segments
    GET _cat/shards
    GET _cat/snapshots
    GET _cat/tasks
    GET _cat/templates
    GET _cat/thread_pool
    

修改索引

修改有两种实现方案

  • 方案一:PUT,改一下请求体中需要修改的数据,再次新增,这样就覆盖了旧的数据,实现修改。

    这种方案的弊端就是,一旦请求体遗漏了字段,就会造成数据丢失。
    ElasticSearch 入门教程笔记

  • 方案二:POST
    ElasticSearch 入门教程笔记

删除索引

  • 命令

    # 删除索引
    DELETE /test1
    

    ElasticSearch 入门教程笔记

    # 删除文档
    DELETE /test3/_doc/1
    

    ElasticSearch 入门教程笔记

文档的基本操作

简单查询

  1. 首先添加一些数据

    PUT /crater/user/1
    {
    	"name": "陨石坑",
    	"age": 24,
    	"desc": "一顿操作猛如虎,一看工资两千五",
    	"tags": ["技术宅","宝藏男孩","直男"]
    }
    
    PUT /crater/user/2
    {
    	"name": "张三",
    	"age": 35,
    	"desc": "法外狂徒",
    	"tags": ["罗老师","刑法"]
    }
    
    PUT /crater/user/3
    {
    	"name": "李四",
    	"age": 30,
    	"desc": "不可描述",
    	"tags": ["淑女","跳舞"]
    }
    

    ElasticSearch 入门教程笔记

  2. 查询数据

    # 命令
    GET /crater/user/1
    

    ElasticSearch 入门教程笔记

修改文档

  1. 更新数据

    方式一:

    # 命令
    PUT /crater/user/3
    {
    	"name": "李四123",
    	"age": 30,
    	"desc": "不可描述",
    	"tags": ["淑女","跳舞"]
    }
    

    ElasticSearch 入门教程笔记

    方式二:推荐使用

    # 命令
    POST /crater/user/3/_update
    {
    	"doc": {
    		"name": "李四456"
    	}
    }
    

简单条件查询

  1. GET 查询

    # 命令
    GET /crater/user/_search?q=name:陨石
    

    ElasticSearch 入门教程笔记

复杂条件查询

条件、投影、排序、分页

排序、分页、高亮、模糊查询、精准查询

  • 一般情况,不使用 GET /crater/user/_search?q=name:陨石 这种形式,而是使用参数实体

    # 再新增一个文档,供测试
    PUT /crater/user/4
    {
    	"name": "陨石坑主",
    	"age": 18,
    	"desc": "操作猛如虎,工资两千五",
    	"tags": ["技术宅","宝藏男孩","直男"]
    }
    
    # 命令
    GET /crater/user/_search
    {
      "query": {
        "match": {
          "name": "陨石"
        }
      }
    }
    

    ElasticSearch 入门教程笔记

  • 也可以过滤查询的属性,类似于数据库的投影

    # 命令
    GET /crater/user/_search
    {
      "query": {
        "match": {
          "name": "陨石"
        }
      },
      "_source": ["name","desc"]
    }
    

    ElasticSearch 入门教程笔记

  • 可以再加上排序,如根据年龄倒序排序

    # 命令
    GET /crater/user/_search
    {
      "query": {
        "match": {
          "name": "陨石"
        }
      },
      "_source": ["name","age"],
      "sort": [
        {
          "age": {
            "order": "desc"
          }
        }
      ]
    }
    

    ElasticSearch 入门教程笔记

  • 可以再加上分页查询

    # 命令
    GET /crater/user/_search
    {
      "query": {
        "match": {
          "name": "陨石"
        }
      },
      "_source": ["name","age"],
      "sort": [
        {
          "age": {
            "order": "desc"
          }
        }
      ],
      "from": 0,	# 起始页码,从0开始
      "size": 1		# 每页条数
    }
    

    ElasticSearch 入门教程笔记

布尔值查询

  • must(and),多条件查询,所有条件都有符合

    # 命令
    GET /crater/user/_search
    {
      "query": {
        "bool": {
          "must": [
            {
              "match": {
                "name": "陨石"
              }
            },
            {
              "match": {
                "age": "24"
              }
            }
          ]
        }
      }
    }
    

    ElasticSearch 入门教程笔记

  • should(or)满足一个条件即可

    # 命令
    GET /crater/user/_search
    {
      "query": {
        "bool": {
          "should": [
            {
              "match": {
                "name": "陨石"
              }
            },
            {
              "match": {
                "age": "24"
              }
            }
          ]
        }
      }
    }
    

    ElasticSearch 入门教程笔记

  • must_not(not)

    # 命令
    GET /crater/user/_search
    {
      "query": {
        "bool": {
          "must_not": [
            {
              "match": {
                "name": "陨石"
              }
            },
            {
              "match": {
                "age": "24"
              }
            }
          ]
        }
      }
    }
    

    ElasticSearch 入门教程笔记

  • 查询的同时,再使用 filter 过滤

    # 命令
    GET /crater/user/_search
    {
      "query": {
        "bool": {
          "must": [
            {
              "match": {
                "name": "陨石"
              }
            }
          ],
          "filter": {
            "range": {
              "age": {
                "gte": 20
              }
            }
          }
        }
      }
    }
    

匹配多个条件

  • 多个条件使用空格隔开,只要满足一个即可以被查出,后续可以使用得分再进行判断筛选

    # 命令
    GET /crater/user/_search
    {
      "query": {
        "match": {
          "tags": "男 宅"
        }
      }
    }
    

精确查询

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

关于分词

  • term:直接精确查询
  • match:会使用分词器解析(先分析文档,然后通过分析的文档进行查询)

两个类型 text 和 keyword

  • text 会被分词器解析,建立索引前会将这些文本进行分词,转化为词的组合,建立索引

  • keyword 类型字段只能用本身来进行检索

  • 搭建测试

    PUT testdb
    {
      "mappings": {
        "properties": {
          "name": {
            "type": "text"
          },
          "desc": {
            "type": "keyword"
          }
        }
      }
    }
    
    PUT testdb/_doc/1
    {
      "name": "陨石坑 name",
      "desc": "陨石坑 desc1"
    }
    
    PUT testdb/_doc/2
    {
      "name": "陨石坑 name",
      "desc": "陨石坑 desc2"
    }
    
  • 两种类型对于分词器的区别

    # 被解析
    GET _analyze
    {
      "analyzer": "standard",
      "text": "陨石坑 name"
    }
    

    ElasticSearch 入门教程笔记

    # 不被解析
    GET _analyze
    {
      "analyzer": "keyword",
      "text": "陨石坑 desc"
    }
    
    

    ElasticSearch 入门教程笔记

  • 测试

    # match 去匹配 {"name":"陨石坑"},可以查询到两条数据
    GET /testdb/_doc/_search
    {
      "query": {
        "match": {
          "name": "陨石坑"
        }
      }
    }
    
    # match 去匹配 {"desc":"陨石坑"},查询不到数据,
    # keyword 类型字段不会被分词器解析,只能用本身来进行检索,如{"desc":"陨石坑 desc1"}
    GET /testdb/_doc/_search
    {
      "query": {
        "match": {
          "desc": "陨石坑"
        }
      }
    }
    
    # 用 term 去匹配 {"name":"陨石坑"}、{"desc":"陨石坑"},都查询不到数据,因为需要全值精确匹配
    # 如果期望查询到数据:
    # 对于 name,需要匹配被解析出的 "陨","石","坑","name"
    # 对于 desc,需要匹配 "陨石坑 desc1"、"陨石坑 desc2"
    GET /testdb/_doc/_search
    {
      "query": {
        "term": {
          "name": "陨石坑"
        }
      }
    }
    GET /testdb/_doc/_search
    {
      "query": {
        "term": {
          "desc": "陨石坑"
        }
      }
    }
    
    # 用 match 匹配 {"name":"陨坑"} 也可以查询到两条数据,
    
  • 总结:

    match(模糊)和 term(精确)指的是查询时匹配程度

    例如 “陨石坑 name” 是一个text,在创建时就被分词成为 “陨”,“石”,“坑”,“name”,所以不能用 term 查询 “陨石坑 name”,但是可以使用 term 查询 “陨”,“石”,“坑”,“name”,因为对比分词的索引能够精确匹配这四个词

    而 “陨石坑 desc1” 是 keyword 类型的,在创建的时候就不分词,只有一个索引,所以无论使用 match 还是 term,都需要全值

    match 和 term 负责查询条件的数据是否分词,text 和 keyword 负责存储的数据是否分词

多个值匹配精确查询

  • 添加测试数据

    PUT testdb/_doc/3
    {
      "t1": "22",
      "t2": "2021-12-20"
    }
    
    PUT testdb/_doc/4
    {
      "t1": "33",
      "t2": "2021-12-17"
    }
    
  • 用 term 精确查询多个值

    GET testdb/_search
    {
      "query": {
        "bool": {
          "should": [
            {
              "term": {
                "t1": "22"
              }
            },
            {
              "term": {
                "t1": "33"
              }
            }
          ]
        }
      }
    }
    

    ElasticSearch 入门教程笔记

高亮查询

  • 查询时,使用 “highlight”,指定高亮的字段

    可以看到,查询结果里,把查询条件里想要查询的词加上了标签

    # 命令
    GET crater/user/_search
    {
      "query": {
        "match": {
          "name": "陨石"
        }
      },
      "highlight": {
        "fields": {
          "name": {}
        }
      }
    }
    

    ElasticSearch 入门教程笔记

  • em 标签是默认标签,也可以指定自定义标签

    # 命令
    GET crater/user/_search
    {
      "query": {
        "match": {
          "name": "陨石"
        }
      },
      "highlight": {
        "pre_tags": "<p class='key' style='color:red'>", 
        "post_tags": "</p>", 
        "fields": {
          "name": {}
        }
      }
    }
    

    可以看到,词被自定义标签包裹起来了

    ElasticSearch 入门教程笔记

集成 SpringBoot

客户端文档:https://www.elastic.co/guide/en/elasticsearch/client/index.html

推荐使用 Java REST Client :https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/index.html

选择 Java High Level REST Client 高级客户端,根据文档操作:

  1. 引入原生依赖

    <dependency>
        <groupId>org.elasticsearch.client</groupId>
        <artifactId>elasticsearch-rest-high-level-client</artifactId>
        <version>7.15.2</version>
    </dependency>
    
  2. 初始化

    绑定集群中的节点,一个节点也是一个集群

    RestHighLevelClient client = new RestHighLevelClient(
            RestClient.builder(
                    new HttpHost("localhost", 9200, "http"),
                    new HttpHost("localhost", 9201, "http")));
    

    使用完毕需要关闭

    client.close();
    

测试

  • 搭建项目环境,最重要的依赖

    ElasticSearch 入门教程笔记

  • 自定义ES依赖,保证和本地一致

    <properties>
            <java.version>1.8</java.version>
            <!-- 自定义ES版本依赖,保证和本地一致 -->
            <elasticsearch.version>7.15.2</elasticsearch.version>
        </properties>
    
  • 注入 RestHighLevelClient (客户端)

    package com.crater.config;
    
    import org.apache.http.HttpHost;
    import org.elasticsearch.client.RestClient;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class ElasticSearchClientConfig {
    
        @Bean
        public RestHighLevelClient restHighLevelClient() {
            RestHighLevelClient client = new RestHighLevelClient(
                    RestClient.builder(new HttpHost("127.0.0.1", 9200, "http"))
            );
            return client;
        }
    }
    

索引 API 测试

  • 测试索引的创建

    @SpringBootTest
    class CraterEsApiApplicationTests {
        @Autowired
        @Qualifier("restHighLevelClient")
        private RestHighLevelClient client;
    
        /**
         * 测试索引的创建
         */
        @Test
        void testCreateIndex() throws IOException {
            // 1.创建索引请求
            CreateIndexRequest request = new CreateIndexRequest("crater_index");
    
            // 2.执行创建请求
            IndicesClient indices = client.indices();
            CreateIndexResponse createIndexResponse = indices.create(request, RequestOptions.DEFAULT);
            System.out.println(createIndexResponse);
        }
    }
    
    # 打印内容
    org.elasticsearch.client.indices.CreateIndexResponse@38e8e841
    
  • 测试获取索引

    /**
     * 测试获取索引
     */
    @Test
    void testExistIndex() throws IOException {
        GetIndexRequest request = new GetIndexRequest("crater_index");
        boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
        System.out.println(exists);
    }
    
  • 测试删除索引

    /**
     * 测试删除索引
     */
    @Test
    void testDeleteIndex() throws IOException {
        DeleteIndexRequest request = new DeleteIndexRequest("crater_index");
        AcknowledgedResponse delete = client.indices().delete(request, RequestOptions.DEFAULT);
        System.out.println(delete.isAcknowledged());
    }
    

文档 API 测试

  • 测试添加文档

    /**
     * 测试添加文档
     */
    @Test
    void testAddDocument() throws IOException {
        // 创建对象
        User user = new User("陨石坑", 17);
        // 创建请求
        IndexRequest request = new IndexRequest("crater_index");
    
        // 规则:GET /crater_index/_doc/1
        request.id("1");
        request.timeout(TimeValue.timeValueSeconds(1));
    
        // 数据放入请求 JSON
        request.source(JSON.toJSONString(user), XContentType.JSON);
    
        // 客户端发送请求,获取响应结果
        IndexResponse indexResponse = client.index(request, RequestOptions.DEFAULT);
        System.out.println(indexResponse.status());
        System.out.println(indexResponse.toString());
    }
    
    # 返回值
    CREATED
    IndexResponse[index=crater_index,type=_doc,id=1,version=1,result=created,seqNo=0,primaryTerm=1,shards={"total":2,"successful":1,"failed":0}]
    
    
  • 获取文档,先判断是否存在

    /**
     * 获取文档,先判断是否存在:GET crater_index/_doc/1
     */
    @Test
    void testIsExist() throws IOException {
        GetRequest getRequest = new GetRequest("crater_index", "1");
        // 不获取返回的 _source 的上下文
        getRequest.fetchSourceContext(new FetchSourceContext(false));
        // 不排序
        getRequest.storedFields("_none_");
    
        boolean exists = client.exists(getRequest, RequestOptions.DEFAULT);
        System.out.println(exists);
    }
    

    获取文档

    /**
     * 获取文档
     */
    @Test
    void testGetDocument() throws IOException {
        GetRequest getRequest = new GetRequest("crater_index", "1");
        GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT);
        System.out.println(getResponse.getSourceAsString());
        System.out.println(getResponse);
    }
    
    # 返回值
    {"age":17,"name":"陨石坑"}
    {"_index":"crater_index","_type":"_doc","_id":"1","_version":1,"_seq_no":0,"_primary_term":1,"found":true,"_source":{"age":17,"name":"陨石坑"}}
    
    
  • 修改文档信息

    /**
     * 更新文档信息
     */
    @Test
    void testUpdateRequest() throws IOException {
        UpdateRequest request = new UpdateRequest("crater_index", "1");
        request.timeout(TimeValue.timeValueSeconds(1));
    
        User user = new User("陨石坑YYDS", 3);
        request.doc(JSON.toJSONString(user), XContentType.JSON);
    
        UpdateResponse updateResponse = client.update(request, RequestOptions.DEFAULT);
        System.out.println(updateResponse.status());
    }
    
  • 批量插入

    /**
     * 批量插入
     */
    @Test
    void testBulkRequest() throws IOException {
        BulkRequest bulkRequest = new BulkRequest();
        bulkRequest.timeout("10s");
    
        List<User> users = new ArrayList<>();
        users.add(new User("陨石坑01", 14));
        users.add(new User("陨石坑02", 14));
        users.add(new User("陨石坑03", 14));
        users.add(new User("陨石坑04", 14));
        users.add(new User("陨石坑05", 14));
    
        // 批处理请求
        for (int i = 0; i < users.size(); i++) {
            bulkRequest.add(
                // 批量更新、批量删除,在这里修改不同Request即可
                new IndexRequest("crater_index")
                .id((i + 1) + "")
                .source(JSON.toJSONString(users.get(i)), XContentType.JSON)
            );
        }
        BulkResponse bulkResponse = client.bulk(bulkRequest, RequestOptions.DEFAULT);
        System.out.println(bulkResponse.status());
    }
    

实战 - 京东搜索

环境搭建

  1. 按照测试环境搭建项目环境

  2. yml 配置

    server.port=9090
    # 关闭 thymeleaf 缓存
    spring.thymeleaf.cache=false
    
  3. 将静态文件复制到目录下

    文件获取:关注 狂神说 ,恢复 ElasticSearch,知识无价,支持正版

  4. 新建 controller

    @Controller
    public class IndexController {
    
        @RequestMapping({"/","index"})
        public String index() {
            return "index";
        }
    }
    
  5. 启动项目,访问 http://127.0.0.1:9090/

    ElasticSearch 入门教程笔记

爬虫

  1. 在京东商城搜索一个词条,可以看到访问的 url

    https://search.jd.com/Search?keyword=java

    ElasticSearch 入门教程笔记

  2. 引入 jsoup 包,用来解析网页的数据

    <dependency>
        <groupId>org.jsoup</groupId>
        <artifactId>jsoup</artifactId>
        <version>1.13.1</version>
    </dependency>
    
  3. 在 utils 包下新建 HtmlParseUtil

    测试,可以获取数据

    @Component
    public class HtmlParseUtil {
        public static void main(String[] args) throws IOException {
            List<Content> javas = new HtmlParseUtil().parseJD("MacBok");
            for (Content java : javas) {
                System.out.println(java);
            }
        }
    
        public List<Content> parseJD(String keyWord) throws IOException{
            String url = "https://search.jd.com/Search?keyword=" + keyWord;
    
            // 解析网页,Jsoup返回的Document就是浏览器Document对象
            Document document = Jsoup.parse(new URL(url), 30000);
            // 操作Document
            Element element = document.getElementById("J_goodsList");
            // 获取 li 标签
            Elements elements = element.getElementsByTag("li");
            // 获取元素中的内容
            List<Content> goodList  = new ArrayList<>();
            for (Element el : elements) {
                // 图片多的网站,为了响应速度,采取懒加载,JD真正的的url保存在data-lazy-img
                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();
    
                goodList.add(new Content(title, img, price));
            }
            return goodList;
        }
    }
    

    ElasticSearch 入门教程笔记

编写业务

  1. 编写 Service 层

    @Service
    public class ContentService {
        @Autowired
        private RestHighLevelClient restHighLevelClient;
        @Autowired
        private HtmlParseUtil htmlParseUtil;
    
        public Boolean parseContent(String keywords) throws IOException {
            List<Content> contents = htmlParseUtil.parseJD(keywords);
            // 把爬取的数据插入 ES
            BulkRequest bulkRequest = new BulkRequest();
            bulkRequest.timeout("2m");
            for (int i = 0; i < contents.size(); i++) {
                bulkRequest.add(
                        new IndexRequest("jd_goods")
                                .source(JSON.toJSONString(contents.get(i)), XContentType.JSON)
                );
            }
            BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
            return !bulk.hasFailures();
        }
    }
    
  2. 编写 Controller 层

    @RestController
    public class ContentController {
        @Autowired
        private ContentService contentService;
    
        @GetMapping("/parse/{keywords}")
        public Boolean parse(@PathVariable("keywords") String keywords) throws IOException {
            return contentService.parseContent(keywords);
        }
    }
    
  3. 调用这个接口,可以把数据加入 ES,前提新建索引 jd_goods

    ElasticSearch 入门教程笔记

  4. 获取这些数据,实现搜索功能

    Service 层:

    /**
     * 爬取的数据插入 ES
     */
    public List<Map<String, Object>> searchPage(String keyword, int pageNo, int pageSize) throws IOException {
        if (pageNo < 1) {
            pageNo = 1;
        }
    
        // 条件搜索
        SearchRequest searchRequest = new SearchRequest("jd_goods");
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    
        // 分页
        sourceBuilder.from(pageNo);
        sourceBuilder.size(pageSize);
    
        // 精准匹配
        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", keyword);
        sourceBuilder.query(termQueryBuilder);
        sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
    
        // 执行搜索
        searchRequest.source(sourceBuilder);
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
    
        // 解析结果
        List<Map<String, Object>> list = new ArrayList<>();
        for (SearchHit documentFields : searchResponse.getHits().getHits()) {
            list.add(documentFields.getSourceAsMap());
        }
        return list;
    }
    

    Controller 层

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

    访问这个接口测试一下

    ElasticSearch 入门教程笔记

前端显示

采用 Vue,但没有使用脚手架

  1. vue.min.js axios.min.js 复制到 /static/js/

    主要代码,详细代码见 gitee

    <!-- 商品详情 -->
    <div class="view grid-nosku">
    
        <div class="product" v-for="result in results">
            <div class="product-iWrap">
                <!--商品封面-->
                <div class="productImg-wrap">
                    <a class="productImg">
                        <img :src="result.img">
                    </a>
                </div>
                <!--价格-->
                <p class="productPrice">
                    <em><b>¥</b>{{result.price}}</em>
                </p>
                <!--标题-->
                <p class="productTitle">
                    <a> {{result.title}} </a>
                </p>
                <!-- 店铺名 -->
                <div class="productShop">
                    <span>店铺: 狂神说Java </span>
                </div>
                <!-- 成交信息 -->
                <p class="productStatus">
                    <span>月成交<em>999笔</em></span>
                    <span>评价 <a>3</a></span>
                </p>
            </div>
        </div>
    </div>
    
    <!-- 前端使用 Vue -->
    <script th:src="@{/js/axios.min.js}"></script>
    <script th:src="@{/js/vue.min.js}"></script>
    <script>
    
    new Vue({
        el: '#app', // 绑定最外层div
        data: {
            keyword: "",    // 搜索关键字
            results: []     // 搜索结果
        },
        methods: {
            searchKey() {
                var keyword = this.keyword;
    
                // 对接后端接口
                axios.get('search/'+keyword+'/1/10').then(res => {
                    this.results = res.data;
                })
            }
        }
    })
    
    </script>
    
  2. 测试

    ElasticSearch 入门教程笔记

高亮显示

  1. 在 ContentService 添加高亮构造器

    // 高亮
    HighlightBuilder highlightBuilder = new HighlightBuilder();
    highlightBuilder.requireFieldMatch(false);  // 是否多个匹配的都高亮
    highlightBuilder.field("title");
    highlightBuilder.preTags("<span style='color:red'>");
    highlightBuilder.postTags("</span>");
    sourceBuilder.highlighter(highlightBuilder);
    
  2. 解析高亮字段,替换原来的内容

    for (SearchHit hit : searchResponse.getHits().getHits()) {
        // 解析高亮的字段
        Map<String, HighlightField> highlightFields = hit.getHighlightFields();
        HighlightField title = highlightFields.get("title");
        Map<String, Object> sourceAsMap = hit.getSourceAsMap(); // 查询的原始结果
        // 将原来的字段,换为高亮的字段
        if (title != null) {
            Text[] fragments = title.fragments();
            StringBuilder newTitle = new StringBuilder();
            for (Text text : fragments) {
                newTitle.append(text);
            }
            sourceAsMap.put("title", newTitle.toString());      // 替换
        }
    
        list.add(sourceAsMap);
    }
    
  3. 页面标题,把值解析成为 html

    <!--标题-->
    <p class="productTitle">
        <a v-html="result.title">  </a>
    </p>
    
  4. 测试,关键词被高亮了

    ElasticSearch 入门教程笔记

完结撒花!!!

上一篇:ELK 收集 K8S (containerd 容器运行时) 二


下一篇:基于流计算 Oceanus 和 Elasticsearch Service 构建百亿级实时监控系统