18--Elasticsearch

一 Elasticsearch介绍

1 全文检索

18--Elasticsearch_第1张图片

Elasticsearch是一个全文检索服务器

全文检索是一种非结构化数据的搜索方式

  • 结构化数据:指具有固定格式固定长度的数据,如数据库中的字段。

18--Elasticsearch_第2张图片

  • 非结构化数据:指格式和长度不固定的数据,如电商网站的商品详情。

18--Elasticsearch_第3张图片

18--Elasticsearch_第4张图片
18--Elasticsearch_第5张图片

结构化数据一般存入数据库,使用sql语句即可快速查询。但由于非结构化数据的数据量大且格式不固定,我们需要采用全文检索的方式进行搜索。全文检索通过建立倒排索引加快搜索效率。

2 倒排索引

18--Elasticsearch_第6张图片

索引

将数据中的一部分信息提取出来,重新组织成一定的数据结构,我们可以根据该结构进行快速搜索,这样的结构称之为索引。

索引即目录,例如字典会将字的拼音提取出来做成目录,通过目录即可快速找到字的位置。

索引分为正排索引倒排索引

正排索引(正向索引)

将文档id建立为索引,通过id快速可以快速查找数据。如数据库中的主键就会创建正排索引。

18--Elasticsearch_第7张图片

倒排索引(反向索引)

非结构化数据中我们往往会根据关键词查询数据。此时我们将数据中的关键词建立为索引,指向文档数据,这样的索引称为倒排索引。

18--Elasticsearch_第8张图片

创建倒排索引流程:

18--Elasticsearch_第9张图片

3 Elasticsearch数据结构

18--Elasticsearch_第10张图片

文档(Document):文档是可被查询的最小数据单元,一个 Document 就是一条数据。类似于关系型数据库中的记录的概念。

类型(Type):具有一组共同字段的文档定义成一个类型,类似于关系型数据库中的数据表的概念。

索引(Index):索引是多种类型文档的集合,类似于关系型数据库中的库的概念。

域(Fied):文档由多个域组成,类似于关系型数据库中的字段的概念。

Elasticsearch跟关系型数据库中概念的对比:

JAVA 项目 实体类 对象 属性
ES Index Type Document Filed
Mysql Database Table Row Column

注:ES7.X之后删除了type的概念,一个索引不会代表一个库, 而是代表一张表。本文中使用ES7.17,所以目前的ES中概念对比为:

JAVA 项目 实体类 对象 属性
ES Index Document Filed
Mysql Database Table Row Column

二 Elasticsearch安装

1 安装ES服务

准备工作

  • 准备一台搭载有CentOS7系统的虚拟机,使用XShell连接虚拟机
  • 关闭防火墙,方便访问ES
#关闭防火墙:
systemctl stop firewalld.service

#禁止防火墙自启动:
systemctl disable firewalld.service
  • 配置最大可创建文件数大小
#打开系统文件:
vim /etc/sysctl.conf

#添加以下配置:
vm.max_map_count=655360

#配置生效:
sysctl -p
  • 由于ES不能以root用户运行,我们需要创建一个非root用户,此 处创建一个名为es的用户:
#创建用户:
useradd es

安装服务

  • 使用xftp将linux版的ES上传至虚拟机
  • 解压ES
#解压:
tar -zxvf elasticsearch-7.17.0-linux-x86_64.tar.gz

#重命名:
mv elasticsearch-7.17.0 elasticsearch1

#移动文件夹:
mv elasticsearch1 /usr/local/

#es用户取得该文件夹权限:
chown -R es:es /usr/local/elasticsearch1

改变文件拥有者chown

语法:

chown [-R] 属主名:属组名 文件名
  • 启动ES服务:
#切换为es用户:
su es

#进入ES安装文件夹:
cd /usr/local/elasticsearch1/bin/

#启动ES服务:
./elasticsearch

#查询ES服务是否启动成功
curl 127.0.0.1:9200

2 安装kibana

18--Elasticsearch_第11张图片

Kibana是一款开源的数据分析和可视化平台,设计用于和 Elasticsearch协作。我们可以使用Kibana对Elasticsearch索引中的数据进行搜索、查看、交互操作。

  • 使用xftp将将Kibana压缩文件上传到Linux虚拟机
  • 解压
tar -zxvf kibana-7.17.0-linux-x86_64.tar.gz  -C /usr/local/
  • 修改配置
# 进入Kibana解压路径
cd /usr/local/kibana-7.17.0-linux-x86_64/config

# 修改配置文件
vim kibana.yml

# 加入以下内容
# kibana主机IP
server.host: "虚拟机IP"
# Elasticsearch路径
elasticsearch.hosts: ["http://127.0.0.1:9200"]
  • 启动:

kibana不能以root用户运行,我们给es用户设置kibana目录的权限,并使用es用户运行kibana

# 给es用户设置kibana目录权限
chown -R es:es /usr/local/kibana-7.17.0-linux-x86_64/

# 切换为es用户
su es

# 启动kibana
cd /usr/local/kibana-7.17.0-linux-x86_64/bin/
./kibana
  • 访问kibana:http://虚拟机IP:5601
  • 点击 Management =>Stack Management => Index Management 可以查看es索引信息。

3 Docker安装

安装Elasticsearch

拉取镜像

docker pull elasticsearch:7.17.0

启动容器

# docker容器间建立通信
docker network create elastic
# 创建es容器
docker run --restart=always -p 9200:9200 -p 9300:9300 -e "discovery.type=singlenode" -e ES_JAVA_OPTS="-Xms512m -Xmx512m" --name='elasticsearch' --net elastic --cpuset-cpus="1" -m 1G -d elasticsearch:7.17.0

安装Kibana

拉取镜像

docker pull kibana:7.17.0

启动容器

docker run --name kibana --net elastic --link elasticsearch:elasticsearch -p 5601:5601 -d kibana:7.17.0

访问kibana:http://虚拟机IP:5601

三 Elasticsearch常用操作

1 索引操作

18--Elasticsearch_第12张图片

Elasticsearch是使用RESTful风格的http请求访问操作的,请求参数和返回值都是Json格式的,我们可以使用kibana发送http请求操作ES。

创建没有结构的索引

路径:ip地址:端口号/索引名

注:在kibana中所有的请求都会省略 ip地址:端口号 ,之后的路径我 们省略写 ip地址:端口号

请求方式:PUT

举例:

PUT /student

为索引添加结构

POST /索引名/_mapping
{
 	"properties":{
 		"域名1":{
 			"type":域的类型,
 			"store":是否存储,
 			"index":是否创建索引,
            "analyzer":分词器
   			},
        
 		"域名2":{
 			...
 			}
 	}
}

举例:

POST /student/_mapping
{
  "properties": {
    "id":{
      "type":"integer"
    },
    "name": {
      "type": "text"
    },
    "age": {
      "type": "integer"
    }
  }
}

创建有结构的索引

18--Elasticsearch_第13张图片

PUT /索引名
{
    "mappings":{
        "properties":{
            "域名1":{
                "type":域的类型,
                "store":是否单独存储,
                "index":是否创建索引,
       			"analyzer":分词器
           },
            "域名2":{
                ...
           }
       }
   }
}

举例:

PUT /student1
{
  "mappings": {
    "properties": {
      "name": {
        "type": "text"
      },
      "age": {
        "type": "integer"
      }
    }
  }
}

删除索引

DELETE /索引名

举例:

DELETE /student1

2 文档操作

18--Elasticsearch_第14张图片

新增/修改文档

POST /索引/_doc/[id值]
{
 "field名":field值
}

注:id值不写时自动生成文档id,id和已有id重复时修改文档

举例:

POST /student/_doc/1
{
  "name": "lxx",
  "age": 18
}

根据id查询文档

GET /索引/_doc/id值

举例:

GET /student/_doc/1

删除文档

DELETE /索引/_doc/id值

举例:

DELETE /student/_doc/1

根据id批量查询文档

GET /索引/_mget
{
    "docs":[
       {"_id":id值},
       {"_id":id值}
   ]
}

举例:

GET /student/_mget
{
  "docs": [
    {
      "_id": 1
    },
    {
      "_id": 2
    }
  ]
}

查询所有文档

GET /索引/_search
{
   "query": {
       "match_all": {}
   }
}

