官网地址: https://www.elastic.co/cn/products/elasticsearch
Elasticsearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web 接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。 设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。
Elasticsearch不仅仅是Lucene和全文搜索引擎,它还提供:
分布式的搜索引擎和数据分析引擎
全文检索
对海量数据进行近实时的处理
REST : (资源的)表现层状态转化((Resources) Representational State Transfer),如果一个架构符合REST原则,就称它为 RESTful 架构风格。
RESTful架构,即符合以下特点:
(1)每一个URI代表一种资源;
(2)客户端和服务器之间,传递这种资源的某种表现层;
(3)客户端通过四个HTTP动词,对服务器端资源进行操作,实现"表现层状态转化"。
参考资料:
Elasticsearch是一个接近实时的搜索平台。这意味着,从索引一个文档直到这个文档能够被搜索到有一个轻微的延 迟(通常是1秒)
一个集群就是由一个或多个节点组织在一起,它们共同持有你整个的数据,并一起提供索引和搜索功能。一个集群 由一个唯一的名字标识,这个名字默认就是“elasticsearch”。这个名字是重要的,因为一个节点只能通过指定某个集 群的名字,来加入这个集群。在产品环境中显式地设定这个名字是一个好习惯,但是使用默认值来进行测试/开发 也是不错的。
一个节点是你集群中的一个服务器,作为集群的一部分,它存储你的数据,参与集群的索引和搜索功能。和集群类 似,一个节点也是由一个名字来标识的,默认情况下,这个名字是一个随机的漫威漫画角色的名字,这个名字会在 启动的时候赋予节点。这个名字对于管理工作来说挺重要的,因为在这个管理过程中,你会去确定网络中的哪些服 务器对应于Elasticsearch集群中的哪些节点。 一个节点可以通过配置集群名称的方式来加入一个指定的集群。默认情况下,每个节点都会被安排加入到一个叫 做“elasticsearch”的集群中,这意味着,如果你在你的网络中启动了若干个节点,并假定它们能够相互发现彼此,它 们将会自动地形成并加入到一个叫做“elasticsearch”的集群中。 在一个集群里,只要你想,可以拥有任意多个节点。而且,如果当前你的网络中没有运行任何Elasticsearch节点, 这时启动一个节点,会默认创建并加入一个叫做“elasticsearch”的集群。
在分布式集群情况下,ES中的节点可分为4类:
一个索引就是一个拥有几分相似特征的文档的集合。比如说,你可以有一个客户数据的索引,另一个产品目录的索 引,还有一个订单数据的索引。一个索引由一个名字来标识(必须全部是小写字母的),并且当我们要对对应于这 个索引中的文档进行索引、搜索、更新和删除的时候,都要使用到这个名字。索引类似于关系型数据库中Database 的概念。在一个集群中,如果你想,可以定义任意多的索引。
在一个索引中,你可以定义一种或多种类型。一个类型是你的索引的一个逻辑上的分类/分区,其语义完全由你来 定。通常,会为具有一组共同字段的文档定义一个类型。比如说,我们假设你运营一个博客平台并且将你所有的数 据存储到一个索引中。在这个索引中,你可以为用户数据定义一个类型,为博客数据定义另一个类型,当然,也可 以为评论数据定义另一个类型。类型类似于关系型数据库中Table的概念。
一个文档是一个可被索引的基础信息单元。比如,你可以拥有某一个客户的文档,某一个产品的一个文档,当然, 也可以拥有某个订单的一个文档。文档以JSON(Javascript Object Notation)格式来表示,而JSON是一个到处存在 的互联网数据交互格式。
在一个index/type里面,只要你想,你可以存储任意多的文档。注意,尽管一个文档,物理上存在于一个索引之 中,文档必须被索引/赋予一个索引的type。文档类似于关系型数据库中Record的概念。实际上一个文档除了用户定 义的数据外,还包括index、 type和_id字段
一个索引可以存储超出单个结点硬件限制的大量数据。比如,一个具有10亿文档的索引占据1TB的磁盘空间,而任 一节点都没有这样大的磁盘空间;或者单个节点处理搜索请求,响应太慢。
为了解决这个问题,Elasticsearch提供了将索引划分成多份的能力,这些份就叫做分片。当你创建一个索引的时 候,你可以指定你想要的分片的数量。每个分片本身也是一个功能完善并且独立的“索引”,这个“索引”可以被放置 到集群中的任何节点上。 分片之所以重要,主要有两方面的原因:
在一个网络/云的环境里,失败随时都可能发生,在某个分片/节点不知怎么的就处于离线状态,或者由于任何原因 消失了。这种情况下,有一个故障转移机制是非常有用并且是强烈推荐的。为此目的,Elasticsearch允许你创建分 片的一份或多份拷贝,这些拷贝叫做复制分片,或者直接叫复制。复制之所以重要,主要有两方面的原因:
总之,每个索引可以被分成多个分片。一个索引也可以被复制0次(意思是没有复制)或多次。一旦复制了,每个 索引就有了主分片(作为复制源的原来的分片)和复制分片(主分片的拷贝)之别。分片和复制的数量可以在索引 创建的时候指定。在索引创建之后,你可以在任何时候动态地改变复制数量,但是不能改变分片的数量。
默认情况下,Elasticsearch中的每个索引被分片5个主分片和1个复制,这意味着,如果你的集群中至少有两个节 点,你的索引将会有5个主分片和另外5个复制分片(1个完全拷贝),这样的话每个索引总共就有10个分片。一个 索引的多个分片可以存放在集群中的一台主机上,也可以存放在多台主机上,这取决于你的集群机器数量。主分片 和复制分片的具体位置是由ES内在的策略所决定的。
Mapping是ES中的一个很重要的内容,它类似于传统关系型数据中table的schema,用于定义一个索引(index)的 某个类型(type)的数据的结构。
在ES中,我们无需手动创建type(相当于table)和mapping(相关与schema)。在默认配置下,ES可以根据插入的数 据自动地创建type及其mapping。
mapping中主要包括字段名、字段数据类型和字段索引类型
ES中文档的新建、删除和修改都是先在主分片上完成的,在主分片上完成这些操作以后,才会进行复制操作。比如有3个节点node-1、node-2和node-3,索引blogs有2个主分片,并且复制2份。
这里es集群会使用轮询的策略读取不同节点上的分片中的文档数据,比如针对以上的查询,下次查询就会读取node-3节点上的R0分片中的文档。
参考资料:https://www.cnblogs.com/Henry-pan/p/7242594.html
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
[root@elasticsearch ~]# rpm -ivh jdk-8u181-linux-x64.rpm
[root@elasticsearch ~]# vi /etc/profile
# 在profile末尾添加以下内容
export JAVA_HOME=/usr/java/latest
export CLASSPATH=.
export PATH=$PATH:$JAVA_HOME/bin
# -------------------------------------------
# 保存并退出
esc键 ---> :wq! ---> Enter键
[root@elasticsearch ~]# source /etc/profile
[root@elasticsearch ~]# tar -zxvf elasticsearch-6.4.0.tar.gz -C /usr
[root@elasticsearch ~]# cd /usr/elasticsearch-6.4.0/
[root@elasticsearch elasticsearch-6.4.0]# bin/elasticsearch
# 后台启动
[root@elasticsearch elasticsearch-6.4.0]# bin/elasticsearch -d
org.elasticsearch.bootstrap.StartupException: java.lang.RuntimeException: can not run elasticsearch as root
原因: Elasticsearch不允许使用root用户启动
解决方式:使用普通用户启动。
[root@elasticsearch elasticsearch-6.4.0]# groupadd elasticsearch
[root@elasticsearch elasticsearch-6.4.0]# useradd -g elasticsearch elasticsearch
[root@elasticsearch elasticsearch-6.4.0]# chown -R elasticsearch:elasticsearch /usr/elasticsearch-6.4.0/
[root@elasticsearch elasticsearch-6.4.0]# su elasticsearch
[elasticsearch@elasticsearch elasticsearch-6.4.0]$ bin/elasticsearch
# 后台启动
[elasticsearch@elasticsearch elasticsearch-6.4.0]# bin/elasticsearch -d
jps指令: 是查看当前系统的java服务指令
[root@elasticsearch ~]# jps
通过启动Elasticsearch服务时打印出的启动日志确认Elasticsearch服务已启动:
可以看到Elasticsearch服务提供的访问端口为9200。
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"
}
[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键
# 后台启动 加 -d
[elasticsearch@elasticsearch elasticsearch-6.4.0]$ bin/elasticsearch
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]
解决方式:修改配置文件
[elasticsearch@elasticsearch elasticsearch-6.4.0]$ su
密码:
[root@elasticsearch elasticsearch-6.4.0]#
[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键
[root@elasticsearch elasticsearch-6.4.0]# sysctl -p
[root@elasticsearch elasticsearch-6.4.0]# reboot
# 关闭防火墙
[root@elasticsearch ~]# systemctl stop firewalld
# 永久关闭防火墙
[root@elasticsearch ~]# systemctl disable firewalld
[root@elasticsearch ~]# cd /usr/elasticsearch-6.4.0/
[root@elasticsearch elasticsearch-6.4.0]# su elasticsearch
# 后台启动 加 -d
[elasticsearch@elasticsearch elasticsearch-6.4.0]$ bin/elasticsearch
+++++++++++++++++++++++++++++环境搭建成功+++++++++++++++++++++++++++++
Kibana是一个针对Elasticsearch的开源数据分析及可视化平台,用来搜索、查看交互存储在Elasticsearch索引中的数据。使用Kibana,可以通过各种图表进行高级数据分析及展示。
Kibana让海量数据更容易理解。它操作简单,基于浏览器的用户界面可以快速创建仪表板(dashboard)实时显示 Elasticsearch查询动态。
设置Kibana非常简单。无需编码或者额外的基础架构,几分钟内就可以完成Kibana安装并启动Elasticsearch索引监测。
将kibana安装包上传至linux操作系统
下载地址:链接:https://pan.baidu.com/s/1srW1x_Q-EWmie2jwoS4MdA 提取码:trrr
[root@elasticsearch ~]# tar -zxvf kibana-6.4.0-linux-x86_64.tar.gz -C /usr
[root@elasticsearch ~]# cd /usr/kibana-6.4.0-linux-x86_64/
[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键
# 先启动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
访问: http://192.168.114.142:5601进行测试。
GET /_cat/health?v
GET /_cat/health?help
GET /_cat/nodes?v
GET /_cat/indice?v
GET /_cat/indices?v&h=health,status,index
PUT /test
DELETE /test
注: 创建的索引必须不存在
以下是创建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 等
# 语法: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
# 语法: PUT /索引名/类型名/id
PUT /test/_doc/1
# request body
{
"name":"zhangsan",
"age":"16",
# 注意:日期的月份与日是两位数
"birthday":"2002-05-05"
}
# 语法: PUT /索引名/类型名
# 自动分配id
POST /test/_doc
{
"name":"lisi",
"age":"18",
"birthday":"2000-05-05"
}
# 语法: GET /索引名/类型名/id
GET /test/_doc/1
GET /test/_doc/-Z5pFGgBRSjQM58P4-v6
# 语法: PUT /索引名/类型名/id
PUT /test/_doc/1
{
"name":"lisi",
"age":"22"
}
# 语法: POST /索引名/类型名/id/_update
# 注意: 所需修改的内容必须放在"doc"当中
POST /test/_doc/1/_update
{
# doc 不可缺少
"doc":{"name":"wangwu"}
}
# 语法: DELETE /索引名/类型名/id
DELETE /test/_doc/-Z5pFGgBRSjQM58P4-v6
除了能够索引、更新和删除单个文档外,Elasticsearch还提供了使用 _bulk API 批量执行上述任何操作的能力。这个 功能非常重要,因为它提供了一种非常有效的机制,可以以尽可能少的网络往返尽可能快地执行多个操作 。
# 语法: 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"}
# 语法: POST /索引名/类型名/_bulk
POST /test/_doc/_bulk
# 修改---------------------
{"update":{"_id":"4"}}
# 修改内容处于doc中
{"doc":{"name":"sunjiu"}}}
# 删除---------------------
{"delete":{"_id":"Yn-dFGgBq7Lfur-3UIiE"}}
搜索有两种方式:
通过 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":"北京市海淀区西二旗地铁辉煌国际大厦负一楼"}
官方示例: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"
}
}
]
}
注:
查询第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
}
查询 address 在海淀区的所有用户,并高亮 :
GET /zpark/user/_search
{
"query": {
"match": {
"address": "海淀区"
}
}
}
GET /zpark/user/_search
{
"query": {
"match": {
"address": "海淀区"
}
},
"highlight": {
# 需要高亮的字段列表
"fields": {
"address": {}
}
}
}
查询 name 是 zs 关键字的用户
URL方式
GET /zpark/user/_search?q=name:zs
DSL方式
GET /zpark/user/_search
{
"query": {
"term": {
"name": {
"value": "zs"
}
}
}
}
查询 name 是 zs 与 tq关键字的用户
URL方式
GET /zpark/user/_search?q=name:zs,tq
DSL方式
GET /zpark/user/_search
{
"query": {
"terms": {
"name": [
"zs",
"tq"
]
}
}
}
查询年龄在 20~30 岁之间的用户 :
DSL方式:
GET /zpark/user/_search
{
"query": {
"range": {
"age": {
"gte": 20,
"lte": 30
}
}
}
}
注:
查询真实姓名以 张 开头的用户 :
DSL方式:
GET /zpark/user/_search
{
"query": {
"prefix": {
"realname": {
"value": "张"
}
}
}
}
注:
查询名字以 ‘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"
}
}
]
}
查询 id 为1,2,3的用户 :
DSL方式:
GET /zpark/user/_search
{
"query": {
"ids": {
"values": [1,2,3]
}
}
}
模糊查询 realname 中包含 张 关键字的用户 :
DSL方式:
GET /zpark/user/_search
{
"query": {
"fuzzy": {
"realname": {
"value": "张"
}
}
}
}
基于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"
}
}
]
}
}
}
查询指定字段 有值 的文档
GET /zpark/user/_search
{
"query": {
"exists": {
"field": "age"
}
}
}
# 或
GET /zpark/user/_search
{
"query": {
"bool": {
"must": [
{
"exists": {"field": "age"}
}
]
}
}
}
没有不存在查询,missing query是exists query的反式运用,结合Boolean query的must_not的查询方式
查询指定字段 没有值 的文档
GET /zpark/user/_search
{
"query": {
"bool": {
"must_not": [
{
"exists": {"field": "age"}
}
]
}
}
}
官方详解: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" }}}
]
}
}
}
单词元过滤器(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"
]
}
}
}
}
}
过滤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"
}
}
}
}
}
}
官方示例:https://www.elastic.co/guide/en/elasticsearch/reference/6.0/query-dsl-type-query.html
GET /zpark/_search
{
"query": {
"type":{
"value":"user"
}
}
}
exists 过滤指定字段没有值的文档 :
GET /zpark/user/_search
{
"query": {
"bool": {
"must": [
{
"match_all": {}
}
],
"filter": {
"exists": {
# 排除age为null的结果
"field": "age"
}
}
}
}
}
需要过滤保留若干指定_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
官网详情: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替代
"" : { // 聚合名(别名),可以是任意的字符串。用做响应的key,便于快速取得正确的响应数据。
"" : { // 聚合类别,就是各种类型的聚合,如min等
// 聚合体,不同的聚合有不同的body
}
[,"aggregations" : { []+ } ]? // 嵌套的子聚合,可以有0或多个
}
[,"" : { ... } ]* // 另外的聚合,可以有0或多个
}
平均值查询,作用于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"
}
}
}
}
最大值查询
查询员工的最高工资 :
GET /zpark/user/_search
{
"aggs": {
# result:自定义别名
"result": {
"max": {
"field": "salary"
}
}
}
}
最小值查询
查询员工的最低工资 :
GET /zpark/user/_search
{
"aggs": {
# result:自定义别名
"result": {
"min": {
"field": "salary"
}
}
}
}
指定number类型字段的求和查询
查询所有员工的工资之和 :
GET /zpark/user/_search
{
"aggs": {
# result:自定义别名
"result": {
"sum": {
"field": "salary"
}
}
}
}
统计查询,一次性统计出某个字段上的常用统计值
统计salary的所有相关常用统计值:
GET /zpark/user/_search
{
"aggs": {
# result:自定义别名
"result": {
"stats": {
"field": "salary"
}
}
}
}
自定义区间范围的聚合,我们可以自己手动地划分区间,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:小于
自定义分组依据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
}
}
}
}
时间区间聚合专门针对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"
}
]
}
}
}
}
数值直方图聚合,它将某个number类型字段等分成n份,统计落在每一个区间内的记录数。它与前面介绍的Range聚合 非常像,只不过Range可以任意划分区间,而Histogram做等间距划分。既然是等间距划分,那么参数里面必然有距 离参数,就是interval参数。
根据年龄间隔(5岁)统计 :
GET /zpark/user/_search
{
"aggs": {
# result:自定义别名
"result": {
"histogram": {
"field": "age",
"interval": 5
}
}
}
}
日期直方图聚合,专门对时间类型的字段做直方图聚合。这种需求是比较常用见得的,我们在统计时,通常就会按 照固定的时间段(1个月或1年等)来做统计。
按年统计用户 :
GET /zpark/user/_search
{
"aggs": {
# result:自定义别名
"result": {
"date_histogram": {
"field": "birthday",
"interval": "year",
"format": "yyyy-MM-dd"
}
}
}
}
聚合操作是可以嵌套使用的。通过嵌套,可以使得metric类型的聚合操作作用在每一bucket上。我们可以使用ES的嵌套聚合操作来完成稍微复杂一点的统计功能。
注: 只能是桶聚合嵌套度量聚合
统计每年中用户的最高工资 :
GET /zpark/user/_search
{
"aggs": {
"result": {
"histogram": {
"field": "age",
"interval": 10
},
"aggs": {
"max_salary": {
"max": {
"field": "salary"
}
}
}
}
}
}
操作Elasticsearch的方式有以下两种:
Restful API
基于http协议,使用JSON为数据交换格式,通过9200端口与Elasticsearch进行通信
JAVA API(Spring Data ElasticSearch)
Spring Data ElasticSearch封装了与ES交互的实现细节,可以使系统开发者以Spring Data Repository 风格实现与ES的数据交互。Elasticsearch为Java用户提供了两种内置客户端:
两个Java客户端都通过9300端口与集群交互,使用Elasticsearch传输协议 (Elasticsearch Transport Protocol)。集群中的节点之间也通过9300端口进行通信。如果此端口未开放,你的节点将不能组成集群。
官网详情:https://docs.spring.io/spring-data/elasticsearch/docs/3.1.3.RELEASE/reference/html
org.springframework.data
spring-data-elasticsearch
3.1.3.RELEASE
org.springframework
spring-test
5.1.3.RELEASE
官网详情:https://docs.spring.io/spring-data/elasticsearch/docs/3.1.3.RELEASE/reference/html/#elasticsearch.repositories
注:使用springboot项目的快速创建方式,并选择elasticsearch,可以自动导入springboot的依赖jar,以及elasticsearch与spring boot的集成jar,自动生成的pom.xml如下:
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.1.1.RELEASE
com.demo
es_springboot_springdata_api
0.0.1-SNAPSHOT
es_springboot_springdata_api
Demo project for Spring Boot
1.8
org.springframework.boot
spring-boot-starter-data-elasticsearch
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-maven-plugin
指定客户端节点及端口号
spring.data.elasticsearch.cluster-nodes=192.168.114.142:9300
在入口类上添加@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);
}
}
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()方法
//-----------------------------------------
}
spring data elsaticsearch提供了三种构建操作模块的方式:
默认提供基本的增删改查
继承spring data elsaticsearch提供的接口 ElasticsearchRepository 即会默认提供基本的增删改查;
接口中按特定规则声明方法
无需实现类,spring data elsaticsearch根据方法名,自动生成实现类,方法名必须符合一定的规则(这里还扩展出一种忽略方法名,根据注解的方式构建操作模块)
自定义repository
在实现类中注入elasticsearchTemplate,实现上面两种方式不易实现的查询(例如:聚合、 分组、深度翻页等)
上面的第一点和第二点只需要声明接口,无需实现类,spring data会扫描并生成实现类
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 {
//不需要提供方法
}
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 personList = new ArrayList();
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 people = personRepository.findAll();
for (Person person : people) {
System.out.println(person);
}
}
/**
* 测试查询所有索引数据文档,同时排序
*/
@Test
public void testQueryAllWithSort(){
//按 age 降序排列
// Iterable people = personRepository.findAll(Sort.by(Sort.Direction.DESC,"age"));
//按 age 升序排列
Iterable 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 people = personRepository.findAll(new PageRequest(0,2));
System.out.println(((Page) people).getTotalElements());
for (Person person : people) {
System.out.println(person);
}
}
}
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 {
/**
* 根据名字与年龄查询 and
* {"query": {"bool": {"must": [{"match": {"name": name}},{"match": {"age": age}}]}}}
* @param name
* @param age
* @return
*/
public List findByNameAndAge(String name, Integer age);
/**
* 根据名字或年龄查询 or
* {"query": {"bool": {"should": [{"match": {"name": name}},{"match": {"age": age}}]}}}
* @param name
* @param age
* @return
*/
public List findByNameOrAge(String name, Integer age);
/**
* 查询年龄在22--24岁之间的数据
* {"query": {"bool": {"must": [{"range": {"age": {"gte": 22,"lte": 24}}}]}}}
* @param start
* @param end
* @return
*/
public List findByAgeBetween(Integer start, Integer end);
/**
* 根据前缀查询
* {"query": {"bool": {"must": [{"prefix": {"name": {"value": "z"}}}]}}}
* @param prefix
* @return
*/
public List findByNameStartingWith(String prefix);
/**
* 对sex模糊查询
* {"query": {"bool": {"must": [{"fuzzy": {"sex": {"value": "ale"}}}]}}}
* @param keyWord
* @return
*/
public List findBySexLike(String keyWord);
/**
* 查询年龄在22--24岁之间,性别是male的数据
* @param start
* @param end
* @param keyWord
* @return
*/
public List findByAgeBetweenAndSexIs(Integer start,Integer end,String keyWord);
/**
* 查询年龄在22--24岁之间,并按薪资降序排列
* @param start
* @param end
* @return
*/
public List findByAgeBetweenOrderBySalaryDesc(Integer start,Integer end);
}
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 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 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 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 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 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 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 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 personList = personRepository.findByQuery("zs");
/*for (Person person : personList) {
System.out.println(person);
}*/
//lambda表达式
personList.forEach(p -> System.out.println(p));
}
}
实现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 {
/**
* 使用注解的方式自定义查询方式
* @Query ---> 相当于query关键字
* @param keyWord
* @return
*/
@Query("{\"bool\": {\"must\": [{\"fuzzy\": {\"name\": {\"value\": \"?0\"}}}]}}")
public List 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 personList = personRepository.findByQuery("zs");
/*for (Person person : personList) {
System.out.println(person);
}*/
//lambda表达式
personList.forEach(p -> System.out.println(p));
}
}
package com.demo.dao;
import com.demo.entity.Person;
import java.util.List;
import java.util.Map;
/**
* 定义普通的dao接口
*/
public interface CustomPersonRepository {
//=================基本的自定义查询=============================
/**
* 声明查询方法
*/
public List findPeopleByAddress(String address);
/**
* 声明高亮查询方法
*/
public List findPeopleByAddressWithHighLight(String address);
//==================使用过滤器的自定义查询=========================
/**
* 基于范围的过滤查询
* @param start
* @param end
* @return
*/
public List findByAgeWithRangeFilter(Integer start,Integer end);
/**
* 基于范围的过滤查询,并且分页
* @param start
* @param end
* @return
*/
public List findByAgeWithRangeFilterAndPage(Integer start,Integer end,Integer page,Integer size);
//====================使用度量聚合的自定义查询======================
/**
* 度量聚合查询 --求平均值
* 查询某一性别的人的平均年龄
* @return
*/
public Map findSexWithAgeAvgAggs(String sex);
//====================使用桶聚合的自定义查询==========================
/**
* 桶聚合 --- 根据出生日期范围的桶聚合查询
* @return
*/
public Map findBybirthdayWithDateRangeAggs();
//=================桶聚合嵌套度量聚合的查询===========================
/**
* 桶聚合嵌套度量聚合查询
* 按照年龄间隔5 统计
* 直方图桶聚合 + max度量聚合
*/
public Map findWithMaxSalaryAggsAndAgeHistogramAggs();
}
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 findPeopleByAddress(String address) {
SearchQuery searchQuery = new NativeSearchQueryBuilder()
//封装query查询的条件;参数一:查询的域(字段);参数二:查询字段的值
.withQuery(QueryBuilders.matchQuery("address",address))
//返回NativeSearchQuery对象
.build();
//打印输出查询条件语法
System.out.println(searchQuery.getQuery().toString());
//获取符合条件的Person
List people = elasticsearchTemplate.queryForList(searchQuery, Person.class);
return people;
}
/**
* 高亮查询方法实现
*/
@Override
public List 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 aggregatedPage = elasticsearchTemplate.queryForPage(searchQuery, Person.class, new SearchResultMapper() {
/**
* 将结果映射到实体类
* @param searchResponse
* @param aClass
* @param pageable
* @param
* @return
*/
@Override
public AggregatedPage mapResults(SearchResponse searchResponse, Class aClass, Pageable pageable) {
//创建集合装Person对象
List people = new ArrayList<>();
Person person = null;
//检索命中的集合对象
SearchHits searchHits = searchResponse.getHits();
for (SearchHit searchHit : searchHits) {
person = new Person();
//封装id
person.setId(searchHit.getId());
//获取命中的对象信息中的各条属性信息
Map 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((List) people);
}
});
//获取封装高亮字段后的集合
List content = aggregatedPage.getContent();
return content;
}
//==================使用过滤器的自定义查询=========================
/**
* 基于范围的过滤查询
* @param start
* @param end
* @return
*/
@Override
public List findByAgeWithRangeFilter(Integer start, Integer end) {
SearchQuery searchQuery = new NativeSearchQueryBuilder()
//封装过滤查询条件
.withFilter(QueryBuilders.rangeQuery("age").gte(start).lte(end))
.build();
List people = elasticsearchTemplate.queryForList(searchQuery, Person.class);
return people;
}
/**
* 基于范围的过滤查询,并且分页
* @param start
* @param end
* @return
*/
@Override
public List 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 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() {
/**
* searchResponse 代表完整的结果集,可抽取聚合信息
* @param searchResponse
* @return
*/
@Override
public Aggregations extract(SearchResponse searchResponse) {
//获取完整的聚合信息
Aggregations aggregations = searchResponse.getAggregations();
return aggregations;
}
});
//获取度量集合---平均值信息
Map 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() {
/**
* searchResponse 代表完整的结果集,就可抽取聚合信息
* @param searchResponse
* @return
*/
@Override
public Aggregations extract(SearchResponse searchResponse) {
Aggregations aggregations = searchResponse.getAggregations();
return aggregations;
}
});
Map 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() {
/**
* searchResponse 代表完整的结果集,可以提取聚合信息
* @param searchResponse
* @return
*/
@Override
public Aggregations extract(SearchResponse searchResponse) {
Aggregations aggregations = searchResponse.getAggregations();
return aggregations;
}
});
//提取查询到的聚合信息
Map aggregationMap = aggregations.getAsMap();
return aggregationMap;
}
}
若是spring项目:配置applicationContext-es.xml
在applicationContext-es.xml配置文件中配置自定义repository实现类,将其交给spring管理。
若是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,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();
}
}
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 people = customPersonRepository.findPeopleByAddress("海淀区");
for (Person person : people) {
System.out.println(person);
}
}
/**
* 测试自定义通过地址查询并高亮显示
*/
@Test
public void testQueryPeopleByAddressWithHighLight(){
List people = customPersonRepository.findPeopleByAddressWithHighLight("海淀区");
for (Person person : people) {
System.out.println(person);
}
}
//==================使用过滤器的自定义查询=========================
/**
* 测试自定义对age进行范围过滤查询
*/
@Test
public void testQueryByAgeWithRangeFilter(){
List people = customPersonRepository.findByAgeWithRangeFilter(22,25);
for (Person person : people) {
System.out.println(person);
}
}
/**
* 测试自定义对age进行范围过滤查询,并分页
*/
@Test
public void testQueryByAgeWithRangeFilterAndPage(){
List 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);
});
}
参考资料:https://github.com/medcl/elasticsearch-analysis-ik
进入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的版本号相同。
下载地址:https://github.com/medcl/elasticsearch-analysis-ik/releases
[root@elasticsearch ~]# cd /usr/elasticsearch-6.4.0/plugins/ && mkdir ik
[root@elasticsearch ~]# yum install unzip
[root@elasticsearch ~]# mv elasticsearch-analysis-ik-6.4.0 /usr/elasticsearch-6.4.0/plugins/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
# 查看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
PUT /news
POST /news/international/_mapping
{
"properties": {
"content":{
"type": "text",
"analyzer": "ik_max_word", # 会将文本做最细粒度的拆分
"search_analyzer": "ik_max_word"
}
}
}
POST /news/international/_bulk
{"index":{"_id":1}}
{"content":"美国留给伊拉克的是个烂摊子吗"}
{"index":{"_id":2}}
{"content":"公安部:各地校车将享最高路权"}
{"index":{"_id":3}}
{"content":"中韩渔警冲突调查:韩警平均每天扣1艘中国渔船"}
{"index":{"_id":4}}
{"content":"中国驻洛杉矶领事馆遭亚裔男子枪击 嫌犯已自首"}
GET /news/international/_search
{
"query": {
"match": {
"content": "中国"
}
},
"highlight": {
"fields": {"content": {}}
}
}
[root@elasticsearch ~]# cd /usr/kibana-6.4.0-linux-x86_64/config/
[root@elasticsearch config]# mkdir analysis-ik
[root@elasticsearch config]# cd analysis-ik/
[root@elasticsearch analysis-ik]# vim IKAnalyzer.cfg.xml
# 配置内容
IK Analyzer 扩展配置
mydict.dic;
[root@elasticsearch analysis-ik]# vim mydict.dic
# 自定义词汇
扣1艘
参考资料:
- 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里高效地取回巨大数 量的结果而不需要付出深分页的代价。
一个滚屏搜索允许我们做一个初始阶段搜索并且持续批量从Elasticsearch里拉取结果直到没 有结果剩下。这有点像传统数据库里的cursors(游标)。 滚屏搜索会及时制作快照。这个快照不会包含任何在初始阶段搜索请求后对index做的修改。 它通过将旧的数据文件保存在手边,所以可以保护index的样子看起来像搜索开始时的样子。
深度分页代价最高的部分是对结果的全局排序,但如果禁用排序,就能以很低的代价获得全 部返回结果。为达成这个目的,可以采用 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官方客户端提供扫描和滚屏的小助手。小助手提供了一个对这个功能 的简单封装。
1. CentOS 6 需要修改IP;CentOS 7自动获取IP时不需要手动修改IP,若是静态IP则需要修改
2. 修改主机名
[root@esnode ~]# cd /usr/elasticsearch-6.4.0/
[root@esnode elasticsearch-6.4.0]# rm -rf data/*
[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键
[root@esnode ~]# cd /usr/elasticsearch-6.4.0/
[root@esnode elasticsearch-6.4.0]# su elasticsearch
[elasticsearch@esnode elasticsearch-6.4.0]$ bin/elasticsearch
[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键
# 查看节点信息
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
}