# Elasticsearch
**Repository Path**: lambertee/elasticsearch
## Basic Information
- **Project Name**: Elasticsearch
- **Description**: es学习
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: main
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2023-06-14
- **Last Updated**: 2023-08-29
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# ElasticSearch
# 一、Elasticsearch概述
## 1、Elasticsearch是什么
The Elastic Stack, 包括 Elasticsearch、Kibana、Beats 和 Logstash(也称为 ELK Stack)。能够安全可靠地获取任何来源、任何格式的数据,然后实时地对数据进行搜索、分析和可视化。Elaticsearch,简称为ES, **ES是一个开源的高扩展的分布式全文搜索引擎**,是整个Elastic Stack技术栈的核心。它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理PB级别的数据。
## 2、全文搜索引擎
Google,百度类的网站搜索,它们都是根据网页中的关键字生成索引,我们在搜索的时候输入关键字,它们会将该关键字即索引匹配到的所有网页返回;还有常见的项目中应用日志的搜索等等。对于这些非结构化的数据文本,关系型数据库搜索不是能很好的支持。
一般传统数据库,全文检索都实现的很鸡肋,因为一般也没人用数据库存文本字段。进行全文检索需要扫描整个表,如果数据量大的话即使对SQL的语法优化,也收效甚微。建立了索引,但是维护起来也很麻烦,对于 insert 和 update 操作都会重新构建索引。
基于以上原因可以分析得出,在一些生产环境中,使用常规的搜索方式,性能是非常差的:
- 搜索的数据对象是大量的非结构化的文本数据。
- 文件记录量达到数十万或数百万个甚至更多。
- 支持大量基于交互式文本的查询。
- 需求非常灵活的全文搜索查询。
- 对高度相关的搜索结果的有特殊需求,但是没有可用的关系数据库可以满足。
- 对不同记录类型、非文本数据操作或安全事务处理的需求相对较少的情况。
为了解决结构化数据搜索和非结构化数据搜索性能问题,我们就需要专业,健壮,强大的全文搜索引擎
这里说到的全文搜索引擎指的是目前广泛应用的主流搜索引擎。**它的工作原理是计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。这个过程类似于通过字典中的检索字表查字的过程。**
## 3、Elasticsearch And Solr
**Lucene**是Apache软件基金会Jakarta项目组的一个子项目,提供了一个简单却强大的应用程式接口,能够做全文索引和搜寻。在Java开发环境里Lucene是一个成熟的免费开源工具。就其本身而言,Lucene是当前以及最近几年最受欢迎的免费Java信息检索程序库。但Lucene只是一个提供全文搜索功能类库的核心工具包,而真正使用它还需要一个完善的服务框架搭建起来进行应用。
**目前市面上流行的搜索引擎软件,主流的就两款:Elasticsearch和Solr**,这两款都是基于Lucene搭建的,可以独立部署启动的搜索引擎服务软件。由于内核相同,所以两者除了服务器安装、部署、管理、集群以外,对于数据的操作 修改、添加、保存、查询等等都十分类似。
在使用过程中,一般都会将Elasticsearch和Solr这两个软件对比,然后进行选型。这两个搜索引擎都是流行的,先进的的开源搜索引擎。它们都是围绕核心底层搜索库 - Lucene构建的 - 但它们又是不同的。像所有东西一样,每个都有其优点和缺点:
## 4、Elasticsearch Or Solr
Elasticsearch和Solr都是 开源搜索引擎,那么我们在使用时该如何选择呢
- Google 搜索趋势结果表明,与 Solr 相比, Elasticsearch 具有很大的吸引力,但这并不意味着 Apache Solr 已经死亡。虽然有些人可能不这么认为,但 Solr 仍然是最受欢迎的搜索引擎之一,拥有强大的社区和开源支持。
- 与 Solr 相比,Elasticsearch 易于安装且非常轻巧。此外,你可以在几分钟内安装并运行Elasticsearch 。但是,如果 Elasticsearch 管理不当,这种易于部署和使用可能会成为一个问题。基于 JSON 的配置很简单,但如果要为文件中的每个配置指定注释,那么它不适合您。总的来说,如果你的应用使用的是 JSON ,那么 Elasticsearch 是一个更好的选择。否则,请使用 Solr ,因为它的 schema.xml 和 solrconfig.xml 都有很好的文档记录。
- Solr 拥有更大,更成熟的用户,开发者和贡献者社区。 ES 虽拥有的规模较小但活跃的用户社区以及不断增长的贡献者社区。
Solr贡献者和提交者来自许多不同的组织,而 Elasticsearch 提交者来自单个公司。
- Solr 更成熟,但 ES 增长迅速,更稳定。
- Solr 是一个非常有据可查的产品,具有清晰的示例和 API 用例场景。 Elasticsearch 的文档组织良好,但它缺乏好的示例和清晰的配置说明。
## 5、Elasticsearch 应用案例
- **GitHub**: 2013年初,抛弃了 Solr,采取 Elasticsearch来做PB级的搜索。 “GitHub 使用Elasticsearch搜索20TB的数据,包括13亿文件和1300亿行代码
- **维基百科** :启动以Elasticsearch为基础的核心搜索架构
- **SoundCloud**:SoundCloud 使用 Elasticsearch为1.8亿用户提供即时而精准的音乐搜索服务
- **百度**:目前广泛使用 Elasticsearch作为文本数据分析,采集百度所有服务器上的各类指标数据及用户自定义数据,通过对各种数据进行多维分析展示,辅助定位分析实例异常或业务层面异常。目前覆盖百度内部 20 多个业务线(包括云分析、网盟、预测、文库、直达号、钱包、 风控等),单集群最大 100 台机器, 200 个 ES 节点,每天导入 30TB+数据。
- **新浪**:使用 Elasticsearch 分析处理 32 亿条实时日志。
- **阿里**:使用 Elasticsearch 构建日志采集和分析体系。
- **StackOverflow**:解决Bug问题的网站,全英文,编程人员交流的网站。
# 二、Elasticsearch入门
## 1、Elasticsearch安装
### 1-1、下载软件
> Elasticsearch官网: https://www.elastic.co/cn/
Elasticsearch 最新的版本是 8.2.2,我们选择 7.16.2 版本(最新版本半年前的版本)
下载地址:https://www.elastic.co/cn/downloads/past-releases#elasticsearch
Elasticsearch 分为 Linux 和 Windows 版本,基于我们主要学习的是Elasticsearch的Java客户端的使用,所以课程中使用的是安装较为简便的 Windows 版本 。
### 1-2、安装软件
Windows版的Elasticsearch的安装很简单,解压即安装完毕,解压后的 Elasticsearch的目录结构如下
| 目录 | 含义 |
| ------- | -------------- |
| bin | 可执行脚本目录 |
| config | 配置目录 |
| jdk | 内置JDK目录 |
| lib | 类库 |
| logs | 日志目录 |
| modules | 模块目录 |
| plugins | 插件目录 |
解压后,进入bin文件目录,点击elasticsearch.bat文件启动 ES 服务
> 注意:**9300**端口为Elasticsearch集群间组件的通信端口,**9200**端口为浏览器访问的http协议RESTful端口。
打开浏览器(推荐使用谷歌浏览器),输入地址:http://localhost:9200 ,测试结果
### 1-3、问题解决
- Elasticsearch 是使用 java 开发的,且 7.8 版本的 ES 需要 JDK 版本 1.8 以上,默认安装包带有 jdk 环境,如果系统配置 JAVA_HOME ,那么使用系统默认的 JDK ,如果没有配置使用自带的JDK ,一般建议使用系统配置的JDK
- 双击启动窗口闪退,通过路径访问追踪错误,如果是“空间不足”,请修改`config/jvm.options`配置文件
```options
# 设置 JVM 初始内存为 1G 。此值可以设置与 Xmx 相同,以避免每次垃圾回收完成后 JVM 重新分配内存
# Xms represents the initial size of total heap space
# 设置 JVM 最大可用内存为 1G
# Xmx represents the maximum size of total heap space
Xms1g
Xmx1g
```
## 2、Elasticsearch基本操作
### 2-1、RESTful
REST指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是 RESTful 。 Web 应用程序最重要的 REST 原则是,客户端和服务器之间的交互在请求之间是无状态的。从客户端到服务器的每个请求都必须包含理解请求所必需的信息。如果服务器在请求之间的任何时间点重启,客户端不会得到通知。此外,无状态请求可以由任何可用服务器回答,这十分适合云计算之类的环境。客户端可以缓存数据以改进性能。
在服务器端,应用程序状态和功能可以分为各种资源。资源是一个有趣的概念实体,它向客户端公开。资源的例子有:应用程序对象、数据库记录、算法等等。**每个资源都使用 URI(Universal Resource Identifier) 得到一个唯一的地址。所有资源都共享统一的接口,以便在客户端和服务器之间传输状态。使用的是标准的 HTTP 方法,比如 GET、PUT、POST和DELETE 。**
在REST样式的Web服务中,每个资源都有一个地址。资源本身都是方法调用的目标,方法列表对所有资源都是一样的。这些方法都是标准方法,包括 HTTP GET、POST、PUT、DELETE,还可能包括HEAD和OPTIONS。简单的理解就是,如果想要访问互联网上的资源,就必须向资源所在的服务器发出请求,请求体中必须包含资源的网络路径,以及对资源进行的操作增删改查 。
### 2-2、客户端安装
如果直接通过浏览器向Elasticsearch 服务器发请求,那么需要在发送的请求中包含HTTP 标准的方法,而HTTP的大部分特性且仅支持 GET 和 POST 方法。所以为了能方便地进行客户端的访问,可以使用 Postman 软件。
Postman是一款强大的网页调试工具,提供功能强大的 Web API 和 HTTP 请求调试。软件功能强大,界面简洁明晰、操作方便快捷,设计得很人性化。 Postman 中文版能够发送任何类型的 HTTP 请求 (GET, POST, PUT..) PUT..),不仅能够表单提交,且可以附带任意类型请求体。
Postman 官网:https://www.getpostman.com
Postman 下载:https://www.getpostman.com/apps
### 2-3、数据格式
Elasticsearch是面向文档型数据库,一条数据在这里就是一个文档。为了方便大家理解,我们将 Elasticsearch 里存储文档数据和关系型数据库 MySQL 存储数据的概念进行一个类比
ES里的 Index 可以看做一个库,而 Types 相当于表, Documents 则相当于表的行。这里Types的概念已经被逐渐弱化, Elasticsearch 6.X 中,一个 index下已经只能包含一个type,**Elasticsearch 7.X 中, Type的概念已经被删除了**。
> 用 JSON 作为文档序列化的格式,比如一条用户信息:
```JSON
{
"name" : "John",
"sex" : "Male",
"age" : 25,
"birthDate": "1990/05/01",
"about" : "I love to go rock climbing",
"interests": [ "sports", "music"]
}
```
### 2-4、HTTP 操作
#### 2-4-1、索引操作
> 正排(正向)索引:用文章id匹配文章内容
>
> id content
>
> 1001 -> my name is zhangsan
>
> 1002 -> my name is lisi
>
> `倒排索引`:用文章内容匹配文章id
>
> keyword id
>
> name -> 1001,1002
>
> zhang-> 1001
##### 1、索引_创建
对比关系型数据库,创建索引就等同于创建数据库在Postman 中,向 ES 服务器发 PUT 请求 http://127.0.0.1:9200/shopping
请求后,服务器返回响应
```json
{
"acknowledged": true, 【响应结果】 true操作成功
"shards_acknowledged": true, 【分片结果】 分片操作成功
"index": "shopping" 【索引名称】
}
# 注意:创建索引库的分片数默认 1 片,在 7.0.0 之前的 Elasticsearch 版本中,默认 5 片
```
如果重复添加索引,会返回错误信息
##### 2、索引_查询所有
在Postman 中,向 ES 服务器发 GET 请求 http://127.0.0.1:9200/_cat/indices?v
这里请求路径中的`_cat表示查看的意思`,`indices 表示索引`,所以整体含义就是查看当前ES服务器中的所有索引,就好像 MySQL 中的 show tables 的感觉,服务器响应结果如下
| 表头 | 含义 |
| -------------- | ------------------------------------------------------------ |
| health | 当前服务器健康状态:
green(集群完整) yellow(单点正常、集群不完整) red(单点不正常) |
| status | 索引打开、关闭状态 |
| index | 索引名 |
| uuid | 索引统一编号 |
| pri | 主分片数量 |
| rep | 副本数量 |
| docs.count | 可用文档数量 |
| docs.deleted | 文档删除状态(逻辑删除) |
| store.size | 主分片和副分片整体占空间大小 |
| pri.store.size | 主分片占空间大小 |
##### 3、索引_查询单个
在Postman 中,向 ES 服务器发 GET 请求 http://127.0.0.1:9200/shopping
查看索引向ES 服务器发送的请求路径和创建索引是一致的。但是 HTTP 方法不一致。这里可以体会一下 RESTful 的意义,
请求后,服务器响应结果如下:
```json
{
"shopping":{【索引名】
"aliases":{},【别名】
"mappings": {}【映射】
"settings": {【设置】
"index": {【设置-索引】
"routing": {
"allocation": {
"include": {
"_tier_preference": "data_content"
}
}
},
"number_of_shards": "1",【设置-索引-主分片数量】
"provided_name": "shopping",【设置-索引-名称】
"creation_date": "1654738239335",【设置-索引-创建时间】
"number_of_replicas": "1",【设置-索引-副分片数量】
"uuid": "In1Smz59QOqKVSO8zGIrdg",【设置-索引-唯一标识】
"version": {【设置-索引-版本】
"created": "7160299"
}
}
}
}
}
```
##### 4、索引_删除
在Postman 中,向 ES 服务器发 DELETE 请求 http://127.0.0.1:9200/shopping
请求结果:
重新访问索引时,服务器返回响应:`索引不存在`
#### 2-4-2、文档操作
##### 1、文档_创建
索引已经创建好了,接下来我们来创建文档,并添加数据。这里的文档可以类比为关系型数据库中的表数据,添加的数据格式为 JSON 格式
在Postman 中,向 ES 服务器发 `POST` 请求 http://127.0.0.1:9200/shopping/_doc
请求体内容为:
```json
{
"tilte":"小米手机",
"category":"小米",
"images":"http://www.gulixueyuan.com/xm.jpg",
"price":3999.00
}
```
请求结果:
```json
{
"_index": "shopping",【索引】
"_type": "_doc",【类型-文档】
"_id": "WlcsR4EBG1gFkeg27y2e",【唯一标识,可以类比MySQL的主键,随机生成】
"_version": 1,【版本】
"result": "created",【结果,这里表示创建成功】
"_shards": {
"total": 2,【分片-总数】
"successful": 1,【分片-成功】
"failed": 0【分片-失败】
},
"_seq_no": 0,
"_primary_term": 1
}
```
上面的数据创建后,由于没有指定数据唯一性标识(ID),默认情况下 ES 服务器会随机生成一个
如果想要自定义唯一性标识,需要在创建时指定http://127.0.0.1:9200/shopping/_doc/1
`此处需要注意:如果增加数据时明确数据主键,那么请求方式也可以为PUT`,
当指定一个id请求时,路径可以更改为:http://127.0.0.1:9200/shopping/create/1 效果一样是创建文档
##### 2、文档_主键查询
查看文档时,需要指明文档的唯一性标识,类似于MySQL中数据的主键查询
在Postman 中,向 ES 服务器发 `GET` 请求 http://127.0.0.1:9200/shopping/_doc/1
查询成功后,服务器响应结果:
```json
{
"_index": "shopping",【索引】
"_type": "_doc",【类型:文档】
"_id": "1001",【
"_version": 1,
"_seq_no": 2,
"_primary_term": 1,
"found": true,【查询结果】
"_source": {【文档源信息】
"tilte": "小米手机",
"category": "小米",
"images": "http://www.gulixueyuan.com/xm.jpg",
"price": 3999.00
}
}
```
##### 3、文档_全查询
在Postman 中,向 ES 服务器发 `GET` 请求:http://127.0.0.1:9200/shopping/_search
请求成功后,服务器响应结果:
##### 3、文档_全量修改
和新增文档一样,输入相同的URL 地址请求,如果请求体变化,会将原有的数据内容覆盖
在Postman中,向ES服务器发`POST/PUT`请求 http://127.0.0.1:9200/shopping/_doc/1
请求体内容:
```json
{
"tilte":"华为手机",
"category":"华为",
"images":"http://www.gulixueyuan.com/hw.jpg",
"price":4999.00
}
```
修改成功后,服务器响应结果:
##### 4、文档_局部修改
修改数据时,也可以只修改某一条数据的局部信息
在Postman中,向ES服务器发`POST`请求http://127.0.0.1:9200/shopping/_update/1
请求体内容:
```json
{
"doc":{
"price":4999.00
}
}
```
修改成功,服务器返回结果:
##### 5、文档_删除
删除一个文档不会立即从磁盘上移除,它只是被标记成已删除(逻辑删除)。
在Postman 中,向ES服务器发`DELETE`请求 http://127.0.0.1:9200/shopping/_doc/1
服务器返回结果:
##### 6、文档_单条件查询
根据字段的内容查询
在Postman 中,向ES服务器发`GET`请求 http://127.0.0.1:9200/shopping/_search?q=category:小米
服务器返回结果:
上面这种查询方式中出现中文,有可能会出现乱码情况,建议查询条件加入到json中
请求http://127.0.0.1:9200/shopping/_search
请求体json:
```json
{
"query":{
"match":{
"category":"小米"
}
}
}
```
、
服务器返回结果:
查询所有文档请求体json:
```json
{
"query":{
"match_all":{
}
}
}
```
> 试试搜索种类为米/华米
搜索米:
搜索华米:
> 这就说明了搜索时elasticsearch会将文字拆开一个一个查,那如果就必须查询一个整体怎么办呢
只需要将json中的match替换成`match_phrase`
这样就会把华米当成一个整体去搜索,match_phrase是一个整体的模糊查询
> 还有一个高亮查询
```json
{
"query":{
"match_phrase":{
"category":"小米"
}
},
"highlight":{
"fields":{
"category":{}//【设置高亮字段】
}
}
}
```
##### 7、文档_分页查询
发`GET`请求到服务器:http://127.0.0.1:9200/shopping/_search
请求体json:
```json
{
"query":{
"match_all":{
}
},
"from": 0,【第几条数据开始】
"size": 2【每页大小】
"_source": ["title","price"]【查看的字段】
"sort":{【排序】
"price":{【根据价格排序】
"order":"desc"【根据价格降序排序】
}
}
}
```
请求返回结果:
##### 8、文档_多条件查询
发`GET`请求到服务器:http://127.0.0.1:9200/shopping/_search
请求体:
```json
{
"query":{【查询】
"bool":{【查询比较相等】
"must":[【[]内多个或单个必须相等】
{
"match":{
"category":"小米"
}
},
{
"match":{
"price":4199.99
}
}
]
}
}
}
```
请求结果只有一条:
当然也可以`两个条件中任意一个条件满足的方法:只需要将must替换成should`
```json
{
"query":{
"bool":{
"should":[【[]内任意一个条件满足】
{
"match":{
"category":"华为"
}
},
{
"match":{
"category":"OPPO"
}
}
]
}
}
}
```
返回结果:
还有个范围查询的方法,should的同级下添加filter
```json
{
"query":{
"bool":{
"must":[
{
"match":{
"category":"OPPO"
}
}
],
"filter":{
"range":{
"price":{
"gt":4000
}
}
}
}
}
}
```
表示从OPPO分类中满足price>4000的数据
##### 9、文档_聚合查询
发`GET`请求到服务器:http://127.0.0.1:9200/shopping/_search
请求体:
```json
{
"aggs":{//聚合操作
"price_group":{//名称 随意
"terms":{ //分组
"field":"price"//分组字段
}
}
}
}
```
统计了价格3999的有6条,2999的有1条....
> 在上面还会输出小米的数据,我们的目的是统计查询,可以隐藏掉
修改请求体json,添加"size":0
```json
{
"aggs":{//聚合操作
"price_group":{//名称 随意
"terms":{ //分组
"field":"price"//分组字段
}
}
},
"size":0 //隐藏掉数据的内容,仅显示统计聚合数据
}
```
> 平均值聚合查询
```json
{
"aggs":{//聚合操作
"price_avg":{//名称 随意
"avg":{ //分组
"field":"price"//分组字段
}
}
},
"size":0 //隐藏掉数据的内容,仅显示统计聚合数据
}
```
查询结果:
##### 10、文档_条件删除文档
一般删除数据都是根据文档的唯一性标识进行删除,实际操作时,也可以根据条件对多条数据进行删除
向ES服务器发`POST`请求 http://127.0.0.1:9200/shopping/_delete_by_query
请求体内容:
```json
{
"query":{
"match":{
"title":"小米手机"
}
}
}
```
删除成功后,服务器响应:
took:【耗时】,timedout:【是否超时】total:【总数】,deleted:【删除总数】
#### 2-4-3、映射操作
有了索引库,等于有了数据库中的database。
接下来就需要建索引库(index)中的映射了,类似于数据库(database)中的表结构 (table) 。创建数据库表需要设置字段名称,类型,长度,约束等;索引库也一样,需要知道这个类型下有哪些字段,每个字段有哪些约束信息,这就叫做映射 (mapper)。
##### 1、映射_创建
在Postman 中,向 ES 服务器发 `PUT` 请求 http://127.0.0.1:9200/user/_mapping
请求体:
```json
{
"properties":{
"name":{
"type":"text", //text文本可以分词查询
"index":true //这个字段可以索引查询
},
"sex":{
"type":"keyword",//keyword不能分词查询
"index":true
},
"tel":{
"type":"keyword",
"index":false //不能被索引,不能被查询
}
}
}
```
服务器响应结果:
- name能够被分词查询
- sex不能被分词查询
- tel不能被作为条件查询
##### 2、映射字段说明
- 字段名:任意填写,下面指定许多属性,例如: title 、 subtitle 、 images 、 price
- type :类型 Elasticsearch 中支持的数据类型非常丰富,说几个关键的:
- String 类型,又分两种:
- text:可分词
- keyword:不可分词,数据会作为完整字段进行匹配
- Numerical :数值类型,分两类
- 基本数据类型:long 、 integer 、 short 、 byte 、 double 、 float 、 half_float
- 浮点数的高精度类型:scaled_float
- Date :日期类型
- Array :数组类型
- Object :对象
- index :是否索引,默认为 true ,也就是说你不进行任何配置,所有字段都会被索引。
- true:字段会被索引,则可以用来进行搜索
- false:字段不会被索引,不能用来搜索
- store :是否将数据进行独立存储,默认为 false
- 原始的文本会存储在_source 里面,默认情况下其他提取出来的字段都不是独立存储的,是从 _source 里面提取出来的。当然你也可以独立的存储某个字段,只要设置"store": true 即可,获取独立存储的字段要比从 _source 中解析快得多,但是也会占用更多的空间,所以要根据实际业务需求来设置。
- analyzer :分词器,这里的 ik_max_word 即使用 ik 分词器 后面会有专门的章节学习
##### 3、映射_查询单个
在Postman 中,向 ES 服务器发 `GET` 请求 http://127.0.0.1:9200/user/_mapping
请求体为空
服务器响应结果:
##### 4、映射_索引映射关联
在Postman 中,向 ES 服务器发 `PUT` 请求 http://127.0.0.1:9200/user1
请求体:
```json
{
"settings":{},
"mappings":{
"properties":{
"name":{
"type":"text",
"index":true
},
"sex":{
"type":"text",
"index":false
},
"tel":{
"type":"text",
"index":false
}
}
}
}
```
服务器响应结果:
### 2-5、Java API操作
ElasticSearch软件也是由Java语言开发的,所以也可以通过JavaAPI的方式对ElasticSearch服务进行访问
#### 2-5-1、创建Maven项目
我们在IDEA开发工具中创建Maven项目`ES`
#### 2-5-2、引入依赖
```xml
org.projectlombok
lombok
1.16.18
org.elasticsearch
elasticsearch
7.16.2
org.elasticsearch.client
elasticsearch-rest-high-level-client
7.16.2
org.apache.logging.log4j
log4j-api
2.17.1
org.apache.logging.log4j
log4j-core
2.17.1
com.fasterxml.jackson.core
jackson-databind
2.9.9
junit
junit
4.12
```
#### 2-5-3、新建操作ES的客户端
```java
public static void main(String[] args) throws IOException {
//创建ES客户端
RestHighLevelClient esClient = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost", 9200,"http"))
);
//操作
//关闭ES客户端
esClient.close();
}
```
#### 2-5-4、添加索引
```java
public static void main(String[] args) throws IOException {
//创建ES客户端
RestHighLevelClient esClient = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost", 9200,"http"))
);
//创建索引
CreateIndexRequest request = new CreateIndexRequest("user");
CreateIndexResponse createIndexResponse =
esClient.indices().create(request, RequestOptions.DEFAULT);
//响应状态
boolean acknowledged = createIndexResponse.isAcknowledged();
System.out.println("索引操作:"+acknowledged);//索引操作:true
//关闭ES客户端
esClient.close();
}
```
#### 2-5-5、查询索引
```java
//创建ES客户端
RestHighLevelClient esClient = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost", 9200, "http"))
);
//查询索引
GetIndexRequest getIndexRequest = new GetIndexRequest("user");
GetIndexResponse getIndexResponse = esClient.indices().get(getIndexRequest, RequestOptions.DEFAULT);
System.out.println("alisaes:" + getIndexResponse.getAliases());
System.out.println("mappings:" + getIndexResponse.getMappings());
System.out.println("settings:" + getIndexResponse.getSettings());
```
结果:
相当于:
#### 2-5-6、删除索引
```java
//删除索引
DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest("user");
AcknowledgedResponse response = esClient.indices().delete(deleteIndexRequest, RequestOptions.DEFAULT);
boolean acknowledged = response.isAcknowledged();
System.out.println("删除索引操作:"+acknowledged);//删除索引操作:true
```
#### 2-5-7、新增文档
```java
public static void main(String[] args) throws IOException {
RestHighLevelClient esClient = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost", 9200, "http"))
);
//插入文档
IndexRequest request = new IndexRequest("user").id("1001");
// request.index("user").id("1001");
User user = new User("zhangsan","男",30);
//向ES插入数据必须将数据转换为JSON格式
ObjectMapper mapper = new ObjectMapper();
String userJson = mapper.writeValueAsString(user);
request.source(userJson, XContentType.JSON);//请求体
IndexResponse response = esClient.index(request, RequestOptions.DEFAULT);
System.out.println(response.getResult());//CREATED
esClient.close();
}
```
POSTMAN请求结果:
#### 2-5-8、修改文档
```java
public static void main(String[] args) throws IOException {
RestHighLevelClient esClient = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost", 9200, "http"))
);
UpdateRequest request = new UpdateRequest("user", "1001");
request.doc(XContentType.JSON, "sex","女");
UpdateResponse updateResponse = esClient.update(request, RequestOptions.DEFAULT);
System.out.println(updateResponse.getResult());//UPDATED
esClient.close();
}
```
POSTMAN请求结果:
#### 2-5-9、查询文档
```java
public static void main(String[] args) throws IOException {
RestHighLevelClient esClient = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost", 9200, "http"))
);
//查询文档
GetRequest request = new GetRequest();
request.index("user").id("1001");
GetResponse response = esClient.get(request, RequestOptions.DEFAULT);
System.out.println(response.getSourceAsString());//{"name":"zhangsan","sex":"女","age":30}
esClient.close();
}
```
#### 2-5-10、删除文档
```java
//删除文档
DeleteRequest request = new DeleteRequest();
request.index("user").id("1001");
DeleteResponse response = esClient.delete(request, RequestOptions.DEFAULT);
System.out.println(response.toString());//DeleteResponse[index=user,type=_doc,id=1001,version=3,result=deleted,shards=ShardInfo{total=2, successful=1, failures=[]}]
esClient.close();
```
#### 2-5-11、批量新增文档
```java
//批量新增文档
BulkRequest request = new BulkRequest();
request.add(new IndexRequest().index("user").id("1001").source(XContentType.JSON,"name","zhangsan"));
request.add(new IndexRequest().index("user").id("1002").source(XContentType.JSON,"name","lisi"));
request.add(new IndexRequest().index("user").id("1003").source(XContentType.JSON,"name","wangwu"));
BulkResponse response = esClient.bulk(request, RequestOptions.DEFAULT);
System.out.println(response.getTook());
System.out.println(response.getItems());
```
#### 2-5-12、批量删除文档
```java
//批量删除文档
BulkRequest request = new BulkRequest();
request.add(new DeleteRequest().index("user").id("1001"));
request.add(new DeleteRequest().index("user").id("1002"));
BulkResponse response = esClient.bulk(request, RequestOptions.DEFAULT);
System.out.println(response.getTook());
System.out.println(response.getItems());
```
#### 2-5-13、高级查询-全量查询
```java
//查询索引中的全部数据
SearchRequest request = new SearchRequest();
request.indices("user");
request.source(new SearchSourceBuilder().query(QueryBuilders.matchAllQuery()));
SearchResponse response = esClient.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
System.out.println(hits.getTotalHits());//6 hits
System.out.println(response.getTook());//2ms
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
//{"name":"zhangsan","age":30,"sex":"男"}
//{"name":"lisi","age":30,"sex":"女"}
//{"name":"wangwu","age":40,"sex":"男"}
//{"name":"wangwu1","age":40,"sex":"女"}
//{"name":"wangwu2","age":50,"sex":"男"}
//{"name":"wangwu3","age":50,"sex":"男"}
}
```
#### 2-5-14、高级查询-条件查询
```java
//高级查询:条件查询
SearchRequest request = new SearchRequest();
request.indices("user");
request.source(new SearchSourceBuilder().query(QueryBuilders.termQuery("sex", "男")));
SearchResponse response = esClient.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();//2 hits
System.out.println(hits.getTotalHits());
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
//{"name":"zhangsan","tel":"13748794433","sex":"男"}
//{"name":"wangwu","tel":"13748794435","sex":"男"}
//{"name":"wangwu2","tel":"13748794437","sex":"男"}
//{"name":"wangwu3","tel":"13748794438","sex":"男"}
}
```
#### 2-5-15、高级查询-分页查询
```java
SearchRequest request = new SearchRequest();
request.indices("user");
SearchSourceBuilder builder = new SearchSourceBuilder().query(QueryBuilders.matchAllQuery());
//from = (当前页码-1)* 每页显示数据条数
builder.from(0);//从第几个开始查
builder.size(2);//页大小
request.source(builder);
SearchResponse response = esClient.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
System.out.println(hits.getTotalHits());
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
//{"name":"zhangsan","tel":"13748794433","sex":"男"}
//{"name":"lisi","tel":"13748794434","sex":"女"}
}
```
#### 2-5-16、高级查询-排序查询
```java
//排序查询
SearchRequest request = new SearchRequest();
request.indices("user");
SearchSourceBuilder builder = new SearchSourceBuilder().query(QueryBuilders.matchAllQuery());
builder.from(0);//从第几个开始查
builder.size(2);//页大小
builder.sort("age", SortOrder.DESC);//根据年龄降序
request.source(builder);
SearchResponse response = esClient.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
System.out.println(hits.getTotalHits());
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
//{"name":"wangwu1","tel":"13748794436","sex":"女","age":"40"}
//{"name":"wangwu3","tel":"13748794438","sex":"男","age":34}
}
```
#### 2-5-17、高级查询-过滤字段
```java
SearchRequest request = new SearchRequest();
request.indices("user");
SearchSourceBuilder builder = new SearchSourceBuilder().query(QueryBuilders.matchAllQuery());
String [] excludes = {"tel"};//排除 排除tel:显示sex,name,age字段
String [] includes = {};//包含name,age->显示name,age字段
builder.fetchSource(includes,excludes);
// builder.from(0);//从第几个开始查
// builder.size(2);//页大小
builder.sort("age", SortOrder.DESC);
request.source(builder);
SearchResponse response = esClient.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
System.out.println(hits.getTotalHits());
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
//{"sex":"女","name":"wangwu1","age":"40"}
//{"sex":"男","name":"wangwu3","age":34}
//{"sex":"男","name":"wangwu2","age":"25"}
//{"sex":"女","name":"lisi","age":"19"}
//{"sex":"男","name":"zhangsan","age":"18"}
//{"sex":"男","name":"wangwu","age":"14"}
}
```
#### 2-5-18、高级查询-组合查询
```java
//组合查询
SearchRequest request = new SearchRequest();
request.indices("user");
SearchSourceBuilder builder = new SearchSourceBuilder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.must(QueryBuilders.matchQuery("age",25));
boolQueryBuilder.must(QueryBuilders.matchQuery("sex","男"));
builder.query(boolQueryBuilder);
request.source(builder);
SearchResponse response = esClient.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
System.out.println(hits.getTotalHits());
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
//{"name":"wangwu2","tel":"13748794437","sex":"男","age":"25"}
}
```
#### 2-5-19、高级查询-范围查询
```java
//范围查询
SearchRequest request = new SearchRequest();
request.indices("user");
SearchSourceBuilder builder = new SearchSourceBuilder();
RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("age");
rangeQueryBuilder.gte(20);//>=20
rangeQueryBuilder.lte(40);//<=40
builder.query(rangeQueryBuilder);
request.source(builder);
SearchResponse response = esClient.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
System.out.println(hits.getTotalHits());
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
//{"name":"wangwu1","tel":"13748794436","sex":"女","age":"40"}
//{"name":"wangwu2","tel":"13748794437","sex":"男","age":"25"}
//{"name":"wangwu3","tel":"13748794438","sex":"男","age":34}
}
```