举例:

GET /student/_search
{
  "query": {
    "match_all": {}
  }
}

修改文档部分字段

POST /索引/_doc/id值/_update
{
    "doc":{
        域名:}
}

注:

Elasticsearch执行删除操作时,ES先标记文档为deleted状态, 而不是直接物理删除。当ES存储空间不足或工作空闲时,才会执行物理删除操作。

Elasticsearch执行修改操作时,ES不会真的修改Document中 的数据,而是标记ES中原有的文档为deleted状态,再创建一个 新的文档来存储数据。

举例:

POST /student/_doc/1/_update
{
  "doc": {
    "name": "newLxx"
  }
}

3 域的属性

index

该域是否创建索引。只有值设置为true,才能根据该域的关键词查询文档。

// 根据关键词查询文档
GET /索引名/_search
{
 "query":{
        "term":{
 			搜索字段: 关键字
 		}
   }
}

案例:

PUT /student1
{
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "index": true
      }
    }
  }
}

PUT /student2
{
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "index": false
      }
    }
  }
}

POST /student1/_doc/1
{
  "name":"I love you"
}

POST /student2/_doc/1
{
  "name":"I love you"
}

GET /student1/_search
{
  "query":{
    "term":{
      "name":"love"
    }
  }
}
// 可以查询到结果

GET /student2/_search
{
  "query":{
    "term":{
      "name":"love"
    }
  }
}
// 查询不到结果

type

域的类型

核心类型 具体类型
字符串类型 text
整数类型 long, integer, short, byte
浮点类型 double, float
日期类型 date
布尔类型 boolean
数组类型 array
对象类型 object
不分词的字符串 keyword

store

是否单独存储。如果设置为true,则该域能够单独查询。

// 单独查询某个域:
GET /索引名/_search
{
  "stored_fields": ["域名"]
}

举例:

PUT /student3
{
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "store": true
      }
    }
  }
}

POST /student3/_doc/1
{
  "name":"I love you1"
}

POST /student3/_doc/2
{
  "name":"I love you2"
}

GET /student3/_search
{
  "stored_fields": [
    "name"
  ]
}

四 分词器

1 默认分词器

18--Elasticsearch_第15张图片

ES文档的数据拆分成一个个有完整含义的关键词,并将关键词与文档对应,这样就可以通过关键词查询文档。要想正确的分词,需要选择合适的分词器。

analyzer:插入文档时,将text类型的字段做分词然后插入倒排索引。

search_analyzer:查询时,先对要查询的text类型的输入做分词,再去倒排索引中搜索。

如果想要让’索引’和’查询’时使用不同的分词器,ElasticSearch也是能支持的,只需要在字段上加上search_analyzer参数。插入时,只会去看字段有没有定义analyzer,有定义的话就用定义的,没定义就用es预设的。查询时,会先去看字段有没有定义search_analyzer,如果没有定义,就去看有没有analyzer,再没有定义,才会去使用es预设的

standard analyzer:Elasticsearch默认分词器,根据空格和标点符号对英文进行分词,会进行单词的大小写转换。

默认分词器是英文分词器,对中文的分词是一字一词。

  • 查看分词效果
GET /_analyze
{
 "text":"测试语句",
 "analyzer":"分词器"
}
  • 举例
GET /_analyze
{
  "text": "I love you",
  "analyzer": "standard"
}

2 IK分词器

18--Elasticsearch_第16张图片

IKAnalyzer是一个开源的,基于java语言开发的轻量级的中文分词工具包。提供了两种分词算法:

  • ik_smart:最少切分
  • ik_max_word:最细粒度划分

安装IK分词器

  • 关闭es服务
  • 使用xftp将ik分词器上传至虚拟机

注:ik分词器的版本要和es版本保持一致。

  • 解压ik分词器到elasticsearch的plugins目录下
unzip elasticsearch-analysis-ik-7.17.0.zip -d /usr/local/elasticsearch1/plugins/analysis-ik
  • 启动ES服务
su es

#进入ES安装文件夹:
cd /usr/local/elasticsearch1/bin/

#启动ES服务:
./elasticsearch -d

测试分词器效果

GET /_analyze
{
 "text":"测试语句",
 "analyzer":"ik_smart/ik_max_word"
}

举例:

GET /_analyze
{
  "text": "湖人总冠军",
  "analyzer": "ik_smart"
}

IK分词器词典

IK分词器根据词典进行分词,词典文件在IK分词器的config目录中。(/usr/local/elasticsearch1/plugins/analysis-ik/config)

  • main.dic:IK中内置的词典。记录了IK统计的所有中文单词。
  • IKAnalyzer.cfg.xml:用于配置自定义词库。
<properties>
        <comment>IK Analyzer 扩展配置comment>
        
        <entry key="ext_dict">ext_dict.dicentry>
         
        <entry key="ext_stopwords">ext_stopwords.dicentry>
        
        
        
        
properties>

3 拼音分词器

18--Elasticsearch_第17张图片

拼音分词器可以将中文分成对应的全拼,全拼首字母等。

安装拼音分词器

  • 关闭es服务
  • 使用xftp将拼音分词器上传至虚拟机

注:ik分词器的版本要和es版本保持一致。

  • 解压拼音分词器到elasticsearch的plugins目录下
unzip elasticsearch-analysis-pinyin-7.17.0.zip -d /usr/local/elasticsearch1/plugins/analysis-pinyin
  • 启动ES服务
su es

#进入ES安装文件夹:
cd /usr/local/elasticsearch1/bin/

#启动ES服务:
./elasticsearch

测试分词器效果

GET /_analyze
{
 "text":"测试语句",
 "analyzer":"pinyin"
}

举例:

GET /_analyze
{
  "text": "湖人总冠军",
  "analyzer": "pinyin"
}

4 自定义分词器

18--Elasticsearch_第18张图片

真实开发中我们往往需要对一段内容既进行文字分词,又进行拼音分词,此时我们需要自定义ik+pinyin分词器。

创建自定义分词器

  • 在创建索引时自定义分词器
PUT /索引名
{
    "settings" : {
        "analysis" : {
            "analyzer" : {
                "ik_pinyin" : { //自定义分词器名
                	"tokenizer":"ik_max_word", // 基本分词器
                	"filter":"pinyin_filter" // 配置分词器过滤
               }
           },
            "filter" : { // 分词器过滤时配置另一个分词器,相当于同时使用两个分词器
               "pinyin_filter" : {
                   "type" : "pinyin", // 另一个分词器
                   // 拼音分词器的配置
                   "keep_separate_first_letter" : false, // 是否分词每个字的首字母
                   "keep_full_pinyin" :true, // 是否分词全拼
                   "keep_original" : true,// 是否保留原始输入
                   "remove_duplicated_term": true // 是否删除重复项
               }
           }
       }
   },
    "mappings":{
        "properties":{
            "域名1":{
                "type":域的类型,
                "store":是否单独存储,
                "index":是否创建索引,
       			"analyzer":分词器
           },
            "域名2":{
                                ...
           }
       }
   }
}
  • 举例:
