Elasticsearch学习笔记
文章目录
- Elasticsearch学习笔记
- 一、概述
- 二、基本概念
- 三、Elasticsearch环境搭建 (虚拟机)
- 四、Elasticsearch的辅助工具
- Kibana概述
- Kibana的安装配置
- 使用Kibana实现基本的增删改查
- 使用Kibana实现深层次复杂操作
- 搜索方式
- 准备测试数据资源
- 查询操作
- 1. 查询所有并排序
- 2. 分页查询
- 3. 分析检索关键词查询
- 4. 基于Term词元查询
- 5. 基于数值范围查询([Range Query](https://www.elastic.co/guide/en/elasticsearch/reference/6.0/query-dsl-range-query.html))
- 6. 基于前缀查询([Prefix Query](https://www.elastic.co/guide/en/elasticsearch/reference/6.0/query-dsl-prefix-query.html))
- 7. 基于通配符查询([Wildcard Query](https://www.elastic.co/guide/en/elasticsearch/reference/6.0/query-dsl-wildcard-query.html))
- 8. 基于ids的查询([Ids Query](https://www.elastic.co/guide/en/elasticsearch/reference/6.0/query-dsl-ids-query.html))
- 9. 模糊查询([Fuzzy Query](https://www.elastic.co/guide/en/elasticsearch/reference/6.0/query-dsl-fuzzy-query.html))
- 10. 多条件查询(Boolean Query)
- 11. 存在查询([Exists Query](https://www.elastic.co/guide/en/elasticsearch/reference/6.0/query-dsl-exists-query.html))
- 12. 不存在查询(missing query)
- 过滤器(Filter)
- 聚合([Aggregations](https://www.elastic.co/guide/en/elasticsearch/reference/6.0/search-aggregations.html))
- 1. 度量聚合([Metrics Aggregations](https://www.elastic.co/guide/en/elasticsearch/reference/6.0/search-aggregations-metrics.html))
- 1. 平均值统计聚合([Avg Aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/6.0/search-aggregations-metrics-avg-aggregation.html))
- 2. 最大值统计聚合([Max Aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/6.0/search-aggregations-metrics-max-aggregation.html))
- 3. 最小值统计聚合([Min Aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/6.0/search-aggregations-metrics-min-aggregation.html))
- 4. 求和统计聚合([Sum Aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/6.0/search-aggregations-metrics-sum-aggregation.html))
- 5. 常用统计值统计聚合([Stats Aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/6.0/search-aggregations-metrics-stats-aggregation.html))
- 2. 桶聚合([Bucket Aggregations](https://www.elastic.co/guide/en/elasticsearch/reference/6.0/search-aggregations-bucket.html))
- 1. 数值范围统计聚合([Range Aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/6.0/search-aggregations-bucket-range-aggregation.html))
- 2. 词元统计聚合([Terms Aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/6.0/search-aggregations-bucket-terms-aggregation.html))
- 3. 日期范围统计聚合([Date Range Aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/6.0/search-aggregations-bucket-daterange-aggregation.html))
- 4. 数值直方图聚合([Histogram Aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/6.0/search-aggregations-bucket-histogram-aggregation.html))
- 5. 日期直方图聚合([Date Histogram Aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/6.0/search-aggregations-bucket-datehistogram-aggregation.html))
- 3. 桶聚合嵌套度量聚合
- 五、Java API操作Elasticsearch
- Spring-Data-Elasticsearch操作Elasticsearch实战开发
- 六、Elasticsearch集成中文分词器IK
- 七、扫描和滚屏
- scroll(滚屏)
- scan(扫描)
- 八、搭建Elasticsearch集群(虚拟机)
一、概述
官网地址: https://www.elastic.co/cn/products/elasticsearch
Elasticsearch是什么
Elasticsearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web 接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。 设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。
Elasticsearch不仅仅是Lucene和全文搜索引擎,它还提供:
-
分布式的搜索引擎和数据分析引擎
-
全文检索
-
对海量数据进行近实时的处理
Elasticsearch的应用场景
- 全文检索 :主要和 Solr 竞争,属于后起之秀。
- NoSQL JSON文档数据库:主要抢占 Mongo 的市场,它在读写性能上优于 Mongo ,同时也支持地理位置查 询,还方便地理位置和文本混合查询。
- 监控:统计、日志类时间序的数据存储和分析、可视化,这方面是引领者。
- 国外:Wikipedia(维基百科)使用ES提供全文搜索并高亮关键字、StackOverflow(IT问答网站)结合全文搜索与地理位置查询、Github使用Elasticsearch检索1300亿行的代码。
- 国内:百度(在云分析、网盟、预测、文库、钱包、风控等业务上都应用了ES,单集群每天导入30TB+数据, 总共每天60TB+)、新浪 、阿里巴巴、腾讯等公司均有对ES的使用。
- 使用比较广泛的平台ELK(Elasticsearch, Logstash, Kibana)。
二、基本概念
RESTful介绍
REST : (资源的)表现层状态转化((Resources) Representational State Transfer),如果一个架构符合REST原则,就称它为 RESTful 架构风格。
- 资源: 所谓"资源",就是网络上的一个实体,或者说是网络上的一个具体信息。可以是一段文本、一张图片、一种服务,总之就是一个具体的实在。 可以用一个URI(统一资源定位符)指向它,每种资源对应一个特定的URI。URI只代表资源的实体,不代表它的形式。
- 表现层:我们把"资源"具体呈现出来的形式,叫做它的"表现层"(Representation)。 如,文本可以用txt格式表现,也可以用HTML格式、XML格式、JSON格式表现,甚至可以采用二进制格式;图片可以用JPG格式表现,也可以用PNG格式表现。
- 状态转化(State Transfer):互联网通信协议HTTP协议,是一个无状态协议。这意味着,所有的状态都保存在服务器端。 如果客户端想要操作服务器,必须通过某种手段,让服务器端发生"状态转 化"(State Transfer)。而这种转化是建立在表现层之上的,所以就是"表现层状态转化"。也就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET用来获取资 源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源。
RESTful架构,即符合以下特点:
(1)每一个URI代表一种资源;
(2)客户端和服务器之间,传递这种资源的某种表现层;
(3)客户端通过四个HTTP动词,对服务器端资源进行操作,实现"表现层状态转化"。
参考资料:
- http://www.ruanyifeng.com/blog/2011/09/restful.html
- http://www.ruanyifeng.com/blog/2018/10/restful-api-best-practices.html
Elasticsearch中涉及到的重要概念
接近实时(NRT)
Elasticsearch是一个接近实时的搜索平台。这意味着,从索引一个文档直到这个文档能够被搜索到有一个轻微的延 迟(通常是1秒)
集群(cluster)
一个集群就是由一个或多个节点组织在一起,它们共同持有你整个的数据,并一起提供索引和搜索功能。一个集群 由一个唯一的名字标识,这个名字默认就是“elasticsearch”。这个名字是重要的,因为一个节点只能通过指定某个集 群的名字,来加入这个集群。在产品环境中显式地设定这个名字是一个好习惯,但是使用默认值来进行测试/开发 也是不错的。
节点(node)
一个节点是你集群中的一个服务器,作为集群的一部分,它存储你的数据,参与集群的索引和搜索功能。和集群类 似,一个节点也是由一个名字来标识的,默认情况下,这个名字是一个随机的漫威漫画角色的名字,这个名字会在 启动的时候赋予节点。这个名字对于管理工作来说挺重要的,因为在这个管理过程中,你会去确定网络中的哪些服 务器对应于Elasticsearch集群中的哪些节点。 一个节点可以通过配置集群名称的方式来加入一个指定的集群。默认情况下,每个节点都会被安排加入到一个叫 做“elasticsearch”的集群中,这意味着,如果你在你的网络中启动了若干个节点,并假定它们能够相互发现彼此,它 们将会自动地形成并加入到一个叫做“elasticsearch”的集群中。 在一个集群里,只要你想,可以拥有任意多个节点。而且,如果当前你的网络中没有运行任何Elasticsearch节点, 这时启动一个节点,会默认创建并加入一个叫做“elasticsearch”的集群。
在分布式集群情况下,ES中的节点可分为4类:
- master节点:配置文件中node.master属性为true(默认为true),就有资格被选为master节点,master节点用于控制整个集群的操作。比如创建或删除索引,管理其它非master节点等
- data节点:配置文件中node.data属性为true(默认为true),就有资格被设置成data节点,data节点主要用于执行数据相关的操作。比如文档的CRUD
- 客户端节点:配置文件中node.master属性和node.data属性均为false。该节点不能作为master节点,也不能作为data节点。可以作为客户端节点,用于响应用户的请求,把请求转发到其他节点
- 部落节点:当一个节点配置tribe.*的时候,它是一个特殊的客户端,它可以连接多个集群,在所有连接的集群上执行搜索和其他操作
索引(index)
一个索引就是一个拥有几分相似特征的文档的集合。比如说,你可以有一个客户数据的索引,另一个产品目录的索 引,还有一个订单数据的索引。一个索引由一个名字来标识(必须全部是小写字母的),并且当我们要对对应于这 个索引中的文档进行索引、搜索、更新和删除的时候,都要使用到这个名字。索引类似于关系型数据库中Database 的概念。在一个集群中,如果你想,可以定义任意多的索引。
类型(type)
在一个索引中,你可以定义一种或多种类型。一个类型是你的索引的一个逻辑上的分类/分区,其语义完全由你来 定。通常,会为具有一组共同字段的文档定义一个类型。比如说,我们假设你运营一个博客平台并且将你所有的数 据存储到一个索引中。在这个索引中,你可以为用户数据定义一个类型,为博客数据定义另一个类型,当然,也可 以为评论数据定义另一个类型。类型类似于关系型数据库中Table的概念。
文档(document)
一个文档是一个可被索引的基础信息单元。比如,你可以拥有某一个客户的文档,某一个产品的一个文档,当然, 也可以拥有某个订单的一个文档。文档以JSON(Javascript Object Notation)格式来表示,而JSON是一个到处存在 的互联网数据交互格式。
在一个index/type里面,只要你想,你可以存储任意多的文档。注意,尽管一个文档,物理上存在于一个索引之 中,文档必须被索引/赋予一个索引的type。文档类似于关系型数据库中Record的概念。实际上一个文档除了用户定 义的数据外,还包括index、 type和_id字段
分片和复制(shards & replicas)
一个索引可以存储超出单个结点硬件限制的大量数据。比如,一个具有10亿文档的索引占据1TB的磁盘空间,而任 一节点都没有这样大的磁盘空间;或者单个节点处理搜索请求,响应太慢。
为了解决这个问题,Elasticsearch提供了将索引划分成多份的能力,这些份就叫做分片。当你创建一个索引的时 候,你可以指定你想要的分片的数量。每个分片本身也是一个功能完善并且独立的“索引”,这个“索引”可以被放置 到集群中的任何节点上。 分片之所以重要,主要有两方面的原因:
- 允许你水平分割/扩展你的内容容量
- 允许你在分片(潜在地,位于多个节点上)之上进行分布式的、并行的操作,进而提高性能/吞吐量 至于一个 分片怎样分布,它的文档怎样聚合回搜索请求,是完全由Elasticsearch管理的,对于作为用户的你来说,这些 都是透明的。
在一个网络/云的环境里,失败随时都可能发生,在某个分片/节点不知怎么的就处于离线状态,或者由于任何原因 消失了。这种情况下,有一个故障转移机制是非常有用并且是强烈推荐的。为此目的,Elasticsearch允许你创建分 片的一份或多份拷贝,这些拷贝叫做复制分片,或者直接叫复制。复制之所以重要,主要有两方面的原因:
- 在分片/节点失败的情况下,提供了高可用性。因为这个原因,注意到复制分片从不与原/主要 (original/primary)分片置于同一节点上是非常重要的。
- 扩展你的搜索量/吞吐量,因为搜索可以在所有的复制上并行运行
总之,每个索引可以被分成多个分片。一个索引也可以被复制0次(意思是没有复制)或多次。一旦复制了,每个 索引就有了主分片(作为复制源的原来的分片)和复制分片(主分片的拷贝)之别。分片和复制的数量可以在索引 创建的时候指定。在索引创建之后,你可以在任何时候动态地改变复制数量,但是不能改变分片的数量。
默认情况下,Elasticsearch中的每个索引被分片5个主分片和1个复制,这意味着,如果你的集群中至少有两个节 点,你的索引将会有5个主分片和另外5个复制分片(1个完全拷贝),这样的话每个索引总共就有10个分片。一个 索引的多个分片可以存放在集群中的一台主机上,也可以存放在多台主机上,这取决于你的集群机器数量。主分片 和复制分片的具体位置是由ES内在的策略所决定的。
映射(Mapping)
Mapping是ES中的一个很重要的内容,它类似于传统关系型数据中table的schema,用于定义一个索引(index)的 某个类型(type)的数据的结构。
在ES中,我们无需手动创建type(相当于table)和mapping(相关与schema)。在默认配置下,ES可以根据插入的数 据自动地创建type及其mapping。
mapping中主要包括字段名、字段数据类型和字段索引类型
Elasticsearch新建,索引及更新过程
ES中文档的新建、删除和修改都是先在主分片上完成的,在主分片上完成这些操作以后,才会进行复制操作。比如有3个节点node-1、node-2和node-3,索引blogs有2个主分片,并且复制2份。
新建文档过程
- 客户端给master节点node-1发送新建文档的请求
- node-1节点根据文档的_id,确定该文档属于属于分片1。分片1的主分片在节点node-2上,故将请求转发到node-2
- node-2上的主分片P1处理文档成功,然后转发请求到node-1和node-3节点上的复制节点上。当所有的复制节点报告成功后,node-2节点报告成功到请求的节点,请求节点再返回给客户端
检索文档过程
- 客户端给master节点node-1发送检索文档的请求
- node-1节点根据文档的_id,确定该文档属于分片0。分片0在3个节点中都存在,本次请求中使用节点node-2,于是将请求转发给node-2节点
- node-2节点得到文档数据,并返回给node-1节点,node-1节点返回给客户端
这里es集群会使用轮询的策略读取不同节点上的分片中的文档数据,比如针对以上的查询,下次查询就会读取node-3节点上的R0分片中的文档。
文档局部更新过程
- 客户端给master节点node-1发送局部更新文档的请求
- node-1节点根据文档的_id,确定该文档属于分片1,并发现找到分片1的主分片在node-2节点上,转发请求到node-2节点上
- node-2节点在主分片P1中找出对应id的文档,修改文档内部的_source属性,之后对文档重建索引。如果这个文档已经被其它进程修改,会重试步骤3 retry_on_conflict 次数(retry_on_conflict可通过参数设置)
- 如果步骤3执行成功,node-2节点转发新版本的文档给node-1和node-3节点上的复制分片,这2个节点对文档进行重建索引。一旦node-1和node-3节点上的复制分片处理成功,node-2节点返回成功给node-1节点,node-1节点返回给客户端
参考资料:https://www.cnblogs.com/Henry-pan/p/7242594.html
三、Elasticsearch环境搭建 (虚拟机)
准备
CentOS(版本需大于7 如:CentOS-7-x86_64-Minimal-1804.iso )
Java(版本需大于1.8 如:jdk-8u181-linux-x64.rpm )
Elasticsearch安装包(如: elasticsearch-6.4.0.tar.gz )
将java与elasticsearch安装包传至linux系统
下载地址:链接:https://pan.baidu.com/s/1rM9Y_x8srmZZIvyKXKO7Bw 提取码:pypu
环境搭建
1. 安装Java
[root@elasticsearch ~]# rpm -ivh jdk-8u181-linux-x64.rpm
2. 配置java环境变量
[root@elasticsearch ~]# vi /etc/profile
# 在profile末尾添加以下内容
export JAVA_HOME=/usr/java/latest
export CLASSPATH=.
export PATH=$PATH:$JAVA_HOME/bin
# -------------------------------------------
# 保存并退出
esc键 ---> :wq! ---> Enter键
3. 使环境变量配置生效
[root@elasticsearch ~]# source /etc/profile
4. 解压缩安装Elasticsearch-6.4.0
[root@elasticsearch ~]# tar -zxvf elasticsearch-6.4.0.tar.gz -C /usr
5. 进入Elasticsearch-6.4.0安装目录
[root@elasticsearch ~]# cd /usr/elasticsearch-6.4.0/
6. 启动Elasticsearch
[root@elasticsearch elasticsearch-6.4.0]# bin/elasticsearch
# 后台启动
[root@elasticsearch elasticsearch-6.4.0]# bin/elasticsearch -d
7. 出现异常
org.elasticsearch.bootstrap.StartupException: java.lang.RuntimeException: can not run elasticsearch as root
原因: Elasticsearch不允许使用root用户启动
解决方式:使用普通用户启动。
1. 创建组
[root@elasticsearch elasticsearch-6.4.0]# groupadd elasticsearch
2. 创建用户
[root@elasticsearch elasticsearch-6.4.0]# useradd -g elasticsearch elasticsearch
3. 修改用户权限
[root@elasticsearch elasticsearch-6.4.0]# chown -R elasticsearch:elasticsearch /usr/elasticsearch-6.4.0/
4. 切换至elasticsearch用户
[root@elasticsearch elasticsearch-6.4.0]# su elasticsearch
5. 启动Elasticsearch
[elasticsearch@elasticsearch elasticsearch-6.4.0]$ bin/elasticsearch
# 后台启动
[elasticsearch@elasticsearch elasticsearch-6.4.0]# bin/elasticsearch -d
6. 查看Elasticsearch服务是否启动
jps指令: 是查看当前系统的java服务指令
[root@elasticsearch ~]# jps
7. 确认Elasticsearch服务已启动
通过启动Elasticsearch服务时打印出的启动日志确认Elasticsearch服务已启动:
可以看到Elasticsearch服务提供的访问端口为9200。
8. 获取Elasticsearch集群信息
curl指令:linux操作系统中的网络命令可以发送任何请求方式的http请求
[root@elasticsearch ~]# curl -XGET 127.0.0.1:9200
# 返回值
{
"name" : "CtsMxHz",
"cluster_name" : "elasticsearch", # 集群名
"cluster_uuid" : "mUTKrAz2QZKe-pkpwMrIJw", #集群编号uuid
"version" : {
"number" : "6.4.0", #集群版本
"build_flavor" : "default",
"build_type" : "tar",
"build_hash" : "595516e",
"build_date" : "2018-08-17T23:18:47.308994Z",
"build_snapshot" : false,
"lucene_version" : "7.4.0",
"minimum_wire_compatibility_version" : "5.6.0",
"minimum_index_compatibility_version" : "5.0.0"
},
"tagline" : "You Know, for Search"
}
9. 配置Elasticsearch的远程访问
1. 停止Elasticsearch服务
2. 修改Elasticsearch的核心配置文件elasticsearch.yml
[elasticsearch@elasticsearch elasticsearch-6.4.0]$ vim config/elasticsearch.yml
修改访问IP:
# 修改
# ------------------------------------------------------------------------------
# ---------------------------------- Network -----------------------------------
#
# Set the bind address to a specific IP (IPv4 or IPv6):
# 将host修改为外网访问IP
network.host: 192.168.114.142
#
# Set a custom port for HTTP:
#
#http.port: 9200
#
# For more information, consult the network module documentation.
#
# -----------------------------------------------------------------------------
# 保存并退出
esc键 ---> :wq! ---> Enter键
3. 启动Elasticsearch服务
# 后台启动 加 -d
[elasticsearch@elasticsearch elasticsearch-6.4.0]$ bin/elasticsearch
4. 出现异常
ERROR: [3] bootstrap checks failed
[1]: max file descriptors [4096] for elasticsearch process is too low, increase to at least [65536]
[2]: max number of threads [3802] for user [elasticsearch] 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]
解决方式:修改配置文件
1. 切换到root用户
[elasticsearch@elasticsearch elasticsearch-6.4.0]$ su
密码:
[root@elasticsearch elasticsearch-6.4.0]#
2. 修改配置文件
[root@elasticsearch elasticsearch-6.4.0]# vim /etc/security/limits.conf
# -----------------------------------
# 在配置文件limits.conf末尾添加以下内容(包括*号)
* soft nofile 65536
* hard nofile 131072
* soft nproc 2048
* hard nproc 4096
# -----------------------------------
# 保存并退出
esc键 ---> :wq! ---> Enter键
[root@elasticsearch elasticsearch-6.4.0]# vim /etc/sysctl.conf
# -----------------------------------
...
# 添加以下内容
vm.max_map_count=655360
# -----------------------------------
# 保存并退出
esc键 ---> :wq! ---> Enter键
3. 使虚拟内存生效
[root@elasticsearch elasticsearch-6.4.0]# sysctl -p
4. 重启linux操作系统
[root@elasticsearch elasticsearch-6.4.0]# reboot
5. 关闭防火墙
# 关闭防火墙
[root@elasticsearch ~]# systemctl stop firewalld
# 永久关闭防火墙
[root@elasticsearch ~]# systemctl disable firewalld
6. 进入Elasticsearch-6.4.0安装目录
[root@elasticsearch ~]# cd /usr/elasticsearch-6.4.0/
7. 切换至elasticsearch用户
[root@elasticsearch elasticsearch-6.4.0]# su elasticsearch
8. 启动Elasticsearch
# 后台启动 加 -d
[elasticsearch@elasticsearch elasticsearch-6.4.0]$ bin/elasticsearch
10. 远程访问测试
+++++++++++++++++++++++++++++环境搭建成功+++++++++++++++++++++++++++++
四、Elasticsearch的辅助工具
Kibana概述
Kibana是一个针对Elasticsearch的开源数据分析及可视化平台,用来搜索、查看交互存储在Elasticsearch索引中的数据。使用Kibana,可以通过各种图表进行高级数据分析及展示。
Kibana让海量数据更容易理解。它操作简单,基于浏览器的用户界面可以快速创建仪表板(dashboard)实时显示 Elasticsearch查询动态。
设置Kibana非常简单。无需编码或者额外的基础架构,几分钟内就可以完成Kibana安装并启动Elasticsearch索引监测。
Kibana的安装配置
1. 准备
将kibana安装包上传至linux操作系统
下载地址:链接:https://pan.baidu.com/s/1srW1x_Q-EWmie2jwoS4MdA 提取码:trrr
2. 解压缩安装Kibana
[root@elasticsearch ~]# tar -zxvf kibana-6.4.0-linux-x86_64.tar.gz -C /usr
3. 进入安装目录kibana-6.4.0-linux-x86_64/
[root@elasticsearch ~]# cd /usr/kibana-6.4.0-linux-x86_64/
4. 修改配置文件kibana.yml
[root@elasticsearch kibana-6.4.0-linux-x86_64]# vim config/kibana.yml
...
# ----------------------------------------------------------------------------------------
4 # Specifies the address to which the Kibana server will bind. IP addresses and host names are both valid values.
5 # The default is 'localhost', which usually means remote machines will not be able to connect.
6 # To allow connections from remote users, set this parameter to a non-loopback address.
7 server.host: "192.168.114.142"
# ----------------------------------------------------------------------------------------
...
# ----------------------------------------------------------------------------------------
27 # The URL of the Elasticsearch instance to use for all your queries.
28 elasticsearch.url: "http://192.168.114.142:9200"
# ----------------------------------------------------------------------------------------
...
# 保存并退出
esc键 ---> :wq! ---> Enter键
5. 启动Kibana服务
# 先启动ElasticSearch服务
[elasticsearch@elasticsearch elasticsearch-6.4.0]$ bin/elasticsearch -d
# 在启动Kibana服务
[root@elasticsearch kibana-6.4.0-linux-x86_64]# bin/kibana
# 后台启动 加 -d
[root@elasticsearch kibana-6.4.0-linux-x86_64]# bin/kibana -d
Kibana启动成功:
6. 远程访问测试
访问: http://192.168.114.142:5601进行测试。
使用Kibana实现基本的增删改查
1. 集群信息操作
1. 查看集群健康信息
GET /_cat/health?v
集群状态(status):
- Green(正常)
- Yellow(正常,但是一些副本还没有分配)
- Red(非正常)
2. 查看每个操作返回结果字段的意义
GET /_cat/health?help
3. 查看集群中节点信息
GET /_cat/nodes?v
4. 查看集群中的索引信息
GET /_cat/indice?v
5. 查看集群中指定字段信息
GET /_cat/indices?v&h=health,status,index
2. 索引操作
1. 创建索引
PUT /test
2. 删除索引
DELETE /test
3. 创建类型Mapping
注: 创建的索引必须不存在
以下是创建test(自定义)索引的_doc(自定义)类型的映射,即对应字段的约束。
PUT /test
{
"mappings": {
"_doc":{
"properties":{
"name":{"type":"text"},
"age":{"type":"integer"},
"birthday":{"type":"date"}
}
}
}
}
Mapping Type:
- 简单类型: text , keyword , date , long , double , boolean or ip
- 其它类型: object , geo_point , geo_shape 等
4. 查看类型Mapping
# 语法:GET /索引名/_mapping/类型名
GET /test2/_mapping/bbb
注意:mapping types将会在ES 7.0版本中移除。原因可参考:https://www.elastic.co/guide/en/elasticsearch/reference/current/removal-of-types.html#_why_are_mapping_types_being_removed
3. 文档操作
1. 新增单个文档 — PUT请求
# 语法: PUT /索引名/类型名/id
PUT /test/_doc/1
# request body
{
"name":"zhangsan",
"age":"16",
# 注意:日期的月份与日是两位数
"birthday":"2002-05-05"
}
2. 新增单个文档 — POST请求
# 语法: PUT /索引名/类型名
# 自动分配id
POST /test/_doc
{
"name":"lisi",
"age":"18",
"birthday":"2000-05-05"
}
3. 查询单个文档
# 语法: GET /索引名/类型名/id
GET /test/_doc/1
GET /test/_doc/-Z5pFGgBRSjQM58P4-v6
4. 修改单个文档
# 语法: PUT /索引名/类型名/id
PUT /test/_doc/1
{
"name":"lisi",
"age":"22"
}
5. 修改单个文档指定字段
# 语法: POST /索引名/类型名/id/_update
# 注意: 所需修改的内容必须放在"doc"当中
POST /test/_doc/1/_update
{
# doc 不可缺少
"doc":{"name":"wangwu"}
}
6. 删除单个文档
# 语法: DELETE /索引名/类型名/id
DELETE /test/_doc/-Z5pFGgBRSjQM58P4-v6
4. 批处理操作
除了能够索引、更新和删除单个文档外,Elasticsearch还提供了使用 _bulk API 批量执行上述任何操作的能力。这个 功能非常重要,因为它提供了一种非常有效的机制,可以以尽可能少的网络往返尽可能快地执行多个操作 。
1. 批量新增文档
# 语法: POST /索引名/类型名/_bulk
POST /test/_doc/_bulk
# 指定生成文档的id
{"index":{"_id":"2"}}
{"name":"zhaoliu","age":"55","birthday":"1885-09-05"}
# 不指定生成文档的id,自动生成
{"index":{}}
{"name":"tianqi","age":"22","birthday":"1998-11-10"}
2. 综合批量操作
# 语法: POST /索引名/类型名/_bulk
POST /test/_doc/_bulk
# 修改---------------------
{"update":{"_id":"4"}}
# 修改内容处于doc中
{"doc":{"name":"sunjiu"}}}
# 删除---------------------
{"delete":{"_id":"Yn-dFGgBq7Lfur-3UIiE"}}
使用Kibana实现深层次复杂操作
搜索方式
搜索有两种方式:
-
通过 URL 参数进行搜索
官方详解:https://www.elastic.co/guide/en/elasticsearch/reference/6.0/search-search.html
-
通过 DSL(Request Body) 进行搜索
DSL:Domain Specified Language,特定领域语言
使用请求体可以让你的JSON数据以一种更加可读和更加富有展现力的方式发送。
官方详解:https://www.elastic.co/guide/en/elasticsearch/reference/6.0/query-dsl.html
准备测试数据资源
# 批量插入测试数据
POST /zpark/user/_bulk
{"index":{"_id":1}}
{"name":"zs","realname":"张三","age":18,"birthday":"2018-12-27","salary":1000.0,"address":"北京市昌平区沙阳路55号"}
{"index":{"_id":2}}
{"name":"ls","realname":"李四","age":20,"birthday":"2017-10-20","salary":5000.0,"address":"北京市朝阳区三里屯街道21号"}
{"index":{"_id":3}}
{"name":"ww","realname":"王五","age":25,"birthday":"2016-03-15","salary":4300.0,"address":"北京市海淀区中关村大街新中关商城2楼511室"}
{"index":{"_id":4}}
{"name":"zl","realname":"赵六","age":20,"birthday":"2003-04-19","salary":12300.0,"address":"北京市海淀区中关村软件园9号楼211室"}
{"index":{"_id":5}}
{"name":"tq","realname":"田七","age":35,"birthday":"2001-08-11","salary":1403.0,"address":"北京市海淀区西二旗地铁辉煌国际大厦负一楼"}
查询操作
1. 查询所有并排序
官方示例:https://www.elastic.co/guide/en/elasticsearch/reference/6.0/term-level-queries.html
查看所有并按照年龄降序排列 :
-
URL方式
# q=*:查询所有;sort=age:desc:降序排序;pretty:使代码更好看 GET /zpark/user/_search?q=*&sort=age:desc&pretty
-
DSL方式
GET /zpark/user/_search { "query": { "match_all": {} }, "sort": [ { "age": { "order": "desc" } } ] }
注:
- “match_all”: {} : 查询所有
- “age”: { “order”: “desc” } : 按照年龄降序排列
2. 分页查询
查询第2页的用户(每页显示2条) :
-
URL方式
# q=*:查询所有;sort=_id:asc:升序排序;from=2:从第二条开始((nowPage-1)*pageSize检索);size=2:每页的条数(pageSize) GET /zpark/user/_search?q=*&sort=_id:asc&from=2&size=2
-
DSL方式
GET /zpark/user/_search { "query": { "match_all": {} }, "sort": [ { "_id": { "order": "asc" } } ], "from": 2, "size": 2 }
3. 分析检索关键词查询
查询 address 在海淀区的所有用户,并高亮 :
1. DSL方式
GET /zpark/user/_search
{
"query": {
"match": {
"address": "海淀区"
}
}
}
2. 高亮关键词
GET /zpark/user/_search
{
"query": {
"match": {
"address": "海淀区"
}
},
"highlight": {
# 需要高亮的字段列表
"fields": {
"address": {}
}
}
}
4. 基于Term词元查询
1. 单词元查询(Term Query)
查询 name 是 zs 关键字的用户
-
URL方式
GET /zpark/user/_search?q=name:zs
-
DSL方式
GET /zpark/user/_search { "query": { "term": { "name": { "value": "zs" } } } }
2. 多词元查询(Terms Query)
查询 name 是 zs 与 tq关键字的用户
-
URL方式
GET /zpark/user/_search?q=name:zs,tq
-
DSL方式
GET /zpark/user/_search { "query": { "terms": { "name": [ "zs", "tq" ] } } }
5. 基于数值范围查询(Range Query)
查询年龄在 20~30 岁之间的用户 :
DSL方式:
GET /zpark/user/_search
{
"query": {
"range": {
"age": {
"gte": 20,
"lte": 30
}
}
}
}
注:
- gte / lte :表示包含,即闭区间
- gt / lt :表示不包含,即开期间
6. 基于前缀查询(Prefix Query)
查询真实姓名以 张 开头的用户 :
DSL方式:
GET /zpark/user/_search
{
"query": {
"prefix": {
"realname": {
"value": "张"
}
}
}
}
7. 基于通配符查询(Wildcard Query)
注:
- ‘?’ 匹配一个字符
- ‘*’ 匹配0~n个字符
查询名字以 ‘s’ 结尾的用户 :
DSL方式:
# '?' 匹配一个字符
GET /zpark/user/_search
{
"query": {
"wildcard": {
"name": {
"value": "?s"
}
}
}
}
# '*' 匹配0~n个字符
GET /zpark/user/_search
{
"query": {
"wildcard": {
"name": {
"value": "*s"
}
}
},
"sort": [
{
"age": {
# 升序排列
"order": "asc"
}
}
]
}
8. 基于ids的查询(Ids Query)
查询 id 为1,2,3的用户 :
DSL方式:
GET /zpark/user/_search
{
"query": {
"ids": {
"values": [1,2,3]
}
}
}
9. 模糊查询(Fuzzy Query)
模糊查询 realname 中包含 张 关键字的用户 :
DSL方式:
GET /zpark/user/_search
{
"query": {
"fuzzy": {
"realname": {
"value": "张"
}
}
}
}
10. 多条件查询(Boolean Query)
基于Boolean的查询(多条件查询)
must :查询结果必须符合该查询条件(列表)。
should :类似于or的查询条件。
must_not :查询结果必须不符合查询条件(列表)。
查询 age 在15-30岁之间并且 name 必须通配 z*,并且name必须不以 s 结尾 :
DSL方式:
GET /zpark/user/_search
{
"query": {
"bool": {
"must": [
{
"wildcard": {
"name": {
"value": "z*"
}
}
},
{
"range": {
"age": {
"gte": 15,
"lte": 30
}
}
}
],
"must_not": [
{
"wildcard": {
"name": "*s"
}
}
]
}
}
}
11. 存在查询(Exists Query)
查询指定字段 有值 的文档
GET /zpark/user/_search
{
"query": {
"exists": {
"field": "age"
}
}
}
# 或
GET /zpark/user/_search
{
"query": {
"bool": {
"must": [
{
"exists": {"field": "age"}
}
]
}
}
}
12. 不存在查询(missing query)
没有不存在查询,missing query是exists query的反式运用,结合Boolean query的must_not的查询方式
查询指定字段 没有值 的文档
GET /zpark/user/_search
{
"query": {
"bool": {
"must_not": [
{
"exists": {"field": "age"}
}
]
}
}
}
过滤器(Filter)
官方详解:https://www.elastic.co/guide/en/elasticsearch/reference/6.0/query-filter-context.html
其实准确来说,ES中的查询操作分为2种:查询(query)和过滤(filter)。
查询即是之前提到的query查询,它 (查询)默认会计算每个返回文档的得分,然后根据得分排序。
过滤(filter)只会筛选出符合的文档,并不计算得分,且它可以缓存文档。所以,单从性能考虑,过滤比查询更快。换句话说,过滤适合在大范围筛选数据,而查询则适合精确匹配数据。过滤器主要用于过滤结构化数据。
一般应用时,应先使用过滤操作过滤数据, 然后使用查询匹配数据。
注意: 过滤查询运行时先执行过滤语句,后执行普通查询
官方使用示例:
GET /_search
{
"query": {
"bool": {
"must": [
{ "match": { "title": "Search" }},
{ "match": { "content": "Elasticsearch" }}
],
"filter": [
{ "term": { "status": "published" }},
{ "range": { "publish_date": { "gte": "2015-01-01" }}}
]
}
}
}
过滤器类型
1.词元(term,terms)过滤器
-
单词元过滤器(term filter)
term用于精确匹配 :
GET /zpark/user/_search { "query": { "bool": { "must": [ { "match_all": {} } ], "filter": { "term": { "name": "zs" } } } } }
-
多词元过滤器(terms filter)
terms用于多词条匹配 :
GET /zpark/user/_search { "query": { "bool": { "must": [ { "match_all": {} } ], "filter": { "terms": { "name": [ "ls", "ww" ] } } } } }
2. 基于范围过滤器(ranage filter)
过滤2016-01-01到2019-01-01,查询工资大于5000的用户:
GET /zpark/user/_search
{
"query": {
"bool": {
"must": [
{
"range": {
"salary": {
"gte": 2000
}
}
}
],
"filter": {
"range": {
"birthday": {
"gte": "2016-01-01",
"lte": "2019-01-01"
}
}
}
}
}
}
3. 基于类型过滤器(type filter)
官方示例:https://www.elastic.co/guide/en/elasticsearch/reference/6.0/query-dsl-type-query.html
GET /zpark/_search
{
"query": {
"type":{
"value":"user"
}
}
}
4. 存在过滤器(exists filter)
exists 过滤指定字段没有值的文档 :
GET /zpark/user/_search
{
"query": {
"bool": {
"must": [
{
"match_all": {}
}
],
"filter": {
"exists": {
# 排除age为null的结果
"field": "age"
}
}
}
}
}
5. 标识符过滤器(ids filter)
需要过滤保留若干指定_id的文档,可使用标识符过滤器(ids filter) 。
GET /zpark/user/_search
{
"query": {
"bool": {
"must": [
{
"match_all": {}
}
],
"filter": {
"ids": {
"values": [
"1",
"2"
]
}
}
}
}
}
Query 关注点:此文档与查询子句的匹配程度
Filter 关注点:此文档与查询子句是否匹配
query与filter实战:尽可能使用filter过滤上下文,替代query查询上下文
Query和Filter更详细的对比可参考:https://blog.csdn.net/laoyang360/article/details/80468757
聚合(Aggregations)
官网详情:https://www.elastic.co/guide/en/elasticsearch/reference/6.x/search-aggregations.html
聚合提供了可以分组并统计你的数据的功能。理解聚合最简单的方式就是可以把它粗略的看做SQL的GROUP BY操作和 SQL的组函数。
ElasticSearch中常用的聚合:
-
metric(度量)聚合:度量类型聚合主要针对的number类型的数据,需要ES做比较多的计算工作
-
bucket(桶)聚合:划分不同的“桶”,将数据分配到不同的“桶”里。非常类似sql中的group by语句的含义
聚合的基本结构:
"aggregations" : { // 表示聚合操作,可以使用aggs替代
"<aggregation_name>" : { // 聚合名(别名),可以是任意的字符串。用做响应的key,便于快速取得正确的响应数据。
"<aggregation_type>" : { // 聚合类别,就是各种类型的聚合,如min等
<aggregation_body> // 聚合体,不同的聚合有不同的body
}
[,"aggregations" : { [<sub_aggregation>]+ } ]? // 嵌套的子聚合,可以有0或多个
}
[,"<aggregation_name_2>" : { ... } ]* // 另外的聚合,可以有0或多个
}
1. 度量聚合(Metrics Aggregations)
1. 平均值统计聚合(Avg Aggregation)
平均值查询,作用于number类型字段上。
-
直接查询统计
查询用户的平均年龄 :
GET /zpark/user/_search { "aggs": { # result:自定义别名 "result": { "avg": { "field": "age" } } } }
-
先查询过滤,再进行统计
GET /zpark/user/_search { "query": { "ids": { "values": [1,2,3] } }, "aggs": { # result:自定义别名 "result": { "avg": { "field": "age" } } } }
2. 最大值统计聚合(Max Aggregation)
最大值查询
查询员工的最高工资 :
GET /zpark/user/_search
{
"aggs": {
# result:自定义别名
"result": {
"max": {
"field": "salary"
}
}
}
}
3. 最小值统计聚合(Min Aggregation)
最小值查询
查询员工的最低工资 :
GET /zpark/user/_search
{
"aggs": {
# result:自定义别名
"result": {
"min": {
"field": "salary"
}
}
}
}
4. 求和统计聚合(Sum Aggregation)
指定number类型字段的求和查询
查询所有员工的工资之和 :
GET /zpark/user/_search
{
"aggs": {
# result:自定义别名
"result": {
"sum": {
"field": "salary"
}
}
}
}
5. 常用统计值统计聚合(Stats Aggregation)
统计查询,一次性统计出某个字段上的常用统计值
统计salary的所有相关常用统计值:
GET /zpark/user/_search
{
"aggs": {
# result:自定义别名
"result": {
"stats": {
"field": "salary"
}
}
}
}
2. 桶聚合(Bucket Aggregations)
1. 数值范围统计聚合(Range Aggregation)
自定义区间范围的聚合,我们可以自己手动地划分区间,ES会根据划分出来的区间将数据分配不同的区间上去。
统计0-20岁,20-35岁,35~60岁用户人数 :
GET /zpark/user/_search
{
"aggs": {
# result:自定义别名
"result": {
"range": {
"field": "age",
"ranges": [
{
"from": 0,
"to": 20
},
{
"from": 20,
"to": 35
},
{
"from": 35,
"to": 60
}
]
}
}
}
}
注: from:大于等于;to:小于
2. 词元统计聚合(Terms Aggregation)
自定义分组依据Term,对分组后的数据进行统计
根据年龄分组,统计相同年龄的用户 :
GET /zpark/user/_search
{
"aggs": {
# result:自定义别名
"result": {
"terms": {
"field": "age",
# 保留的统计数据条数
"size": 10
}
}
}
}
# 或
GET /zpark/user/_search
{
"aggs": {
# result:自定义别名
"result": {
"terms": {
"field": "birthday",
"size": 10
}
}
}
}
3. 日期范围统计聚合(Date Range Aggregation)
时间区间聚合专门针对date类型的字段,它与Range Aggregation的主要区别是其可以使用时间运算表达式。
now+10y:表示从现在开始的第10年。
now+10M:表示从现在开始的第10个月。
1990-01-10||+20y:表示从1990-01-01开始后的第20年,即2010-01-01。
now/y:表示在年位上做舍入运算。
统计生日在2018年、2017年、2016年的用户 :
GET /zpark/user/_search
{
"aggs": {
# result:自定义别名
"result": {
"date_range": {
"field": "birthday",
"format": "yyyy-MM-dd",
"ranges": [
{
"from": "now/y-3y",
"to": "now/y-2y"
},
{
"from": "now/y-2y",
"to": "now/y-1y"
},
{
# 当前年上一年的1月1日
"from": "now/y-1y",
# 当前年的1月1日 now: 为当前时间
"to": "now/y"
}
]
}
}
}
}
4. 数值直方图聚合(Histogram Aggregation)
数值直方图聚合,它将某个number类型字段等分成n份,统计落在每一个区间内的记录数。它与前面介绍的Range聚合 非常像,只不过Range可以任意划分区间,而Histogram做等间距划分。既然是等间距划分,那么参数里面必然有距 离参数,就是interval参数。
根据年龄间隔(5岁)统计 :
GET /zpark/user/_search
{
"aggs": {
# result:自定义别名
"result": {
"histogram": {
"field": "age",
"interval": 5
}
}
}
}
5. 日期直方图聚合(Date Histogram Aggregation)
日期直方图聚合,专门对时间类型的字段做直方图聚合。这种需求是比较常用见得的,我们在统计时,通常就会按 照固定的时间段(1个月或1年等)来做统计。
按年统计用户 :
GET /zpark/user/_search
{
"aggs": {
# result:自定义别名
"result": {
"date_histogram": {
"field": "birthday",
"interval": "year",
"format": "yyyy-MM-dd"
}
}
}
}
3. 桶聚合嵌套度量聚合
聚合操作是可以嵌套使用的。通过嵌套,可以使得metric类型的聚合操作作用在每一bucket上。我们可以使用ES的嵌套聚合操作来完成稍微复杂一点的统计功能。
注: 只能是桶聚合嵌套度量聚合
统计每年中用户的最高工资 :
GET /zpark/user/_search
{
"aggs": {
"result": {
"histogram": {
"field": "age",
"interval": 10
},
"aggs": {
"max_salary": {
"max": {
"field": "salary"
}
}
}
}
}
}
五、Java API操作Elasticsearch
操作Elasticsearch的方式有以下两种:
-
Restful API
基于http协议,使用JSON为数据交换格式,通过9200端口与Elasticsearch进行通信
-
JAVA API(Spring Data ElasticSearch)
Spring Data ElasticSearch封装了与ES交互的实现细节,可以使系统开发者以Spring Data Repository 风格实现与ES的数据交互。Elasticsearch为Java用户提供了两种内置客户端:
- 节点客户端 (node client): 节点客户端以无数据节点(none data node)身份加入集群,换言之,它自己不存储任何数据,但是它知道数据在集群中的具体位置,并且能够直接转发请求到对应的节点上。
- 传输客户端 (Transport client): 这个更轻量的传输客户端能够发送请求到远程集群。它自己不加入集群,只是简单转发请求给集群中的节点。
两个Java客户端都通过9300端口与集群交互,使用Elasticsearch传输协议 (Elasticsearch Transport Protocol)。集群中的节点之间也通过9300端口进行通信。如果此端口未开放,你的节点将不能组成集群。
官网详情:https://docs.spring.io/spring-data/elasticsearch/docs/3.1.3.RELEASE/reference/html
Spring-Data-Elasticsearch操作Elasticsearch实战开发
1. Spring环境
1. 创建Maven项目
2. 导入依赖jar
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>3.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.3.RELEASE</version>
</dependency>
3. 准备配置文件
官网详情:https://docs.spring.io/spring-data/elasticsearch/docs/3.1.3.RELEASE/reference/html/#elasticsearch.repositories
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:elasticsearch="http://www.springframework.org/schema/data/elasticsearch"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/data/elasticsearch
http://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch-1.0.xsd">
<!-- Spring data 自动扫描es repository接口,生成实现类 -->
<elasticsearch:repositories base-package="com.acme.repositories"/>
<!-- 声明传输客户端对象 ip:port换成具体的ip和端口,多个以逗号分隔 -->
<elasticsearch:transport-client id="client" cluster-name="elasticsearch" cluster-nodes="192.168.114.142:9300"></elasticsearch:transport-client>
<!-- es自定义的操作对象 -->
<bean id="elasticsearchTemplate" class="org.springframework.data.elasticsearch.core.ElasticsearchTemplate">
<constructor-arg index="0" ref="client"></constructor-arg>
</bean>
</beans>
2. Spring Boot环境
1. 创建Springboot的Maven项目
注:使用springboot项目的快速创建方式,并选择elasticsearch,可以自动导入springboot的依赖jar,以及elasticsearch与spring boot的集成jar,自动生成的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 http://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.1.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.demo</groupId>
<artifactId>es_springboot_springdata_api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>es_springboot_springdata_api</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>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2. 编辑配置文件application.properties
指定客户端节点及端口号
spring.data.elasticsearch.cluster-nodes=192.168.114.142:9300
3. 编写入口类
在入口类上添加@EnableElasticsearchRepositories(“com.demo.dao”)注解,指定dao的位置
package com.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
@SpringBootApplication
@EnableElasticsearchRepositories("com.demo.dao")
public class EsSpringbootSpringdataApiApplication {
public static void main(String[] args) {
SpringApplication.run(EsSpringbootSpringdataApiApplication.class, args);
}
}
3. 准备映射实体类
package com.demo.entity;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import java.util.Date;
/**
* 实体类 --建立--> es中某个索引中的type的映射关系
*/
@Document(indexName = "mall",type = "person")
public class Person {
@Id
private String id;
private String name;
private String sex;
private Integer age;
private Double salary;
//定义日期类型对应的格式
@Field(format = DateFormat.basic_date) //yyy-MM-dd
private Date birthday;
private String address;
//-----------------------------------------
// 有参、无参构造
// get / set方法
// toString()方法
//-----------------------------------------
}
4. 实现操作模块
spring data elsaticsearch提供了三种构建操作模块的方式:
-
默认提供基本的增删改查
继承spring data elsaticsearch提供的接口 ElasticsearchRepository 即会默认提供基本的增删改查;
-
接口中按特定规则声明方法
无需实现类,spring data elsaticsearch根据方法名,自动生成实现类,方法名必须符合一定的规则(这里还扩展出一种忽略方法名,根据注解的方式构建操作模块)
-
自定义repository
在实现类中注入elasticsearchTemplate,实现上面两种方式不易实现的查询(例如:聚合、 分组、深度翻页等)
上面的第一点和第二点只需要声明接口,无需实现类,spring data会扫描并生成实现类
1. 测试默认提供的基本的增删改查
1. 实现ElasticsearchRepository接口
package com.demo.dao;
import com.demo.entity.Person;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import java.util.List;
/**
* 通用的用户dao接口
* 泛型一:返回值的泛型 ---> Person
* 泛型二:主键属性的泛型 ---> String
*/
public interface PersonRepository extends ElasticsearchRepository<Person,String> {
//不需要提供方法
}
2. 测试类:
package com.demo.test;
import com.demo.dao.PersonRepository;
import com.demo.entity.Person;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext-es.xml")
public class PersonRepositoryTest {
@Autowired
private PersonRepository personRepository;
/**
* 测试添加索引数据文档
*/
@Test
public void testAddDocument(){
Person person = new Person("2","ls","male",23,20000D,new Date(),"北京市海淀区四里屯村");
//参数:添加的数据对象 ;返回值:添加的数据对象
Person back = personRepository.save(person);
System.out.println(back);
}
/**
* 测试批量添加索引数据文档
*/
@Test
public void testAddBatch(){
ArrayList<Person> personList = new ArrayList<Person>();
Person person1 = new Person("7","ww","famale",24,30000D,new Date(),"北京市海淀区五里屯村");
Person person2 = new Person("8","zl","famale",25,40000D,new Date(),"北京市海淀区六里屯村");
Person person3 = new Person("5","tq","famale",26,50000D,new Date(),"北京市海淀区七里屯村");
Person person4 = new Person("6","jb","famale",27,60000D,new Date(),"北京市海淀区八里屯村");
personList.add(person1);
personList.add(person2);
personList.add(person3);
personList.add(person4);
personRepository.saveAll(personList);
}
/**
* 测试查询所有索引数据文档
*/
@Test
public void testQueryAll(){
Iterable<Person> people = personRepository.findAll();
for (Person person : people) {
System.out.println(person);
}
}
/**
* 测试查询所有索引数据文档,同时排序
*/
@Test
public void testQueryAllWithSort(){
//按 age 降序排列
// Iterable<Person> people = personRepository.findAll(Sort.by(Sort.Direction.DESC,"age"));
//按 age 升序排列
Iterable<Person> people = personRepository.findAll(Sort.by(Sort.Direction.ASC,"age"));
for (Person person : people) {
System.out.println(person);
}
}
/**
* 测试通过id删除索引数据文档
*/
@Test
public void testRemoveById(){
// personRepository.delete(new Person("6",null,null,null,null,null,null));
personRepository.deleteById("5");
}
/**
* 测试修改索引数据文档
*/
@Test
public void testModifyById(){
//spring data elasticsearch 没有修改方法,可以通过添加方法save进行覆盖修改
personRepository.save(new Person("4","zxl","male",25,40000D,new Date(),"北京市海淀区六里屯村"));
}
/**
* 测试分页查询索引数据文档
*/
@Test
public void testQueryByPage(){
//PageRequest 参数一:当前页减1; 参数二:每页条数
Iterable<Person> people = personRepository.findAll(new PageRequest(0,2));
System.out.println(((Page<Person>) people).getTotalElements());
for (Person person : people) {
System.out.println(person);
}
}
}
2. 测试在ElasticsearchRepository接口中按特定规则的方法名声明方法(Query methods)
1. 实现ElasticsearchRepository接口并声明特定规则方法名的方法
package com.demo.dao;
import com.demo.entity.Person;
import org.springframework.data.elasticsearch.annotations.Query;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import java.util.List;
/**
* 通用的用户dao接口
* 泛型一:返回值的泛型 ---> Person
* 泛型二:主键属性的泛型 ---> String
*/
public interface PersonRepository extends ElasticsearchRepository<Person,String> {
/**
* 根据名字与年龄查询 and
* {"query": {"bool": {"must": [{"match": {"name": name}},{"match": {"age": age}}]}}}
* @param name
* @param age
* @return
*/
public List<Person> findByNameAndAge(String name, Integer age);
/**
* 根据名字或年龄查询 or
* {"query": {"bool": {"should": [{"match": {"name": name}},{"match": {"age": age}}]}}}
* @param name
* @param age
* @return
*/
public List<Person> findByNameOrAge(String name, Integer age);
/**
* 查询年龄在22--24岁之间的数据
* {"query": {"bool": {"must": [{"range": {"age": {"gte": 22,"lte": 24}}}]}}}
* @param start
* @param end
* @return
*/
public List<Person> findByAgeBetween(Integer start, Integer end);
/**
* 根据前缀查询
* {"query": {"bool": {"must": [{"prefix": {"name": {"value": "z"}}}]}}}
* @param prefix
* @return
*/
public List<Person> findByNameStartingWith(String prefix);
/**
* 对sex模糊查询
* {"query": {"bool": {"must": [{"fuzzy": {"sex": {"value": "ale"}}}]}}}
* @param keyWord
* @return
*/
public List<Person> findBySexLike(String keyWord);
/**
* 查询年龄在22--24岁之间,性别是male的数据
* @param start
* @param end
* @param keyWord
* @return
*/
public List<Person> findByAgeBetweenAndSexIs(Integer start,Integer end,String keyWord);
/**
* 查询年龄在22--24岁之间,并按薪资降序排列
* @param start
* @param end
* @return
*/
public List<Person> findByAgeBetweenOrderBySalaryDesc(Integer start,Integer end);
}
2. 测试类:
package com.demo.test;
import com.demo.dao.PersonRepository;
import com.demo.entity.Person;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext-es.xml")
public class PersonRepositoryTest {
@Autowired
private PersonRespository personRepository;
/**
* 测试在接口中按特定规则声明方法实现--按照名字与年龄查找 and
*/
@Test
public void testQueryByNameAndAge(){
List<Person> personList = personRepository.findByNameAndAge("zs", 22);
/*for (Person person : personList) {
System.out.println(person);
}*/
//lambda表达式
personList.forEach(p -> System.out.println(p));
}
/**
* 测试在接口中按特定规则声明方法实现--按照名字或年龄查找 or
*/
@Test
public void testQueryByNameOrAge(){
List<Person> personList = personRepository.findByNameOrAge("zs", 23);
/*for (Person person : personList) {
System.out.println(person);
}*/
//lambda表达式
personList.forEach(p -> System.out.println(p));
}
/**
* 测试在接口中按特定规则声明方法实现--按年龄范围来查找 between
*/
@Test
public void testQueryByAgeBetween(){
List<Person> personList = personRepository.findByAgeBetween(22,24);
/*for (Person person : personList) {
System.out.println(person);
}*/
//lambda表达式
personList.forEach(p -> System.out.println(p));
}
/**
* 测试在接口中按特定规则声明方法实现--根据名字前缀查询 prefix
*/
@Test
public void testQueryByNameStartingWith(){
List<Person> personList = personRepository.findByNameStartingWith("z");
/*for (Person person : personList) {
System.out.println(person);
}*/
//lambda表达式
personList.forEach(p -> System.out.println(p));
}
/**
* 测试在接口中按特定规则声明方法实现--对sex模糊查询 fuzzy
*/
@Test
public void testQueryBySixLike(){
List<Person> personList = personRepository.findBySexLike("mal");
/*for (Person person : personList) {
System.out.println(person);
}*/
//lambda表达式
personList.forEach(p -> System.out.println(p));
}
/**
* 测试在接口中按特定规则声明方法实现--按年龄范围与性别是male来查找 between,and,is
*/
@Test
public void testQueryByAgeBetweenAndSexIs(){
List<Person> personList = personRepository.findByAgeBetweenAndSexIs(22,24,"male");
/*for (Person person : personList) {
System.out.println(person);
}*/
//lambda表达式
personList.forEach(p -> System.out.println(p));
}
/**
* 测试在接口中按特定规则声明方法实现--按年龄范围来查找,按薪资降序排列 between,order by,desc
*/
@Test
public void testQueryByAgeBetweenAndOrderBySalaryDesc(){
List<Person> personList = personRepository.findByAgeBetweenOrderBySalaryDesc(22,24);
/*for (Person person : personList) {
System.out.println(person);
}*/
//lambda表达式
personList.forEach(p -> System.out.println(p));
}
/**
* 测试在接口中按特定规则声明方法实现--按年龄范围来查找,按薪资降序排列 between,order by,desc
*/
@Test
public void testQueryByQuery(){
List<Person> personList = personRepository.findByQuery("zs");
/*for (Person person : personList) {
System.out.println(person);
}*/
//lambda表达式
personList.forEach(p -> System.out.println(p));
}
}
3. 扩展(@Query)— 忽略方法名由注解确定方法实现方式
-
实现ElasticsearchRepository接口并声明方法(方法名任意)
package com.demo.dao; import com.demo.entity.Person; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; import java.util.List; /** * 通用的用户dao接口 * 泛型一:返回值的泛型 ---> Person * 泛型二:主键属性的泛型 ---> String */ public interface PersonRepository extends ElasticsearchRepository<Person,String> { /** * 使用注解的方式自定义查询方式 * @Query ---> 相当于query关键字 * @param keyWord * @return */ @Query("{\"bool\": {\"must\": [{\"fuzzy\": {\"name\": {\"value\": \"?0\"}}}]}}") public List<Person> findByQuery(String keyWord); }
-
测试类
package com.demo.test; import com.demo.dao.PersonRepository; import com.demo.entity.Person; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import java.util.List; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext-es.xml") public class PersonRepositoryTest { @Autowired private PersonRespository personRepository; /** * 测试在接口在接口中使用@Query注解,自定义方法的查询方式 */ @Test public void testQueryByQuery(){ List<Person> personList = personRepository.findByQuery("zs"); /*for (Person person : personList) { System.out.println(person); }*/ //lambda表达式 personList.forEach(p -> System.out.println(p)); } }
3. 测试自定义repository接口及实现类
1. 自定义repository接口并声明方法
package com.demo.dao;
import com.demo.entity.Person;
import java.util.List;
import java.util.Map;
/**
* 定义普通的dao接口
*/
public interface CustomPersonRepository {
//=================基本的自定义查询=============================
/**
* 声明查询方法
*/
public List<Person> findPeopleByAddress(String address);
/**
* 声明高亮查询方法
*/
public List<Person> findPeopleByAddressWithHighLight(String address);
//==================使用过滤器的自定义查询=========================
/**
* 基于范围的过滤查询
* @param start
* @param end
* @return
*/
public List<Person> findByAgeWithRangeFilter(Integer start,Integer end);
/**
* 基于范围的过滤查询,并且分页
* @param start
* @param end
* @return
*/
public List<Person> findByAgeWithRangeFilterAndPage(Integer start,Integer end,Integer page,Integer size);
//====================使用度量聚合的自定义查询======================
/**
* 度量聚合查询 --求平均值
* 查询某一性别的人的平均年龄
* @return
*/
public Map findSexWithAgeAvgAggs(String sex);
//====================使用桶聚合的自定义查询==========================
/**
* 桶聚合 --- 根据出生日期范围的桶聚合查询
* @return
*/
public Map findBybirthdayWithDateRangeAggs();
//=================桶聚合嵌套度量聚合的查询===========================
/**
* 桶聚合嵌套度量聚合查询
* 按照年龄间隔5 统计
* 直方图桶聚合 + max度量聚合
*/
public Map findWithMaxSalaryAggsAndAgeHistogramAggs();
}
2. 实现自定义repository接口及方法
package com.demo.dao;
import com.demo.entity.Person;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.ResultsExtractor;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
public class CustomPersonRepositoryImpl implements CustomPersonRepository {
//注入elasticsearch的自定义操作对象
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
//=================基本的自定义查询=============================
/**
* 查询方法实现
*/
@Override
public List<Person> findPeopleByAddress(String address) {
SearchQuery searchQuery = new NativeSearchQueryBuilder()
//封装query查询的条件;参数一:查询的域(字段);参数二:查询字段的值
.withQuery(QueryBuilders.matchQuery("address",address))
//返回NativeSearchQuery对象
.build();
//打印输出查询条件语法
System.out.println(searchQuery.getQuery().toString());
//获取符合条件的Person
List<Person> people = elasticsearchTemplate.queryForList(searchQuery, Person.class);
return people;
}
/**
* 高亮查询方法实现
*/
@Override
public List<Person> findPeopleByAddressWithHighLight(String address) {
SearchQuery searchQuery = new NativeSearchQueryBuilder()
//封装query查询的条件;参数一:查询的域(字段);参数二:查询字段的值
.withQuery(QueryBuilders.matchQuery("address",address))
//封装高亮字段
.withHighlightFields(new HighlightBuilder.Field("address"))
//返回NativeSearchQuery对象
.build();
//打印输出查询条件语法
System.out.println(searchQuery.getQuery().toString());
//获取封装高亮字段后的对象集合的AggregatedPage对象
AggregatedPage<Person> aggregatedPage = elasticsearchTemplate.queryForPage(searchQuery, Person.class, new SearchResultMapper() {
/**
* 将结果映射到实体类
* @param searchResponse
* @param aClass
* @param pageable
* @param <T>
* @return
*/
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
//创建集合装Person对象
List<Person> people = new ArrayList<>();
Person person = null;
//检索命中的集合对象
SearchHits searchHits = searchResponse.getHits();
for (SearchHit searchHit : searchHits) {
person = new Person();
//封装id
person.setId(searchHit.getId());
//获取命中的对象信息中的各条属性信息
Map<String, Object> resultMap = searchHit.getSourceAsMap();
/*resultMap.forEach((k,v) -> {
System.out.println(k+" | "+v);
});*/
//封装其他属性
person.setName(resultMap.get("name").toString());
person.setAge((Integer) resultMap.get("age"));
person.setBirthday(new Date((Long) resultMap.get("birthday")));
person.setSalary((Double) resultMap.get("salary"));
person.setSex(resultMap.get("sex").toString());
//封装address
person.setAddress(searchHit.getHighlightFields().get("address").getFragments()[0].toString());
//将封装后的person放入list集合
people.add(person);
}
return new AggregatedPageImpl<T>((List<T>) people);
}
});
//获取封装高亮字段后的集合
List<Person> content = aggregatedPage.getContent();
return content;
}
//==================使用过滤器的自定义查询=========================
/**
* 基于范围的过滤查询
* @param start
* @param end
* @return
*/
@Override
public List<Person> findByAgeWithRangeFilter(Integer start, Integer end) {
SearchQuery searchQuery = new NativeSearchQueryBuilder()
//封装过滤查询条件
.withFilter(QueryBuilders.rangeQuery("age").gte(start).lte(end))
.build();
List<Person> people = elasticsearchTemplate.queryForList(searchQuery, Person.class);
return people;
}
/**
* 基于范围的过滤查询,并且分页
* @param start
* @param end
* @return
*/
@Override
public List<Person> findByAgeWithRangeFilterAndPage(Integer start, Integer end,Integer page,Integer size) {
SearchQuery searchQuery = new NativeSearchQueryBuilder()
//封装过滤查询条件
.withFilter(QueryBuilders.rangeQuery("age").gte(start).lte(end))
//封装分页查询条件
.withPageable(new PageRequest(page,size))
.build();
List<Person> people = elasticsearchTemplate.queryForList(searchQuery, Person.class);
return people;
}
//====================使用度量聚合的自定义查询======================
/**
* 度量聚合查询 --求平均值
* 查询某一性别的人的平均年龄
* @param sex
* @return
*/
@Override
public Map findSexWithAgeAvgAggs(String sex) {
SearchQuery searchQuery = new NativeSearchQueryBuilder()
//封装查询条件
.withQuery(QueryBuilders.termQuery("sex",sex))
//封装度量聚合查询条件 --- 求年龄的平均值
.addAggregation(AggregationBuilders.avg("result").field("age"))
//封装度量聚合查询条件 --- 获取age相关的所有统计结果(最大值、最小值、和 ...)
//.addAggregation(AggregationBuilders.stats("result").field("age"))
.build();
Aggregations aggregations = elasticsearchTemplate.query(searchQuery, new ResultsExtractor<Aggregations>() {
/**
* searchResponse 代表完整的结果集,可抽取聚合信息
* @param searchResponse
* @return
*/
@Override
public Aggregations extract(SearchResponse searchResponse) {
//获取完整的聚合信息
Aggregations aggregations = searchResponse.getAggregations();
return aggregations;
}
});
//获取度量集合---平均值信息
Map<String, Aggregation> aggregationMap = aggregations.getAsMap();
return aggregationMap;
}
//====================使用桶聚合的自定义查询==========================
/**
* 桶聚合 --- 根据出生日期范围的桶聚合查询
* @return
*/
@Override
public Map findBybirthdayWithDateRangeAggs() {
SearchQuery searchQuery = new NativeSearchQueryBuilder()
//封装聚合查询信息
.addAggregation(AggregationBuilders
//封装日期桶聚合查询别名
.dateRange("result")
//封装桶聚合查询字段(域)
.field("birthday")
//封装日期桶聚合查询范围
.addRange(new DateTime(1514816718000L),new DateTime(1546698377000L))
.addRange(new DateTime(1483280718000L),new DateTime(1514816718000L))
.addRange(new DateTime(1451658318000L),new DateTime(1483280718000L)))
.build();
Aggregations aggregations = elasticsearchTemplate.query(searchQuery, new ResultsExtractor<Aggregations>() {
/**
* searchResponse 代表完整的结果集,就可抽取聚合信息
* @param searchResponse
* @return
*/
@Override
public Aggregations extract(SearchResponse searchResponse) {
Aggregations aggregations = searchResponse.getAggregations();
return aggregations;
}
});
Map<String, Aggregation> aggregationMap = aggregations.getAsMap();
return aggregationMap;
}
//=================桶聚合嵌套度量聚合的查询===========================
/**
* 桶聚合嵌套度量聚合查询
* 按照年龄间隔5 统计
* 直方图桶聚合 + max度量聚合
*/
@Override
public Map findWithMaxSalaryAggsAndAgeHistogramAggs() {
SearchQuery searchQuery = new NativeSearchQueryBuilder()
//封装聚合查询信息
.addAggregation(AggregationBuilders
//封装直方图桶聚合别名
.histogram("result")
//封装直方图桶聚合统计字段
.field("age")
//封装直方图桶聚合统计间隔
.interval(5D)
//嵌套度量聚合使用subAggregation,封装度量聚合信息
.subAggregation(AggregationBuilders
//封装嵌套的度量聚合max
.max("max_salary")
//封装嵌套的度量聚合查询字段
.field("salary"))
)
//指定查询的索引
.withIndices("mall")
//指定查询的类型
.withTypes("person")
.build();
Aggregations aggregations = elasticsearchTemplate.query(searchQuery, new ResultsExtractor<Aggregations>() {
/**
* searchResponse 代表完整的结果集,可以提取聚合信息
* @param searchResponse
* @return
*/
@Override
public Aggregations extract(SearchResponse searchResponse) {
Aggregations aggregations = searchResponse.getAggregations();
return aggregations;
}
});
//提取查询到的聚合信息
Map<String, Aggregation> aggregationMap = aggregations.getAsMap();
return aggregationMap;
}
}
3. 将自定义的接口实现类加入到spring工厂
-
若是spring项目:配置applicationContext-es.xml
在applicationContext-es.xml配置文件中配置自定义repository实现类,将其交给spring管理。
<!-- 声明自定义的dao接口的实现类 --> <bean id="customPersonRepository" class="com.demo.dao.CustomPersonRepositoryImpl"></bean>
-
若是springboot项目:有两种方式:
-
实现ElasticsearchRepository接口,同时实现自定义接口
package com.demo.dao; import com.demo.entity.Poem; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; /** * 通用es的dao接口 */ public interface PoemElasticsearchRepository extends ElasticsearchRepository<Poem,Integer>,PoemCustomRepository{ }
-
创建一个配置类(@Configuration),使用@Bean注解
package com.demo.config; import com.demo.dao.es.CustomPoetryRepositoryImpl; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class ESConfig { @Bean public PoemCustomRepositoryImpl createPoemCustomRepositoryImpl(){ return new PoemCustomRepositoryImpl(); } }
-
4. 编写测试类
package com.demo.test;
import com.demo.dao.CustomPersonRepository;
import com.demo.entity.Person;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
import java.util.Map;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext-es.xml")
public class CustomPersonRepositoryTest {
@Autowired
private CustomPersonRepository customPersonRepository;
//=================基本的自定义查询=============================
/**
* 测试自定义通过地址查询
*/
@Test
public void testQueryPeopleByAddress(){
List<Person> people = customPersonRepository.findPeopleByAddress("海淀区");
for (Person person : people) {
System.out.println(person);
}
}
/**
* 测试自定义通过地址查询并高亮显示
*/
@Test
public void testQueryPeopleByAddressWithHighLight(){
List<Person> people = customPersonRepository.findPeopleByAddressWithHighLight("海淀区");
for (Person person : people) {
System.out.println(person);
}
}
//==================使用过滤器的自定义查询=========================
/**
* 测试自定义对age进行范围过滤查询
*/
@Test
public void testQueryByAgeWithRangeFilter(){
List<Person> people = customPersonRepository.findByAgeWithRangeFilter(22,25);
for (Person person : people) {
System.out.println(person);
}
}
/**
* 测试自定义对age进行范围过滤查询,并分页
*/
@Test
public void testQueryByAgeWithRangeFilterAndPage(){
List<Person> people = customPersonRepository.findByAgeWithRangeFilterAndPage(21,24,1,2);
for (Person person : people) {
System.out.println(person);
}
}
//====================使用度量聚合的自定义查询======================
/**
* 测试自定义度量聚合查询 --求平均值
*/
@Test
public void testQuerySexWithAgeAvgAggs(){
Map ageAvgAggs = customPersonRepository.findSexWithAgeAvgAggs("male");
ageAvgAggs.forEach((k,v) -> {
System.out.println(k+" | "+v);
});
}
//====================使用桶聚合的自定义查询==========================
/**
* 测试自定义桶聚合查询 -- 日期范围桶聚合
*/
@Test
public void testQueryBybirthdayWithDateRangeAggs(){
Map ageAvgAggs = customPersonRepository.findBybirthdayWithDateRangeAggs();
ageAvgAggs.forEach((k,v) -> {
System.out.println(k+" | "+v);
});
}
//=================桶聚合嵌套度量聚合的查询===========================
/**
* 测试自定义桶聚合聚合嵌套度量聚合查询
*/
@Test
public void testQueryWithMaxSalaryAggsAndAgeHistogramAggs(){
Map ageAvgAggs = customPersonRepository.findWithMaxSalaryAggsAndAgeHistogramAggs();
ageAvgAggs.forEach((k,v) -> {
System.out.println(k+" | "+v);
});
}
六、Elasticsearch集成中文分词器IK
参考资料:https://github.com/medcl/elasticsearch-analysis-ik
1. 安装IK分词器
1. 在线安装
进入Elasticsearch的安装目录,并执行安装命令:
# 进入安装目录
[root@elasticsearch ~]# cd /usr/elasticsearch-6.4.0/
# 切换到普通用户
[root@elasticsearch elasticsearch-6.4.0]# su elasticsearch
# 执行在线安装命令
[elasticsearch@elasticsearch elasticsearch-6.4.0]$ ./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.4.0/elasticsearch-analysis-ik-6.4.0.zip
注: 安装的Ik分词器的版本必须是你当前使用的Elasticsearch的版本,即“ /v6.4.0/elasticsearch-analysis-ik-6.4.0.zip ”中的版本号与使用的Elasticsearch的版本号相同。
2. 离线安装
1. 下载准备Ik的安装包,上传至Linux操作系统
下载地址:https://github.com/medcl/elasticsearch-analysis-ik/releases
2. 进入Elasticsearch的安装目录下的plugins目录并创建ik目录
[root@elasticsearch ~]# cd /usr/elasticsearch-6.4.0/plugins/ && mkdir ik
3. 安装unzip工具
[root@elasticsearch ~]# yum install unzip
4. 将Ik的安装包移动到ik目录
[root@elasticsearch ~]# mv elasticsearch-analysis-ik-6.4.0 /usr/elasticsearch-6.4.0/plugins/ik/
5. 进入ik目录,解压缩ik安装包
# 进入ik目录
[root@elasticsearch ~]# cd /usr/elasticsearch-6.4.0/plugins/ik/
# 解压缩
[root@elasticsearch ik]# unzip elasticsearch-analysis-ik-6.4.0
# 删除压缩包
[root@elasticsearch ik]# rm -rf elasticsearch-analysis-ik-6.4.0
6. 重启Elasticsearch服务
# 查看Elasticsearch服务进程
[root@elasticsearch ik]# jps
35836 Jps
35660 Elasticsearch
# 杀死进程
[root@elasticsearch ik]# kill -9 35660
# 切换到普通用户
[root@elasticsearch elasticsearch-6.4.0]# su elasticsearch
# 重启服务
[elasticsearch@elasticsearch ik]# ../../bin/elasticsearch
2. 测试
1. 创建测试索引
PUT /news
2. 创建类型映射
POST /news/international/_mapping
{
"properties": {
"content":{
"type": "text",
"analyzer": "ik_max_word", # 会将文本做最细粒度的拆分
"search_analyzer": "ik_max_word"
}
}
}
3. 插入测试数据
POST /news/international/_bulk
{"index":{"_id":1}}
{"content":"美国留给伊拉克的是个烂摊子吗"}
{"index":{"_id":2}}
{"content":"公安部:各地校车将享最高路权"}
{"index":{"_id":3}}
{"content":"中韩渔警冲突调查:韩警平均每天扣1艘中国渔船"}
{"index":{"_id":4}}
{"content":"中国驻洛杉矶领事馆遭亚裔男子枪击 嫌犯已自首"}
4. 根据关键词高亮查询
GET /news/international/_search
{
"query": {
"match": {
"content": "中国"
}
},
"highlight": {
"fields": {"content": {}}
}
}
3. IK扩展词典配置
1. 进入ik的config目录,创建analysis-ik 目录并进入
[root@elasticsearch ~]# cd /usr/kibana-6.4.0-linux-x86_64/config/
[root@elasticsearch config]# mkdir analysis-ik
[root@elasticsearch config]# cd analysis-ik/
2. 创建IKAnalyzer.cfg.xml 配置文件并配置
[root@elasticsearch analysis-ik]# vim IKAnalyzer.cfg.xml
# 配置内容
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict">mydict.dic;</entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords"></entry>
<!--用户可以在这里配置远程扩展字典 -->
<!--<entry key="remote_ext_dict">location</entry>-->
<!--用户可以在这里配置远程扩展停止词字典-->
<!--<entry key="remote_ext_stopwords">http://xxx.com/xxx.dic</entry>-->
</properties>
3. 创建自己的扩展词典
[root@elasticsearch analysis-ik]# vim mydict.dic
# 自定义词汇
扣1艘
4. 重启Elasticsearch服务
5. 测试
七、扫描和滚屏
参考资料:
- https://docs.spring.io/spring-data/elasticsearch/docs/3.1.3.RELEASE/reference/html/#elasticsearch.scroll
- https://es.xiaoleilu.com/060_Distributed_Search/20_Scan_and_scroll.html
scan(扫描) 搜索类型是和 scroll(滚屏) API一起使用来从Elasticsearch里高效地取回巨大数 量的结果而不需要付出深分页的代价。
scroll(滚屏)
一个滚屏搜索允许我们做一个初始阶段搜索并且持续批量从Elasticsearch里拉取结果直到没 有结果剩下。这有点像传统数据库里的cursors(游标)。 滚屏搜索会及时制作快照。这个快照不会包含任何在初始阶段搜索请求后对index做的修改。 它通过将旧的数据文件保存在手边,所以可以保护index的样子看起来像搜索开始时的样子。
scan(扫描)
深度分页代价最高的部分是对结果的全局排序,但如果禁用排序,就能以很低的代价获得全 部返回结果。为达成这个目的,可以采用 scan(扫描) 搜索模式。扫描模式让Elasticsearch不 排序,只要分片里还有结果可以返回,就返回一批结果。
为了使用scan-and-scroll(扫描和滚屏),需要执行一个搜索请求,将 search_type 设置 成 scan ,并且传递一个 scroll 参数来告诉Elasticsearch滚屏应该持续多长时间。
# 保持滚屏开启1分钟
GET /old_index/_search?search_type=scan&scroll=1m
{
"query": { "match_all": {}},
"size": 1000
}
这个请求的应答没有包含任何命中的结果,但是包含了一个Base-64编码的 _scroll_id(滚屏 id) 字符串。现在我们可以将 _scroll_id 传递给 _search/scroll 末端来获取第一批结果:
# 保持滚屏开启另一分钟
GET /_search/scroll?scroll=1m
# _scroll_id 可以在body或者URL里传递,也可以被当做查询参数传递
c2Nhbjs1OzExODpRNV9aY1VyUVM4U0NMd2pjWlJ3YWlBOzExOTpRNV9aY1VyUVM4U0NMd2pjWlJ3YWlBOzExNjpRNV9aY1VyUVM4U0NMd2pjWlJ3YWlBOzExNzpRNV9aY1VyUVM4U0NMd2pjWlJ3YWlBOzEyMDpRNV9aY1VyUVM4U0NMd2pjWlJ3YWlBOzE7dG90YWxfaGl0czoxOw==
注意:要再次指定 ?scroll=1m 。滚屏的终止时间会在我们每次执行滚屏请求时刷新,所以他 只需要给我们足够的时间来处理当前批次的结果而不是所有的匹配查询的document。
这个滚屏请求的应答包含了第一批次的结果。虽然指定了一个1000的size,但是获得了更多的document。当扫描时,size 被应用到每一个分片上,所以我们在每个批次里最多或获得 size * number_of_primary_shards(size*主分片数) 个document。
注意: 滚屏请求也会返回一个新的 _scroll_id 。每次做下一个滚屏请求时,必须传递前一次请 求返回的 _scroll_id 。
如果没有更多的命中结果返回,就处理完了所有的命中匹配的document。
提示: 一些Elasticsearch官方客户端提供扫描和滚屏的小助手。小助手提供了一个对这个功能 的简单封装。
八、搭建Elasticsearch集群(虚拟机)
1. 准备两台克隆机
1. CentOS 6 需要修改IP;CentOS 7自动获取IP时不需要手动修改IP,若是静态IP则需要修改
2. 修改主机名
2. 清空各个克隆机中的历史数据
[root@esnode ~]# cd /usr/elasticsearch-6.4.0/
[root@esnode elasticsearch-6.4.0]# rm -rf data/*
3. 修改Elasticsearch的配置文件elasticsearch.yml
[root@esnode ~]# vim /usr/elasticsearch-6.4.0/config/elasticsearch.yml
# 修改
...
# 所有节点的名称必须保持一致
cluster.name: elasticsearch-cluster
...
# 修改为当前服务器的各自的节点名称
node.name: node1
...
# 修改为当前服务器的IP地址
network.host: 192.168.114.148
...
# 修改为所有节点服务器的IP+es访问端口
discovery.zen.ping.unicast.hosts: ["192.168.114.148:9300", "192.168.114.149:9300"]
...
# 保存并退出
esc键 --> :wq! --> Enter键
4. 启动Elasticsearch服务
[root@esnode ~]# cd /usr/elasticsearch-6.4.0/
[root@esnode elasticsearch-6.4.0]# su elasticsearch
[elasticsearch@esnode elasticsearch-6.4.0]$ bin/elasticsearch
5. 修改节点1的kibana配置文件
[root@esnode ~]# vim /usr/kibana-6.4.0-linux-x86_64/config/kibana.yml
# 修改
...
# 修改为本机IP
server.host: "192.168.114.142"
...
# 修改为该elasticsearch集群任意节点IP+访问端口
elasticsearch.url: "http://192.168.114.148:9200"
...
# 保存并退出
esc键 --> :wq! --> Enter键
6. 测试
# 查看节点信息
GET /_cat/nodes?v
# ----------------节点信息---------------------------------------------------
ip heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name
192.168.114.148 14 94 6 0.08 0.16 0.15 mdi * node1
192.168.114.149 12 93 4 0.03 0.02 0.05 mdi - node2
# 创建索引
PUT /demo
# 查看索引信息
GET /_cat/indices?v
# ---------------索引信息---------------------------------------------------
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
# 可以看到索引状态为green
green open demo LhHyP9RUSdiFdOw2e-F8fA 5 1 0 0 2.2kb 1.1kb
# 添加索引数据
PUT /demo/user/1
{
"name":"1"
}
# ---------------索引信息---------------------------------------------------
{
"_index": "demo",
"_type": "user",
"_id": "1",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
# 可看到成功两条,即主分片与复制分片
"successful": 2,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1
}