#### 2-5-20、高级查询-模糊查询
```java
//模糊查询
SearchRequest request = new SearchRequest();
request.indices("user");
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.query(QueryBuilders.fuzzyQuery("name","wangwu").fuzziness(Fuzziness.ONE));//能接受一个字符的偏差
request.source(builder);
SearchResponse response = esClient.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
System.out.println(hits.getTotalHits());
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
//{"name":"wangwu","tel":"13748794435","sex":"男","age":"14"}
//{"name":"wangwu1","tel":"13748794436","sex":"女","age":"40"}
//{"name":"wangwu2","tel":"13748794437","sex":"男","age":"25"}
//{"name":"wangwu3","tel":"13748794438","sex":"男","age":34}
}
```
#### 2-5-21、高级查询-高亮查询
```java
SearchRequest request = new SearchRequest();
request.indices("user");
SearchSourceBuilder builder = new SearchSourceBuilder();
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", "zhangsan");
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.preTags("");
highlightBuilder.postTags("");
highlightBuilder.field("name");
builder.highlighter(highlightBuilder);
builder.query(termQueryBuilder);
request.source(builder);
SearchResponse response = esClient.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
System.out.println(hits.getTotalHits());
for (SearchHit hit : hits) {
System.out.println(hit.getHighlightFields());
//{name=[name], fragments[[zhangsan]]}
System.out.println(hit.getSourceAsString());
//{"name":"zhangsan","tel":"13748794433","sex":"男","age":"18"}
}
```
#### 2-5-22、高级查询-聚合查询(最大值查询&分组查询)
```java
SearchRequest request = new SearchRequest();
request.indices("user");
SearchSourceBuilder builder = new SearchSourceBuilder();
AggregationBuilder aggregationBuilder = AggregationBuilders.terms("ageGroup").field("age");
builder.aggregation(aggregationBuilder);
request.source(builder);
SearchResponse response = esClient.search(request, RequestOptions.DEFAULT);
System.out.println(response);
//年龄最大值查询:...."aggregations":{"max#maxAge":{"value":40.0}}
//年龄分组查询:...."buckets":[{"key":14,"doc_count":1},{"key":18,"doc_count":1},{"key":19,"doc_count":1},{"key":25,"doc_count":1},{"key":34,"doc_count":1},{"key":40,"doc_count":1}]}}
esClient.close();
```
# 三、Elasticsearch环境
## 1、相关概念
### 1-1、单机 & 集群
单台Elasticsearch服务器提供服务,往往都有最大的负载能力,超过这个阈值,服务器性能就会大大降低甚至不可用,所以生产环境中,一般都是运行在指定服务器集群中。
除了负载能力,单点服务器也存在其他问题:
- 单台机器存储容量有限
- 单服务器容易出现单点故障,无法实现高可用
- 单服务的并发处理能力有限
配置服务器集群时,集群中节点数量没有限制,大于等于2个节点就可以看做是集群了。一般出于高性能及高可用方面来考虑集群中节点数量都是3个以上。
### 1-2、集群 Cluster
一个集群就是由一个或多个服务器节点组织在一起,共同持有整个的数据,并一起提供索引和搜索功能。一个Elasticsearch集群有一个唯一的名字标识,这个名字默认就是”elasticsearch”。这个名字是重要的,因为一个节点只能通过指定某个集群的名字,来加入这个集群。
### 1-3、节点 Node
集群中包含很多服务器,一个节点就是其中的一个服务器。作为集群的一部分,它存储数据,参与集群的索引和搜索功能。
一个节点也是由一个名字来标识的,默认情况下,这个名字是一个随机的漫威漫画角色的名字,这个名字会在启动的时候赋予节点。这个名字对于管理工作来说挺重要的,因为在这个管理过程中,你会去确定网络中的哪些服务器对应于Elasticsearch集群中的哪些节点。
一个节点可以通过配置集群名称的方式来加入一个指定的集群。默认情况下,每个节点都会被安排加入到一个叫做“elasticsearch”的集群中,这意味着,如果你在你的网络中启动了若干个节点,并假定它们能够相互发现彼此,它们将会自动地形成并加入到一个叫做“elasticsearch”的集群中。
在一个集群里,只要你想,可以拥有任意多个节点。而且,如果当前你的网络中没有运行任何Elasticsearch节点,这时启动一个节点,会默认创建并加入一个叫做“elasticsearch”的集群。
## 2、Windows集群
### 2-1、部署集群
> 准备一个全新的elasticsearch-7.16.2(用过的删除logs和data文件夹即可)
1. #### 创建elasticsearch-cluster文件夹,在内部复制三个elasticsearch服务