PUT /student4
{
  "settings": {
    "analysis": {
      "analyzer": {
        "ik_pinyin": {
          "tokenizer": "ik_max_word",
          "filter": "pinyin_filter"
        }
      },
      "filter": {
        "pinyin_filter": {
          "type": "pinyin",
          "keep_separate_first_letter": false,
          "keep_full_pinyin": true,
          "keep_original": true,
          "remove_duplicated_term": true
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "store": true,
        "index": true,
        "analyzer": "ik_pinyin"
      },
      "age": {
        "type": "integer"
      }
    }
  }
}

测试自定义分词器

GET /索引/_analyze
{
  "text": "测试语句",
  "analyzer": "ik_pinyin"
}

举例:

GET /student4/_analyze
{
  "text": "湖人总冠军",
  "analyzer": "ik_pinyin"
}

五 Elasticsearch搜索文档

1 准备工作

18--Elasticsearch_第19张图片

Elasticsearch提供了全面的文档搜索方式,在学习前我们添加一些文档数据

PUT /students
{
  "mappings": {
    "properties": {
      "id": {
        "type": "integer",
        "index": true
      },
      "name": {
        "type": "text",
        "store": true,
        "index": true,
        "analyzer": "ik_smart"
      },
      "info": {
        "type": "text",
        "store": true,
        "index": true,
        "analyzer": "ik_smart"
      }
    }
  }
}
POST /students/_doc/
{
  "id": 1,
  "name": "百战程序员",
  "info": "I love baizhan"
}
POST /students/_doc/
{
  "id": 2,
  "name": "美羊羊",
  "info": "美羊羊是羊村最漂亮的人"
}
POST /students/_doc/
{
  "id": 3,
  "name": "懒羊羊",
  "info": "懒羊羊的成绩不是很好"
}
POST /students/_doc/
{
  "id": 4,
  "name": "小灰灰",
  "info": "小灰灰的年纪比较小"
}
POST /students/_doc/
{
  "id": 5,
  "name": "沸羊羊",
  "info": "沸羊羊喜欢美羊羊"
}
POST /students/_doc/
{
  "id": 6,
  "name": "灰太狼",
  "info": "灰太狼是小灰灰的父亲,每次都会说我一定会回来的"
}
  • 文档搜索
GET /索引/_search
{
 "query":{
        搜索方式:搜索参数
   }
}

2 搜索方式

18--Elasticsearch_第20张图片

  • match_all:查询所有文档
{
 "query":{
        "match_all":{}
   }
}

举例:

GET /students/_search
{
  "query": {
    "match_all": {}
  }
}
  • match:全文检索。将查询条件分词后再进行搜索。
{
 "query":{
        "match":{
            "搜索字段":"搜索条件"
       }
   }
}

注:在搜索时关键词有可能会输入错误,ES搜索提供了自动纠错功能,即ES的模糊查询。使用match方式可以实现模糊查询。模糊查询对中文的支持效果一般,我们使用英文数据测试模糊查询。

{
    "query":{
        "match":{
            "域名":{
                "query":"搜索条件",
                "fuzziness":"最多错误字符数,不能超过2"
            }
        }
    }
}

举例:

GET /students/_search
{
  "query": {
    "match": {
      "info": "我喜欢成绩好的"
    }
  }
}
GET /students/_search
{
  "query": {
    "match": {
      "info": {
        "query": "lovr",
        "fuzziness": 1
      }
    }
  }
}
  • range:范围搜索。对数字类型的字段进行范围搜索
{
 "query":{
        "range":{
            搜索字段:{
                "gte":最小值,
                "lte":最大值
           }
       }
   }
}
gt/lt:大于/小于
gte/lte:大于等于/小于等于

举例:

GET /students/_search
{
  "query": {
    "range": {
      "id": {
        "gte": 2,
        "lte": 4
      }
    }
  }
}
  • match_phrase:短语检索。搜索条件不做任何分词解析,在搜索字段对应的倒排索引中精确匹配。
{
 "query":{
        "match_phrase":{
            搜索字段:搜索条件
       }
   }
}

举例:

GET /students/_search
{
  "query": {
    "match_phrase": {
      "info": "喜欢"
    }
  }
}
  • term/terms:单词/词组搜索。搜索条件不做任何分词解析,在搜索字段对应的倒排索引中精确匹配
{
 "query":{
        "term":{  
 			搜索字段: 搜索条件
       }
   }
}
{
 "query":{
        "terms":{  
 			搜索字段: [搜索条件1,搜索条件2]
       }
   }
}

举例:

GET /students/_search
{
  "query": {
    "term": {
      "info": "喜欢"
    }
  }
}

GET /students/_search
{
  "query": {
    "terms": {
      "info": ["喜欢","漂亮"]
    }
  }
}

3 复合搜索

18--Elasticsearch_第21张图片

GET /索引/_search
{
     "query": {
        "bool": {
            // 必须满足的条件
            "must": [
 				搜索方式:搜索参数,
 				搜索方式:搜索参数
           ],
            // 多个条件有任意一个满足即可
            "should": [
 				搜索方式:搜索参数,
   				搜索方式:搜索参数
   			],
 			// 必须不满足的条件
   			"must_not":[
  				搜索方式:搜索参数,
   				搜索方式:搜索参数
   			]
   		}
   	}
}

举例:

GET /students/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "info": "美羊羊喜欢成绩好的同学"
          }
        }
      ],
      "must_not": [
        {
          "range": {
            "id": {
              "gte": 1,
              "lte": 3
            }
          }
        }
      ]
    }
  }
}

4 结果排序

18--Elasticsearch_第22张图片

ES中默认使用相关度分数实现排序,可以通过搜索语法定制化排序。

GET /索引/_search
{
  "query": "搜索条件",
  "sort": [
    {
      "字段1": {
        "order": "asc"
      }
    },
    {
      "字段2": {
        "order": "desc"
      }
    }
  ]
}

由于ES对text类型字段数据会做分词处理,使用哪一个单词做排序都是不合理的,所以ES中默认不允许对text类型的字段做排序。如果需要使用字符串做结果排序,可以使用 keyword类型 的字段作为排序依据,因为keyword字段不做分词处理。

举例:

GET /students/_search
{
  "query": {
    "match": {
      "name": "羊"
    }
  },
  "sort": [
    {
      "id": {
        "order": "desc"
      }
    }
  ]
}

5 分页查询

18--Elasticsearch_第23张图片

GET /索引/_search
{
     "query": 搜索条件,
     "from": 起始下标,
     "size": 查询记录数
}

举例:

GET /students/_search
{
  "query": {
    "match_all": {}
  },
  "from": 0,
  "size": 2
}

GET /students/_search
{
  "query": {
    "match_all": {}
  },
  "from": 2,
  "size": 2
}

6 高亮查询

18--Elasticsearch_第24张图片

在进行关键字搜索时,搜索出的内容中的关键字会显示不同的颜色,称之为高亮。

为什么在网页中关键字会显示不同的颜色,我们通过开发者工具查看网页源码:

18--Elasticsearch_第25张图片

我们可以在关键字左右加入标签字符串,数据传入前端即可完成高亮显示,ES可以对查询出的内容中关键字部分进行标签和样式的设置。

GET /索引/_search
{
 "query":搜索条件,
 "highlight":{
     "fields": {
               "高亮显示的字段名": {
                   // 返回高亮数据的最大长度
                   "fragment_size":100,
                   // 返回结果最多可以包含几段不连续的文字
                   "number_of_fragments":5
               }
            },
            "pre_tags":["前缀"],
            "post_tags":["后缀"]
       }
  }
}

举例:

GET /students/_search
{
  "query": {
    "match": {
      "info": "我喜欢成绩好的"
    }
  },
  "highlight": {
    "fields": {
      "info": {
        "fragment_size": 20,
        "number_of_fragments": 5
      }
    },
    "pre_tags": [
      ""
    ],
    "post_tags": [
      ""
    ]
  }
}

7 SQL查询

在ES7之后,支持SQL语句查询文档:

GET /_sql?format=txt
{
 "query": SQL语句
}

开源版本的ES并不支持通过Java操作SQL进行查询,如果需要操 作 SQL查询,则需要氪金(购买白金版)

六 原生JAVA操作ES

1 搭建项目

18--Elasticsearch_第26张图片

原生JAVA可以对ES的索引和文档进行操作,但操作较复杂,我们了解即可。

  • 创建maven项目
  • maven项目引入以下依赖:
        <dependency>
            <groupId>org.elasticsearchgroupId>
            <artifactId>elasticsearchartifactId>
            <version>7.17.0version>
        dependency>
        <dependency>
            <groupId>org.elasticsearch.clientgroupId>
            <artifactId>elasticsearch-rest-high-level-clientartifactId>
            <version>7.17.0version>
        dependency>

2 索引操作

创建空索引

//索引操作
public class IndexTest {

