Elasticsearch
Elasticsearch简介与安装
什么是Elasticsearch?
ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。
Lucene与Elasticsearch关系?
Lucene不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎。想要使用它,你必须使用Java来作为开发语言并将其直接集成到你的应用中,更糟糕的是,Lucene非常复杂,你需要深入了解检索的相关知识来理解它是如何工作的。
Elasticsearch也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。
当然Elasticsearch并不仅仅是Lucene这么简单,它不但包括了全文搜索功能,还可以进行以下工作:
-
分布式实时文件存储,并将每一个字段都编入索引,使其可以被搜索。
-
实时分析的分布式搜索引擎。
-
可以扩展到上百台服务器,处理PB级别的结构化或非结构化数据。
这么多的功能被集成到一台服务器上,你可以轻松地通过客户端或者任何你喜欢的程序语言与ES的RESTful API进行交流。
Elasticsearch的上手是非常简单的。它附带了很多非常合理的默认值,这让初学者很好地避免一上手就要面对复杂的理论,
它安装好了就可以使用了,用很小的学习成本就可以变得很有生产力。
随着越学越深入,还可以利用Elasticsearch更多高级的功能,整个引擎可以很灵活地进行配置。可以根据自身需求来定制属于自己的Elasticsearch。
Elasticsearch与Solr对比
优缺点
Elasticsearch
优点
-
Elasticsearch是分布式的。不需要其他组件,分发是实时的,被叫做”Push replication”。
-
Elasticsearch 完全支持 Apache Lucene 的接近实时的搜索。
-
处理多租户(multitenancy)不需要特殊配置,而Solr则需要更多的高级设置。
-
Elasticsearch 采用 Gateway 的概念,使得完备份更加简单。
-
各节点组成对等的网络结构,某些节点出现故障时会自动分配其他节点代替其进行工作。
缺点
- 还不够自动(不适合当前新的Index Warmup API,即冷启动/预热数据的方式。当系统长期处于低水位的情况下,流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。)
Solr
简介
Solr是Apache Lucene项目的开源企业搜索平台。其主要功能包括全文检索、命中标示、分面搜索、动态聚类、数据库集成,以及富文本(如Word、PDF)的处理。Solr是高度可扩展的,并提供了分布式搜索和索引复制。Solr是最流行的企业级搜索引擎,Solr4 还增加了NoSQL支持。
Solr是用Java编写、运行在Servlet容器(如 Apache Tomcat 或Jetty)的一个独立的全文搜索服务器。 Solr采用了 Lucene Java 搜索库为核心的全文索引和搜索,并具有类似REST的HTTP/XML和JSON的API。Solr强大的外部配置功能使得无需进行Java编码,便可对 其进行调整以适应多种类型的应用程序。Solr有一个插件架构,以支持更多的高级定制。
2010年 Apache Lucene 和 Apache Solr 项目合并,两个项目是由同一个Apache软件基金会开发团队制作实现的。提到技术或产品时,Lucene/Solr或Solr/Lucene是一样的。
现实生活中我们都知道大多数网站或应用都必须具有某种搜索功能,问题是搜索功能往往是巨大的资源消耗并且它们由于沉重的数据库加载而拖垮你的应用的性能。
这就是为什么转移负载到一个外部的搜索服务器是一个不错的主意,Apache Solr是一个独立的**企业级搜索应用服务器,**它对外提供类似于Web-service的API接口。用户可以通过http请求,向搜索引擎服务器提交一定格式的XML文件,生成索引;也可以通过Http Get操作提出查找请求,并得到XML格式的返回结果。它通过使用类似REST的HTTP API,确保你能从几乎任何编程语言来使用Solr。
优点
-
Solr有一个更大、更成熟的用户、开发和贡献者社区。
-
支持添加多种格式的索引,如:HTML、PDF、微软 Office 系列软件格式以及 JSON、XML、CSV 等纯文本格式。
-
Solr比较成熟、稳定。
-
不考虑建索引的同时进行搜索,速度更快。
缺点
- 建立索引时,搜索效率下降,实时索引搜索效率不高。
性能
当单纯的对已有数据进行搜索时,Solr更快。
当实时建立索引时, Solr会产生io阻塞,查询性能较差, Elasticsearch具有明显的优势。
随着数据量的增加,Solr的搜索效率会变得更低,而Elasticsearch却没有明显的变化。
综上所述,Solr的架构不适合实时搜索的应用。
实际生产环境测试
下图为将搜索引擎从Solr转到Elasticsearch以后的平均查询速度有了50倍的提升。
热度
可以百度指数—大数据分享平台进行比对:
https://index.baidu.com/v2/main/index.html#/trend/elasticsearch?words=elasticsearch,solr
结果如下:
Elasticsearch与关系型数据库对比
-
一个ES集群可以包含多个索引(数据库),每个索引又包含了很多类型(ES7中已作废),类型中包含了很多文档(行),每个文档又包含了很多字段(列)。
-
传统数据库为特定列增加一个索引,例如B-Tree索引来加速检索。Elasticsearch和Lucene使用一种叫做倒排索引(inverted index)的数据结构来达到相同目的。
-
倒排索引源于实际应用中需要根据属性的值来查找记录。这种索引表中的每一项都包括一个属性值和具有该属性值的各记录的地址。由于不是由记录来确定属性值,而是由属性值来确定记录的位置,因而称为倒排索引(inverted index)。
Elasticsearch部署与启动
下载
https://www.elastic.co/downloads/
安装
单机版
将文件上传至服务器
创建 es 目录
mkdir -p /usr/local/elasticsearch/es1
解压文件至 es 目录
tar -zxvf elasticsearch-7.4.2-linux-x86_64.tar.gz -C /usr/local/elasticsearch/es1/
启动,报错
cd /usr/local/elasticsearch/es1/elasticsearch-7.4.2/
bin/elasticsearch
错误1
future versions of Elasticsearch will require Java 11; your Java version from
[/usr/local/java/jdk1.8.0_231/jre] does not meet this requirement
ES 7.4.2需要JDK11,如果电脑安装了过低的JDK版本,会提示如下信息,ES 为了方便用户使用,自己集成了OpenJDK,但是如果系统环境变量中有JDK会优先使用环境变量中的JDK。所以我们如果既想使用自己的JDK版本又想使用ES 7 版本需要修改 ES 配置文件。
解决:
修改配置文件 elasticsearch-env
vim bin/elasticsearch-env
在文件首行添加如下信息
JAVA_HOME="/usr/local/elasticsearch/es1/elasticsearch-7.4.2/jdk/"
错误2
java.lang.RuntimeException: can not run elasticsearch as root
因为当前是 root 用户,es 默认不允许 root 用户操作。
解决:
创建 es 用户组和 es 用户,并将其添加到用户组 es 中
groupadd es
useradd es -g es
更改 es 文件夹及内部文件的所属用户及组为 es:es
chown -Rf es:es /usr/local/elasticsearch/
切换到 es 用户再次启动
su es
bin/elasticsearch
Elasticsearch的对外服务端口默认是9200,客户端访问是9300。通过启动日志信息可以看到四个警告信息。记住它们,因为一会将会变为错误信息,我们需要对他们做出处理。
[2019-12-02T10:18:37,235][WARN ][o.e.b.BootstrapChecks ] [localhost.localdomain] max file descriptors [4096] for elasticsearch process is too low, increase to at least [65535]
[2019-12-02T10:18:37,240][WARN ][o.e.b.BootstrapChecks ] [localhost.localdomain] max number of threads [3795] for user [es] is too low, increase to at least [4096]
[2019-12-02T10:18:37,244][WARN ][o.e.b.BootstrapChecks ] [localhost.localdomain] max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]
[2019-12-02T10:18:37,247][WARN ][o.e.b.BootstrapChecks ] [localhost.localdomain] the default discovery settings are unsuitable for production use; at least one of [discovery.seed_hosts, discovery.seed_providers, cluster.initial_master_nodes] must be configured
ElasticSearch后端启动命令
bin/elasticsearch -d
在本机通过curl命令
[es@localhost root]$ curl http://127.0.0.1:9200
{
"name" : "localhost.localdomain",
"cluster_name" : "elasticsearch",
"cluster_uuid" : "2I3zMPlQTV2c2_HWCJf5cQ",
"version" : {
"number" : "7.4.2",
"build_flavor" : "default",
"build_type" : "tar",
"build_hash" : "2f90bbf7b93631e52bafb59b3b049cb44ec25e96",
"build_date" : "2019-10-28T20:40:44.881551Z",
"build_snapshot" : false,
"lucene_version" : "8.2.0",
"minimum_wire_compatibility_version" : "6.8.0",
"minimum_index_compatibility_version" : "6.0.0-beta1"
},
"tagline" : "You Know, for Search"
}
如果需要远程访问,还需要以下步骤
修改config/elasticsearch.yml
文件
vi config/elasticsearch.yml
实际生产环境请添加允许访问的IP,学习时使用0.0.0.0放行所有IP
network.host: 0.0.0.0
切换至root用户添加防火墙规则,重启防火墙,学习时直接关闭防火墙
su root
-A INPUT -p tcp -m state --state NEW -m tcp --dport 9200 -j ACCEPT
systemctl restart iptables.service
切换es用户重启elasticsearch,重启之前先查询es进程然后杀死进程
kill -9 进程号
bin/elasticsearch -d
访问,报错
ERROR: [4] bootstrap checks failed
[1]: max file descriptors [4096] for elasticsearch process is too low, increase to at least [65535]
[2]: max number of threads [3795] for user [es] is too low, increase to at least [4096]
[3]: max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]
[4]: the default discovery settings are unsuitable for production use; at least one of [discovery.seed_hosts, discovery.seed_providers, cluster.initial_master_nodes] must be configured
错误1:Elasticsearch进程的最大文件描述符[4096]太低,请至少增加到[65535]
https://www.elastic.co/guide/en/elasticsearch/reference/7.4/vm-max-map-count.html
错误2 :用户[es]可以创建的最大线程数[3795]太低,请至少增加到[4096]
https://www.elastic.co/guide/en/elasticsearch/reference/7.4/max-number-of-threads.html
解决
vim /etc/security/limits.conf
在文件末尾添加如下信息
es soft nofile 65535
es hard nofile 65535
es soft nproc 4096
es hard nproc 4096
错误3:最大虚拟内存区域vm.max_map_count[65530]太低,请至少增加到[262144]
https://www.elastic.co/guide/en/elasticsearch/reference/7.4/vm-max-map-count.html#vm-max-map-count
解决
vim /etc/sysctl.conf
在文件末尾添加如下信息
vm.max_map_count = 262144
重新加载虚拟内存配置
sysctl -p
错误4:当前配置不适合生产环境使用;必须至少配置[discovery.seed_hosts,discovery.seed_providers,cluster.initial_master_nodes]
之一
discovery.seed_hosts
:集群发现配置,提供集群中符合主机要求的节点的列表. 每个值的格式为host:port
或host
,其中port
默认为设置transport.profiles.default.port
discovery.seed_providers
:以文件的方式提供主机列表,可以动态修改,而不用重启节点(容器化环境适用)
cluster.initial_master_nodes
:指定可以成为 master
的所有节点的 name 或者 ip,这些配置将会在第一次选举中进行计算
过时配置 | 新配置 |
---|---|
discovery.zen.ping.unicast.hosts | discovery.seed_hosts |
discovery.zen.hosts_provider | discovery.seed_providers |
无 | cluster.initial_master_nodes(7新添加) |
解决:
vi config/elasticsearch.yml
在文件末尾添加如下信息
discovery.seed_hosts: ["192.168.10.100"]
cluster.initial_master_nodes: ["192.168.10.100"]
重启,访问
ES已经可以正常访问了,但是我们在每次启动时都会看到一个警告信息
OpenJDK 64-Bit Server VM warning: Option UseConcMarkSweepGC was deprecated in version 9.0 and will likely be removed in a future release.
这是提醒你 CMS
垃圾收集器在 JDK 9 就开始被标注为@Deprecated
,JDK 11支持的垃圾回收器为G1
和ZGC
,而ZGC
在JDK 11 还处于实验阶段。
参考资料:
https://docs.oracle.com/en/java/javase/11/gctuning/garbage-first-garbage-collector.html#GUID-ED3AB6D3-FD9B-4447-9EDF-983ED2F7A573
修改config/jvm.options
配置文件
vi config/jvm.options
将:-XX:+UseConcMarkSweepGC
改为:-XX:+UseG1GC
以后启动将不再有警告信息。
集群版
Elasticsearch天生就是为分布式而生的搜索引擎,我们搭建一下集群环境
注意:不要使用刚才的单机版,重新解压一份新的ES搭建,因为刚才的单机版ES已经运行过会生成一些默认配置如果在其之上继续搭建可能会导致ES无法组成集群环境。
修改config/elasticsearch.yml
vim config/elasticsearch.yml
cluster.name: es # 集群名称,同一集群要一致
node.name: node-3 # 集群下各节点名称
http.port: 9200 # 端口
network.host: 0.0.0.0 #配置访问
# 跨域请求配置(为了让类似head的第三方插件可以请求es)
http.cors.enabled: true
http.cors.allow-origin: "*"
# 集群发现配置
discovery.seed_hosts: ["192.168.25.100", "192.168.25.101", "192.168.25.102"]
cluster.initial_master_nodes: ["node-1", "node-2", "node-3"]
discovery.zen.ping_timeout: 60s
注意:如果设置了node.name
需要将cluster.initial_master_nodes
改为对应节点名称。
切换至root用户,将刚才的es1目录复制两份分别为es2、es3
su root
cd /usr/local/elasticsearch
cp -Rf es1/ es2
cp -Rf es1/ es3
更改es文件夹及内部文件的所属用户及组为es:es
chown -Rf es:es es2/
chown -Rf es:es es3/
切换至es用户,修改es2的config/elasticsearch.yml
cluster.name: es # 集群名称,同一集群要一致
node.name: node-2 # 集群下各节点名称
http.port: 9201 # 端口
切换至es用户,修改es3的config/elasticsearch.yml
cluster.name: es # 集群名称,同一集群要一致
node.name: node-3 # 集群下各节点名称
http.port: 9202 # 端口
添加防火墙规则并重启防火墙,学习时关闭防火墙
-A INPUT -p tcp -m state --state NEW -m tcp --dport 9201 -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 9202 -j ACCEPT
systemctl restart iptables.service
前台启动两台es服务器
/usr/local/elasticsearch/es1/elasticsearch-7.4.2/bin/elasticsearch
/usr/local/elasticsearch/es2/elasticsearch-7.4.2/bin/elasticsearch
先启动node-1和node-2,我们还有一个知识点需要讲解了以后再启动node-3效果会更好。
访问:http://192.168.25.100:9200/_cluster/health?pretty
“status”: “red”
表示集群环境不ok,“status”: “green”
表示集群环境ok
- green:最健康得状态,说明所有的分片包括备份都可用
- yellow:基本的分片可用,但是备份不可用(或者是没有备份)
- red:部分的分片可用,表明分片有一部分损坏。此时执行查询部分数据仍然可以查到,遇到这种情况,还是赶快解决比较好
提示:如果虚拟机1 G 1 CPU,这时候就算你启动node-3也会报错
OpenJDK 64-Bit Server VM warning: INFO: os::commit_memory(0x00000000c5330000, 986513408, 0) failed; error='Not enough space' (errno=12)
at org.elasticsearch.tools.launchers.JvmErgonomics.flagsFinal(JvmErgonomics.java:111)
at org.elasticsearch.tools.launchers.JvmErgonomics.finalJvmOptions(JvmErgonomics.java:79)
at org.elasticsearch.tools.launchers.JvmErgonomics.choose(JvmErgonomics.java:57)
at org.elasticsearch.tools.launchers.JvmOptionsParser.main(JvmOptionsParser.java:89)
错误信息的意思就是默认分配的 jvm 空间大小不足,无法启动。
修改每个节点下的config/jvm.options
配置文件
vi config/jvm.options
将默认大小1g改为512m,注意着点别改成512g了。
-Xms512m
-Xmx512m
Elasticsearch插件安装
可视化工具
在学习和使用Elasticsearch的过程中,必不可少需要通过一些工具查看es的运行状态以及数据。如果都是通过rest请求,未免太过麻烦,而且也不够人性化。
以下插件三选一即可,不需要都安装。
head
在学习和使用Elasticsearch的过程中,必不可少需要通过一些工具查看es的运行状态以及数据。如果都是通过rest请求,未免太过麻烦,而且也不够人性化。head可以完美帮我们快速学习和使用es。
head 插件在ES 5版本以前开箱即用非常简单,ES 5版本以后需要运行在node环境下,所以我们要先准备一下环境。
安装
- 安装Git
yum -y install git
- 安装Node
根据自己的需求下载对应的版本
将文件上传至服务器并解压
mkdir -p /usr/local/nodejs
tar -xvf node-v12.13.1-linux-x64.tar.xz -C /usr/local/nodejs/
配置环境变量
配置环境变量
export NODE_HOME=/usr/local/nodejs/node-v12.13.1-linux-x64
export PATH=$PATH:$NODE_HOME/bin
查看版本
node -v
安装cnpm
npm install -g cnpm --registry=https://registry.npm.taobao.org
- 安装head
官网:https://github.com/mobz/elasticsearch-head
https://github.com/mobz/elasticsearch-head/releases
下载:
cd /usr/local
git clone git://github.com/mobz/elasticsearch-head.git
或者直接从网页下载并上传至服务器
- 修改配置
进入Elasticsearch安装目录下的config目录,修改elasticsearch.yml
文件.在文件的末尾加入以下代码,然后去掉network.host: 192.168.0.1
的注释并改为network.host: 0.0.0.0
。
配置说明:
https://www.ibm.com/support/knowledgecenter/zh/SSFPJS_8.5.6/com.ibm.wbpm.main.doc/topics/rfps_esearch_configoptions.html
# 如果启用了 HTTP 端口,那么此属性会指定是否允许跨源 REST 请求。
http.cors.enabled: true
# 如果 http.cors.enabled 的值为 true,那么该属性会指定允许 REST 请求来自何处。
http.cors.allow-origin: "*"
- 配置插件
进入 elasticsearch-head 目录,修改Gruntfile.js
文件
cd /usr/local/elasticsearch-head/
vi Gruntfile.js
添加hostname: '*'
非必须:修改elasticsearch-head/_site/app.js
,也可以不修改此文件,在启动head插件以后通过浏览器输入参数的方式连接也可以。
启动head插件以后通过浏览器输入参数的方式连接。
在elasticsearch-head目录下执行npm install
安装(或者cnpm install
),完成后在elasticsearch-head目录下执行npm run start
运行head插件。
npm install
npm run start
或者
npm run start & # 后台启动
提示:如果启动失败提示grunt相关错误信息,重新安装grunt再重新启动
npm install -g grunt-cli # 安装grunt命令行工具grunt-cli
npm install grunt --save-dev # 安装grunt及其插件
- 启动访问
启动Elasticsearch,访问http://192.168.25.100:9100
这里就要提一下分片的概念了。还记得之前节点3一直没有启动,现在启动节点3
su es
/usr/local/elasticsearch/es3/elasticsearch-7.4.2/bin/elasticsearch
重新访问,如下:
说明:一个索引库默认5个分片(一组完整的数据),分片可以自定义设置修改,每个分片又有备份分片,所以按默认值来计算,一个索引库就会有10个分片(两组完整数据),这10个分片会被分配到所有节点中。
-
head插件操作Elasticsearch
创建索引库
创建document
索引名字是:es_head
,本记录的id是:1;
返回的信息可以看到创建是成功的,并且版本号是1;ES会对记录修改进行版本跟踪,第一次创建记录为1,同一条记录每修改一次就追加1。 至此一条记录就提交到ES中建立了索引,注意HTTP的方法是PUT,PUT需要指定_id
插入数据,否则报错。POST无需指定_id
插入数据,会使用随机值。
更新document
结果中的version字段已经成了2,因为我们这是是修改,索引版本递增;更新接口与创建接口完全一样,ES会查询记录是否存在,如果不存在就是创建,存在就是更新操作。
PUT操作必须指定_id
,否则报错,_id
存在则为更新否则新增。POST操作无需指定_id
,会使用随机值,_id
存在则为更新否则新增。
查询document
查询所有
根据_id查询
found
值为true,表明查询到该文档,_source
字段是文档的内容。
删除document
删除索引库
cerebro
cerebro是一个开源(MIT许可)的Elasticsearch可视化管理工具,使用Scala,AngularJS,Bootstrap构建。cerebro需要Java 1.8或更新的运行环境。
下载地址:https://github.com/lmenezes/cerebro/releases
不同平台的资源文件
将文件上传至服务器,解压至指定路径
tar -zxvf cerebro-0.8.5.tgz -C /usr/local/elasticsearch/plugins/
启动(默认端口9000可访问IP 0.0.0.0),访问
cd /usr/local/elasticsearch/plugins/cerebro-0.8.5/
bin/cerebro
或者指定端口和可访问IP
bin/cerebro -Dhttp.port=1234 -Dhttp.address=127.0.0.1
输入需要连接的ES服务器地址
非必须:如果经常使用的话,可以先在conf/application.conf
中配置好Elasticsearch服务器地址。
点击配置好的es cluster连接ES
界面如下
elasticHD
elasticHD 是一款 Elasticsearch的可视化管理工具。不依赖ES的插件安装,更便捷;导航栏直接填写对应的ES IP和端口就可以操作Es了。支持Es监控、实时搜索,Index template快捷替换修改,索引列表信息查看, SQL converts to DSL等。
下载地址:https://github.com/360EntSecGroup-Skylar/ElasticHD/releases/
不同平台的资源文件
将文件上传至服务器,安装unzip
yum -y install unzip
将文件解压至指定路径
unzip elasticHD_linux_amd64.zip -d /usr/local/elasticsearch/plugins/
启动(默认端口9800可访问IP 0.0.0.0),访问
cd /usr/local/elasticsearch/plugins/
./ElasticHD
界面如下
Kibana
首先明确一点,Kibana是一个软件,不是插件。
Kibana 是一款开源的数据分析和可视化平台,它是 Elastic Stack 成员之一,设计用于和 Elasticsearch 协作。您可以使用 Kibana 对 Elasticsearch 索引中的数据进行搜索、查看、交互操作。可以很方便的利用图表、表格及地图对数据进行多元化的分析和呈现。
Kibana 可以使大数据通俗易懂。它很简单,基于浏览器的界面便于您快速创建和分享动态数据仪表板来追踪 Elasticsearch 的实时数据变化。
搭建 Kibana 非常简单。可以分分钟完成 Kibana 的安装并开始探索 Elasticsearch 的索引数据——没有代码、不需要额外的基础设施。
2.1、安装
官网:https://www.elastic.co/products/kibana
下载地址:https://www.elastic.co/cn/downloads/kibana
将文件上传至服务器
创建文件夹并解压
mkdir -p /usr/local/kibana
tar -zxvf kibana-7.4.2-linux-x86_64.tar.gz -C /usr/local/kibana/
修改kibana.yml配置文件
vi config/kibana.yml
修改以下三处内容
# 服务端口,默认5601
server.port: 5601
# 允许访问IP
server.host: "0.0.0.0"
# 设置 elasticsearch 节点及端口
elasticsearch.hosts: ["http://192.168.25.100:9200", "http://192.168.25.101:9200", "http://192.168.25.102:9200"]
启动kibana(需要先启动es)
bin/kibana --allow-root
访问:http://192.168.25.100:5601/
下图意思是:通过提供基本功能的使用统计信息来帮助我们改善Elastic Stack。 我们不会在Elastic之外共享此数据。选择yes
下图意思是:您可以尝试我们的示例数据和仪表板,也可以使用自己的数据。
最终界面
访问head发现多了几个存储kibana基础数据的索引库
关联索引库
操作索引库
通过Discover查询
通过Dev Tools查询
删除关联索引库
注意左侧,如果操作Kibana中Index Patterns
只是删除关联索引库,ES中索引库并不会被删除,如果操作Elasticsearch中Index Management
就是直接操作ES了,删除就是直接删除索引库。
IK Analysis中文分词器
IK Analysis插件将Lucene IK分析器集成到elasticsearch中,支持自定义词典。
下载
下载地址:https://github.com/medcl/elasticsearch-analysis-ik/releases
将文件上传至服务器
方式一:创建ik目录,然后将ik分词器解压至ik目录(三个节点都需要操作)
# 创建ik目录
mkdir -p /usr/local/elasticsearchH/elasticsearch-7.4.2/plugins/ik
mkdir -p /usr/local/elasticsearch/es2/elasticsearch-7.4.2/plugins/ik
mkdir -p /usr/local/elasticsearch/es3/elasticsearch-7.4.2/plugins/ik
# 解压至ik目录
unzip elasticsearch-analysis-ik-7.4.2.zip -d /usr/local/elasticsearchH/elasticsearch-7.4.2/plugins/ik/
unzip elasticsearch-analysis-ik-7.4.2.zip -d /usr/local/elasticsearch/es2/elasticsearch-7.4.2/plugins/ik/
unzip elasticsearch-analysis-ik-7.4.2.zip -d /usr/local/elasticsearch/es3/elasticsearch-7.4.2/plugins/ik/
方式二:在bin目录下执行命令来安装插件(三个节点都需要安装)
cd /usr/local/elasticsearch/es1/elasticsearch-7.4.2/
bin/elasticsearch-plugin install file:/root/elasticsearch-analysis-ik-7.4.2.zip
在每个ES节点的plugins目录中可以看到我们安装好的ik分词器
授权,因为安装了很多软件和插件,直接将elasticsearch目录全部授权给es用户即可
chown -Rf es:es /usr/local/elasticsearch/
重启ES,测试
测试
创建索引库
curl -X PUT http://localhost:9200/ik -H 'Content-Type:application/json' -d'{
"settings": {
"number_of_shards": 5,
"number_of_replicas": 1
}
}'
设置mapping
Analyzer分词配置解释:
-
ik_smart:粗粒度分词,比如中华人民共和国国歌,会拆分为中华人民共和国,国歌;
-
ik_max_word:细粒度分词,比如中华人民共和国国歌,会拆分为中华人民共和国,中华人民,中华,华人,人民共和国,人民,人,民,共和国,共和,和,国国,国歌,会穷尽各种可能的组合。
关于字段类型type配置解释:
Text
数据类型被用来索引长文本,比如说电子邮件的主体部分或者一款产品的介绍。这些文本会被分析,在建立索引前会将这些文本进行分词,转化为词的组合,建立索引。允许 ES来检索这些词语。Text
数据类型不能用来排序和聚合。
Keyword
数据类型用来建立电子邮箱地址、姓名、邮政编码和标签等数据,不需要进行分词。可以被用来检索过滤、排序和聚合。keyword
类型字段只能用本身来进行检索。
当然还有其他类型,比如Double
等我们用到再详细讲解吧。
curl -XPOST http://localhost:9200/ik/_mapping -H 'Content-Type:application/json' -d'{
"properties": {
"content": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
}
}
}'
插入数据
curl -XPOST http://localhost:9200/ik/_create/1 -H 'Content-Type:application/json' -d'
{"content":"美国留给伊拉克的是个烂摊子吗"}
'
curl -XPOST http://localhost:9200/ik/_create/2 -H 'Content-Type:application/json' -d'
{"content":"公安部:各地校车将享最高路权"}
'
curl -XPOST http://localhost:9200/ik/_create/3 -H 'Content-Type:application/json' -d'
{"content":"中韩渔警冲突调查:韩警平均每天扣1艘中国渔船"}
'
curl -XPOST http://localhost:9200/ik/_create/4 -H 'Content-Type:application/json' -d'
{"content":"中国驻洛杉矶领事馆遭亚裔男子枪击 嫌犯已自首"}
'
查询
curl -XGET http://localhost:9200/ik/_search?pretty -H 'Content-Type:application/json' -d'{
"query" : { "term" : { "content" : "中国"}}
}'
返回结果
{
"took" : 267,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 0.2876821,
"hits" : [
{
"_index" : "ik",
"_type" : "_doc",
"_id" : "3",
"_score" : 0.2876821,
"_source" : {
"content" : "中韩渔警冲突调查:韩警平均每天扣1艘中国渔船"
}
},
{
"_index" : "ik",
"_type" : "_doc",
"_id" : "4",
"_score" : 0.2876821,
"_source" : {
"content" : "中国驻洛杉矶领事馆遭亚裔男子枪击 嫌犯已自首"
}
}
]
}
}
输入:中
,查不到数据是因为中不是一个词,所以大家要有概念,不是说包含什么去查,而是有哪些分词可以供我们查询使用。
高亮查询
curl -XGET http://localhost:9200/ik/_search?pretty -H 'Content-Type:application/json' -d'
{
"query" : { "match" : { "content" : "中国" }},
"highlight" : {
"pre_tags" : ["<font color=red>"],
"post_tags" : ["</font>"],
"fields" : {
"content" : {}
}
}
}'
返回结果
{
"took" : 1587,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 0.2876821,
"hits" : [
{
"_index" : "ik",
"_type" : "_doc",
"_id" : "3",
"_score" : 0.2876821,
"_source" : {
"content" : "中韩渔警冲突调查:韩警平均每天扣1艘中国渔船"
},
"highlight" : {
"content" : [
"中韩渔警冲突调查:韩警平均每天扣1艘<font color=red>中国</font>渔船"
]
}
},
{
"_index" : "ik",
"_type" : "_doc",
"_id" : "4",
"_score" : 0.2876821,
"_source" : {
"content" : "中国驻洛杉矶领事馆遭亚裔男子枪击 嫌犯已自首"
},
"highlight" : {
"content" : [
"<font color=red>中国</font>驻洛杉矶领事馆遭亚裔男子枪击 嫌犯已自首"
]
}
}
]
}
}
安装MySQL
下载
下载地址:https://dev.mysql.com/downloads/repo/yum/
默认下载MySQL当前最新版本,选择对应CentOS版本的rpm资源包。
本文使用5.7.2X版本,如果找不到对应的版本信息,可以通过该地址获取:http://repo.mysql.com/
安装
将文件上传至服务器
安装
yum -y install mysql57-community-release-el7.rpm
yum -y install mysql-community-server
注意:因为我们使用的是CentOS的Minimal版本,所以可能会报错:没有可用软件包mysql-community-server
。错误原因:没有更新 wget命令包
解决办法是安装wget命令,然后再重新安装mysql-community-server
即可
yum -y install wget
安装截图如下,这种安装方式会将MySQL添加至环境变量和启动服务
启动服务
systemctl start mysqld
查看服务状态
systemctl status mysqld
下图表示MySQL正常运行
获取密码
登录MySQL必须要通过密码,此时root用户的密码可以通过以下命令在日志文件中获取
grep "password" /var/log/mysqld.log
下图红框内为初始密码
访问数据库
输入以下命令,并输入初始密码进行登录
mysql -u root –p
修改密码
MySQL默认必须修改密码之后才能操作数据库,所以执行以下命令
set password = password('root');
报错,因为MySQL有密码设置规范,具体是与validate_password_policy
的值有关
MySQL完整的初始密码规则可以通过如下命令查看(修改过密码以后方可查看)
mysql> SHOW VARIABLES LIKE 'validate_password%';
+--------------------------------------+--------+
| Variable_name | Value |
+--------------------------------------+--------+
| validate_password_check_user_name | OFF |
| validate_password_dictionary_file | |
| validate_password_length | 8 |
| validate_password_mixed_case_count | 1 |
| validate_password_number_count | 1 |
| validate_password_policy | MEDIUM |
| validate_password_special_char_count | 1 |
+--------------------------------------+--------+
rows in set (0.01 sec)
-
validate_password_dictionary_file:密码策略文件,策略为STRONG才需要
-
validate_password_length:密码最少长度
-
validate_password_mixed_case_count:大小写字符长度,至少1个
-
validate_password_number_count:数字至少1个
-
validate_password_policy:密码安全策略,默认MEDIUM策略
-
validate_password_special_char_count:特殊字符至少1个
解决办法一:按照要求修改为符合规范的复杂密码
解决办法二:修改密码规则(将策略要求置为LOW,长度要求置为4)
set global validate_password_policy=0;
set global validate_password_length=6;
修改密码
set password = password('123456');
退出重新登录,便可查看修改后的密码规则
授权
授权远程用户连接
# 改表法
# 选择数据库
use mysql;
# 最后授权MySQL,允许远程用户登录访问MySQL
update user set host = '%' where user = 'root';
# 刷新权限
flush privileges;
# 授权法
# 允许192.168.10.105连接访问所有库的所有表(*.*),连接密码为1234
GRANT ALL PRIVILEGES ON *.* TO 'root'@'192.168.10.105' IDENTIFIED BY '1234' WITH GRANT OPTION;
# 允许所有连接访问所有库的所有表(*.*),连接密码为1234
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'root' WITH GRANT OPTION;
# 刷新权限
FLUSH PRIVILEGES;
设置防火墙或者关闭防火墙
学习阶段直接关闭防火墙
关闭防火墙:
Centos6版本:
service iptables stop
Centos7版本:
安装iptables -> systemctl stop iptables
自带firewalld -> systemctl stop firewalld
设置防火墙:
vi /etc/sysconfig/iptables
# 添加3306端口
-A INPUT -p tcp -m state --state NEW -m tcp --dport 3306 -j ACCEPT
操作数据库
# 展示所有数据库
show databases;
# 选择数据库
use mysql;
# 展示所有表
show tables;
# 忘记密码后修改密码
# 第一步的作用是启动时跳过验证
1. vim /etc/my.cnf在[mysqld]里加入skip-grant-tables
2. 重启mysql服务,连接mysql无密码登入use mysql;选择数据库
3. 敲下面这行代码修改密码
update mysql.user set authentication_string=password('123456') where user='root';
4. vim /etc/my.cnf在[mysqld]里删除skip-grant-tables
重启mysql服务,新密码登录即可
MySQL各文件目录
可以通过以下命令来查找相关MySQL安装后产生的文件和目录
find / -name mysql
find / -name mysqld
远程连接数据库并导入sql文件
创建MySQL连接
填写连接信息
接下来就是大家熟悉的界面操作了
Elasticsearch导入MySQL数据
Logstash
ES官网:https://www.elastic.co/products/logstash
下载地址:https://www.elastic.co/cn/downloads/logstash
Logstash 是开源的服务器端数据处理管道,能够同时从多个来源采集数据,转换数据,然后将数据发送到您最喜欢的 “存储库” 中。(我们的存储库当然是 Elasticsearch。)
安装
将文件上传至服务器
创建目录,解压至该目录
mkdir -p /usr/local/logstash
tar -zxvf logstash-7.4.2.tar.gz -C /usr/local/logstash/
运行
cd /usr/local/logstash/logstash-7.4.2
bin/logstash -e 'input { stdin {} } output { stdout {} }'
使用-e
参数在命令行中指定配置是一种方式,不过如果需要配置更多设置则需要很长的内容。这种情况,我们首先创建一个配置文件,并且指定logstash使用这个配置文件。标准配置为文件含有input{}
,filter{}
和output{}
三部分。
看到如下界面后,输入hello world并看到如下效果则表示安装启动成功
安装logstash-input-jdbc插件(高版本可跳过)
logstash5.x版本自身已经集成了这个插件,不需要去单独安装,直接使用即可。(可以跳过1.2和1.3步骤)
logstash-input-jdbc
插件是使用ruby语言开发的,所以我们需要安装ruby环境。
如果没有安装gem的话,先安装gem
yum -y install gem
验证是否安装成功
gem sources -l
修改Gemfile文件数据源地址(高版本可跳过)
替换Ruby镜像为国内镜像
gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.org/
修改Gemfile配置文件
vim /opt/es/plugins/logstash-5.3.2/Gemfile
修改source的值为:"https://gems.ruby-china.com/"
安装
cd /opt/es/plugins/logstash-5.3.2/
bin/logstash-plugin install logstash-input-jdbc
或者跳过检查直接安装
bin/logstash-plugin install --no-verify logstash-input-jdbc
可能需要下载半个小时左右,请大家耐心等待
同步MySQL数据
需要建立两个文件,一个jdbc.conf
,一个jdbc.sql
,名称自定义;
导入MySQL的Java驱动包:mysql-connector-java-8.0.18.jar
,并确保数据库服务可用。
注意:请将mysql的驱动包上传至logstash-7.4.2/logstash-core/lib/jars/
目录下
/usr/local/logstash/logstash-7.4.2/logstash-core/lib/jars/
jdbc.conf
cd /usr/local/logstash/logstash-7.4.2
vim jdbc.conf
编写如下内容
input {
stdin {
}
jdbc {
# 配置数据库信息
jdbc_connection_string => "jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"
jdbc_driver_class => "com.mysql.cj.jdbc.Driver"
jdbc_user => "root"
jdbc_password => "root"
jdbc_paging_enabled => "true"
jdbc_page_size => "50000"
jdbc_default_timezone => "Asia/Shanghai"
# 执行 sql 语句文件
statement_filepath => "/usr/local/logstash/logstash-7.4.2/jdbc.sql"
# 定时字段 各字段含义(由左至右)分、时、天、月、年,全部为*默认含义为每分钟都更新
schedule => "* * * * *"
# 是否将 sql 中 column 名称转小写
lowercase_column_names => false
}
}
output {
elasticsearch {
hosts => ["127.0.0.1:9200"]
index => "shop"
# 文档_id,%{goods_id}意思是取查询出来的goods_id的值,并将其映射到es的_id字段中
# 文档_id,%{goodsId}如果是别名,意思是取查询出来的goodsId的值,并将其映射到es的_id字段中
document_id => "%{goodsId}"
}
stdout {
codec => json_lines
}
}
jdbc.sql
别名驼峰会被默认设置为全小写,需要配置lowercase_column_names => false
SELECT
goods_id goodsId,
goods_name goodsName,
market_price marketPrice,
original_img originalImg
FROM
t_goods
检测配置文件是否编写正确
bin/logstash -f /usr/local/logstash/logstash-7.4.2/jdbc.conf -t
创建索引库
curl -X PUT http://localhost:9200/shop -H 'Content-Type:application/json' -d'{
"settings": {
"number_of_shards": 5,
"number_of_replicas": 1
}
}'
设置mapping
Analyzer分词配置解释:
-
ik_smart:粗粒度分词,比如中华人民共和国国歌,会拆分为中华人民共和国,国歌;
-
ik_max_word:细粒度分词,比如中华人民共和国国歌,会拆分为中华人民共和国,中华人民,中华,华人,人民共和国,人民,人,民,共和国,共和,和,国国,国歌,会穷尽各种可能的组合。
curl -XPOST http://localhost:9200/shop/_mapping -H 'Content-Type:application/json' -d'{
"properties": {
"goodsName": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_max_word"
}
}
}'
执行导入
cd /usr/local/logstash/logstash-7.4.2/
bin/logstash -f /usr/local/logstash/logstash-7.4.2/jdbc.conf
该工具不会断开连接,会持续和ES保持连接,并按照指定的时间循环执行导入。
查询
Elasticsearch的JavaAPI
JavaAPI
创建项目
添加依赖
pom.xml
<!-- junit 单元测试依赖 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- elasticsearch 服务依赖 -->
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.4.2</version>
</dependency>
<!-- rest-client 客户端依赖 -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
<version>7.4.2</version>
</dependency>
<!-- rest-high-level-client 客户端依赖 -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.4.2</version>
</dependency>
Java连接Elasticsearch
Elasticsearch有两种连接方式:transport
、rest
。transport
通过TCP方式访问ES(只支持java),rest
方式通过http API 访问ES(没有语言限制)。
ES官方建议使用rest
方式, transport
在7.0
版本中不建议使用,在8.X
的版本中废弃。
你可以用Java客户端做很多事情:
执行标准的index
,get
,delete
,update
,search
等操作。
在正在运行的集群上执行管理任务。
但是,通过官方文档可以得知,现在存在至少三种Java客户端。
-
Transport Client
-
Java High Level REST Client
-
Java Low Level Rest Client
造成这种混乱的原因是:
长久以来,ES并没有官方的Java客户端,并且Java自身是可以简单支持ES的API的,于是就先做成了TransportClient
。但是TransportClient
的缺点是显而易见的,它没有使用RESTful风格的接口,而是二进制的方式传输数据。
之后ES官方推出了Java Low Level REST Client
,它支持RESTful,用起来也不错。但是缺点也很明显,因为TransportClient
的使用者把代码迁移到Low Level REST Client
的工作量比较大。官方文档专门为迁移代码出了一堆文档来提供参考。
现在ES官方推出Java High Level REST Client
,它是基于Java Low Level REST Client
的封装,并且API接收参数和返回值和TransportClient
是一样的,使得代码迁移变得容易并且支持了RESTful的风格,兼容了这两种客户端的优点。当然缺点是存在的,就是版本的问题。ES的小版本更新非常频繁,在最理想的情况下,客户端的版本要和ES的版本一致(至少主版本号一致),次版本号不一致的话,基本操作也许可以,但是新API就不支持了。
package com.xxxx;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.junit.After;
import org.junit.Before;
import java.io.IOException;
public class ElasticsearchTest {
// ES服务器IP
private final static String HOST = "192.168.10.100";
// ES服务器连接方式
private final static String SCHEME = "http";
// 初始化 ES 服务器集群
// 参数分别为:IP,端口,连接方式(默认为http)
private final static HttpHost[] httpHosts = {
new HttpHost(HOST, 9200, SCHEME),
new HttpHost(HOST, 9201, SCHEME),
new HttpHost(HOST, 9202, SCHEME)
};
// 客户端
private RestHighLevelClient client = null;
/**
* 获取客户端
*/
@Before
public void getConnect() {
client = new RestHighLevelClient(RestClient.builder(httpHosts));
}
/**
* 关闭连接
*/
@After
public void closeConnect() {
try {
if (null != client) client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
增删查改
- 查询时,如果索引库不存在会报错
ElasticsearchStatusException[Elasticsearch exception [type=index_not_found_exception, reason=no such index [索引名]]
- 查询时,如果不设置size,默认只返回10条数据,默认
from:0
,size:10
ElasticsearchTest.java
/**
* 添加数据
*/
@Test
public void testCreate() throws IOException {
// 准备数据
Map<String, Object> jsonMap = new HashMap<>();
jsonMap.put("username", "zhangsan");
jsonMap.put("age", 18);
jsonMap.put("address", "sh");
// 指定索引库和id及数据
IndexRequest indexRequest = new IndexRequest("ik").id("5").source(jsonMap);
// 执行请求
IndexResponse indexResponse = client.index(indexRequest, RequestOptions.DEFAULT);
System.out.println(indexResponse.toString());
}
/**
* 查询数据
*/
@Test
public void testRetrieve() throws IOException {
// 指定索引库和id
GetRequest getRequest = new GetRequest("ik", "5");
// 执行请求
GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT);
System.out.println(getResponse.getSource());
}
/**
* 修改数据
*/
@Test
public void testUpdate() throws IOException {
// 准备数据
Map<String, Object> jsonMap = new HashMap<>();
jsonMap.put("username", "lisi");
jsonMap.put("age", 20);
jsonMap.put("address", "bj");
// 指定索引库和id及数据
UpdateRequest updateRequest = new UpdateRequest("ik", "5").doc(jsonMap);
// 执行请求
UpdateResponse updateResponse = client.update(updateRequest, RequestOptions.DEFAULT);
System.out.println(updateResponse.toString());
}
/**
* 删除数据
*/
@Test
public void testDelete() throws IOException {
// 指定索引库和id
DeleteRequest deleteRequest = new DeleteRequest("ik", "5");
DeleteResponse deleteResponse = client.delete(deleteRequest, RequestOptions.DEFAULT);
System.out.println(deleteResponse.toString());
}
/**
* 批量增删改操作
*/
@Test
public void testCUD() throws IOException {
// 初始化 BulkRequest
BulkRequest request = new BulkRequest();
// 指定索引库和id及数据
// 批量添加
request.add(new IndexRequest("ik").id("6")
.source(XContentType.JSON, "username", "zhangsan", "age", 18));
request.add(new IndexRequest("ik").id("7")
.source(XContentType.JSON, "username", "lisi", "age", 20));
// 批量修改
request.add(new UpdateRequest("ik", "6")
.doc(XContentType.JSON, "", ""));
// 批量删除
request.add(new DeleteRequest("ik", "6"));
// 执行请求
BulkResponse bulkResponse = client.bulk(request, RequestOptions.DEFAULT);
System.out.println(bulkResponse);
}
/**
* 批量查询-查询所有
*/
@Test
public void testRetrieveAll() throws IOException {
// 指定索引库
SearchRequest searchRequest = new SearchRequest("ik", "shop");
// 构建查询对象
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 添加查询条件
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
// 执行请求
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
// 总条数
System.out.println(searchResponse.getHits().getTotalHits().value);
// 结果数据(如果不设置返回条数,大于十条默认只返回十条)
SearchHit[] hits = searchResponse.getHits().getHits();
for (SearchHit hit : hits) {
System.out.println("分数:" + hit.getScore());
Map<String, Object> source = hit.getSourceAsMap();
System.out.println("index -> " + hit.getIndex());
System.out.println("id -> " + hit.getId());
for (Map.Entry<String, Object> s : source.entrySet()) {
System.out.println(s.getKey() + " -- " + s.getValue());
}
System.out.println("----------------------------");
}
}
/**
* 批量查询-匹配查询
*/
@Test
public void testRetrieveMatch() throws IOException {
// 指定索引库
SearchRequest searchRequest = new SearchRequest("ik", "shop");
// 构建查询对象
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 添加查询条件
// 指定从 content 和 goodsName 字段中查询
String key = "中国";
searchSourceBuilder.query(QueryBuilders.multiMatchQuery(key, "content", "goodsName"));
// 执行请求
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
// 总条数
System.out.println(searchResponse.getHits().getTotalHits().value);
// 结果数据(如果不设置返回条数,大于十条默认只返回十条)
SearchHit[] hits = searchResponse.getHits().getHits();
for (SearchHit hit : hits) {
System.out.println("分数:" + hit.getScore());
Map<String, Object> source = hit.getSourceAsMap();
System.out.println("index -> " + hit.getIndex());
System.out.println("id -> " + hit.getId());
for (Map.Entry<String, Object> s : source.entrySet()) {
System.out.println(s.getKey() + " -- " + s.getValue());
}
System.out.println("----------------------------");
}
}
/**
* 批量查询-分页查询-按分数或id排序
*/
@Test
public void testRetrievePage() throws IOException {
// 指定索引库
SearchRequest searchRequest = new SearchRequest("shop");
// 构建查询对象
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 添加分页条件,从第 0 个开始,返回 5 个
searchSourceBuilder.from(0).size(5);
// 添加查询条件
// 指定从 goodsName 字段中查询
String key = "中国移动联通电信";
searchSourceBuilder.query(QueryBuilders.multiMatchQuery(key, "goodsName"));
// 按照 score 正序排列(默认倒序)
//searchSourceBuilder.sort(SortBuilders.scoreSort().order(SortOrder.ASC));
// 并且按照 id 倒序排列(分数字段会失效返回 NaN)
//searchSourceBuilder.sort(SortBuilders.fieldSort("_id").order(SortOrder.DESC));
// 执行请求
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
// 总条数
System.out.println(searchResponse.getHits().getTotalHits().value);
// 结果数据(如果不设置返回条数,大于十条默认只返回十条)
SearchHit[] hits = searchResponse.getHits().getHits();
for (SearchHit hit : hits) {
System.out.println("分数:" + hit.getScore());
Map<String, Object> source = hit.getSourceAsMap();
System.out.println("index -> " + hit.getIndex());
System.out.println("id -> " + hit.getId());
for (Map.Entry<String, Object> s : source.entrySet()) {
System.out.println(s.getKey() + " -- " + s.getValue());
}
System.out.println("----------------------------");
}
}
/**
* 批量查询-分页查询-高亮查询
*/
@Test
public void testHighlight() throws IOException {
// 指定索引库
SearchRequest searchRequest = new SearchRequest("shop");
// 构建查询对象
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 添加分页条件,从第 0 个开始,返回 5 个
searchSourceBuilder.from(0).size(5);
// 构建高亮对象
HighlightBuilder highlightBuilder = new HighlightBuilder();
// 指定高亮字段和高亮样式
highlightBuilder.field("goodsName")
.preTags("<span style='color:red;'>")
.postTags("</span>");
searchSourceBuilder.highlighter(highlightBuilder);
// 添加查询条件
// 指定从 goodsName 字段中查询
String key = "中国移动联通电信";
searchSourceBuilder.query(QueryBuilders.multiMatchQuery(key, "goodsName"));
// 执行请求
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
// 总条数
System.out.println(searchResponse.getHits().getTotalHits().value);
// 结果数据(如果不设置返回条数,大于十条默认只返回十条)
SearchHit[] hits = searchResponse.getHits().getHits();
for (SearchHit hit : hits) {
// 构建项目中所需的数据结果集
String highlightMessage = String.valueOf(hit.getHighlightFields().get("goodsName").fragments()[0]);
Integer goodsId = Integer.valueOf((Integer) hit.getSourceAsMap().get("goodsId"));
String goodsName = String.valueOf(hit.getSourceAsMap().get("goodsName"));
BigDecimal marketPrice = new BigDecimal(String.valueOf(hit.getSourceAsMap().get("marketPrice")));
String originalImg = String.valueOf(hit.getSourceAsMap().get("originalImg"));
System.out.println("goodsId -> " + goodsId);
System.out.println("goodsName -> " + goodsName);
System.out.println("highlightMessage -> " + highlightMessage);
System.out.println("marketPrice -> " + marketPrice);
System.out.println("originalImg -> " + originalImg);
System.out.println("----------------------------");
}
}
SpringBoot整合Elasticsearch
创建项目
创建SpringBoot项目添加Elasticsearch依赖
添加依赖
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.xxxx</groupId>
<artifactId>spring-data-elasticsearch-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-data-elasticsearch-demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置文件
application.yml
#配置es
spring:
elasticsearch:
rest:
uris: 192.168.10.100:9200,192.168.10.100:9201,192.168.10.100:9202
实体类
Goods.java
package com.xxxx.springdataelasticsearchdemo.pojo;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* @author zhoubin
* @since 1.0.0
*/
@Document(indexName = "shop1",shards = 5,replicas = 1,createIndex = false)
public class Goods implements Serializable {
/**
* 商品id
*/
@Id
private Integer goodsId;
/**
* 商品名称
*/
@Field(type = FieldType.Text,analyzer = "ik_max_word")
private String goodsName;
/**
* 市场价
*/
@Field(type = FieldType.Double)
private BigDecimal marketPrice;
/**
* 商品上传原始图
*/
@Field(type = FieldType.Keyword)
private String originalImg;
/**
* t_goods
*/
private static final long serialVersionUID = 1L;
public Integer getGoodsId() {
return goodsId;
}
public void setGoodsId(Integer goodsId) {
this.goodsId = goodsId;
}
public String getGoodsName() {
return goodsName;
}
public void setGoodsName(String goodsName) {
this.goodsName = goodsName == null ? null : goodsName.trim();
}
public BigDecimal getMarketPrice() {
return marketPrice;
}
public void setMarketPrice(BigDecimal marketPrice) {
this.marketPrice = marketPrice;
}
public String getOriginalImg() {
return originalImg;
}
public void setOriginalImg(String originalImg) {
this.originalImg = originalImg == null ? null : originalImg.trim();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
sb.append(" [");
sb.append("Hash = ").append(hashCode());
sb.append(", goodsId=").append(goodsId);
sb.append(", goodsName=").append(goodsName);
sb.append(", marketPrice=").append(marketPrice);
sb.append(", originalImg=").append(originalImg);
sb.append("]");
return sb.toString();
}
public Goods() {
}
public Goods(Integer goodsId, String goodsName, BigDecimal marketPrice, String originalImg) {
this.goodsId = goodsId;
this.goodsName = goodsName;
this.marketPrice = marketPrice;
this.originalImg = originalImg;
}
}
- 为需要使用索引库的实体类加上注解
@Document
部分属性如下indexName="索引库名"
shards = 分片数量(默认1)
replicas = 副本数量(默认1)
- 为id属性 添加
@Id
注释 - 各个字段加上注解并制定类型
@Field
部分属性如下type= FieldType.枚举
: 指定字段类型 Keyword不分词, Text分词 对应着elasticsearch的字段类型
- 为需要分词的字段添加分词器
analyzer="分词器名"
(ik分词器固定写法ik_max_word
) - 是否创建索引
createIndex=boolean(默认true)
Dao层
package com.xxxx.springdataelasticsearchdemo.dao;
import com.xxxx.springdataelasticsearchdemo.pojo.Goods;
import org.springframework.data.elasticsearch.annotations.Query;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import java.util.List;
/**
* @author zhoubin
* @since 1.0.0
*/
public interface GoodsRepository extends ElasticsearchRepository<Goods,Integer> {
/**
* 根据商品名查询
* @param goodsName
* @return
*/
List<Goods> findByGoodsName(String goodsName);
/**
* 根据id查询商品
* ?0为占位符
* @param id
* @return
*/
@Query("{\"match\": {\"goodsId\":{ \"query\": \"?0\"}}}")
Goods findByIdValue(Integer id);
}
测试类
SpringDataElasticsearchDemoApplicationTests.java
package com.xxxx.springdataelasticsearchdemo;
import com.xxxx.springdataelasticsearchdemo.dao.GoodsRepository;
import com.xxxx.springdataelasticsearchdemo.pojo.Goods;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.IndexOperations;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@SpringBootTest
public class SpringDataElasticsearchDemoApplicationTests {
@Autowired
private GoodsRepository goodsRepository;
@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;
/**
* 批量插入,查询所有
*/
@Test
public void testSaveAll() {
//批量插入
List<Goods> list = new ArrayList<>();
list.add(new Goods(152, "测试手机1", new BigDecimal("500"), "jpg"));
list.add(new Goods(153, "测试手机2", new BigDecimal("800"), "png"));
goodsRepository.saveAll(list);
//查询所有
Iterable<Goods> all = goodsRepository.findAll();
all.forEach(System.out::println);
}
/**
* 根据商品名查询
*/
@Test
public void testFindByName() {
List<Goods> list = goodsRepository.findByGoodsName("%中国%");
list.forEach(System.out::println);
}
/**
* 根据商品id查询
*/
@Test
public void testFindById() {
System.out.println(goodsRepository.findByIdValue(150));
}
/**
* 索引操作
*/
@Test
public void testIndex() {
//设置索引信息(实体类),返回indexOperations
IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(Goods.class);
indexOperations.create();
//创建索引映射
Document mapping = indexOperations.createMapping();
//将映射写入索引
indexOperations.putMapping(mapping);
//获取索引
Map<String, Object> map = indexOperations.getMapping();
map.forEach((k, v) -> System.out.println(k + "-->" + v));
//索引是否存在
boolean exists = indexOperations.exists();
System.out.println(exists);
//删除索引
indexOperations.delete();
}
/**
* 增删改
*/
@Test
public void testDocument() {
/**
* 根据id和索引删除,返回删除的id
* 第一个参数:id,String类型
* 第二个参数:索引库对象
*/
// String count = elasticsearchRestTemplate.delete("150", IndexCoordinates.of("shop"));
// System.out.println(count);
/**
* 删除查询结果
* 第一个参数:查询对象
* 第二个参数:索引类字节码
* 第三个参数:索引库对象
*/
elasticsearchRestTemplate.delete(
new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.matchQuery("goodsName", "测试"))
.build(),
Goods.class,
IndexCoordinates.of("shop"));
//新增/更新(id不存在就新增,存在就更新)
List<Goods> list = new ArrayList<>();
list.add(new Goods(150, "测试手机3", new BigDecimal("100"), "jpg"));
list.add(new Goods(151, "测试手机4", new BigDecimal("200"), "png"));
Iterable<Goods> save = elasticsearchRestTemplate.save(list);
save.forEach(System.out::println);
}
/**
* 匹配查询
*/
@Test
public void testSearchMatch() {
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
NativeSearchQuery query = nativeSearchQueryBuilder
/**
* 第一个参数:关键词
* 第二个参数:对应es的字段
*/
.withQuery(QueryBuilders.multiMatchQuery("中国移动联通电信", "goodsName"))
.build();
SearchHits<Goods> search = elasticsearchRestTemplate.search(query, Goods.class);
search.forEach(searchHit -> System.out.println(searchHit.getContent()));
}
/**
* 分页,排序,高亮查询
*/
@Test
public void testSearchPage() {
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
NativeSearchQuery query = nativeSearchQueryBuilder
/**
* 第一个参数:关键词
* 第二个参数:对应es的字段
*/
.withQuery(QueryBuilders.multiMatchQuery("中国移动联通电信", "goodsName"))
/**
* 第一个参数:当前页,0开始
* 第二个参数:每个条数
* 第三个参数:排序对象
* 升序/降序
* 比较字段
*/
// .withPageable(PageRequest.of(0,5, Sort.Direction.DESC,"goodsId","marketPrice"))
// 分页
.withPageable(PageRequest.of(0, 5))
// .withSort(SortBuilders.fieldSort("marketPrice").order(SortOrder.ASC))
//高亮,默认样式<em></em>(斜体)
// .withHighlightFields(new HighlightBuilder.Field("goodsName"))
//高亮,指定样式
.withHighlightBuilder(new HighlightBuilder().field("goodsName").preTags("<span style='color:red;'>").postTags("</span>"))
.build();
SearchHits<Goods> search = elasticsearchRestTemplate.search(query, Goods.class);
for (SearchHit<Goods> searchHit : search) {
//id
System.out.println(searchHit.getId());
//分数
System.out.println(searchHit.getScore());
//排序的值
Integer sortValues = (Integer) searchHit.getSortValues().get(0);
System.out.println(sortValues);
//高亮信息
String highlightMessage = searchHit.getHighlightField("goodsName").get(0);
System.out.println(highlightMessage);
//结果对象
System.out.println(searchHit.getContent());
}
}
}
)
.build(),
Goods.class,
IndexCoordinates.of(“shop”));
//新增/更新(id不存在就新增,存在就更新)
List list = new ArrayList<>();
list.add(new Goods(150, “测试手机3”, new BigDecimal(“100”), “jpg”));
list.add(new Goods(151, “测试手机4”, new BigDecimal(“200”), “png”));
Iterable save = elasticsearchRestTemplate.save(list);
save.forEach(System.out::println);
}
/**
* 匹配查询
*/
@Test
public void testSearchMatch() {
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
NativeSearchQuery query = nativeSearchQueryBuilder
/**
* 第一个参数:关键词
* 第二个参数:对应es的字段
*/
.withQuery(QueryBuilders.multiMatchQuery("中国移动联通电信", "goodsName"))
.build();
SearchHits<Goods> search = elasticsearchRestTemplate.search(query, Goods.class);
search.forEach(searchHit -> System.out.println(searchHit.getContent()));
}
/**
* 分页,排序,高亮查询
*/
@Test
public void testSearchPage() {
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
NativeSearchQuery query = nativeSearchQueryBuilder
/**
* 第一个参数:关键词
* 第二个参数:对应es的字段
*/
.withQuery(QueryBuilders.multiMatchQuery("中国移动联通电信", "goodsName"))
/**
* 第一个参数:当前页,0开始
* 第二个参数:每个条数
* 第三个参数:排序对象
* 升序/降序
* 比较字段
*/
// .withPageable(PageRequest.of(0,5, Sort.Direction.DESC,"goodsId","marketPrice"))
// 分页
.withPageable(PageRequest.of(0, 5))
// .withSort(SortBuilders.fieldSort("marketPrice").order(SortOrder.ASC))
//高亮,默认样式<em></em>(斜体)
// .withHighlightFields(new HighlightBuilder.Field("goodsName"))
//高亮,指定样式
.withHighlightBuilder(new HighlightBuilder().field("goodsName").preTags("<span style='color:red;'>").postTags("</span>"))
.build();
SearchHits<Goods> search = elasticsearchRestTemplate.search(query, Goods.class);
for (SearchHit<Goods> searchHit : search) {
//id
System.out.println(searchHit.getId());
//分数
System.out.println(searchHit.getScore());
//排序的值
Integer sortValues = (Integer) searchHit.getSortValues().get(0);
System.out.println(sortValues);
//高亮信息
String highlightMessage = searchHit.getHighlightField("goodsName").get(0);
System.out.println(highlightMessage);
//结果对象
System.out.println(searchHit.getContent());
}
}
}