2. 修改集群文件目录中每个节点的 config/elasticsearch.yml配置文件
`node-1001`
```yaml
# ---------------------------------- Cluster -----------------------------------
# 集群名称,节点之间要保持一致
cluster.name: my-elasticsearch
#
# ------------------------------------ Node ------------------------------------
#节点名称,集群内要唯一
node.name: node-1001
node.master: true
node.data: true
# ---------------------------------- Network -----------------------------------
#ip地址
network.host: localhost
#http端口
http.port: 1001
#tcp 监听端口
transport.tcp.port: 9301
# --------------------------------- Discovery ----------------------------------
#集群内的可以被选为主节点的节点列表
#cluster.initial_master_nodes: ["node-1", "node-2"]
# ---------------------------------- Various -----------------------------------
#action.destructive_requires_name: true
#跨域配置
http.cors.enabled: true
http.cors.allow-origin: "*"
```
`node-1002`
```yaml
# ---------------------------------- Cluster -----------------------------------
cluster.name: my-elasticsearch
# ------------------------------------ Node ------------------------------------
node.name: node-1002
node.master: true
node.data: true
# ---------------------------------- Network -----------------------------------
network.host: localhost
#http 端口
http.port: 1002
#tcp 监听端口
transport.tcp.port: 9302
discovery.seed_hosts: ["localhost:9301"]
discovery.zen.fd.ping_timeout: 1m
discovery.zen.fd.ping_retries: 5
# --------------------------------- Discovery ----------------------------------
#集群内的可以被选为主节点的节点列表
#cluster.initial_master_nodes: ["node 1", "node 2","node 3"]
# ---------------------------------- Various -----------------------------------
#跨域配置
#action.destructive_requires_name: true
http.cors.enabled: true
http.cors.allow-origin: "*"
```
`node-1003`
```yaml
# ---------------------------------- Cluster -----------------------------------
cluster.name: my-elasticsearch
#
# ------------------------------------ Node ------------------------------------
node.name: node-1003
node.master: true
node.data: true
# ---------------------------------- Network -----------------------------------
network.host: localhost
#http 端口
http.port: 1003
#tcp 监听端口
transport.tcp.port: 9303
#候选主节点的地址,在开启服务后可以被选为主节点
discovery.seed_hosts: ["localhost:9301","localhost:9302"]
discovery.zen.fd.ping_timeout: 1m
discovery.zen.fd.ping_retries: 5
# --------------------------------- Discovery ----------------------------------
#集群内的可以被选为主节点的节点列表
#cluster.initial_master_nodes: ["node 1", "node 2","node 3"]
# ---------------------------------- Various -----------------------------------
#跨域配置
#action.destructive_requires_name: true
http.cors.enabled: true
http.cors.allow-origin: "*"
```
### 2-2、启动集群
1. 启动前先删除每个节点中的data目录中所有内容(如果存在)