    // 创建空索引
    @Test
    public void createIndex() throws IOException {
        // 1.创建客户端对象,连接ES
        RestHighLevelClient client = new
                RestHighLevelClient(RestClient.builder(new
                HttpHost("192.168.66.113", 9200, "http")));
        // 2.创建请求对象
        CreateIndexRequest request = new CreateIndexRequest("student");
        // 3.发送请求
        CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
        // 4.操作响应结果
        System.out.println(response.index());
        // 5.关闭客户端
        client.close();
    }

外部无法访问ES的解决方案:

打开Elasticsearch安装路径下config目录下的elasticsearch.yml 文件,加入如下配置:

discovery.seed_hosts: ["host1"]
network.host: 0.0.0.0

重新启动ES即可。

PS:如果修改配置文件后,启动报错:

1、max file descriptors [4096] for elasticsearch process is too low, increase to at least [65536]

每个进程最大同时打开文件数太小

修改/etc/security/limits.conf文件,增加配置,用户退出后重新登录生效

* soft nofile 65536
* hard nofile 65536

2、max number of threads [3818] for user [hadoop] is too low, increase to at least [4096]

问题同上,最大线程个数太低。

修改/etc/security/limits.conf文件,增加配置,用户退出后重新登录生效

* soft nproc 4096 
* hard nproc 4096

给索引添加结构

   //给索引添加结构
    @Test
    public void mappingIndex() throws IOException {
        // 1.创建客户端对象,连接ES
        RestHighLevelClient client = new
                RestHighLevelClient(RestClient.builder(new
                HttpHost("192.168.66.113", 9200, "http")));
        // 2.创建请求对象
        PutMappingRequest request = new PutMappingRequest("student");
        request.source("{\n" +
                "  \"properties\": {\n" +
                "    \"id\":{\n" +
                "      \"type\":\"integer\"\n" +
                "    },\n" +
                "    \"name\": {\n" +
                "      \"type\": \"text\"\n" +
                "    },\n" +
                "    \"age\": {\n" +
                "      \"type\": \"integer\"\n" +
                "    }\n" +
                "  }\n" +
                "}", XContentType.JSON);
        // 3.发送请求
        AcknowledgedResponse response = client.indices().putMapping(request, RequestOptions.DEFAULT);
        // 4.操作响应结果
        System.out.println(response.isAcknowledged());
        // 5.关闭客户端
        client.close();
    }

删除索引

    // 删除索引
    @Test
    public void deleteIndex() throws IOException {
        // 1.创建客户端对象,连接ES
        RestHighLevelClient client = new
                RestHighLevelClient(RestClient.builder(new
                HttpHost("192.168.66.113", 9200, "http")));
        // 2.创建请求对象
        DeleteIndexRequest request = new DeleteIndexRequest("student");
        // 3.发送请求
        AcknowledgedResponse response = client.indices().delete(request, RequestOptions.DEFAULT);
        // 4.操作响应结果

        System.out.println(response.isAcknowledged(
        ));
        // 5.关闭客户端
        client.close();
    }

3 文档操作

新增&修改文档

    //新增&修改文档
    @Test
    public void addDocument() throws IOException {
        // 1.创建客户端对象,连接ES
        RestHighLevelClient client = new
                RestHighLevelClient(RestClient.builder(new
                HttpHost("192.168.66.113", 9200, "http")));
        // 2.创建请求对象
        IndexRequest request = new IndexRequest("student").id("1");

        request.source(XContentFactory.jsonBuilder()
                .startObject()
                .field("id", 1)
                .field("name", "i love lxx")
                .field("age", 20)
                .endObject());
        // 3.发送请求
        IndexResponse response = client.index(request, RequestOptions.DEFAULT);
        // 4.操作响应结果
        System.out.println(response.status());
        // 5.关闭客户端
        client.close();
    }

根据id查询文档

    // 根据id查询文档
    @Test
    public void findByIdDocument() throws IOException {
        // 1.创建客户端对象,连接ES
        RestHighLevelClient client = new
                RestHighLevelClient(RestClient.builder(new
                HttpHost("192.168.66.113", 9200, "http")));
        // 2.创建请求对象
        GetRequest request = new GetRequest("student", "1");
        // 3.发送请求
        GetResponse response = client.get(request, RequestOptions.DEFAULT);
        // 4.操作响应结果

        System.out.println(response.getSourceAsString());
        // 5.关闭客户端
        client.close();
    }

删除文档

    // 删除文档
    @Test
    public void DeleteDocument() throws
            IOException {
        // 1.创建客户端对象,连接ES
        RestHighLevelClient client = new
                RestHighLevelClient(RestClient.builder(new
                HttpHost("192.168.66.113", 9200, "http")));
        // 2.创建请求对象
        DeleteRequest request = new DeleteRequest("student", "1");
        // 3.发送请求
        DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
        // 4.操作响应结果
        System.out.println(response.status());
        // 5.关闭客户端
        client.close();
    }

3 搜索操作

搜索所有文档

    //搜索所有文档
    @Test
    public void queryAllDocument() throws IOException {
        // 1.创建客户端对象,连接ES
        RestHighLevelClient client = new
                RestHighLevelClient(RestClient.builder(new
                HttpHost("192.168.66.113", 9200, "http")));
        // 创建搜索条件
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.matchAllQuery());
        // 创建请求对象
        SearchRequest request = new SearchRequest("student").source(searchSourceBuilder);
        // 发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 输出返回结果
        for (SearchHit hit : response.getHits()) {
            System.out.println(hit.getSourceAsString());
        }
        // 关闭客户端
        client.close();
    }

根据关键词搜索文档

  //根据关键词搜索文档
    @Test
    public void queryTermDocument() throws
            IOException {
        // 创建客户端对象,链接ES
        RestHighLevelClient client = new
                RestHighLevelClient(
                RestClient.builder(new
                        HttpHost("192.168.66.113", 9200, "http")));
        // 创建请求条件
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.termQuery("name", "lxx"));
        // 创建请求对象
        SearchRequest request = new SearchRequest("student").source(searchSourceBuilder);
        // 发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 输出返回结果
        for (SearchHit hit : response.getHits()) {
            System.out.println(hit.getSourceAsString());
        }
        // 关闭客户端
        client.close();
    }

七 SpringDataES

1 入门案例

18--Elasticsearch_第27张图片

项目搭建

Spring Data ElasticSearch是Spring对原生JAVA操作Elasticsearch 封装之后的产物。它通过对原生API的封装,使得JAVA程序员可以简单的对Elasticsearch进行操作。

  • 创建SpringBoot项目,加入Spring Data Elasticsearch起步依赖:
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-elasticsearchartifactId>
        dependency>
  • 编写配置文件:
spring:
  elasticsearch:
    uris: http://192.168.66.113:9200

此时Spring Data ElasticSearch项目已经搭建完成。

创建实体类

一个实体类的所有对象都会存入ES的一个索引中,所以我们在创建实体类时关联ES索引

@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(indexName = "product", createIndex = true)
public class Product {
    @Id
    @Field(type = FieldType.Integer, store = true, index = true)
    private Integer id;

    @Field(type = FieldType.Text, store = true, index = true,
            analyzer = "ik_max_word", searchAnalyzer = "ik_max_word")
    private String productName;

    @Field(type = FieldType.Text, store = true, index = true,
            analyzer = "ik_max_word", searchAnalyzer = "ik_max_word")
    private String productDesc;

}
  • @Document:标记在类上,标记实体类为文档对象,一般有如下属性:

    indexName:对应索引的名称

    createIndex:是否自动创建索引

  • @Id:标记在成员变量上,标记一个字段为主键,该字段的值会同步到ES该文档的id值。

  • @Field:标记在成员变量上,标记为文档中的域,一般有如下属性:

    type:域的类型

    index:是否创建索引,默认是 true

    store:是否单独存储,默认是 false

    analyzer:分词器

    searchAnalyzer:搜索时的分词器

创建Repository接口

18--Elasticsearch_第28张图片

创建Repository接口继承ElasticsearchRepository,该接口提供了文档的增删改查方法

public interface ProductRepository extends ElasticsearchRepository<Product, Integer> {
}

测试方法

编写测试类,注入Repository接口并测试Repository接口的增删改 查方法

@SpringBootTest
public class ProductRepositoryTest {

    @Autowired
    private ProductRepository repository;

    @Test
    public void addDocument() {
        Product product = new Product(1, "iphone30", "iphone30是苹果最新手机");
        repository.save(product);
    }

    @Test
    public void updateDocument() {
        Product product = new Product(1, "iphone31", "iphone31是苹果最新手机");
        repository.save(product);
    }

    @Test
    public void findAllDocument() {
        Iterable<Product> all = repository.findAll();
        for (Product product : all) {
            System.out.println(product);
        }
    }

