1-8 ElasticSearch基础 1.初识ElasticSearch ElasticSearch是一个搜索服务器
1.1-基于数据库查询的问题
性能低、功能弱
1.2-倒排索引 倒排索引 :将文档中的内容进行分词 ,形成词条 ,然后记录词条和数据唯一标识id 的对应关系。即为反向索引。
以唐诗为例,所处包含“前”的诗句
正向索引:由《静夜思》–>窗前明月光—>“前”字
反向索引:“前”字–>窗前明月光–>《静夜思》
反向索引的实现就是对诗句进行分词,分成单个的词,由词推据,即为反向索引
“床前明月光”–> 分词
将一段文本按照一定的规则,拆分为不同的词条(term)
1.3-ES存储和查询的原理
数据库查询存在的问题:
性能低:使用模糊查询,左边有通配符,不会走索引,会全表扫描,性能低
功能弱:如果以”华为手机“作为条件,查询不出来数据
Es使用倒排索引,对title 进行分词
使用“手机”作为关键字查询
生成的倒排索引中,词条会排序,形成一颗树形结构,提升词条的查询速度
使用“华为手机”作为关键字查询
华为:1,3
手机:1,2,3
1.4-ES概念详解 概述
ElasticSearch是一个基于Lucene的搜索服务器
应用场景
概念
MySQL有事务性,而ElasticSearch没有事务性,所以你删了的数据是无法恢复的。
ElasticSearch没有物理外键这个特性,如果你的数据强一致性要求此较高,还是建议慎用
ElasticSearch和MySq|分工不同,MySQL负责存储数据,ElasticSearch负责搜索数据。
2.3-安装ElasticSearch 2.3.1-ES安装 安装 1、上传ElasticSearch安装包
alt+p # 打开sftp窗口 # 上传es安装包 put e:/software/elasticsearch-7.4.0-linux-x86_64.tar.gz
2、执行解压操作 ,如下图
# 将elasticsearch-7.4.0-linux-x86_64.tar.gz解压到opt文件夹下. -C 大写 tar -zxvf elasticsearch-7.4.0-linux-x86_64.tar.gz -C /opt
3、创建普通用户
因为安全问题,Elasticsearch 不允许root用户直接运行,所以要创建新用户,在root用户中创建新用户,执行如下命令:
useradd itheima # 新增itheima用户 passwd itheima # 为itheima用户设置密码
5、为新用户授权,如下图
chown -R itheima:itheima /opt/elasticsearch-7.4.0 #文件夹所有者
将 /opt/elasticsearch-7.4.0文件夹授权给itheima用户,由上图可见,我们的文件夹权限赋给了itheima
6、修改elasticsearch.yml文件
vim /opt/elasticsearch-7.4.0/config/elasticsearch.yml
# ======================== Elasticsearch Configuration ========================= cluster.name: my-application node.name: node-1 network.host: 0.0.0.0 http.port: 9200 cluster.initial_master_nodes: ["node-1"]
cluster.name:配置elasticsearch的集群名称,默认是elasticsearch。建议修改成一个有意义的名称
node.name:节点名,elasticsearch会默认随机指定一个名字,建议指定一个有意义的名称,方便管理
network.host:设置为0.0.0.0允许外网访问
http.port:Elasticsearch的http访问端口
cluster.initial_master_nodes:初始化新的集群时需要此配置来选举master
7、修改配置文件
新创建的itheima用户最大可创建文件数太小,最大虚拟内存太小,切换到root用户,编辑下列配置文件, 添加类似如下内容
# 切换到root用户 su root # 1. ===最大可创建文件数太小======= vim /etc/security/limits.conf # 在文件末尾中增加下面内容 # 用户最大可创建文件数 itheima soft nofile 65536 itheima hard nofile 65536 # ===== vim /etc/security/limits.d/20-nproc.conf # 在文件末尾中增加下面内容 itheima soft nofile 65536 itheima hard nofile 65536 # 文件可执行的最大进程的数目 * hard nproc 4096 # 注:* 代表Linux所有用户名称 # 2. ===最大虚拟内存太小======= vim /etc/sysctl.conf # 在文件中增加下面内容 vm.max_map_count=655360 # 重新加载,输入下面命令: sysctl -p
8、启动elasticsearch
su itheima # 切换到itheima用户启动 cd /opt/elasticsearch-7.4.0/bin ./elasticsearch #启动
通过上图我们可以看到elasticsearch已经成功启动
访问 1、在访问elasticsearch前,请确保防火墙是关闭的,执行命令:
# 暂时关闭防火墙 systemctl stop firewalld # 或者 # 永久设置防火墙状态 systemctl enable firewalld.service #打开防火墙,永久性生效,重启后不会复原 systemctl disable firewalld.service #关闭防火墙,永久性生效,重启后不会复原
浏览器输入http://192.168.149.135:9200/,如下图
此时elasticsearch已成功启动:
重点几个关注下即可: number" : "7.4.0" 表示elasticsearch版本 lucene_version" : "8.2.0" 表示lucene版本 name : 默认启动的时候指定了 ES 实例名称 cluster_name : 默认名为 elasticsearch
查看状态 参见ElasticSearch-ES安装.md
查看elastic是否启动
2.3.2-ES辅助工具安装 Kibana安装 1、什么是Kibana
Kibana是一个针对Elasticsearch的开源分析及可视化平台,用来搜索、查看交互存储在Elasticsearch索引中的数据。使用Kibana,可以通过各种图表进行高级数据分析及展示。
Kibana让海量数据更容易理解。它操作简单,基于浏览器的用户界面可以快速创建仪表板(dashboard)实时显示Elasticsearch查询动态。
2、上传kibana
CRT中克隆一个窗口,上传Kibana
put E:\software\kibana-7.4.0-linux-x86_64.tar.gz
2、解压kibana
tar -xzf kibana-7.4.0-linux-x86_64.tar.gz -C /opt
解压到当前目录(/opt)下
3、修改kibana配置
vim /opt/kibana-7.4.0-linux-x86_64/config/kibana.yml
server.port: 5601 server.host: "0.0.0.0" server.name: "kibana-itcast" elasticsearch.hosts: ["http://127.0.0.1:9200"] elasticsearch.requestTimeout: 99999
server.port:http访问端口
server.host:ip地址,0.0.0.0表示可远程访问
server.name:kibana服务名
elasticsearch.hosts:elasticsearch地址
elasticsearch.requestTimeout:请求elasticsearch超时时间,默认为30000,此处可根据情况设置
4、启动kibana
由于kibana不建议使用root用户启动,如果用root启动,需要加–allow-root参数
# 切换到kibana的bin目录 cd /opt/kibana-7.4.0-linux-x86_64/bin # 启动 ./kibana --allow-root
启动成功。
5、访问kibana
1.浏览器输入http://192.168.149.135:5601/,如下图:
http://192.168.149.135:5601/
看到这个界面,说明Kibanan已成功安装。
Discover
:可视化查询分析器
Visualize
:统计分析图表
Dashboard
:自定义主面板(添加图表)
Timelion
:Timelion是一个kibana时间序列展示组件(暂时不用)
Dev Tools
:Console控制台(同CURL/POSTER,操作ES代码工具,代码提示,很方便)
Management
:管理索引库(index)、已保存的搜索和可视化结果(save objects)、设置 kibana 服务器属性。
head安装 head插件是ES的一个可视化管理插件,用来监视ES的状态,并通过head客户端和ES服务进行交互,比如创建映射、创建索引等。
在登陆和访问head插件地址和ElasticSearch前需要事先在服务器上安装和配置好ElasticSearch以及head插件。安装完后,默认head插件的web端口为9100,ElasticSearch服务的端口为9200,使用浏览器访问head地址,如http://IP地址:9100/ ,推荐使用Chrome浏览器,head插件对Chrome浏览器兼容更佳。进入head页面后将ElasticSearch连接输入框中填写正确的ElasticSearch服务地址,就可以监控ElasticSearch运行信息
node.js
什么是Node
简单的说 Node.js 就是运行在服务端的 JavaScript。Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。Node.js 的包管理器 npm ,是全球最大的开源库生态系统。
下载Node
上一节我们已经安装好了Elasticsearch,接下来我们来安装head插件,由于elasticsearch-head插件是由nodejs语言编写,所以安装elasticsearch-head前需要先安装nodejs。 首先,执行以下命令安装nodejs和grunt
打开虚拟机,执行wget命令下载Node,如下图:
wget https://nodejs.org/dist/v10.15.2/node-v10.15.2-linux-x64.tar.xz
3)解压Node包
tar xvf node-v10.15.2-linux-x64.tar.xz
4)设置软连接
解压文件的 bin 目录底下包含了 node、npm 等命令,我们可以使用 ln 命令来设置软连接:
ln -s bin/npm /usr/local/bin/ ln -s bin/node /usr/local/bin/
在/etc/profile中配置好path环境变量
vi ~/.bash_profile export NODE_HOME=/opt/nodejs/node-v10.15.2-linux-x64 export PATH=$PATH:$NODE_HOME/bin
保存退出,使文件生效
查看node安装版本,执行 node -v 验证安装如下图:
grunt安装 安装grunt(运行在Node.js上面的任务管理器(task runner)),为了获得Grunt的更多产品特性,需要全局安装Grunt’s 命令行接口(CLI),使用npm进行安装,如下:
查看grunt版本
输出grunt版本信息,表示安装成功。
head安装
执行命令安装git
切换到/opt目录下,执行下面的克隆命令
git clone git://github.com/mobz/elasticsearch-head.git
进入到elasticsearch-head目录
运行
在运行之前我们需要修改下elasticsearch.yml,因为ES默认不开启跨域访问,需要添加以下配置:
# 开启cors跨域访问支持,默认为false http.cors.enabled: true # 跨域访问允许的域名地址,(允许所有域名)以上使用正则 http.cors.allow-origin: "*"
然后开始执行运行命令:
访问head
浏览器输入ip:port:9100,如下图
看到这个界面说明我们的head插件成功安装并且成功连接Elasticsearch。
查看状态 后台启动
3-ElasticSearch核心概念
索引(index)
ElasticSearch存储数据的地方,可以理解成关系型数据库中的数据库 概念。
映射(mapping)
mapping定义了每个字段的类型、字段所使用的分词器等。相当于关系型数据库中的表结构 。
文档(document)
Elasticsearch中的最小数据单元,常以json格式显示。一个document相当于关系型数据库中的一行数据 。
倒排索引
一个倒排索引由文档中所有不重复词的列表构成,对于其中每个词,对应一个包含它的文档id列表 。
类型(type)
一种type就像一类表。如用户表、角色表等。在Elasticsearch7.X默认type为_doc
\- ES 5.x中一个index可以有多种type。
\- ES 6.x中一个index只能有一种type。
\- ES 7.x以后,将逐步移除type这个概念,现在的操作已经不再使用,默认_doc
4-脚本操作ES 4.1-RESTful风格介绍 1.ST(Representational State Transfer),表述性状态转移,是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是RESTful。就是一种定义接口的规范。
2.基于HTTP。
3.使用XML格式定义或JSON格式定义。
4.每一个URI代表1种资源。
5.客户端使用GET、POST、PUT、DELETE 4个表示操作方式的动词对服务端资源进行操作:
GET:用来获取资源
POST:用来新建资源(也可以用于更新资源)
PUT:用来更新资源
DELETE:用来删除资源
4.2-操作索引 添加
查询
GET http://ip:端口/索引名称 # 查询单个索引信息 GET http://ip:端口/索引名称1,索引名称2... # 查询多个索引信息 GET http://ip:端口/_all # 查询所有索引信息
删除索引
关闭、打开索引
POST http://ip:端口/索引名称/_close POST http://ip:端口/索引名称/_open
4.3-ES数据类型 简单数据类型
字符串
text:会分词,不支持聚合
keyword:不会分词,将全部内容作为一个词条,支持聚合
聚合:相当于mysql 中的sum(求和)
数值
布尔
二进制
范围类型
integer_range, float_range, long_range, double_range, date_range
日期
复杂数据类型
4.4-操作映射 #创建索引 PUT person #查询索引 GET person #添加映射 PUT /person/_mapping { "properties" : { "name" : { "type" : "text" } , "age" : { "type" : "integer" } } } #查询映射 GET person/_mapping #删除索引 DELETE person #创建索引并添加映射 PUT /person1 { "mappings" : { "properties" : { "name" : { "type" : "text" } , "age" : { "type" : "integer" } } } } GET person1/_mapping #添加字段 PUT /person1/_mapping { "properties" : { "name" : { "type" : "text" } , "age" : { "type" : "integer" } } }
4.5-操作文档
#添加文档(可以使用POST/PUT) PUT /person1/_doc/2 { "name" : "张三" , "age" : 18 , "address" : "北京" } #查询文档 GET /person1/_doc/1
#添加文档,不指定id(必须是POST) POST /person1/_doc/ { "name" : "张三" , "age" : 18 , "address" : "北京" } #查询所有文档 GET /person1/_search
#和添加的修改一样,造作相同的id时,第二次自动认为是修改 POST /person1/_doc/ { "name" : "张三" , "age" : 18 , "address" : "北京" }
#删除指定id文档 DELETE /person1/_doc/1
5-分词器 分词器:讲一段文本,按照一定逻辑,分析称多个词语的一种工具
ES自带的分词器对中文很不友好,分词规则:一个字一个词
因此需要配置别的分词器
5.1分词器-介绍
安装包在资料文件夹中提供
5.2-ik分词器安装 存储时使用细粒度进行存储
搜索时使用粗粒度进行搜索
1、环境准备 Elasticsearch 要使用 ik,就要先构建 ik 的 jar包,这里要用到 maven 包管理工具,而 maven 需要java 环境,而 Elasticsearch 内置了jdk, 所以可以将JAVA_HOME设置为Elasticsearch 内置的jdk
1)设置JAVA_HOME
vim /etc/profile # 在profile文件末尾添加 # java environment export JAVA_HOME=/opt/elasticsearch-7.4.0/jdk export PATH=$PATH:${JAVA_HOME}/bin # 保存退出后,重新加载profile source /etc/profile
2)下载maven安装包
wget http://mirror.cc.columbia.edu/pub/software/apache/maven/maven-3/3.1.1/binaries/apache-maven-3.1.1-bin.tar.gz
3)解压maven安装包(有就不用了)
tar xzf apache-maven-3.1.1-bin.tar.gz
4)设置软连接(根据自身maven操作)
ln -s apache-maven-3.1.1 maven
5)设置path(环境变量)
打开文件
vim /etc/profile.d/maven.sh
将下面的内容复制到文件,保存
export MAVEN_HOME=/opt/maven export PATH=${MAVEN_HOME}/bin:${PATH}
设置好Maven的路径之后,需要运行下面的命令使其生效
source /etc/profile.d/maven.sh
6)验证maven是否安装成功
2、安装IK分词器 1)下载IK
wget https://github.com/medcl/elasticsearch-analysis-ik/archive/v7.4.0.zip
2)解压IK
由于这里是zip包不是gz包,所以我们需要使用unzip命令进行解压,如果本机环境没有安装unzip,请执行:
yum install zip yum install unzip
解压IK
3)编译jar包
# 切换到 elasticsearch-analysis-ik-7.4.0目录 cd elasticsearch-analysis-ik-7.4.0/ # 打包 mvn package
4) jar包移动
package执行完毕后会在当前目录下生成target/releases目录,将其中的elasticsearch-analysis-ik-7.4.0.zip。拷贝到elasticsearch目录下的新建的目录plugins/analysis-ik,并解压
# 切换目录 cd /opt/elasticsearch-7.4.0/plugins/ # 新建目录 mkdir analysis-ik cd analysis-ik # 执行拷贝 cp -R /opt/elasticsearch-analysis-ik-7.4.0/target/releases/elasticsearch-analysis-ik-7.4.0.zip /opt/elasticsearch-7.4.0/plugins/analysis-ik # 执行解压 unzip /opt/elasticsearch-7.4.0/plugins/analysis-ik/elasticsearch-analysis-ik-7.4.0.zip
5)拷贝辞典
将elasticsearch-analysis-ik-7.4.0目录下的config目录中的所有文件 拷贝到elasticsearch的config目录
cp -R /opt/elasticsearch-analysis-ik-7.4.0/config/* /opt/elasticsearch-7.4.0/config
记得一定要重启Elasticsearch!!!
注意 执行如下命令时如果出现 打包失败(501码)将maven镜像换成阿里云的
/opt/apache-maven-3.1.1/conf/setting.xml
<mirror > <id > alimaven</id > <name > aliyun maven</name > <url > http://maven.aliyun.com/nexus/content/groups/public/</url > <mirrorOf > central</mirrorOf > </mirror >
5.3-ik分词器使用 IK分词器有两种分词模式:ik_max_word和ik_smart模式。
1、ik_max_word
会将文本做最细粒度的拆分,比如会将“乒乓球明年总冠军”拆分为“乒乓球、乒乓、球、明年、总冠军、冠军。
#方式一ik_max_word GET /_analyze { "analyzer" : "ik_max_word" , "text" : "乒乓球明年总冠军" }
ik_max_word分词器执行如下:
{ "tokens" : [ { "token" : "乒乓球" , "start_offset" : 0 , "end_offset" : 3 , "type" : "CN_WORD" , "position" : 0 } , { "token" : "乒乓" , "start_offset" : 0 , "end_offset" : 2 , "type" : "CN_WORD" , "position" : 1 } , { "token" : "球" , "start_offset" : 2 , "end_offset" : 3 , "type" : "CN_CHAR" , "position" : 2 } , { "token" : "明年" , "start_offset" : 3 , "end_offset" : 5 , "type" : "CN_WORD" , "position" : 3 } , { "token" : "总冠军" , "start_offset" : 5 , "end_offset" : 8 , "type" : "CN_WORD" , "position" : 4 } , { "token" : "冠军" , "start_offset" : 6 , "end_offset" : 8 , "type" : "CN_WORD" , "position" : 5 } ] }
2、ik_smart 会做最粗粒度的拆分,比如会将“乒乓球明年总冠军”拆分为乒乓球、明年、总冠军。
#方式二ik_smart GET /_analyze { "analyzer" : "ik_smart" , "text" : "乒乓球明年总冠军" }
ik_smart分词器执行如下:
{ "tokens" : [ { "token" : "乒乓球" , "start_offset" : 0 , "end_offset" : 3 , "type" : "CN_WORD" , "position" : 0 } , { "token" : "明年" , "start_offset" : 3 , "end_offset" : 5 , "type" : "CN_WORD" , "position" : 1 } , { "token" : "总冠军" , "start_offset" : 5 , "end_offset" : 8 , "type" : "CN_WORD" , "position" : 2 } ] }
由此可见 使用ik_smart可以将文本”text”: “乒乓球明年总冠军”分成了【乒乓球】【明年】【总冠军】
这样看的话,这样的分词效果达到了我们的要求。
5.4使用IK分词器-查询文档
创建索引,添加映射,并指定分词器为ik分词器
PUT person2 { "mappings" : { "properties" : { "name" : { "type" : "keyword" #keyword不进行分词,将全部内容作为一个词条,支持聚合 } , "address" : { "type" : "text" , "analyzer" : "ik_max_word" #指定分词器 } } } }
2.添加文档
POST /person2/_doc/1 { "name" : "张三" , "age" : 18 , "address" : "北京海淀区" } POST /person2/_doc/2 { "name" : "李四" , "age" : 18 , "address" : "北京朝阳区" } POST /person2/_doc/3 { "name" : "王五" , "age" : 18 , "address" : "北京昌平区" } #当创建文档,加入映射中没有的字段时,会自动在映射中加入相应的字段
3.查询映射
4.查看分词效果
GET _analyze { "analyzer" : "ik_max_word" , "text" : "北京海淀" }
5.词条查询:term
查询person2中匹配到”北京”两字的词条
GET /person2/_search { "query" : { "term" : { "address" : { "value" : "北京" } } } }
查询person2中匹配到”北京海淀”两字的词条
北京海淀GET /person2/_search { "query" : { "term" : { "address" : { "value" : "北京海淀" } } } } #因为没有对应的分词,所以查不到消息
6.全文查询:match
全文查询会分析查询条件,先将查询条件进行分词,然后查询,求并集
GET /person2/_search { "query" : { "match" : { "address" : "北京昌平" } } } #可以查到,因为先做了分词
5.5添加到IK中 在plugin/ik/main.dic文件中,加入库中没有的词
6-ElasticSearch JavaApi 6.1SpringBoot整合ES ①搭建SpringBoot工程
②引入ElasticSearch相关坐标
<dependency > <groupId > org.elasticsearch.client</groupId > <artifactId > elasticsearch-rest-high-level-client</artifactId > <version > 7.4.0</version > </dependency > <dependency > <groupId > org.elasticsearch.client</groupId > <artifactId > elasticsearch-rest-client</artifactId > <version > 7.4.0</version > </dependency > <dependency > <groupId > org.elasticsearch</groupId > <artifactId > elasticsearch</artifactId > <version > 7.4.0</version > </dependency >
③测试
ElasticSearchConfig
@Configuration @ConfigurationProperties(prefix="elasticsearch") public class ElasticSearchConfig { private String host; private int port; public String getHost () { return host; } public void setHost (String host) { this .host = host; } public int getPort () { return port; } public void setPort (int port) { this .port = port; } @Bean public RestHighLevelClient client () { return new RestHighLevelClient (RestClient.builder( new HttpHost (host,port,"http" ) )); } }
ElasticsearchDay01ApplicationTests
注意:使用@Autowired注入RestHighLevelClient 如果报红线,则是因为配置类所在的包和测试类所在的包,包名不一致造成的
@SpringBootTest class ElasticsearchDay01ApplicationTests { @Autowired RestHighLevelClient client; @Test void contextLoads () { System.out.println(client); } }
6.2-创建索引 1.添加索引
@Test public void addIndex () throws IOException { IndicesClient indices = client.indices(); CreateIndexRequest createIndexRequest=new CreateIndexRequest ("itheima" ); CreateIndexResponse createIndexResponse = indices.create(createIndexRequest, RequestOptions.DEFAULT); System.out.println(createIndexResponse.isAcknowledged()); }
2.添加索引,并添加映射
@Test public void addIndexAndMapping () throws IOException { IndicesClient indices = client.indices(); CreateIndexRequest createIndexRequest = new CreateIndexRequest ("itcast" ); String mapping = "{\n" + " \"properties\" : {\n" + " \"address\" : {\n" + " \"type\" : \"text\",\n" + " \"analyzer\" : \"ik_max_word\"\n" + " },\n" + " \"age\" : {\n" + " \"type\" : \"long\"\n" + " },\n" + " \"name\" : {\n" + " \"type\" : \"keyword\"\n" + " }\n" + " }\n" + " }" ; createIndexRequest.mapping(mapping,XContentType.JSON); CreateIndexResponse createIndexResponse = indices.create(createIndexRequest, RequestOptions.DEFAULT); System.out.println(createIndexResponse.isAcknowledged()); }
6.3-查询、删除、判断索引 查询索引
@Test public void queryIndex () throws IOException { IndicesClient indices = client.indices(); GetIndexRequest getRequest=new GetIndexRequest ("itcast" ); GetIndexResponse response = indices.get(getRequest, RequestOptions.DEFAULT); Map<String, MappingMetaData> mappings = response.getMappings(); for (String key : mappings.keySet()) { System.out.println(key+"===" +mappings.get(key).getSourceAsMap()); } }
删除索引
@Test public void deleteIndex () throws IOException { IndicesClient indices = client.indices(); DeleteIndexRequest deleteRequest=new DeleteIndexRequest ("itheima" ); AcknowledgedResponse delete = indices.delete(deleteRequest, RequestOptions.DEFAULT); System.out.println(delete.isAcknowledged()); }
索引是否存在
@Test public void existIndex () throws IOException { IndicesClient indices = client.indices(); GetIndexRequest getIndexRequest=new GetIndexRequest ("itheima" ); boolean exists = indices.exists(getIndexRequest, RequestOptions.DEFAULT); System.out.println(exists); }
6.4-添加文档 1.添加文档,使用map作为数据
@Test public void addDoc1 () throws IOException { Map<String, Object> map=new HashMap <>(); map.put("name" ,"张三" ); map.put("age" ,"18" ); map.put("address" ,"北京二环" ); IndexRequest request=new IndexRequest ("itcast" ).id("1" ).source(map); IndexResponse response = client.index(request, RequestOptions.DEFAULT); System.out.println(response.getId()); }
2.添加文档,使用对象作为数据
@Test public void addDoc2 () throws IOException { Person person=new Person (); person.setId("2" ); person.setName("李四" ); person.setAge(20 ); person.setAddress("北京三环" ); String data = JSON.toJSONString(person); IndexRequest request=new IndexRequest ("itcast" ).id(person.getId()).source(data,XContentType.JSON); IndexResponse response = client.index(request, RequestOptions.DEFAULT); System.out.println(response.getId()); }
6.5-修改、查询、删除文档 1.修改文档:添加文档时,如果id存在则修改,id不存在则添加
@Test public void UpdateDoc () throws IOException { Person person=new Person (); person.setId("2" ); person.setName("李四" ); person.setAge(20 ); person.setAddress("北京三环车王" ); String data = JSON.toJSONString(person); IndexRequest request=new IndexRequest ("itcast" ).id(person.getId()).source(data,XContentType.JSON); IndexResponse response = client.index(request, RequestOptions.DEFAULT); System.out.println(response.getId()); }
2.根据id查询文档
@Test public void getDoc () throws IOException { GetRequest indexRequest=new GetRequest ("itcast" ,"2" ); GetResponse response = client.get(indexRequest, RequestOptions.DEFAULT); System.out.println(response.getSourceAsString()); }
3.根据id删除文档
@Test public void delDoc () throws IOException { DeleteRequest deleteRequest=new DeleteRequest ("itcast" ,"1" ); DeleteResponse response = client.delete(deleteRequest, RequestOptions.DEFAULT); System.out.println(response.getId()); }
7-ElasticSearch高级操作 7.1-bulk批量操作-脚本 脚本:
测试用的5号文档
POST /person1/_doc/5 { "name":"张三5号", "age":18, "address":"北京海淀区" }
批量操作文本
#批量操作 #1. 删除5 号 #新增8 号 #更新2 号 name为2 号 POST _bulk { "delete" : { "_index" : "person1" , "_id" : "5" } } { "create" : { "_index" : "person1" , "_id" : "8" } } { "name" : "八号" , "age" : 18 , "address" : "北京" } { "update" : { "_index" : "person1" , "_id" : "2" } } { "doc" : { "name" : "2号" } } #文档操作
结果
{ "took" : 51 , "errors" : true , "items" : [ { "delete" : { "_index" : "person1" , "_type" : "_doc" , "_id" : "5" , "_version" : 2 , "result" : "deleted" , "_shards" : { "total" : 2 , "successful" : 1 , "failed" : 0 } , "_seq_no" : 6 , "_primary_term" : 2 , "status" : 200 } } , { "create" : { "_index" : "person1" , "_type" : "_doc" , "_id" : "8" , "_version" : 1 , "result" : "created" , "_shards" : { "total" : 2 , "successful" : 1 , "failed" : 0 } , "_seq_no" : 7 , "_primary_term" : 2 , "status" : 201 } } , { "update" : { "_index" : "person1" , "_type" : "_doc" , "_id" : "2" , "_version" : 2 , "result" : "updated" , "_shards" : { "total" : 2 , "successful" : 1 , "failed" : 0 } , "_seq_no" : 10 , "_primary_term" : 2 , "status" : 200 } } ] }
7.2-bulk批量操作-JavaAPI @Autowired RestHighLevelClient client; @Test public void test2 () throws IOException { BulkRequest bulkRequest = new BulkRequest (); DeleteRequest deleteRequest=new DeleteRequest ("person1" ,"5" ); bulkRequest.add(deleteRequest); Map<String, Object> map=new HashMap <>(); map.put("name" ,"六号" ); IndexRequest indexRequest=new IndexRequest ("person1" ).id("6" ).source(map); bulkRequest.add(indexRequest); Map<String, Object> mapUpdate=new HashMap <>(); mapUpdate.put("name" ,"三号" ); UpdateRequest updateRequest=new UpdateRequest ("person1" ,"3" ).doc(mapUpdate); bulkRequest.add(updateRequest); BulkResponse response = client.bulk(bulkRequest, RequestOptions.DEFAULT); System.out.println(response.status()); }
7.3-导入数据-分析&创建索引 数据库
创建索引
PUT goods { "mappings" : { "properties" : { "title" : { "type" : "text" , "analyzer" : "ik_smart" } , "price" : { "type" : "double" } , "createTime" : { "type" : "date" } , "categoryName" : { "type" : "keyword" } , "brandName" : { "type" : "keyword" } , "spec" : { "type" : "object" } , "saleNum" : { "type" : "integer" } , "stock" : { "type" : "integer" } } } }
7.4-导入数据-代码实现 实体类
spec以map存储,通过specStr接收数据库信息
dao
实现
@Test public void test3 () throws IOException { List<Goods> goodsList = goodsMapper.findAll(); BulkRequest bulkRequest=new BulkRequest (); for (Goods goods : goodsList) { String specStr = goods.getSpecStr(); Map map = JSON.parseObject(specStr, Map.class); goods.setSpec(map); String data = JSON.toJSONString(goods); IndexRequest indexRequest=new IndexRequest ("goods" ).source(data,XContentType.JSON); bulkRequest.add(indexRequest); } BulkResponse response = client.bulk(bulkRequest, RequestOptions.DEFAULT); System.out.println(response.status()); }
若不想要spec字段,可以采用以下方式
7.5-导入数据-代码实现-详解(选放) 当传输的数据中有json字符串一定要换成map或者其他形式,不能直接传string,会报错,而且很难找!!!
转换成JSON的原因:
#spec配置的数据类型是JSON对象,所以当存放字符串的时候报错 "spec" : { "type" : "object" } ,
错误信息
8-ElasticSearch查询 8.1-matchAll-脚本 # 默认情况下,es一次展示10 条数据, 通过from和size来控制分页 # 查询结果详解 GET goods/_search { "query" : { "match_all" : { } } , "from" : 0 , "size" : 100 } GET goods
内容详解
8.2-matchAll-JavaAPI @Test public void testMatchAll () throws IOException { SearchRequest searchRequest = new SearchRequest ("goods" ); SearchSourceBuilder sourceBuilder = new SearchSourceBuilder (); QueryBuilder query = QueryBuilders.matchAllQuery(); sourceBuilder.query(query); searchRequest.source(sourceBuilder); sourceBuilder.from(0 ); sourceBuilder.size(100 ); SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT); SearchHits searchHits = searchResponse.getHits(); long value = searchHits.getTotalHits().value; System.out.println("总记录数:" +value); List<Goods> goodsList = new ArrayList <>(); SearchHit[] hits = searchHits.getHits(); for (SearchHit hit : hits) { String sourceAsString = hit.getSourceAsString(); Goods goods = JSON.parseObject(sourceAsString, Goods.class); goodsList.add(goods); } for (Goods goods : goodsList) { System.out.println(goods); } }
设置条件的疑问点
8.3-termQuery term查询和字段类型有关系,首先回顾一下ElasticSearch两个数据类型
ElasticSearch两个数据类型
text:会分词,不支持聚合 keyword:不会分词,将全部内容作为一个词条,支持聚合
term查询:不会对查询条件进行分词,适合差一些不用分词的索引 。
GET goods/_search { "query": { "term": { "title": { "value": "华为" } } } }
term查询,查询text类型字段时,只有其中的单词相匹配都会查到,text字段会对数据进行分词
例如:查询title 为“华为”的,title type 为text
查询categoryName 字段时,categoryName字段为keyword ,keyword:不会分词,将全部内容作为一个词条,
即完全匹配,才能查询出结果
GET goods/_search { "query" : { "term" : { "categoryName" : { "value" : "华为手机" } } } }
@Test public void testTermQuery () throws IOException { SearchRequest searchRequest = new SearchRequest ("goods" ); SearchSourceBuilder sourceBulider = new SearchSourceBuilder (); QueryBuilder query = QueryBuilders.termQuery("title" ,"华为" ); sourceBulider.query(query); searchRequest.source(sourceBulider); SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT); SearchHits searchHits = searchResponse.getHits(); long value = searchHits.getTotalHits().value; System.out.println("总记录数:" +value); List<Goods> goodsList = new ArrayList <>(); SearchHit[] hits = searchHits.getHits(); for (SearchHit hit : hits) { String sourceAsString = hit.getSourceAsString(); Goods goods = JSON.parseObject(sourceAsString, Goods.class); goodsList.add(goods); } for (Goods goods : goodsList) { System.out.println(goods); } }
8.4-matchQuery match查询:
会对查询条件进行分词。
然后将分词后的查询条件和词条进行等值匹配
默认取并集 (OR)
# match查询(取并集) GET goods/_search { "query" : { "match" : { "title" : "华为手机" } } , "size" : 500 } #取交集 GET goods/_search { "query" : { "match" : { "title" : { "query" : "华为手机" , "operator" : "操作(or/and)" } } } , "size" : 500 }
match 的默认搜索(or 并集)
例如:华为手机,会分词为 “华为”,“手机” 只要出现其中一个词条都会搜索到
match的 and(交集) 搜索
例如:例如:华为手机,会分词为 “华为”,“手机” 但要求“华为”,和“手机”同时出现在词条中
@Test public void testMatchQuery () throws IOException { SearchRequest searchRequest = new SearchRequest ("goods" ); SearchSourceBuilder sourceBulider = new SearchSourceBuilder (); MatchQueryBuilder query = QueryBuilders.matchQuery("title" , "华为手机" ); query.operator(Operator.AND); sourceBulider.query(query); searchRequest.source(sourceBulider); SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT); SearchHits searchHits = searchResponse.getHits(); long value = searchHits.getTotalHits().value; System.out.println("总记录数:" +value); List<Goods> goodsList = new ArrayList <>(); SearchHit[] hits = searchHits.getHits(); for (SearchHit hit : hits) { String sourceAsString = hit.getSourceAsString(); Goods goods = JSON.parseObject(sourceAsString, Goods.class); goodsList.add(goods); } for (Goods goods : goodsList) { System.out.println(goods); } }
总结:
term query会去倒排索引中寻找确切的term,它并不知道分词器的存在。这种查询适合keyword 、numeric 、date
match query知道分词器的存在。并且理解是如何被分词的
8.5-模糊查询-脚本 8.5.1-wildcard查询 wildcard查询:会对查询条件进行分词。还可以使用通配符 ?(任意单个字符) 和 * (0个或多个字符)
"*华*" 包含华字的 "华*" 华字后边多个字符 "华?" 华字后边多个字符 "*华"或"?华" 会引发全表(全索引)扫描 注意效率问题
# wildcard 查询。查询条件分词,模糊查询 GET goods/_search { "query" : { "wildcard" : { "title" : { "value" : "华*" } } } }
8.5.2正则查询 \W:匹配包括下划线的任何单词字符,等价于 [A-Z a-z 0-9_] 开头的反斜杠是转义符 +号多次出现 (.)*为任意字符 正则查询取决于正则表达式的效率
GET goods/_search { "query" : { "regexp" : { "title" : "\\w+(.)*" } } }
8.5.3前缀查询 对keyword类型支持比较好
# 前缀查询 对keyword类型支持比较好 GET goods/_search { "query" : { "prefix" : { "brandName" : { "value" : "三" } } } }
8.6-模糊查询-JavaAPI @Test public void testWildcardQuery () throws IOException { SearchRequest searchRequest = new SearchRequest ("goods" ); SearchSourceBuilder sourceBulider = new SearchSourceBuilder (); WildcardQueryBuilder query = QueryBuilders.wildcardQuery("title" , "华*" ); sourceBulider.query(query); searchRequest.source(sourceBulider); SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT); SearchHits searchHits = searchResponse.getHits(); long value = searchHits.getTotalHits().value; System.out.println("总记录数:" +value); List<Goods> goodsList = new ArrayList <>(); SearchHit[] hits = searchHits.getHits(); for (SearchHit hit : hits) { String sourceAsString = hit.getSourceAsString(); Goods goods = JSON.parseObject(sourceAsString, Goods.class); goodsList.add(goods); } for (Goods goods : goodsList) { System.out.println(goods); } } @Test public void testRegexpQuery () throws IOException { SearchRequest searchRequest = new SearchRequest ("goods" ); SearchSourceBuilder sourceBulider = new SearchSourceBuilder (); RegexpQueryBuilder query = QueryBuilders.regexpQuery("title" , "\\w+(.)*" ); sourceBulider.query(query); searchRequest.source(sourceBulider); SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT); SearchHits searchHits = searchResponse.getHits(); long value = searchHits.getTotalHits().value; System.out.println("总记录数:" +value); List<Goods> goodsList = new ArrayList <>(); SearchHit[] hits = searchHits.getHits(); for (SearchHit hit : hits) { String sourceAsString = hit.getSourceAsString(); Goods goods = JSON.parseObject(sourceAsString, Goods.class); goodsList.add(goods); } for (Goods goods : goodsList) { System.out.println(goods); } } @Test public void testPrefixQuery () throws IOException { SearchRequest searchRequest = new SearchRequest ("goods" ); SearchSourceBuilder sourceBulider = new SearchSourceBuilder (); PrefixQueryBuilder query = QueryBuilders.prefixQuery("brandName" , "三" ); sourceBulider.query(query); searchRequest.source(sourceBulider); SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT); SearchHits searchHits = searchResponse.getHits(); long value = searchHits.getTotalHits().value; System.out.println("总记录数:" +value); List<Goods> goodsList = new ArrayList <>(); SearchHit[] hits = searchHits.getHits(); for (SearchHit hit : hits) { String sourceAsString = hit.getSourceAsString(); Goods goods = JSON.parseObject(sourceAsString, Goods.class); goodsList.add(goods); } for (Goods goods : goodsList) { System.out.println(goods); } }
8.7-范围&排序查询 # 范围查询 GET goods/_search { "query" : { "range" : { "price" : { "gte" : 2000 , #大于等于 "lte" : 3000 #小于等于 } } } , "sort" : [ { "price" : { "order" : "desc" } } ] }
@Test public void testRangeQuery () throws IOException { SearchRequest searchRequest = new SearchRequest ("goods" ); SearchSourceBuilder sourceBulider = new SearchSourceBuilder (); RangeQueryBuilder query = QueryBuilders.rangeQuery("price" ); query.gte(2000 ); query.lte(3000 ); sourceBulider.query(query); sourceBulider.sort("price" , SortOrder.DESC); searchRequest.source(sourceBulider); SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT); SearchHits searchHits = searchResponse.getHits(); long value = searchHits.getTotalHits().value; System.out.println("总记录数:" +value); List<Goods> goodsList = new ArrayList <>(); SearchHit[] hits = searchHits.getHits(); for (SearchHit hit : hits) { String sourceAsString = hit.getSourceAsString(); Goods goods = JSON.parseObject(sourceAsString, Goods.class); goodsList.add(goods); } for (Goods goods : goodsList) { System.out.println(goods); } }
8.8-queryString多条件查询 queryString 多条件查询
会对查询条件进行分词。
然后将分词后的查询条件和词条进行等值匹配
默认取并集(OR)
可以指定多个查询字段
query_string:识别query中的连接符 (or 、and)
# queryString GET goods/_search { "query" : { "query_string" : { "fields" : [ "title" , "categoryName" , "brandName" ] , #多个字段 "query" : "华为 AND 手机" } } }
simple_query_string:不识别query中的连接符 (or 、and),查询时会将 “华为”、”and”、“手机”分别进行查询
GET goods/_search { "query": { "simple_query_string": { "fields": ["title","categoryName","brandName"], "query": "华为 AND 手机" } } }
query_string:有default_operator连接符的脚本
GET goods/_search { "query" : { "query_string" : { "fields" : [ "title" , "brandName" , "categoryName" ] , "query" : "华为手机 " , "default_operator" : "AND" } } }
simple_query_string:有default_operator连接符的脚本
GET goods/_search { "query" : { "simple_query_string" : { "fields" : [ "title" , "brandName" , "categoryName" ] , "query" : "华为手机 " , "default_operator" : "OR" } } }
java代码
@Test public void testQueryStringQuery () throws IOException { SearchRequest searchRequest = new SearchRequest ("goods" ); SearchSourceBuilder sourceBulider = new SearchSourceBuilder (); QueryStringQueryBuilder query = QueryBuilders.queryStringQuery("华为手机" ).field("title" ).field("categoryName" ).field("brandName" ).defaultOperator(Operator.AND); sourceBulider.query(query); searchRequest.source(sourceBulider); SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT); SearchHits searchHits = searchResponse.getHits(); long value = searchHits.getTotalHits().value; System.out.println("总记录数:" +value); List<Goods> goodsList = new ArrayList <>(); SearchHit[] hits = searchHits.getHits(); for (SearchHit hit : hits) { String sourceAsString = hit.getSourceAsString(); Goods goods = JSON.parseObject(sourceAsString, Goods.class); goodsList.add(goods); } for (Goods goods : goodsList) { System.out.println(goods); } }
注意:query中的or and 是查询时 匹配条件是否同时出现—-or 出现一个即可,and 两个条件同时出现
default_operator的or and 是对结果进行 并集(or)、交集(and)
8.9-布尔查询-脚本 boolQuery:对多个查询条件连接。连接方式:
得分: 即条件匹配度,匹配度越高,得分越高(计算得分比较耗时间)
# boolquery #must和filter配合使用时,max_score(得分)是显示的 #must 默认数组形式 GET goods/_search { "query" : { "bool" : { #声明布尔查询 "must" : [ #布尔查询的连接方式 { "term" : { "brandName" : { "value" : "华为" } } } ] , "filter" : [ #过滤 { "term" : { "title" : "手机" } } , { "range" : { "price" : { "gte" : 2000 , "lte" : 3000 } } } ] } } } #filter 单独使用 filter可以是单个条件,也可多个条件(数组形式) GET goods/_search { "query" : { "bool" : { "filter" : [ { "term" : { "brandName" : { "value" : "华为" } } } ] } } }
8.10-布尔查询-JavaAPI 布尔查询:boolQuery
查询品牌名称为:华为
查询标题包含:手机
查询价格在:2000-3000
must 、filter为连接方式
term、match为不同的查询方式
@Test public void testBoolQuery () throws IOException { SearchRequest searchRequest = new SearchRequest ("goods" ); SearchSourceBuilder sourceBulider = new SearchSourceBuilder (); BoolQueryBuilder query = QueryBuilders.boolQuery(); QueryBuilder termQuery = QueryBuilders.termQuery("brandName" ,"华为" ); query.must(termQuery); QueryBuilder matchQuery = QueryBuilders.matchQuery("title" ,"手机" ); query.filter(matchQuery); QueryBuilder rangeQuery = QueryBuilders.rangeQuery("price" ); ((RangeQueryBuilder) rangeQuery).gte(2000 ); ((RangeQueryBuilder) rangeQuery).lte(3000 ); query.filter(rangeQuery); sourceBulider.query(query); searchRequest.source(sourceBulider); SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT); SearchHits searchHits = searchResponse.getHits(); long value = searchHits.getTotalHits().value; System.out.println("总记录数:" +value); List<Goods> goodsList = new ArrayList <>(); SearchHit[] hits = searchHits.getHits(); for (SearchHit hit : hits) { String sourceAsString = hit.getSourceAsString(); Goods goods = JSON.parseObject(sourceAsString, Goods.class); goodsList.add(goods); } for (Goods goods : goodsList) { System.out.println(goods); } }
8.11-聚合查询-脚本
# 聚合查询 # 指标聚合 聚合函数 GET goods/_search { "query" : { "match" : { "title" : "手机" } } , "aggs" : { "max_price" : { #自己起的名字 "max" : { "field" : "price" } } } } #查询price最大的“手机”
# 桶聚合 分组 GET goods/_search { "query" : { "match" : { "title" : "手机" } } , "aggs" : { "goods_brands" : { #自己起的名字 "terms" : { #分组 "field" : "brandName" , "size" : 100 #前一百条 } } } } #查询“手机”有多少个brandName
8.12-聚合查询-JavaAPI 聚合查询:桶聚合,分组查询
查询title包含手机的数据
查询品牌列表
@Test public void testAggQuery () throws IOException { SearchRequest searchRequest=new SearchRequest ("goods" ); SearchSourceBuilder sourceBuilder=new SearchSourceBuilder (); MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("title" , "手机" ); sourceBuilder.query(queryBuilder); AggregationBuilder aggregation=AggregationBuilders.terms("goods_brands" ).field("brandName" ).size(100 ); sourceBuilder.aggregation(aggregation); searchRequest.source(sourceBuilder); SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT); SearchHits hits = searchResponse.getHits(); Long total= hits.getTotalHits().value; System.out.println("总数:" +total); Aggregations aggregations = searchResponse.getAggregations(); Map<String, Aggregation> aggregationMap = aggregations.asMap(); Terms goods_brands = (Terms) aggregationMap.get("goods_brands" ); List<? extends Terms .Bucket> buckets = goods_brands.getBuckets(); Map<String,Object>map=new HashMap <>(); for (Terms.Bucket bucket : buckets) { System.out.println(bucket.getKey()); map.put(bucket.getKeyAsString(),bucket.getDocCount()); } System.out.println(map); }
8.13-高亮查询-脚本 高亮三要素:
默认前后缀 :em
GET goods/_search { #要包含的字段 "query" : { "match" : { "title" : "电视" } } , "highlight" : { #包含字段的样式 "fields" : { "title" : { "pre_tags" : "<font color='red'>" , #前缀 "post_tags" : "</font>" #后缀 } } } }
8.14-高亮查询-JavaAPI @Test public void testHighLightQuery () throws IOException { SearchRequest searchRequest = new SearchRequest ("goods" ); SearchSourceBuilder sourceBulider = new SearchSourceBuilder (); MatchQueryBuilder query = QueryBuilders.matchQuery("title" , "手机" ); sourceBulider.query(query); HighlightBuilder highlighter = new HighlightBuilder (); highlighter.field("title" ); highlighter.preTags("<font color='red'>" ); highlighter.postTags("</font>" ); sourceBulider.highlighter(highlighter); searchRequest.source(sourceBulider); SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT); SearchHits searchHits = searchResponse.getHits(); long value = searchHits.getTotalHits().value; System.out.println("总记录数:" +value); List<Goods> goodsList = new ArrayList <>(); SearchHit[] hits = searchHits.getHits(); for (SearchHit hit : hits) { String sourceAsString = hit.getSourceAsString(); Goods goods = JSON.parseObject(sourceAsString, Goods.class); Map<String, HighlightField> highlightFields = hit.getHighlightFields(); HighlightField HighlightField = highlightFields.get("title" ); Text[] fragments = HighlightField.fragments(); goods.setTitle(fragments[0 ].toString()); goodsList.add(goods); } for (Goods goods : goodsList) { System.out.println(goods); } }
8.15-重建索引&索引别名 #查询别名 默认别名无法查看,默认别名同索引名 GET goods/_alias/ #结果 { "goods" : { "aliases" : { } } }
1.新建student_index_v1索引
# -------重建索引----------- # 新建student_index_v1。索引名称必须全部小写 PUT student_index_v1 { "mappings" : { "properties" : { "birthday" : { "type" : "date" } } } } #查看 student_index_v1 结构 GET student_index_v1 #添加数据 PUT student_index_v1/_doc/1 { "birthday" : "1999-11-11" } #查看数据 GET student_index_v1/_search #添加数据 PUT student_index_v1/_doc/1 { "birthday" : "1999年11月11日" }
2.重建索引:将student_index_v1 数据拷贝到 student_index_v2
# 业务变更了,需要改变birthday字段的类型为text # 1. 创建新的索引 student_index_v2 # 2. 将student_index_v1 数据拷贝到 student_index_v2 # 创建新的索引 student_index_v2 PUT student_index_v2 { "mappings" : { "properties" : { "birthday" : { "type" : "text" } } } } # 将student_index_v1 数据拷贝到 student_index_v2 # _reindex 拷贝数据 POST _reindex { "source" : { "index" : "student_index_v1" } , "dest" : { "index" : "student_index_v2" } } GET student_index_v2/_search PUT student_index_v2/_doc/2 { "birthday" : "1999年11月11日" }
3.创建索引库别名:
注意:DELETE student_index_v1 这一操作将删除student_index_v1索引库,并不是删除别名
# 思考: 现在java代码中操作es,还是使用的实student_index_v1老的索引名称。 # 1. 改代码(不推荐) # 2. 索引别名(推荐) # 步骤: # 0. 先删除student_index_v1 # 1. 给student_index_v2起个别名 student_index_v1 # 先删除student_index_v1 #DELETE student_index_v1 这一操作将删除student_index_v1索引库 #索引库默认的别名与索引库同名,无法删除 # 给student_index_v1起个别名 student_index_v11 POST student_index_v2/_alias/student_index_v11 #测试删除命令 POST /_aliases { "actions" : [ { "remove" : { "index" : "student_index_v1" , "alias" : "student_index_v11" } } ] } # 给student_index_v2起个别名 student_index_v1 POST student_index_v2/_alias/student_index_v1 #查询别名 GET goods/_alias/ GET student_index_v1/_search GET student_index_v2/_search
9-ElasticSearch 集群 9.1-集群介绍
集群和分布式:
集群:多个人做一样的事。
分布式:多个人做不一样的事
集群解决的问题:
分布式解决的问题:
集群和分布式架构往往是并存的
9.2-ES集群相关概念 es 集群特点:
ES集群相关概念 :
集群(cluster):一组拥有共同的 cluster name 的 节点。
节点(node) :集群中的一个 Elasticearch 实例
索引(index) :es存储数据的地方。相当于关系数据库中的database概念
分片(shard) :索引可以被拆分为不同的部分进行存储,称为分片。在集群环境下,一个索引的不同分片可以拆分到不同的节点中
主分片(Primary shard):相对于副本分片的定义。
副本分片(Replica shard)每个主分片可以有一个或者多个副本,数据和主分片一样。
9.3-集群搭建 1.1 搭建集群 Elasticsearch如果做集群的话Master节点至少三台服务器或者三个Master实例加入相同集群,三个Master节点最多只能故障一台Master节点,如果故障两个Master节点,Elasticsearch将无法组成集群.会报错,Kibana也无法启动,因为Kibana无法获取集群中的节点信息。
由于,我们使用只有一台虚拟机,所以我们在虚拟机中安装三个ES实例,搭建伪集群,而ES启动比较耗内存,所以先设置虚拟机的内存3G和CPU个数4个
1.1.1 整体步骤 步骤如下:
拷贝opt目录下的elasticsearch-7.4.0安装包3个,分别命名:
elasticsearch-7.4.0-itcast1
elasticsearch-7.4.0-itcast2
elasticsearch-7.4.0-itcast3
然后修改elasticsearch.yml文件件。
然后启动启动itcast1、itcast2、itcast3三个节点。
打开浏览器输⼊:http://192.168.149.135:9200/_cat/health?v ,如果返回的node.total是3,代表集 群搭建成功
在此,需要我们特别注意的是,像本文这样单服务器多节点( 3 个节点)的情况,仅供测试使用 ,集群环境如下:
cluster name
node name
IP Addr
http端口 / 通信端口
itcast-es
itcast1
192.168.149.135
9201 / 9700
itcast-es
itcast2
192.168.149.135
9202 / 9800
itcast-es
itcast3
192.168.149.135
9203 / 9900
1.1.2 拷贝副本 拷贝opt目录下的elasticsearch-7.4.0安装包3个,打开虚拟机到opt目录
执行 拷贝三份
cd /opt cp -r elasticsearch-7.4.0 elasticsearch-7.4.0-itcast1 cp -r elasticsearch-7.4.0 elasticsearch-7.4.0-itcast2 cp -r elasticsearch-7.4.0 elasticsearch-7.4.0-itcast3
1.1. 3 修改elasticsearch.yml配置文件 1)、创建日志目录
cd /opt mkdir logs mkdir data # 授权给itheima用户 chown -R itheima:itheima ./logs chown -R itheima:itheima ./data chown -R itheima:itheima ./elasticsearch-7.4.0-itcast1 chown -R itheima:itheima ./elasticsearch-7.4.0-itcast2 chown -R itheima:itheima ./elasticsearch-7.4.0-itcast3
打开elasticsearch.yml配置,分别配置下面三个节点的配置文件
vim /opt/elasticsearch-7.4.0-itcast1/config/elasticsearch.yml vim /opt/elasticsearch-7.4.0-itcast2/config/elasticsearch.yml vim /opt/elasticsearch-7.4.0-itcast3/config/elasticsearch.yml
2)、下面是elasticsearch-7.4.0-itcast1配置文件
cluster.name: itcast-es node.name: itcast-1 node.master: true node.data: true node.max_local_storage_nodes: 3 network.host: 0.0 .0 .0 http.port: 9201 transport.tcp.port: 9700 discovery.seed_hosts: ["localhost:9700" ,"localhost:9800" ,"localhost:9900" ]cluster.initial_master_nodes: ["itcast-1" , "itcast-2" ,"itcast-3" ]path.data: /opt/data path.logs: /opt/logs
cluster.name: itcast-es node.name: itcast-1 node.master: true node.data: true node.max_local_storage_nodes: 3 network.host: 0.0 .0 .0 http.port: 9201 transport.tcp.port: 9700 discovery.seed_hosts: ["localhost:9700" ,"localhost:9800" ,"localhost:9900" ]cluster.initial_master_nodes: ["itcast-1" , "itcast-2" ,"itcast-3" ] path.data: /opt/data path.logs: /opt/logs
3)、下面是elasticsearch-7.4.0-itcast2配置文件
cluster.name: itcast-es node.name: itcast-2 node.master: true node.data: true node.max_local_storage_nodes: 3 network.host: 0.0 .0 .0 http.port: 9202 transport.tcp.port: 9800 discovery.seed_hosts: ["localhost:9700" ,"localhost:9800" ,"localhost:9900" ]cluster.initial_master_nodes: ["itcast-1" , "itcast-2" ,"itcast-3" ]path.data: /opt/data path.logs: /opt/logs
cluster.name: itcast-es node.name: itcast-2 node.master: true node.data: true node.max_local_storage_nodes: 3 network.host: 0.0 .0 .0 http.port: 9202 transport.tcp.port: 9800 discovery.seed_hosts: ["localhost:9700" ,"localhost:9800" ,"localhost:9900" ]cluster.initial_master_nodes: ["itcast-1" , "itcast-2" ,"itcast-3" ] path.data: /opt/data path.logs: /opt/logs
4)、下面是elasticsearch-7.4.0-itcast3 配置文件
cluster.name: itcast-es node.name: itcast-3 node.master: true node.data: true node.max_local_storage_nodes: 3 network.host: 0.0 .0 .0 http.port: 9203 transport.tcp.port: 9900 discovery.seed_hosts: ["localhost:9700" ,"localhost:9800" ,"localhost:9900" ]cluster.initial_master_nodes: ["itcast-1" , "itcast-2" ,"itcast-3" ] path.data: /opt/data path.logs: /opt/logs
cluster.name: itcast-es node.name: itcast-3 node.master: true node.data: true node.max_local_storage_nodes: 3 network.host: 0.0 .0 .0 http.port: 9203 transport.tcp.port: 9900 discovery.seed_hosts: ["localhost:9700" ,"localhost:9800" ,"localhost:9900" ]cluster.initial_master_nodes: ["itcast-1" , "itcast-2" ,"itcast-3" ] path.data: /opt/data path.logs: /opt/logs
1.1.4 执行授权 在root用户下执行 chown -R itheima:itheima /opt/elasticsearch-7.4.0-itcast1 chown -R itheima:itheima /opt/elasticsearch-7.4.0-itcast2 chown -R itheima:itheima /opt/elasticsearch-7.4.0-itcast3 如果有的日志文件授权失败,可使用(也是在root下执行) cd /opt/elasticsearch-7.4.0-itcast1/logs chown -R itheima:itheima ./* cd /opt/elasticsearch-7.4.0-itcast2/logs chown -R itheima:itheima ./* cd /opt/elasticsearch-7.4.0-itcast3/logs chown -R itheima:itheima ./*
1.1.5 启动三个节点 启动之前,设置ES的JVM占用内存参数,防止内存不足错误
vim /opt/elasticsearch-7.4.0-itcast1/bin/elasticsearch
可以发现,ES启动时加载/config/jvm.options文件
vim /opt/elasticsearch-7.4.0-itcast1/config/jvm.options
默认情况下,ES启动JVM最小内存1G,最大内存1G
修改为256m
启动成功访问节点一:
可以从日志中看到:master not discovered yet。还没有发现主节点
访问集群状态信息 http://192.168.149.135:9201/_cat/health?v 不成功
启动成功访问节点二:
可以从日志中看到:master not discovered yet。还没有发现主节点master node changed.已经选举出主节点itcast-2
访问集群状态信息 http://192.168.149.135:9201/_cat/health?v 成功
健康状况结果解释: cluster 集群名称 status 集群状态 green代表健康; yellow代表分配了所有主分片,但至少缺少一个副本,此时集群数据仍旧完整; red 代表部分主分片不可用,可能已经丢失数据。 node.total代表在线的节点总数量 node.data代表在线的数据节点的数量 shards 存活的分片数量 pri 存活的主分片数量 正常情况下 shards的数量是pri的两倍。 relo迁移中的分片数量,正常情况为 0 init 初始化中的分片数量 正常情况为 0 unassign未分配的分片 正常情况为 0 pending_ tasks准备中的任务,任务指迁移分片等 正常情况为 0 max_ task_ wait_ time任务最长等待时间 active_ shards_ percent正常分片百分比 正常情况为 100
启动成功访问节点三
访问集群状态信息 http://192.168.149.135:9201/_cat/health?v 成功
可以看到节点已经变为3个,至此,ES集群已经搭建成功~
1.2 使用Kibana配置和管理集群 1.2.1 集群配置 因为之前我们在单机演示的时候也使用到了Kibana,我们先复制出来一个Kibana,然后修改它的集群配置
cd /opt/ cp -r kibana-7.4.0-linux-x86_64 kibana-7.4.0-linux-x86_64-cluster # 由于 kibana 中文件众多,此处会等待大约1分钟的时间
修改Kibana的集群配置
vim kibana-7.4.0-linux-x86_64-cluster/config/kibana.yml 加入下面的配置 elasticsearch.hosts: ["http://localhost:9201","http://localhost:9202","http://localhost:9203"]
启动Kibana
1.2.2 管理集群 1、打开Kibana,点开 Stack Monitoring 集群监控
2、点击【Nodes】查看节点详细信息
在上图可以看到,第一个红框处显示【Green】,绿色,表示集群处理健康状态
第二个红框是我们集群的三个节点,注意,itcast-3旁边是星星,表示是主节点
9.4-kibina管理集群 vim kibana-7.4.0-linux-x86_64-cluster/config/kibana.yml
kibana.yml
i18n.locale: "zh-CN" server.port: 5602 server.host: "0.0.0.0" server.name: "kibana-itcast-cluster" elasticsearch.hosts: ["http://localhost:9201" ,"http://localhost:9202" ,"http://localhost:9203" ]elasticsearch.requestTimeout: 99999
9.5-JavaAPI 访问集群 PUT cluster_test { "mappings" : { "properties" : { "name" : { "type" : "text" } } } } GET cluster_test GET cluster_test/_search POST /cluster_test/_doc/1 { "name" : "张三" }
测试类
@Resource(name="clusterClient") RestHighLevelClient clusterClient; @Test public void testCluster () throws IOException { GetRequest indexRequest=new GetRequest ("cluster_test" ,"1" ); GetResponse response = clusterClient.get(indexRequest, RequestOptions.DEFAULT); System.out.println(response.getSourceAsString()); }
ElasticSearchConfig
private String host1;private int port1;private String host2;private int port2;private String host3;private int port3;@Bean("clusterClient") public RestHighLevelClient clusterClient () { return new RestHighLevelClient (RestClient.builder( new HttpHost (host1,port1,"http" ), new HttpHost (host2,port2,"http" ), new HttpHost (host3,port3,"http" ) )); }
application.yml
elasticsearch: host: 192.168 .140 .130 port: 9200 host1: 192.168 .140 .130 port1: 9201 host2: 192.168 .140 .130 port2: 9202 host3: 192.168 .140 .130 port3: 9203
9.6-分片配置
分片配置
#分片配置 #"number_of_shards" : 3 , 主分片数量 #"number_of_replicas" : 1 主分片备份数量,每一个主分片有一个备份 # 3 个主分片+3 个副分片=6 个分片 PUT cluster_test1 { "settings" : { "number_of_shards" : 3 , #主分片 "number_of_replicas" : 1 #每一个主分片有一个备份分片 } , "mappings" : { "properties" : { "name" : { "type" : "text" } } } }
三个节点正常运行(0、1、2分片标号)
itcast-3 挂掉
将挂掉节点的分片,自平衡到其他节点
itcast-3 恢复正常后,节点分片将自平衡回去(并不一定是原来的分片)
分片与自平衡
注意:分片数量一旦确定好,不能修改。
索引分片推荐配置方案:
1.每个分片推荐大小10-30GB
2.分片数量推荐 = 节点数量 * 1~3倍
思考:比如有1000GB数据,应该有多少个分片?多少个节点
1.每个分片20GB 则可以分为40个分片
2.分片数量推荐 = 节点数量 * 1~3倍 –> 40/2=20 即20个节点
9.7-路由原理 路由原理
文档存入对应的分片,ES计算分片编号的过程 ,称为路由。
Elasticsearch 是怎么知道一个文档应该存放到哪个分片中呢?
查询时,根据文档id查询文档, Elasticsearch 又该去哪个分片中查询数据呢?
路由算法 :shard_index = hash(id) % number_of_primary_shards
分片角标的索引 = 经过hash处理的文档id % 主分片的数量
查询id为5的文档:假如hash(5)=17 ,根据算法17%3=2
9.8-脑裂 ElasticSearch 集群正常状态:
脑裂现象:
脑裂问题的出现就是因为从节点在选择主节点上出现分歧导致一个集群出现多个主节点从而使集群分裂,使得集群处于异常状态。
脑裂产生的原因:
网络原因:网络延迟
一般es集群会在内网部署,也可能在外网部署,比如阿里云。
内网一般不会出现此问题,外网的网络出现问题的可能性大些。
节点负载
主节点的角色既为master又为data。数据访问量较大时,可能会导致Master节点停止响应(假死状态)。
JVM内存回收
当Master节点设置的JVM内存较小时,引发JVM的大规模内存回收,造成ES进程失去响应。
避免脑裂 :
1.网络原因:discovery.zen.ping.timeout 超时时间配置大一点。默认是3S(发现超时的时间)
2.节点负载:角色分离策略
候选主节点配置为
node.master: true
node.data: false
数据节点配置为
node.master: false
node.data: true
3.JVM内存回收:修改 config/jvm.options 文件的 -Xms 和 -Xmx 为服务器的内存一半。、
9.9-ElasticSearch 集群-集群扩容 按照集群搭建步骤再复制Es节点进行配置,参见ElasticSearch 集群-集群搭建.md
10.logstach Logstash是ES下的一款开源软件,它能够同时 从多个来源采集数据、转换数据,然后将数据发送到Eleasticsearch中创建索引。
本项目使用Logstash将MySQL中的数据采用到ES索引中。
10.1 下载Logstash 下载Logstash6.2.1版本,和本项目使用的Elasticsearch6.2.1版本一致。
解压:
logstash-input-jdbc 是ruby开发的,先下载ruby并安装
下载地址: https://rubyinstaller.org/downloads/
下载2.5版本即可。
安装完成查看是否安装成功
Logstash5.x以上版本本身自带有logstash-input-jdbc,6.x版本本身不带logstash-input-jdbc插件,需要手动安装
安装成功后我们可以在logstash根目录下的以下目录查看对应的插件版本
解压老师提供的logstash-6.2.1.zip,此logstash中已集成了logstash-input-jdbc插件。
10.3 创建模板文件 Logstash的工作是从MySQL中读取数据,向ES中创建索引,这里需要提前创建mapping的模板文件以便logstash使用。
在logstach的config目录创建user_test.json,内容如下:
本教程的user_test.json目录是:D:/ElasticSearch/logstash-6.2.1/config/user_test.json
{ "mappings" : { "doc" : { "properties" : { "sex" : { "type" : "keyword" }, "username" : { "analyzer" : "ik_max_word", "search_analyzer" : "ik_smart", "type" : "text" }, "birthday" : { "format" : "yyyy-MM-dd HH:mm:ss", "type" : "date" }, "address" : { "analyzer" : "ik_max_word", "search_analyzer" : "ik_smart", "type" : "text" } } } }, "template" : "xc_course" }
10.4 配置mysql.conf 在logstash的config目录下配置mysql.conf文件供logstash使用,logstash会根据mysql.conf文件的配置的地址从MySQL中读取数据向ES中写入索引。
参考https://www.elastic.co/guide/en/logstash/current/plugins-inputs-jdbc.html
配置输入数据源和输出数据源。
input { stdin { } jdbc { jdbc_connection_string => "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC" # the user we wish to excute our statement as jdbc_user => "root" jdbc_password => "59852369" # the path to our downloaded jdbc driver jdbc_driver_library => "D:/repository/mysql/mysql-connector-java/5.1.40/mysql-connector-java-5.1.40.jar" # the name of the driver class for mysql jdbc_driver_class => "com.mysql.jdbc.Driver" jdbc_paging_enabled => "true" jdbc_page_size => "50000" #要执行的sql文件 #statement_filepath => "/conf/course.sql" statement => "select * from user" #定时配置 schedule => "* * * * *" record_last_run => true last_run_metadata_path => "G:/Elastic/logstash/logstash-6.2.1/config/logstash_metadata" } } output { elasticsearch { #ES的ip地址和端口 hosts => "localhost:9200" #hosts => ["localhost:9200","localhost:9202","localhost:9203"] #ES索引库名称 index => "user-index" document_id => "%{id}" document_type => "doc" template =>"G:/Elastic/logstash/logstash-6.2.1/config/user_test.json" template_name =>"test" template_overwrite =>"true" } stdout { #日志输出 codec => json_lines } }
说明:
1、ES采用UTC时区问题
ES采用UTC 时区,比北京时间早8小时,所以ES读取数据时让最后更新时间加8小时
where timestamp > date_add(:sql_last_value,INTERVAL 8 HOUR)
2、logstash每个执行完成会在D:/ElasticSearch/logstash-6.2.1/config/logstash_metadata记录执行时间下次以此时间为基准进行增量同步数据到索引库。
10.5 测试 启动logstash.bat:
.\logstash.bat -f ..\config\mysql.conf