2. 分别双击执行 `bin/elasticsearch.bat`, 启动节点服务器,启动后,会自动加入指定名称的集群
### 2-3、测试集群

- node-1001节点:
在Postman 中,向 ES 服务器发 `GET` 请求 :http://localhost:1001/_cluster/health

- node-1002节点:
在Postman 中,向 ES 服务器发 `GET` 请求 :http://localhost:1002/_cluster/health

- node-1003节点:
在Postman 中,向 ES 服务器发 `GET` 请求 :http://localhost:1003/_cluster/health

- 向集群中的node-1001节点增加索引

- 向集群中的node-1002节点查询索引

## 3、Linux单机
### 3-1、软件下载
软件下载地址:https://www.elastic.co/cn/downloads/past-releases/elasticsearch-7-16-2

### 3-2、软件安装
#### 3-2-1、解压软件
```shell
# 解压缩
tar -zxvf elasticsearch-7.16.2-linux-x86_64.tar.gz -C /opt/module
# 改名
mv elasticsearch-7.16.2 es
```
#### 3-2-2、创建用户
因为安全问题,Elasticsearch 不允许 root 用户直接运行,所以要创建新用户,在 root 用户中创建新用户
```shell
useradd es # 新增es用户
passwd es # 为es用户设置密码
userdel -r es # 如果错了,可以删除再加
chown -R es:es /opt/module/es # 文件夹所有者
```
> 云服务器要必须要给es用户设置复杂的密码,不然会被黑客入侵服务器
#### 3-2-3、修改配置文件
- 修改`/opt/module/es/config/elasticsearch.yml` 文件
```yaml
# 加入如下配置
cluster.name: elasticsearch
node.name: node-1
network.host: 0.0.0.0
http.port: 9200
cluster.initial_master_nodes: ["node-1"]
```
- 修改`/etc/security/limits.conf`
```shell
# 在文件末尾中增加下面内容
# 每个进程可以打开的文件数的限制
es soft nofile 65536
es hard nofile 65536
```
- 修改`/etc/security/limits.d/20-nproc.conf`
```shell
# 在文件末尾中增加下面内容
# 每个进程可以打开的文件数的限制
es soft nofile 65536
es hard nofile 65536
# 操作系统级别对每个用户创建的进程数的限制
* hard nproc 4096
# 注: :* 代表 Linux 所有用户名称
```
- 修改`/etc/sysctl.conf`
```shell
# 在文件中增加下面内容
# 一个进程可以拥有的 VMA( 虚拟内存区域 的数量 默认值为 65536
vm.max_map_count=655360
```
- 命令:重新加载
```shell
sysctl p
```
### 3-3、启动软件
使用ES用户启动
```shell
# 切换用户
su es
#进去根目录
cd /opt/module/es/
#启动
bin/elasticsearch
#后台启动
bin/elasticsearch -d
```
启动时,会动态生成文件,如果文件所属用户不匹配,会发生错误,需要重新进行修改用户和用户组