    @Test
    public void findDocumentById() {
        Optional<Product> product = repository.findById(1);
        System.out.println(product.get());
    }
}

2 查询方式

18--Elasticsearch_第29张图片

接下来我们讲解SpringDataES支持的查询方式,首先准备一些文档数据:

		// 添加一些数据
		repository.save(new Product(2, "三体1", "三体1 是优秀的科幻小说"));
        repository.save(new Product(3, "三体2", "三体2 是优秀的科幻小说"));
        repository.save(new Product(4, "三体3", "三体3 是优秀的科幻小说"));
        repository.save(new Product(5, "elasticsearch", "elasticsearch是基于lucene开发的优秀的搜索引擎"));

使用Repository继承的方法查询文档

该方式我们之前已经讲解过了

使用DSL语句查询文档

ES通过json类型的请求体查询文档,方法如下:

GET /索引/_search
{
    "query":{
        搜索方式:搜索参数
   }
}

query后的json对象称为DSL语句,我们可以在接口方法上使用 @Query注解自定义DSL语句查询

   @Query("{\n" +
            "    \"match\": {\n" +
            "      \"productDesc\": \"?0\"\n" +
            "    }\n" +
            "  }")
    List<Product> findByProductDescMatch(String keyword);

    @Query("{\n" +
            "    \"match\": {\n" +
            "      \"productDesc\": {\n" +
            "        \"query\": \"?0\",\n" +
            "        \"fuzziness\": 1\n" +
            "      }\n" +
            "    }\n" +
            "  }")
    List<Product> findByProductDescFuzzy(String keyword);

按照规则命名方法进行查询

  • 只需在Repository接口中按照SpringDataES的规则命名方法,该方法就能完成相应的查询。
  • 规则:查询方法以findBy开头,涉及查询条件时,条件的属性用条件关键字连接。
关键字 命名规则 解释 示例
and findByField1AndField2 根据Field1和Field2 获得数据 findByTitleAndContent
or findByField1OrField2 根 据Field1或Field2 获得数据 findByTitleOrContent
is findByField 根据Field获得数据 findByTitle
not findByFieldNot 根据Field获得补集数据 findByTitleNot
between findByFieldBetween 获得指定范围的数据 findByPriceBetween
	List<Product> findByProductName(String productName);

    List<Product> findByProductNameOrProductDesc(String productName, String productDesc);

    List<Product> findByIdBetween(Integer startId, Integer endId);

3 分页查询

18--Elasticsearch_第30张图片

使用继承或自定义的方法时,在方法中添加Pageable类型的参数, 返回值为Page类型即可进行分页查询。

   // 测试继承的方法:
    @Test
    public void testFindPage() {
        // 参数1:页数,参数2:每页条数
        Pageable pageable = PageRequest.of(1, 3);
        Page<Product> page = repository.findAll(pageable);
        System.out.println("总条数" + page.getTotalElements());
        System.out.println("总页数" + page.getTotalPages());
        System.out.println("数据" + page.getContent());
    }
	 // 自定义方法
    Page<Product> findByProductDesc(String productDesc, Pageable pageable);


	// 测试自定义方法
    @Test
    public void testFindPage2() {
        Pageable pageable = PageRequest.of(0, 2);
        Page<Product> page = repository.findByProductDesc("三体", pageable);
        System.out.println("总条数" + page.getTotalElements());
        System.out.println("总页数" + page.getTotalPages());
        System.out.println("数据" + page.getContent());
    }

4 结果排序

18--Elasticsearch_第31张图片

使用继承或自定义的方法时,在方法中添加Sort类型的参数即可进行结果排序。

   // 结果排序
    @Test
    public void testFindSort() {
        Sort sort = Sort.by(Sort.Direction.DESC, "id");
        Iterable<Product> all = repository.findAll(sort);
        for (Product product : all) {
            System.out.println(product);
        }
    }

    // 测试分页加排序
    @Test
    public void testFindPage3() {
        Sort sort = Sort.by(Sort.Direction.DESC, "id");
        Pageable pageable = PageRequest.of(0, 2, sort);
        Page<Product> page = repository.findByProductDesc("三体", pageable);
        System.out.println("总条数" + page.getTotalElements());
        System.out.println("总页数" + page.getTotalPages());
        System.out.println("数据" + page.getContent());
    }

5 template工具类

18--Elasticsearch_第32张图片

SpringDataElasticsearch提供了一个工具类

ElasticsearchRestTemplate,我们使用该类对象也能对ES进行操作。

操作索引

    @Autowired
    private ElasticsearchRestTemplate template;

    // 新增索引
    @Test
    public void addIndex() {
        // 获得索引操作对象
        IndexOperations indexOperations = template.indexOps(Product.class);
        // 创建索引,注:该方法无法设置索引结构,不推荐使用
        indexOperations.create();
    }

    // 删除索引
    @Test
    public void delIndex() {
        // 获得索引操作对象
        IndexOperations indexOperations = template.indexOps(Product.class);
        // 删除索引
        indexOperations.delete();
    }

增删改文档

template操作文档的常用方法:

  • save():新增/修改文档
  • delete():删除文档
   // 新增/修改文档
    @Test
    public void addDocument() {
        Product product = new Product(7, "es1", "es是一款优秀的搜索引擎");
        template.save(product);
    }

    // 删除文档
    @Test
    public void delDocument() {
        template.delete("7", Product.class);
    }

查询文档

template的search方法可以查询文档:

SearchHits<T> search(Query query, Class<T> clazz):查询文档,query是查询条件对象,clazz是结果
类型。

用法如下:

   // 查询文档
    @Test
    public void searchDocument() {
        // 1.确定查询方式
        //  MatchAllQueryBuilder builder = QueryBuilders.matchAllQuery();
        //  TermQueryBuilder builder =QueryBuilders.termQuery("productDesc", "手机");
        MatchQueryBuilder builder =
                QueryBuilders.matchQuery("productDesc", "我喜欢看科幻小说");
        // 2.构建查询条件
        NativeSearchQuery query = new
                NativeSearchQueryBuilder().withQuery(builder).build();
        // 3.查询
        SearchHits<Product> result = template.search(query, Product.class);
        // 4.处理查询结果
        for (SearchHit<Product> productSearchHit : result) {
            Product product = productSearchHit.getContent();
            System.out.println(product);
        }
    }

复杂条件查询

    @Test
    public void searchDocument2() {
//      String productName ="三体";
//      String productDesc = "小说";
        String productName = null;
        String productDesc = null;
        // 1.确定查询方式
        BoolQueryBuilder builder = QueryBuilders.boolQuery();
        // 如果没有传入参数,查询所有
        if (productName == null && productDesc == null) {
            MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
            builder.must(matchAllQueryBuilder);
        } else {
            if (productName != null && productName.length() > 0) {
                MatchQueryBuilder queryBuilder1 =
                        QueryBuilders.matchQuery("productName", productName);
                builder.must(queryBuilder1);
            }
            if (productDesc != null && productDesc.length() > 0) {
                MatchQueryBuilder queryBuilder2
                        = QueryBuilders.matchQuery("productDesc", productDesc);
                builder.must(queryBuilder2);
            }
        }
        // 2.构建查询条件
        NativeSearchQuery query = new NativeSearchQueryBuilder().withQuery(builder).build();
        // 3.查询
        SearchHits<Product> result = template.search(query, Product.class);
        // 4.处理查询结果
        for (SearchHit<Product> productSearchHit : result) {
            Product product = productSearchHit.getContent();
            System.out.println(product);
        }
    }

分页查询

  // 分页查询文档
    @Test
    public void searchDocumentPage() {
        // 1.确定查询方式
        MatchAllQueryBuilder builder = QueryBuilders.matchAllQuery();
        // 2.构建查询条件
        // 分页条件
        Pageable pageable = PageRequest.of(0, 3);
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(builder)
                .withPageable(pageable)
                .build();
        // 3.查询
        SearchHits<Product> result = template.search(query, Product.class);
        // 4.将查询结果封装为Page对象
        List<Product> content = new ArrayList();
        for (SearchHit<Product> productSearchHit : result) {
            Product product = productSearchHit.getContent();
            content.add(product);
        }
        /**
         * 封装Page对象,参数1:具体数据,参数2:分页条件对象,参数3:总条数
         */
        Page<Product> page = new PageImpl(content, pageable, result.getTotalHits());

        System.out.println(page.getTotalElements());
        System.out.println(page.getTotalPages());
        System.out.println(page.getContent());
    }

结果排序

    @Test
    public void searchDocumentSort() {
        // 1.确定查询方式
        MatchAllQueryBuilder builder = QueryBuilders.matchAllQuery();
        // 2.构建查询条件
        // 排序条件
        SortBuilder sortBuilder = SortBuilders.fieldSort("id").order(SortOrder.DESC);
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(builder)
                .withSorts(sortBuilder)
                .build();
        // 3.查询
        SearchHits<Product> result = template.search(query, Product.class);
        // 4.处理查询结果
        for (SearchHit<Product> productSearchHit : result) {
            Product product = productSearchHit.getContent();
            System.out.println(product);
        }
    }

八 Elasticsearch集群

1 概念

18--Elasticsearch_第33张图片

在单台ES服务器上,随着一个索引内数据的增多,会产生存储、效率、安全等问题。

  • 假设项目中有一个500G大小的索引,但我们只有几台200G硬盘的服务器,此时是不可能将索引放入其中某一台服务器中的。

18--Elasticsearch_第34张图片

  • 此时我们需要将索引拆分成多份,分别放入不同的服务器中,此时这几台服务器维护了同一个索引,我们称这几台服务器为一个集群,其中的每一台服务器为一个节点,每一台服务器中的数据称为一个分片

18--Elasticsearch_第35张图片

  • 此时如果某个节点故障,则会造成集群崩溃,所以每个节点的分片往往还会创建副本,存放在其他节点中,此时一个节点的崩溃就不会影响整个集群的正常运行。

18--Elasticsearch_第36张图片

节点(node):一个节点是集群中的一台服务器,是集群的一部分。它存储数据,参与集群的索引和搜索功能。集群中有一个为主节点,主节点通过ES内部选举产生。

集群(cluster):一组节点组织在一起称为一个集群,它们共同持有整个的数据,并一起提供索引和搜索功能。

分片(shards):ES可以把完整的索引分成多个分片,分别存储在不同的节点上。

副本(replicas):ES可以为每个分片创建副本,提高查询效率, 保证在分片数据丢失后的恢复。

18--Elasticsearch_第37张图片

注:

分片的数量只能在索引创建时指定,索引创建后不能再更改分片数量,但可以改变副本的数量。

为保证节点发生故障后集群的正常运行,ES不会将某个分片和它的副本存在同一台节点上。

2 搭建集群

18--Elasticsearch_第38张图片

安装第一个ES节点

  • 安装
#解压:
tar -zxvf elasticsearch-7.17.0-linux-x86_64.tar.gz

#重命名:
mv elasticsearch-7.17.0 myes1

#移动文件夹:
mv myes1 /usr/local/

#安装ik分词器
unzip elasticsearch-analysis-ik-7.17.0.zip -d /usr/local/myes1/plugins/analysis-ik

#安装拼音分词器
unzip elasticsearch-analysis-pinyin-7.17.0.zip -d /usr/local/myes1/plugins/analysis-pinyin

#es用户取得该文件夹权限:
chown -R es:es /usr/local/myes1
  • 修改配置文件
#打开节点一配置文件:
vim /usr/local/myes1/config/elasticsearch.yml

配置如下信息:

#集群名称,保证唯一
cluster.name: my_elasticsearch
#节点名称,必须不一样
node.name: node1
#可以访问该节点的ip地址
network.host: 0.0.0.0
#该节点服务端口号
http.port: 9200
#集群间通信端口号
transport.tcp.port: 9300
#候选主节点的设备地址
discovery.seed_hosts: ["127.0.0.1:9300","127.0.0.1:9301","127.0.0.1:9302"]
#候选主节点的节点名
cluster.initial_master_nodes: ["node1","node2","node3"]
  • 启动
#切换为es用户:
su es
#后台启动第一个节点:
ES_JAVA_OPTS="-Xms512m -Xmx512m" /usr/local/myes1/bin/elasticsearch -d

安装第二个ES节点

  • 安装
#解压:
tar -zxvf elasticsearch-7.17.0-linux-x86_64.tar.gz

#重命名:
mv elasticsearch-7.17.0 myes2

#移动文件夹:
mv myes2 /usr/local/

#安装ik分词器
unzip elasticsearch-analysis-ik-7.17.0.zip -d /usr/local/myes2/plugins/analysis-ik

#安装拼音分词器
unzip elasticsearch-analysis-pinyin-7.17.0.zip -d /usr/local/myes2/plugins/analysis-pinyin

#es用户取得该文件夹权限:
chown -R es:es /usr/local/myes2
  • 修改配置文件
#打开节点二配置文件:
vim /usr/local/myes2/config/elasticsearch.yml

配置如下信息:

#集群名称,保证唯一
cluster.name: my_elasticsearch
#节点名称,必须不一样
node.name: node2
#可以访问该节点的ip地址
network.host: 0.0.0.0
#该节点服务端口号
http.port: 9201
#集群间通信端口号
transport.tcp.port: 9301
#候选主节点的设备地址
discovery.seed_hosts: ["127.0.0.1:9300","127.0.0.1:9301","127.0.0.1:9302"]
#候选主节点的节点名
cluster.initial_master_nodes: ["node1","node2","node3"]
  • 启动
#切换为es用户:
su es

#后台启动第二个节点:
ES_JAVA_OPTS="-Xms512m -Xmx512m" /usr/local/myes2/bin/elasticsearch -d

安装第三个ES节点

  • 安装
#解压:
tar -zxvf elasticsearch-7.17.0-linux-x86_64.tar.gz

#重命名:
mv elasticsearch-7.17.0 myes3

#移动文件夹:
mv myes3 /usr/local/

#安装ik分词器
unzip elasticsearch-analysis-ik-7.17.0.zip -d /usr/local/myes3/plugins/analysis-ik

#安装拼音分词器
unzip elasticsearch-analysis-pinyin-7.17.0.zip -d /usr/local/myes3/plugins/analysis-pinyin

#es用户取得该文件夹权限:
chown -R es:es /usr/local/myes3
  • 修改配置文件
#打开节点三配置文件:
vim /usr/local/myes3/config/elasticsearch.yml

配置如下信息:

#集群名称,保证唯一
cluster.name: my_elasticsearch
#节点名称,必须不一样
node.name: node3
#可以访问该节点的ip地址
network.host: 0.0.0.0
#该节点服务端口号
http.port: 9202
#集群间通信端口号
transport.tcp.port: 9302
#候选主节点的设备地址
discovery.seed_hosts: ["127.0.0.1:9300","127.0.0.1:9301","127.0.0.1:9302"]
#候选主节点的节点名
cluster.initial_master_nodes: ["node1","node2","node3"]
  • 启动
#切换为es用户:
su es

#后台启动第三个节点:
ES_JAVA_OPTS="-Xms512m -Xmx512m" /usr/local/myes3/bin/elasticsearch -d

测试集群

访问 http://虚拟机IP:9200/_cat/nodes 查看是否集群搭建成功。

kibana连接es集群

  • 在kibana中访问集群
# 打开kibana配置文件
vim /usr/local/kibana-7.17.0-linux-x86_64/config/kibana.yml

添加如下配置

# 该集群的所有节点
elasticsearch.hosts: ["http://虚拟机IP:9200","http://虚拟机IP:9201","http://虚拟机IP:9202"]
  • 启动kibana
#切换为es用户:
su es

#启动kibana:
/usr/local/kibana-7.17.0-linux-x86_64/bin/kibana
  • 访问kibana: http://虚拟机IP:5601

3 测试集群状态

  • 在集群中创建一个索引
PUT /product1
{
	"settings": {
		"number_of_shards": 5,// 分片数
		"number_of_replicas": 1// 每个分片的副本数

	},
	"mappings": {
		"properties": {
			"id": {
				"type": "integer",
				"store": true,
				"index": true
			},
			"productName": {
				"type": "text",
				"store": true,
				"index": true
			},
			"productDesc": {
				"type": "text",
				"store": true,
				"index": true
			}
		}
	}
}
  • 查看集群状态
# 查看集群健康状态
GET /_cat/health?v