关闭防火墙
```shell
#暂时关闭防火墙
systemctl stop firewalld
#永久关闭防火墙
systemctl enable firewalld.service # 打开防火墙永久性生效,重启后不会复原
systemctl disable firewalld.service # 关闭防火墙,永久性生效,重启后不会复原
```
### 3-4、测试软件
浏览器中输入地址:http://linux1:9200/
> linux1是虚拟机IP

## 4、Linux集群
### 4-1、软件下载
软件下载地址:https://www.elastic.co/cn/downloads/past-releases/elasticsearch-7-16-2

### 4-2、软件安装
#### 4-2-1、解压软件
```shell
# 解压缩
tar -zxvf elasticsearch-7.16.2-linux-x86_64.tar.gz -C /opt/module
# 改名
mv elasticsearch-7.16.2 es-cluster
# 将软件分发到其他节点:linux2, linux3
[root@xxx module] # xsync es-cluster/
```
#### 4-2-2、创建用户
因为安全问题,Elasticsearch 不允许 root 用户直接运行,所以要创建新用户,在 root 用户中创建新用户
```shell
useradd es # 新增es用户
passwd es # 为es用户设置密码
userdel -r es # 如果错了,可以删除再加
chown -R es:es /opt/module/es-cluster # 文件夹所有者
```
#### 4-2-3、修改配置文件
- 修改`/opt/module/es/config/elasticsearch.yml` 文件
```yaml
# 加入如下配置
#集群名称
cluster.name: cluster-es
# 节点名称, 每个节点的名称不能重复
node.name: node-1
# ip 地址, 每个节点的地址不能重复
network.host: linux1
# 是不是有资格主节点
node.master: true
node.data: true
http.port: 9200
# head 插件需要这打开这两个配置
http.cors.allow-origin: "*"
http.cors.enabled: true
http.max_content_length: 200mb
#es7.x 之后新增的配置,初始化一个新的集群时需要此配置来选举 master
cluster.initial_master_nodes: ["node-1"]
#es7.x 之后新增的配置,节点发现
discovery.seed_hosts: ["linux1:9300","linux2:9300","linux3:9300"]
gateway.recover_after_nodes: 2
network.tcp.keep_alive: true
network.tcp.no_delay: true
transport.tcp.compress: true
# 集群内同时启动的数据任务个数,默认是 2 个
cluster.routing.allocation.cluster_concurrent_rebalance: 16
#添加或删除节点及负载均衡时并发恢复的线程个数,默认 4 个
cluster.routing.allocation.node_concurrent_recoveries: 16
# 初始化数据恢复时,并发恢复线程的个数,默认 4 个
cluster.routing.allocation.node_initial_primaries_recoveries: 16
```
- 修改`/etc/security/limits.conf`,分发文件
```shell
# 在文件末尾中增加下面内容
es soft nofile 65536
es hard nofile 65536
```
- 修改`/etc/security/limits.d/20-nproc.conf` ,分发文件
```shell
# 在文件末尾中增加下面内容
es soft nofile 65536
es hard nofile 65536
* hard nproc 4096
# 注: :* 带表 Linux 所有用户名称
```
- 修改`/etc/sysctl.conf`
```shell
# 在文件中增加下面内容
vm.max_map_count=655360
```
- 重新加载
```shell
sysctl p
```
> 以上操作在其他节点均需要同时修改
>
> yml文件只需要修改 network.host 和 node.name 其余相同
### 4-3、启动软件
分别在不同节点上启动ES软件
```shell
# 切换用户
su es
cd /opt/module/es-cluster
# 启动
bin/elasticsearch
# 后台启动
bin/elasticsearch -d
```
### 4-4、测试集群