# 查看索引状态
GET /_cat/indices?v

# 查看分片状态
GET /_cat/shards?v

4 故障应对&水平扩容

  • 关闭一个节点,可以发现ES集群可以自动进行故障应对。
  • 重新打开该节点,可以发现ES集群可以自动进行水平扩容。
  • 分片数不能改变,但是可以改变每个分片的副本数:
PUT /索引/_settings
{
    "number_of_replicas": 副本数
}

九 Elasticsearch优化

1 磁盘选择

ES的优化即通过调整参数使得读写性能更快

磁盘通常是服务器的瓶颈。Elasticsearch重度使用磁盘,磁盘的效 率越高,Elasticsearch的执行效率就越高。这里有一些优化磁盘的技巧:

  • 使用SSD(固态硬盘),它比机械磁盘优秀多了。
  • 使用RAID0模式(将连续的数据分散到多个硬盘存储,这样可以并行进行IO操作),代价是一块硬盘发生故障就会引发系统故障。
  • 不要使用远程挂载的存储。

2 内存设置

ES默认占用内存是4GB,我们可以修改config/jvm.option设置ES的 堆内存大小,Xms表示堆内存的初始大小,Xmx表示可分配的最大内存。

  • Xmx和Xms的大小设置为相同的,可以减轻伸缩堆大小带来的压力。
  • Xmx和Xms不要超过物理内存的50%,因为ES内部的Lucene也要占据一部分物理内存。
  • Xmx和Xms不要超过32GB,由于Java语言的特性,堆内存超过32G会浪费大量系统资源,所以在内 存足够的情况下,最终我们都会采用设置为31G:
-Xms 31g
-Xmx 31g

例如:在一台128GB内存的机器中,我们可以创建两个节点,每个节点分配31GB内存。

3 分片策略

分片和副本数并不是越多越好。每个分片的底层都是一个Lucene索引,会消耗一定的系统资源。且搜索请求需要命中索引中的所有分片,分片数过多会降低搜索性能。索引的分片数需要架构师和技术人员对业务的增长有预先的判断,一般来说我们遵循以下原则:

  • 每个分片占用的硬盘容量不超过ES的最大JVM的堆空间设置(一 般设置不超过32G)。比如:如果索引的总容量在500G左右, 那分片数量在16个左右即可。
  • 分片数一般不超过节点数的3倍。比如:如果集群内有10个节点,则分片数不超过30个。
  • 推迟分片分配:节点中断后集群会重新分配分片。但默认集群会等待一分钟来查看节点是否重新加入。我们可以设置等待的时长,减少重新分配的次数:
PUT  /索引/_settings
{
    "settings":{
      "index.unassianed.node_left.delayed_timeout":"5m"
   }
}
  • 减少副本数量:进行写入操作时,需要把写入的数据都同步到副本,副本越多写入的效率就越慢。我们进行大批量进行写入操作时可以先设置副本数为0,写入完成后再修改回正常的状态。

十 Elasticsearch案例

1 需求说明

接下来我们使用ES模仿百度搜索,即自动补全+搜索引擎效果:

18--Elasticsearch_第39张图片

2 ES自动补全

es为我们提供了关键词的自动补全功能:

GET /索引/_search
{
    "suggest": {
        "prefix_suggestion": {// 自定义推荐名
            "prefix": "elastic",// 被补全的关键字
            "completion": {
                "field": "productName",// 查询的域
                "skip_duplicates": true, //忽略重复结果
                "size": 10 //最多查询到的结果数
           }
       }
   }
}

注:自动补全对性能要求极高,ES不是通过倒排索引来实现的,所以需要将对应的查询字段类型设置为completion。

PUT /product2
{
	"mappings": {
		"properties": {
			"id": {
				"type": "integer",
				"store": true,
				"index": true
			},
			"productName": {
				"type": "completion"
			},
			"productDesc": {
				"type": "text",
				"store": true,
				"index": true
			}
		}
	}
}

POST /product2/_doc
{
    "id":1,
    "productName":"elasticsearch1",
    "productDesc":"elasticsearch1 is a good search engine"
}
POST /product2/_doc
{
    "id":2,
    "productName":"elasticsearch2",
    "productDesc":"elasticsearch2 is a good search engine"
}
POST /product2/_doc
{
    "id":3,
    "productName":"elasticsearch3",
    "productDesc":"elasticsearch3 is a good search engine"
}

测试自动补全功能:

GET /product2/_search
{
  "suggest": {
    "prefix_suggestion": {
      "prefix": "elastic",
      "completion": {
        "field": "productName",
        "skip_duplicates": true,
        "size": 10
      }
    }
  }
}

3 创建索引

PUT /news
{
	"settings": {
		"analysis": {
			"analyzer": {
				"ik_pinyin": {
					"tokenizer": "ik_smart",
					"filter": "pinyin_filter"
				},
				"tag_pinyin": {
					"tokenizer": "keyword",
					"filter": "pinyin_filter"
				}
			},
			"filter": {
				"pinyin_filter": {
					"type": "pinyin",
					"keep_joined_full_pinyin": true,
					"keep_original": true,
					"remove_duplicated_term": true
				}
			}
		}
	},
	"mappings": {
		"properties": {
			"id": {
				"type": "integer",
				"index": true
			},
			"title": {
				"type": "text",
				"index": true,
				"analyzer": "ik_pinyin",
				"search_analyzer": "ik_smart"
			},
			"content": {
				"type": "text",
				"index": true,
				"analyzer": "ik_pinyin",
				"search_analyzer": "ik_smart"
			},
			"url": {
				"type": "keyword",
				"index": true
			},
			"tags": {
				"type": "completion",
				"analyzer": "tag_pinyin",
				"search_analyzer": "tag_pinyin"
			}
		}
	}
}

4 准备数据

将提前准备好的sql导入数据库:

/*
SQLyog Ultimate v12.09 (64 bit)
MySQL - 5.5.40-log : Database - news
*********************************************************************
*/


/*!40101 SET NAMES utf8 */;

/*!40101 SET SQL_MODE=''*/;

/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`news` /*!40100 DEFAULT CHARACTER SET utf8 */;

USE `news`;

/*Table structure for table `news` */

DROP TABLE IF EXISTS `news`;

CREATE TABLE `news` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) NOT NULL,
  `url` varchar(255) DEFAULT NULL,
  `content` text,
  `tags` varchar(1000) DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=92 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

/*Data for the table `news` */

insert  into `news`(`id`,`title`,`url`,`content`,`tags`) values (1,'略...','略...','略...','略...';

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

使用logstash工具可以将mysql数据同步到es中:

  • 解压logstash-7.17.0-windows-x86_64.zip

logstash要和elastisearch版本一致

  • 在解压路径下的/config中创建mysql.conf文件,文件写入以下脚本内容:
input {
   jdbc {
       jdbc_driver_library => "F:\001-after-end\笔记\14-全文检索与日志管理\Elasticsearch\软件\案例\mysql-connector-java-5.1.37-bin.jar"
       jdbc_driver_class => "com.mysql.jdbc.Driver"
       jdbc_connection_string => "jdbc:mysql:///news"
       jdbc_user => "root"
       jdbc_password => "123456"
       schedule => "* * * * *"
       jdbc_default_timezone => "Asia/Shanghai"
       statement => "SELECT * FROM news;"
   }
}

filter {
 mutate {
  split => {"tags" => ","}
 }
}

output {
   elasticsearch {
 	   hosts => ["192.168.66.113:9200"]
	   index => "news"
	   document_id => "%{id}"
   }
}

  • 在解压路径下打开cmd黑窗口,运行命令:
bin\logstash -f config\mysql.conf

注意:

logstash解压路径不能有中文;

mysql.conf的编码必须为utf-8;

配置es可以远程访问(参照第六章配置)。

  • 测试自动补齐
GET /news/_search
{
    "suggest": {
        "my_suggest": {
            "prefix": "li",
            "completion": {
                "field": "tags",
                "skip_duplicates": true,
                "size": 10
           }
       }
   }
}

5 项目搭建

创建Springboot项目,加入SpringDataElasticsearch和SpringMVC 的起步依赖

<dependency>
  	<groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-data-elasticsearchartifactId>
dependency>

<dependency>  
	<groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-webartifactId>
dependency>

<dependency>
    <groupId>org.projectlombokgroupId>
    <artifactId>lombokartifactId>
    <optional>trueoptional>
dependency>

写配置文件:

spring:
  elasticsearch:
    uris: 192.168.66.113:9200
 
logging:
  pattern:
    console: '%d{HH:mm:ss.SSS} %clr(%-5level) --- [%-15thread]%cyan(%-50logger{50}):%msg%n'

6 创建实体类

//索引已经提前创建好了,下面的实体类则不用添加那些和创建索引有关的属性了
@Document(indexName = "news")
@Data
public class News {
    @Id
    @Field
    private Integer id;
    @Field
    private String title;
    @Field
    private String content;
    @Field
    private String url;
    @CompletionField
    @Transient
    private Completion tags;
}

7 创建Repository接口

public interface NewsRepository extends ElasticsearchRepository<News, Integer> {
}

8 自动补全功能


@Service
public class NewsService {
    @Autowired
    private ElasticsearchRestTemplate template;

    // 自动补齐
    public List<String> autoSuggest(String keyword) {
        // 1.创建补全请求
        SuggestBuilder suggestBuilder = new SuggestBuilder();
        // 2.构建补全条件
        SuggestionBuilder suggestionBuilder = SuggestBuilders
                .completionSuggestion("tags")
                .prefix(keyword)
                .skipDuplicates(true)
                .size(10);
        suggestBuilder.addSuggestion("prefix_suggestion", suggestionBuilder);
        // 3.发送请求
        SearchResponse response = template.suggest(suggestBuilder, IndexCoordinates.of("news"));
        // 4.处理结果
        List<String> result = response.getSuggest()
                .getSuggestion("prefix_suggestion")
                .getEntries()
                .get(0)
                .getOptions()
                .stream()
                .map(Suggest.Suggestion.Entry.Option::getText)
                .map(Text::toString)
                .collect(Collectors.toList());
        return result;
    }
}

对应的原生es搜索为:

GET /news/_search
{
    "suggest": {
        "prefix_suggestion": {
            "prefix": "li",
            "completion": {
                "field": "tags",
                "skip_duplicates": true,
                "size": 10
           }
       }
   }
}

结果为:

{
  "took" : 33,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 0,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "suggest" : {
    "prefix_suggestion" : [
      {
        "text" : "li",
        "offset" : 0,
        "length" : 2,
        "options" : [
         	 {
            "text" : "利哈伊谷",
            "_index" : "news",
            "_type" : "_doc",
            "_id" : "18",
            "_score" : 1.0,
            "_source" : {
              "@timestamp" : "2023-05-06T08:39:01.668Z",
              "tags" : [
                "美国",
                "美国黑五",
                "利哈伊谷",
                "购物中心",
                "视频",
                "脸书",
                "保安",
                "塞缪尔·萨法迪",
                "海军陆战队",
                "现役海军陆战队员",
                "退役海军陆战队员",
                "礼品店",
                "礼品",
                "打斗",
                "安全人员",
                "安全"
              ],
              "content" : "海外网12月1日电 近日,一年一度的“黑色星期五”购物节拉开帷幕,热情的购物者涌向百货商店,都希望能买到打折商品。然而,美国各地也因此发生了几起暴力事件。美媒甚至感慨,“如果一年有一天会失去对人性的希望,那就是‘黑五’。”福克斯新闻网报道了本周内美国各个州因“黑五”引发的冲突事件,目击者拍下视频,画面在社交平台上疯传。当地时间11月29日晚上,在宾夕法尼亚州利哈伊谷购物中心的Forever 21商店外,发生了一场打斗事件。有网友将视频拍摄下来,略......",
              "id" : 18,
              "url" : "https://news.sina.com.cn/w/2019-12-01/doc-iihnzhfz2885717.shtml",
              "title" : """美国"黑五"冲突不断多地发生斗殴 有人鼻子被打断""",
              "@version" : "1"
            }
          },
          .......]
      }
    ]
  }
}

9 搜索关键字功能

在repository接口中添加高亮搜索关键字方法

// 高亮搜索关键字
@Highlight(fields = {@HighlightField(name = "title"), @HighlightField(name = "content")})
List<SearchHit<News>> findByTitleMatchesOrContentMatches(String title, String content);

service类中调用该方法

    @Autowired
    NewsRepository repository;

   // 查询关键字
    public List<News> highLightSearch(String keyword) {
        List<SearchHit<News>> result = repository.findByTitleMatchesOrContentMatches(keyword, keyword);
        // 处理结果,封装为News类型的集合
        List<News> newsList = new ArrayList();
        for (SearchHit<News> newsSearchHit : result) {
            News news = newsSearchHit.getContent();
            // 高亮字段
            Map<String, List<String>> highlightFields = newsSearchHit.getHighlightFields();
            if (highlightFields.get("title") != null) {
                news.setTitle(highlightFields.get("title").get(0));
            }
            if (highlightFields.get("content") != null) {
                news.setContent(highlightFields.get("content").get(0));
            }
            newsList.add(news);
        }
        return newsList;
    }

对应的原生es搜索为:

GET /news/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "title": "江西"
          }
        },
        {
          "match": {
            "content": "江西"
          }
        }
      ]
    }
  },
  "highlight": {
    "fields": [
      {
        "content": {
          "fragment_size": 20,
          "number_of_fragments": 5
        }
      },
      {
        "title": {
          "fragment_size": 20,
          "number_of_fragments": 5
        }
      }
    ],
    "pre_tags": [
      ""
    ],
    "post_tags": [
      ""
    ]
  }
}

结果为:

{
  "took" : 15,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 11.891368,
    "hits" : [
      {
        "_index" : "news",
        "_type" : "_doc",
        "_id" : "91",
        "_score" : 11.891368,
        "_source" : {
          "@timestamp" : "2023-05-06T08:39:01.686Z",
          "tags" : [
            "江西九江",
            "江西",
            "九江",
            "吴城",
            "吴城水上公路",
            "老鼠",
            "江西暴雨",
            "暴雨",
            "鄱阳湖",
            "洪水",
            "长江",
            "三峡"
          ],
          "content" : "7月4日,江西九江,吴城水上公路因暴雨被洪水淹没,有一辆车在水中熄火动弹不了,一市民和她老公去现场救援时发现公路旁有个亭子,发现里面竟有七八十只老鼠在亭内躲避洪水,并表示第一次看到这么多老鼠。该市民称,每年雨季这条公路都会被淹没,在此呼吁广大市民,雨季行车注意安全。江西省继续发布洪水预警,鄱阳湖防洪对长江流域相当重要今日10时,江西省继续发布洪水红色预警,鄱阳湖水位超警戒3.60米,形势严峻。鄱阳湖是江西的“集水盆”,江西境内五大河流经,略......",
          "id" : 91,
          "url" : "https://baijiahao.baidu.com/s?id=1672108752181366032&wfr=spider&for=pc",
          "title" : """江西暴雨近百只老鼠凉亭内躲洪水:密密麻麻紧贴石墩

江西暴雨近百只老鼠凉亭内躲洪水:密密麻麻紧贴石墩""",
          "@version" : "1"
        },
        "highlight" : {
          "title" : [
            "江西暴雨近百只老鼠凉亭内躲洪水:密密麻麻紧贴石墩",
            "江西暴雨近百只老鼠凉亭内躲洪水:密密麻麻紧贴石墩"
          ],
          "content" : [
            "7月4日,江西九江,吴城水上公路因暴雨被洪水淹没",
            "鄱阳湖是江西的“集水盆”,江西境内五大河流经鄱阳湖集纳后进入长江"
          ]
        }
      },......
    ]
  }
}

10 创建Controller类

@RestController
public class NewsController {
    @Autowired
    private NewsService newsService;

    @GetMapping("/autoSuggest")
    public List<String> autoSuggest(String term) { // 前端使用jqueryUI,发送的参数默认名为term
        return newsService.autoSuggest(term);
    }

    @GetMapping("/highLightSearch")
    public List<News> highLightSearch(String term) {
        return newsService.highLightSearch(term);
    }
}

11 前端页面

我们使用jqueryUI中的autocomplete插件完成项目的前端实现

略。。。

你可能感兴趣的:(学习整理,elasticsearch)