ElasticSearch学习笔记 1-简介 1.1-什么是ElasticSearch? Elasticsearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java语言开发的,并作为Apache许可条款下的开放源码发布,是一种流行的企业级搜索引擎。Elasticsearch用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。官方客户端在Java、.NET(C#)、PHP、Python、Apache Groovy、Ruby和许多其他语言中都是可用的。根据DB-Engines的排名显示,Elasticsearch是最受欢迎的企业搜索引擎,其次是Apache Solr,也是基于Lucene。
1.2-什么是Lucene? Lucene 采用了基于倒排表的设计原理,可以非常高效地实现文本查找,在底层采用了分段的存储模式,使它在读写时几乎完全避免了锁的出现,大大提升了读写性能。
Lucene 可以说是当下最先进、高性能、全功能的搜索引擎库——无论是开源还是私有,但它也仅仅只是一个库。为了充分发挥其功能,你需要使用 Java 并将 Lucene 直接集成到应用程序中。 更糟糕的是,您可能需要获得信息检索学位才能了解其工作原理,因为Lucene 非常复杂。
为了解决Lucene使用时的繁复性,于是Elasticsearch便应运而生。它使用 Java 编写,内部采用 Lucene 做索引与搜索,但是它的目标是使全文检索变得更简单,简单来说,就是对Lucene 做了一层封装,它提供了一套简单一致的 RESTful API 来帮助我们实现存储和检索。
当然,Elasticsearch 不仅仅是 Lucene,并且也不仅仅只是一个全文搜索引擎。 它可以被下面这样准确地形容:
一个分布式的实时文档存储,每个字段可以被索引与搜索; 一个分布式实时分析搜索引擎; 能胜任上百个服务节点的扩展,并支持 PB 级别的结构化或者非结构化数据。 由于Elasticsearch的功能强大和使用简单,维基百科、卫报、Stack Overflow、GitHub等都纷纷采用它来做搜索。现在,Elasticsearch已成为全文搜索领域的主流软件之一。
1.3-ES是如何产生的? 1. 大规模数据如何检索? 如:当系统数据量上了10亿、100亿条的时候,我们在做系统架构的时候通常会从以下角度去考虑问题:
用什么数据库好?(mysql、sybase、oracle、达梦、神通、mongodb、hbase…) 如何解决单点故障;(lvs、F5、A10、Zookeep、MQ) 如何保证数据安全性;(热备、冷备、异地多活) 如何解决检索难题;(数据库代理中间件:mysql-proxy、Cobar、MaxScale等;) 如何解决统计分析问题;(离线、近实时) 2. 传统数据库的应对解决方案 对于关系型数据,我们通常采用以下或类似架构去解决查询瓶颈和写入瓶颈:解决要点:
通过主从备份解决数据安全性问题; 通过数据库代理中间件心跳监测,解决单点故障问题; 通过代理中间件将查询语句分发到各个slave节点进行查询,并汇总结果 。 3. 非关系型数据库的解决方案 对于Nosql数据库,以mongodb为例,其它原理类似:解决要点:
通过副本备份保证数据安全性; 通过节点竞选机制解决单点问题; 先从配置库检索分片信息,然后将请求分发到各个节点,最后由路由节点合并汇总结果 。 4. 完全把数据放入内存怎么样? 我们知道,完全把数据放在内存中是不可靠的,实际上也不太现实,当我们的数据达到PB级别时,按照每个节点96G内存计算,在内存完全装满的数据情况下,我们需要的机器是:1PB=1024T=1048576G 节点数=1048576/96=10922个 实际上,考虑到数据备份,节点数往往在2.5万台左右。成本巨大决定了其不现实!
从前面讨论我们了解到,把数据放在内存也好,不放在内存也好,都不能完完全全解决问题。 全部放在内存速度问题是解决了,但成本问题上来了。 为解决以上问题,从源头着手分析,通常会从以下方式来寻找方法:
存储数据时按有序存储; 将数据和索引分离; 压缩数据; 这就引出了Elasticsearch。
1.4-什么是全文检索? 全文检索是指计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。这个过程类似于通过字典中的检索字表查字的过程。
全文检索的方法主要分为按字检索和按词检索两种。按字检索是指对于文章中的每一个字都建立索引,检索时将词分解为字的组合。对于各种不同的语言而言,字有不同的含义,比如英文中字与词实际上是合一的,而中文中字与词有很大分别。按词检索指对文章中的词,即语义单位建立索引,检索时按词检索,并且可以处理同义项等。英文等西方文字由于按照空白切分词,因此实现上与按字处理类似,添加同义处理也很容易。中文等东方文字则需要切分字词,以达到按词索引的目的,关于这方面的问题,是当前全文检索技术尤其是中文全文检索技术中的难点,在此不做详述。
1.5-正排索引和倒排索引 1. 正排索引 正排索引也称为”前向索引”,它是创建倒排索引的基础。 这种组织方法在建立索引的时候结构比较简单,建立比较方便且易于维护;因为索引是基于文档建立的,若是有新的文档加入,直接为该文档建立一个新的索引块,挂接在原来索引文件的后面。若是有文档删除,则直接找到该文档号文档对应的索引信息,将其直接删除。 他适合根据文档ID来查询对应的内容。但是在查询一个keyword在哪些文档里包含的时候需对所有的文档进行扫描以确保没有遗漏,这样就使得检索时间大大延长,检索效率低下。 比如有几个文档及里面的内容,他正排索引构建的结果如下图:
优点: 工作原理非常的简单。缺点: 检索效率太低,只能在一起简单的场景下使用。
2. 倒排索引 倒排索引(英文:Inverted Index),是一种索引方法,常被用于全文检索系统中的一种单词文档映射结构。现代搜索引擎绝大多数的索引都是基于倒排索引来进行构建的,这源于在实际应用当中,用户在使用搜索引擎查找信息时往往只输入信息中的某个属性关键字,如一些用户不记得歌名,会输入歌词来查找歌名;输入某个节目内容片段来查找该节目等等。
面对海量的信息数据,为满足用户需求,顺应信息时代快速获取信息的趋势,聪明的开发者们在进行搜索引擎开发时对这些信息数据进行逆向运算,研发了“关键词——文档”形式的一种映射结构,实现了通过物品属性信息对物品进行映射时,可以帮助用户快速定位到目标信息,从而极大降低了信息获取难度。 倒排索引又叫反向索引,它是一种逆向思维运算,是现代信息检索领域里面最有效的一种索引结构。
在搜索引擎中每个文件都对应一个文件ID,文件内容被表示为一系列关键词的集合(文档要除去一些无用的词,比如’的’这些,剩下的词就是关键词,每个关键词都有自己的ID)。例如“文档1”经过分词,提取了3个关键词,每个关键词都会记录它所在在文档中的出现频率及出现位置。
那么上面的文档及内容构建的倒排索引结果会如下图(注:这个图里没有记录展示该词在出现在哪个文档的具体位置):
如何来查询呢? 比如我们要查询‘搜索引擎’这个关键词在哪些文档中出现过。首先我们通过倒排索引可以查询到该关键词出现的文档位置是在1和3中;然后再通过正排索引查询到文档1和3的内容并返回结果。
倒排索引组成 倒排索引主要由单词词典 (Term Dictionary)和倒排列表 (Posting List)及倒排文件 (Inverted File)组成。 他们三者的关系如下图:
单词词典(Term Dictionary): 搜索引擎的通常索引单位是单词,单词词典是由文档集合中出现过的所有单词构成的字符串集合,单词词典内每条索引项记载单词本身的一些信息以及指向“倒排列表”的指针。倒排列表(PostingList): 倒排列表记载了出现过某个单词的所有文档的文档列表及单词在该文档中出现的位置信息及频率(作关联性算分),每条记录称为一个倒排项(Posting)。根据倒排列表,即可获知哪些文档包含某个单词。倒排文件(Inverted File): 所有单词的倒排列表往往顺序地存储在磁盘的某个文件里,这个文件即被称之为倒排文件,倒排文件是存储倒排索引的物理文件。
以查找搜索引擎查找为例:
单词词典查询定位问题 对于一些规模很大的文档集合来讲,他里面可能包括了上百万的关键单词(term),能否快速定位到具体单词(term),这会直接影响到响应速度。
假设我们有很多个 term,比如:
Carla,Sara,Elin,Ada,Patty,Kate,Selena
如果按照这样的顺序排列,找出某个特定的 term 一定很慢,因为 term 没有排序,需要全部过滤一遍才能找出特定的 term。
排序之后就变成了:
Ada,Carla,Elin,Kate,Patty,Sara,Selena
这样我们可以用二分查找的方式,比全遍历更快地找出目标的 term。这个就是 term dictionary。有了 term dictionary 之后,可以用 logN 次磁盘查找得到目标。但是磁盘的随机读操作仍然是非常昂贵的(一次 random access 大概需要 10ms 的时间)。所以尽量少的读磁盘,有必要把一些数据缓存到内存里。但是整个 term dictionary 本身又太大了,无法完整地放到内存里。于是就有了 term index。term index 有点像一本字典的大的章节表。
目前常用的方式是通过hash加链表结构和树型结构 (b树或者b+)。
hash加链表:
这是很常用的一种数据结构。这种方式就可以快速计算单词的hash值从而定位到他所有在的hash表中,如果该表是又是一个链表结构(两个单词的hash值可能会一样),那么就需要遍历这个链表然后再对比返回结果。这种方式最大的缺点就是如果有范围查询的时候就很难做到。
树型结构: B树(或者B+树)是另外一种高效查找结构,下图是一个 B树结构示意图。B树与哈希方式查找同,需要字典项能够按照大小排序(数字或者字符序),而哈希方式则无须数据满足此项要求。
B树形成了层级查找结构,中间节点用于指出一定顺序范围的词典项目存储在哪个子树中,起到根据词典项比较大小进行导航的作用,最底层的叶子节点存储单词的地址信息,根据这个地址就可以提取出单词字符串。
1.5-ElasticSearch的基本概念 1. ElaticSearch 和 DB 的关系 在 Elasticsearch 中,文档归属于一种类型 type,而这些类型存在于索引 index 中,我们可以列一些简单的不同点,来类比传统关系型数据库:
Relational DB -> Databases -> Tables -> Rows -> Columns Elasticsearch -> Indices -> Types -> Documents -> Fields Elasticsearch 集群可以包含多个索引 indices,每一个索引可以包含多个类型 types,每一个类型包含多个文档 documents,然后每个文档包含多个字段 Fields。而在 DB 中可以有多个数据库 Databases,每个库中可以有多张表 Tables,没个表中又包含多行Rows,每行包含多列Columns。
ES和数据库关系对比:
2. 索引 索引基本概念(indices):
索引是含义相同属性的文档集合,是 ElasticSearch 的一个逻辑存储,可以理解为关系型数据库中的数据库,ElasticSearch 可以把索引数据存放到一台服务器上,也可以 sharding 后存到多台服务器上,每个索引有一个或多个分片,每个分片可以有多个副本。
索引类型(index_type):
索引可以定义一个或多个类型,文档必须属于一个类型。在 ElasticSearch 中,一个索引对象可以存储多个不同用途的对象,通过索引类型可以区分单个索引中的不同对象,可以理解为关系型数据库中的表。每个索引类型可以有不同的结构,但是不同的索引类型不能为相同的属性设置不同的类型。
3. 文档 文档(document):
文档是可以被索引的基本数据单位。存储在 ElasticSearch 中的主要实体叫文档 document,可以理解为关系型数据库中表的一行记录。每个文档由多个字段构成,ElasticSearch 是一个非结构化的数据库,每个文档可以有不同的字段,并且有一个唯一的标识符。
4. 映射 映射(mapping):
ElasticSearch 的 Mapping 非常类似于静态语言中的数据类型:声明一个变量为 int 类型的变量,以后这个变量都只能存储 int 类型的数据。同样的,一个 number 类型的 mapping 字段只能存储 number 类型的数据。
同语言的数据类型相比,Mapping 还有一些其他的含义,Mapping 不仅告诉 ElasticSearch 一个 Field 中是什么类型的值, 它还告诉 ElasticSearch 如何索引数据以及数据是否能被搜索到。
ElaticSearch 默认是动态创建索引和索引类型的 Mapping 的。这就相当于无需定义 Solr 中的 Schema,无需指定各个字段的索引规则就可以索引文件,很方便。但有时方便就代表着不灵活。比如,ElasticSearch 默认一个字段是要做分词的,但我们有时要搜索匹配整个字段却不行。如有统计工作要记录每个城市出现的次数。对于 name 字段,若记录 new york 文本,ElasticSearch 可能会把它拆分成 new 和 york 这两个词,分别计算这个两个单词的次数,而不是我们期望的 new york。
5. Near Realtime(NRT) 几乎实时 Elasticsearch是一个几乎实时的搜索平台。意思是,从索引一个文档到这个文档可被搜索只需要一点点的延迟,这个时间一般为毫秒级。
6. Cluster 集群 群集是一个或多个节点(服务器)的集合, 这些节点共同保存整个数据,并在所有节点上提供联合索引和搜索功能。一个集群由一个唯一集群ID确定,并指定一个集群名(默认为“elasticsearch”)。该集群名非常重要,因为节点可以通过这个集群名加入群集,一个节点只能是群集的一部分。
确保在不同的环境中不要使用相同的群集名称,否则可能会导致连接错误的群集节点。例如,你可以使用logging-dev、logging-stage、logging-prod分别为开发、阶段产品、生产集群。
7. Node节点 节点是单个服务器实例,它是群集的一部分,可以存储数据,并参与群集的索引和搜索功能。就像一个集群,节点的名称默认为一个随机的通用唯一标识符(UUID),确定在启动时分配给该节点。如果不希望默认,可以定义任何节点名。这个名字对管理很重要,目的是要确定你的网络服务器对应于你的ElasticSearch群集节点。
我们可以通过群集名配置节点以连接特定的群集。默认情况下,每个节点设置加入名为“elasticSearch”的集群。这意味着如果你启动多个节点在网络上,假设他们能发现彼此都会自动形成和加入一个名为“elasticsearch”的集群。
在单个群集中,您可以拥有尽可能多的节点。此外,如果“elasticsearch”在同一个网络中,没有其他节点正在运行,从单个节点的默认情况下会形成一个新的单节点名为”elasticsearch”的集群。
8. Shards & Replicas分片与副本 索引可以存储大量的数据,这些数据可能超过单个节点的硬件限制。例如,十亿个文件占用磁盘空间1TB的单指标可能不适合对单个节点的磁盘或可能太慢服务仅从单个节点的搜索请求。
为了解决这一问题,Elasticsearch提供细分你的指标分成多个块称为分片的能力。当你创建一个索引,你可以简单地定义你想要的分片数量。每个分片本身是一个全功能的、独立的“指数”,可以托管在集群中的任何节点。
Shards分片的重要性主要体现在以下两个特征:
分片允许您水平拆分或缩放内容的大小 分片允许你分配和并行操作的碎片(可能在多个节点上)从而提高性能/吞吐量 这个机制中的碎片是分布式的以及其文件汇总到搜索请求是完全由ElasticSearch管理,对用户来说是透明的。 在同一个集群网络或云环境上,故障是任何时候都会出现的,拥有一个故障转移机制以防分片和结点因为某些原因离线或消失是非常有用的,并且被强烈推荐。为此,Elasticsearch允许你创建一个或多个拷贝,你的索引分片进入所谓的副本或称作复制品的分片,简称Replicas。
Replicas
的重要性主要体现在以下两个特征:
副本为分片或节点失败提供了高可用性。为此,需要注意的是,一个副本的分片不会分配在同一个节点作为原始的或主分片,副本是从主分片那里复制过来的。 副本允许用户扩展你的搜索量或吞吐量,因为搜索可以在所有副本上并行执行。 2-ElasticSearch的安装和使用 2.1-ElasticSearch的安装 1. Linux下安装ES Linux服务器是CentOS 7.x
Elasticsearch下载地址:
https ://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.1.1-linux-x86_64.tar.gz
解压elasticsearch-7.1.1-linux-x86_64.tar.gz到/usr/local/目录:
tar -avxf elasticsearch-7.1.1-linux-x86_64.tar.gz -C /usr/local/
进入解压后的elasticsearch目录
准备启动es 进入/bin目录执行命令:
我这里出现如下错误:
Java HotSpot(TM) 64-Bit Server VM warning: INFO: os::commit_memory(0x00000000c5330000, 986513408, 0) failed; error='Cannot allocate memory' (errno=12) [root@VM_0_2_centos bin]#
看来是我这1G的内存太小了啊,elasticsearch使用java的jvm默认是使用1G的内存的,这里我们修改一下内存,直接把内存改到200m。
cd 到es目录修改 ./config/jvm.options:
修改该内容:
:wq 保存并退出vim,再次启动es
再次启动出现如下错误:
[2019-06-21T16 :20:03,039][WARN ][o.e.b.ElasticsearchUncaughtExceptionHandler] [node-1] uncaught exception in thread [main] org.elasticsearch.bootstrap.StartupException : java.lang.RuntimeException: can not run elasticsearch as root at org.elasticsearch.bootstrap.Elasticsearch.init(Elasticsearch.java:163) ~[elasticsearch-7.1.1.jar:7.1.1] at org.elasticsearch.bootstrap.Elasticsearch.execute(Elasticsearch.java:150) ~[elasticsearch-7.1.1.jar:7.1.1] at org.elasticsearch.cli.EnvironmentAwareCommand.execute(EnvironmentAwareCommand.java:86) ~[elasticsearch-7.1.1.jar:7.1.1] at org.elasticsearch.cli.Command.mainWithoutErrorHandling(Command.java:124) ~[elasticsearch-cli-7.1.1.jar:7.1.1] at org.elasticsearch.cli.Command.main(Command.java:90) ~[elasticsearch-cli-7.1.1.jar:7.1.1] at org.elasticsearch.bootstrap.Elasticsearch.main(Elasticsearch.java:115) ~[elasticsearch-7.1.1.jar:7.1.1] at org.elasticsearch.bootstrap.Elasticsearch.main(Elasticsearch.java:92) ~[elasticsearch-7.1.1.jar:7.1.1] Caused by: java.lang.RuntimeException: can not run elasticsearch as root at org.elasticsearch.bootstrap.Bootstrap.initializeNatives(Bootstrap.java:102) ~[elasticsearch-7.1.1.jar:7.1.1] at org.elasticsearch.bootstrap.Bootstrap.setup(Bootstrap.java:169) ~[elasticsearch-7.1.1.jar:7.1.1] at org.elasticsearch.bootstrap.Bootstrap.init(Bootstrap.java:325) ~[elasticsearch-7.1.1.jar:7.1.1] at org.elasticsearch.bootstrap.Elasticsearch.init(Elasticsearch.java:159) ~[elasticsearch-7.1.1.jar:7.1.1] ... 6 more [root@VM_0_2_centos elasticsearch-7.1.1]#
这是不能使用root用户操作,添加一个其他的用户再试试:
[root@VM_0_2_centos elasticsearch-7.1.1]# adduser es [root@VM_0_2_centos elasticsearch-7.1.1]# passwd es Changing password for user es. New password: Retype new password: passwd : all authentication tokens updated successfully.
改一下es目录所属用户:
[root@VM_0_2_centos elasticsearch-7.1.1]# chown es /usr/local/elasticsearch-7.1.1/ -R
vim 编辑 /etc/security/limits.conf,在末尾加上:
es soft nofile 65536 es hard nofile 65536 es soft nproc 4096 es hard nproc 4096
vim 编辑 vim /etc/security/limits.d/20-nproc.conf,将* 改为用户名(es):
es soft nproc 4096 root soft nproc unlimited
vim 编辑 /etc/sysctl.conf,在末尾加上:
vm.max_map_count = 655360
执行:
[root@VM_0_2_centos ~]# sysctl -p kernel.printk = 5 vm.max_map_count = 655360 [root@VM_0_2_centos ~]#
登录刚才新建的es用户,并启动elasticsearch,OK
[root@VM_0_2_centos elasticsearch-7.1.1]# su es [es@VM_0_2_centos elasticsearch-7.1.1]$ ./bin/elasticsearch
2. Docker下安装ES 搜索ES镜像
docker search elasticsearch
docker安装es
docker pull elasticsearch:7.2.0
启动es
docker run --name elasticsearch -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -d elasticsearch:7.2.0
修改配置,解决跨域访问问题
首先进入到容器中,然后进入到指定目录修改elasticsearch.yml文件。
-> docker exec -it elasticsearch /bin/bash -> cd /usr/share/elasticsearch/config/ -> vi elasticsearch.yml
http.cors.enabled : true http.cors.allow-origin : "*"
:wq保存
-> exit -> docker restart elasticsearch
安装ik分词器
es自带的分词器对中文分词不是很友好,所以我们下载开源的IK分词器来解决这个问题。首先进入到plugins目录中下载分词器,下载完成后然后解压,再重启es即可。具体步骤如下: 注意:elasticsearch的版本和ik分词器的版本需要保持一致,不然在重启的时候会失败。可以在这查看所有版本,选择合适自己版本
-> cd /usr/share/elasticsearch/plugins/ -> elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.2.0/elasticsearch-analysis-ik-7.2.0.zip -> exit -> docker restart elasticsearch 然后可以在kibana界面的dev tools中验证是否安装成功;
验证
POST test/_analyze { "analyzer" : "ik_max_word", "text" : "你好我是东邪Jiafly" }
不添加”analyzer”: “ik_max_word”,则是每个字分词,可以在下面kibana安装完成以后尝试一下。
docker安装kibana
启动kibana
安装完成以后需要启动kibana容器,使用–link连接到elasticsearch容器,命令如下:
-> docker run --name kibana --link=elasticsearch:test -p 5601:5601 -d kibana:7.2.0 -> docker start kibana
启动以后可以打开浏览器输入http://localhost:5601
就可以打开kibana的界面了。
安装elasticsearch head插件监控管理(不推荐)
docker pull mobz/elasticsearch-head:5 docker run -d -p 9100:9100 docker.io/mobz/elasticsearch-head:5
2.2-ElasticSearch的使用 1. ELasticSearch常用命令 (1) 集群信息 http ://112.xx.xx.xx:9200/
http ://112.xx.xx.xx:9200/_cluster/health GET /_cluster/health
http ://112.xx.xx.xx:9200/_cluster/settings?include_defaults GET /_cluster/settings?include_defaults
http ://112.xx.xx.xx:9200/_cat/nodes?v GET /_cat/nodes?v
(2) 索引 查看索引:
GET /_cat/indices/my_index
如果 index 的状态为 yellow,可能是因为副本分片未分配出去 GET /_cat/shards?v&h=index,shard,prirep,state,unassigned.reason
创建索引:
用明确的 mapping 创建索引
PUT /my_index { "mappings" : { "dynamic" : false, "properties" : { "age" : { "type": "integer" }, "email" : { "type": "keyword" }, "name" : { "type": "text" } } } }
删除索引:
重建索引:
重建 my_index 到 my_index_new POST /_reindex { "source" : { "index" : "my_index", # 原有索引 "size" : 5000 # 一个批次处理的数据量 }, "dest" : { "index" : "my_index_new" # 新索引 } }
GET /_tasks?detailed=true&actions=*reindex
索引模板:
GET _template/my_index_tpl
PUT /_template/my_index_tpl { "order" : 0, "index_patterns" : [ "my_index_*" ], "settings" : { "index" : { "number_of_shards" : 12, "number_of_replicas" : 1 } }, "mappings" : { "_source" : { "enabled" : false }, "dynamic" : false, "properties" : { "name_en" : { "analyzer" : "english", "type" : "text", "fields" : { "keyword" : { "type" : "keyword" } } }, "name_cn" : { "analyzer" : "ik_max_word", "search_analyzer" : "ik_smart", "type" : "text" }, "country" : { "type" : "keyword" }, "full_field" : { "index" : false, "store" : true, "type" : "text" } } }, "aliases" : {} }
(3) 索引别名 POST /_aliases { "actions" : [ { "add" : { "indices" : [ "my_index_1", "my_index_2" ], "alias" : "my_index_alias" } } ] } POST /_aliases { "actions" : [ { "add" : { "indices" : [ "my_index_*" ], "alias" : "my_index_alias" } } ] }
POST /_aliases { "actions" : [ { "remove" : { "index" : "my_index_1", "alias" : "my_index_alias" } }, { "remove" : { "index" : "my_index_2", "alias" : "my_index_alias" } } ] }
POST /_aliases { "actions" : [ { "remove" : { "indices" : [ "my_index_1_20201011", "my_index_2_20201011" ], "alias" : "my_index_alias" } }, { "add" : { "indices" : [ "my_index_1_20201022", "my_index_2_20201022" ], "alias" : "my_index_alias" } } ] }
(4) settings 分片(shard)
PUT /my_temp_index { "settings" : { "number_of_shards" : 1, # 主分片数,不可动态修改 "number_of_replicas" : 0 # 副本分片数,可以动态修改 } }
PUT /my_index/_settings { "number_of_replicas" : 0 }
GET /_cat/shards?v GET /_cat/shards/my_index?v
(5) mapping 新增索引字段
PUT /my_index/_mapping { "properties" : { "employee-id" : { "type" : "keyword" } } } POST /my_index/_update_by_query
(6) 文档的增删改查(CRUD) Elasticsearch 类比MySQL 说明 Index replcae into Index在索引不存在时会创建索引, replace into 并不会创建库或表 Create insert into 增加 Read select 读取 Update update 更新 Delete delete 删除
(7) Elasticsearch SQL 用法示例
POST _sql?format=txt { "query" : "SELECT Carrier FROM kibana_sample_data_flights LIMIT 100" }
将 SQL 转化为 DSL
POST _sql/translate { "query" : "SELECT Carrier FROM kibana_sample_data_flights LIMIT 100" } { "size" : 100, "_source" : false, "stored_fields" : "_none_", "docvalue_fields" : [ { "field" : "Carrier" } ], "sort" : [ { "_doc" : { "order" : "asc" } } ] }
2. Mapping之字段类型
(1) 基本类型 string (字符串类型) string类型在ElasticSearch 旧版本中使用较多,从ElasticSearch 5.x开始不再支持string,由text和keyword类型替代。
text
文本类型,在索引文件中,存储的不是原字符串,而是使用分词器对内容进行分词处理后得到一系列的词根,然后一一存储在index的倒排索引中。当一个字段是要被全文搜索的,比如Email内容、产品描述,应该使用text类型。设置text类型以后,字段内容会被分析,在生成倒排索引以前,字符串会被分析器分成一个一个词项。text类型的字段不用于排序,很少用于聚合。
{ "match_mapping_type" : "string" ,"mapping" : {"type" : "text" ,"store" : true } }
keyword
关键字类型,将原始输入内容当成一个词根存储在倒排索引中,与text字段的区别是该字段不会使用分词器进行分词。 keyword类型适用于索引结构化的字段,比如email地址、主机名、状态码和标签。如果字段需要进行过滤(比如查找已发布博客中status属性为published的文章)、排序、聚合。keyword类型的字段只能通过精确值搜索到。
{ "foo" : { "type" : "text" , "fields" : { "keyword" : { "type" : "keyword" , "ignore_above" : 256 } } } }
numeric datatypes(数字类型)
在满足需求的情况下,尽可能选择范围小的数据类型。比如,某个字段的取值最大值不会超过100,那么选择byte类型即可。迄今为止吉尼斯记录的人类的年龄的最大值为134岁,对于年龄字段,short足矣。字段的长度越短,索引和搜索的效率越高。
浮点类型
对于float、half_float和scaled_float,-0.0和+0.0是不同的值,使用term查询查找-0.0不会匹配+0.0,同样range查询中上边界是-0.0不会匹配+0.0,下边界是+0.0不会匹配-0.0。
其中scaled_float,比如价格只需要精确到分,price为57.34的字段缩放因子为100,存起来就是5734 优先考虑使用带缩放因子的scaled_float浮点类型。
date(日期类型) 日期类型表示格式可以是以下几种:
日期格式的字符串,比如 “2018-01-13” 或 “2018-01-13 12:10:30” long类型的毫秒数( milliseconds-since-the-epoch,epoch就是指UNIX诞生的UTC时间1970年1月1日0时0分0秒) integer的秒数(seconds-since-the-epoch) ElasticSearch 内部会将日期数据转换为UTC,并存储为milliseconds-since-the-epoch的long型整数。 例子:日期格式数据
"properties" : { "postdate" :{ "type" :"date" , "format" : "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis" } }
boolean类型 逻辑类型(布尔类型)可以接受true/false/”true”/”false”值
"properties" : { "empty" :{ "type" :"boolean" } }
binary类型 二进制字段是指用base64来表示索引中存储的二进制数据,可用来存储二进制形式的数据,例如图像。默认情况下,该类型的字段只存储不索引。二进制类型只支持index_name属性。
range datatype 数据范围类型,一个字段表示一个范围,具体包含如下类型:
integer_range float_range double_range date_range ip_range 所谓的范围类型,就是一个值自身就代表一个范围,例如:
1PUT range_index 2 { 3 "mappings" : { 4 "_doc" : { 5 "properties" : { 6 "expected_attendees" : { 7 "type" : "integer_range" 8 } 9 } 10 }11 }12 }
其索引数据时:
1public static void index_mapping_integer_range ( ) { 2 RestHighLevelClient client = EsClient.getClient(); 3 try { 4 IndexRequest request = new IndexRequest("mapping_test_ranger" , "_doc" ); 5 Map <String , Object > data = new HashMap<>(); 6 Map <String , Integer> pd = new HashMap<>(); 7 pd.put("gte" , 10 ); 8 pd.put("lte" , 20 ); 9 data.put("expected_attendees" , pd); 10 request.source(data);11 System.out.println(client.index(request, RequestOptions.DEFAULT));12 } catch (Throwable e) {13 e.printStackTrace();14 } finally {15 EsClient.close(client);16 }17 }
搜索方式:
1public static void search_integer_range ( ) { 2 RestHighLevelClient client = EsClient.getClient(); 3 try { 4 SearchRequest searchRequest = new SearchRequest(); 5 searchRequest.indices("mapping_test_ranger" ); 6 SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); 7 sourceBuilder.query( 8 9 10 11 QueryBuilders.rangeQuery("expected_attendees" ).lte(30 ).gte(21 ) 12 ); 13 searchRequest.source(sourceBuilder);14 SearchResponse result = client.search(searchRequest, RequestOptions.DEFAULT);15 System.out.println(result);16 } catch (Throwable e) {17 e.printStackTrace();18 } finally {19 EsClient.close(client);20 }21 }
代码@1:可以通过termQuery精确匹配。 代码@2:只有定义的范围中,任意一个值匹配查询条件,则文档匹配。 代码@3:不匹配文档。
range类型支持如下映射类型参数:co-erce、boost、index、store。
(2) 复合类型 array 数组类型,不需要使用额外的类型定义,例如定义如下字段映射:
1 "properties" : {2 "status_code" : { 3 "type" : "keyword" 4 }5 }
定义的类型为:keyword,在索引时是直接支持数组的。
1String [] keywords = new String [] {"abc" ,"def" }; 2Map <String , Object > source = new HashMap<>(); 3souce.put("status_code" ,keywords )。
Object datatype 数据类型,对象或json对象字符串。
Nested datatype 嵌套数据类型,用于关联查询。
Geo datatypes 地图数据类型。
geo_point 地图坐标;存储经纬度。其使用场景:
Geo Bounding Box Query 找出落在指定矩形框中的坐标点 Geo Distance Query 找出与指定位置在给定距离内的点 找出与指定点距离在给定最小距离和最大)距离之间的点 Geo Polygon Query 查找包含在多边形范围内的文档 与地理位置相关的查询,将在整个SearchAPI讲解完成后再详细学习。 geo_shape datatype geo_shape数据类型方便了对任意地理形状(如矩形和多边形)进行索引和搜索。当正在索引的数据或正在执行的查询包含除了点以外的形状时应该使用它。
(3) 特定类型 ip类型 IP地址类型。可以存储和索引ipv4、ipv6的IP地址。
1PUT my_index 2 { 3 "mappings" : { 4 "_doc" : { 5 "properties" : { 6 "ip_addr" : { 7 "type" : "ip" 8 } 9 } 10 }11 }12 }13 14PUT my_index/_doc/1 15 {16 "ip_addr" : "192.168.1.1" 17 }18GET my_index/_search 19 {20 "query" : {21 "term" : {22 "ip_addr" : "192.168.0.0/16" 23 }24 }25 }
Completion datatype 类型值:completion ;为了优化在查找时输入自动补全而设计的类型,输入自动补全会在查询部分专题详解。
Token count datatype 类型值:token_count,再接收一个字符串经过分析后将返回词根的个数,举例说明如下:
1PUT my_index 2 { 3 "mappings" : { 4 "_doc" : { 5 "properties" : { 6 "name" : { 7 "type" : "text" , 8 "fields" : { 9 "length" : { 10 "type" : "token_count" , 11 "analyzer" : "standard" 12 }13 }14 }15 }16 }17 }18 }
@1:定义name.length字段,其类型为token_count,使用标准分词器对原始字段name的值进行分析,返回返回分析后的词根个数。 @2:分词器。
查询示例:
1PUT my_index/_doc/1 2 { "name" : "John Smith" } 3 4PUT my_index/_doc/2 5 { "name" : "Rachel Alice Williams" } 6 7GET my_index/_search 8 { 9 "query" : { 10 "term" : {11 "name.length" : 3 12 }13 }14 }
该查询条件能匹配_doc/2,因为name字段的值经过标准分词器分词后,能得到3个词根,与”name.length”:3匹配。
join datatype 类型值:join。join类型允许在同一个索引中(同一个类型type)中定义多个不同类型的文档(例如学生文档、班级文档-)这些类型是个一对多关联关系(父子级联关系)。
下面根据示例来学习join type,下面先创建一个join的映射。
1PUT my_index 2 { 3 "mappings" : { 4 "_doc" : { 5 "properties" : { 6 "my_join_field" : { 7 "type" : "join" , 8 "relations" : { 9 "question" : "answer" 10 }11 }12 }13 }14 }15 }
代码@1:定义join字段名称。 代码@2:通过relations字段来定义父子关系,其定义方式为 “父实例名称” : “子实例名称”,question是answer的父类型。 索引父文档的方式如下:
1PUT my_index/_doc/1 ?refresh 2 { 3 "text" : "This is a question" , 4 "my_join_field" : { 5 "name" : "question" 6 } 7 } 8PUT my_index/_doc/2 ?refresh 9 { 10 "text" : "This is a another question" ,11 "my_join_field" : {12 "name" : "question" 13 }14 }
索引父文档时,在souce字段中必须指定其关系,例如”name”: “question”,上述索引,用JAVA实现如下:
1public static void createMapping_join_demo ( ) { 2 RestHighLevelClient client = EsClient.getClient(); 3 try { 4 CreateIndexRequest request = new CreateIndexRequest("map_test_join" ); 5 Map relations = new HashMap<>(); 6 relations.put("question" , "answer" ); 7 XContentBuilder jsonBuilder = XContentFactory.jsonBuilder() 8 .startObject() 9 .startObject("properties" ) 10 .startObject("text" )11 .field("type" , "text" )12 .endObject()13 .startObject("my_join_field" )14 .field("type" , "join" )15 .field("relations" , relations)16 .endObject()17 .endObject()18 .endObject();19 request.mapping("_doc" , jsonBuilder);20 System.out.println(client.indices().create(request, RequestOptions.DEFAULT));21 } catch (Throwable e) {22 e.printStackTrace();23 } finally {24 EsClient.close(client);25 }26 }27 28Map <String , Object > data1 = new HashMap(); 29 data1.put("text" ,"This is a another question" );30 Map my_join_field = new HashMap<>();31 my_join_field.put("name" , "question" );32 data1.put("my_join_field" ,my_join_field);33 34 35PUT my_index/_doc/3 ?routing=1 &refresh 36 {37 "text" : "This is an answer" ,38 "my_join_field" : {39 "name" : "answer" , 40 "parent" : "1" 41 }42 }
关于join字段的查询,将在DSL Join ty-pe相关查询时重点介绍。
Alias datatype 类型值为:alias,可以字段指定别名,其映射定义方式如下:
1PUT trips 2 { 3 "mappings" : { 4 "_doc" : { 5 "properties" : { 6 "distance" : { 7 "type" : "long" 8 }, 9 "route_length_miles" : { 10 "type" : "alias" ,11 "path" : "distance" 12 },13 "transit_mode" : {14 "type" : "keyword" 15 }16 }17 }18 }19 }
通过使用path来指定是哪个字段的别名。
2.3-DSL语法和搜索 Elasticsearch提供了一个基于JSON的完整查询DSL(领域特定语言)来定义查询。把查询DSL看作是查询的AST(抽象语法树),由两种类型的子句组成:
Leaf query clauses(叶查询字句) 叶子查询子句指在特定的字段中寻找特定的值,例如匹配、范围查询或term(完全匹配)。这些查询可以单独使用。
Compound query clauses(复合查询字句) 复合查询字句包装其他叶子或复合字句,用于以逻辑方式组合多个查询(如bool、dis_max)或改变他们的行为(如常量查询)。
查询子句的行为取决于它是在查询上下文中使用还是在过滤上下文中使用:
在查询上下文中使用的查询子句,查询字句回答了“这个文档与这个查询子句(查询条件)匹配得有多好?”除了决定文档是否匹配之外,查询子句还计算一个分数,表示相对与其他文档该文档匹配的程度。每当一个查询子句传递给查询参数(query)时,查询上下文就会生效,比如搜索API中的查询参数。
在过滤上下文中,查询子句回答“这个文档是否匹配这个查询子句?”答案是简单的“是”或“否”——没有计算出分数。过滤上下文主要用于过滤结构化数据(相当与关系型数据库 的过滤条件)。例如这个时间戳是否会在2015年到2016年之间?文章的状态是为“发布”吗?等等。
经常使用的过滤器(filter context)会被Elasticsearch自动缓存,以提高性能。每当一个查询子句被传递给过滤器参数(filter)时,过滤器上下文就会生效,例如bool查询中的filter或must_not参数、或filter查询中的常量查询(constant_score)或filter查询。
举例如下:
GET /_search { "query" : { "bool" : { "must" : [ { "match" : { "title" : "Search" }}, { "match" : { "content" : "Elasticsearch" }} ], "filter" : [ { "term" : { "status" : "published" }}, { "range" : { "publish_date" : { "gte" : "2015-01-01" }}} ] } } }
代码@1:query参数定义查询上下文,query参数为elasticsearch的查询上下文。
代码@2:使用elasticsearch的bool查询表达式,会在后续详细介绍。
代码@3:查询上下文,使用关键字match,表示title字段中包含”Search”字符即认为匹配。(可以类比关系型数据库 a.title like ‘%Search%’)
代码@4:查询上下文,使用关键字match,表示content字段中包含”Elasticsearch”字符即认为匹配。
代码@5:定义过滤上下文。
代码@6:使用term(完整匹配),即status字段的值是否是“published”。(相当于关系型数据库的 a.status = ‘published’)
代码@7:使用range,代表范围匹配,即publish_date字段的值是否大于等于2015-01-01。(相当于a.publish_date >= 2015-01-01’)。
1. 全文检索 match query
标准的全文检索模式,包含模糊匹配、前缀或近似匹配等。
match_phrase query
与match query类似,但只是用来精确匹配的短语。
match_phrase_prefix query
与match_phrase查询类似,但是在最后一个单词上执行通配符搜索。
multi_match query
支持多字段的match query。
common terms query
相比match query,消除停用词与高频词对相关度的影响。
query_string query
查询字符串方式。
simple_query_string query
简单查询字符串方式。
(1)match query详解 # 查询所有 GET /tmpl-word-log*/_search { "query" : { "match_all" : {} } } # 查询固定条数(默认size=10 ) GET /tmpl-word-log*/_search { "size" : 2 , "query" : { "match_all" : {} } } # 查询从10 -15 条数据(from 默认从0 开始) GET /tmpl-word-log*/_search { "query" : { "match_all" : {} } "form" : 9 , "size" : 5 } # 按某字段(时间)排序,desc降序,asc升序 GET /tmpl-word-log*/_search { "query" : { "match_all" : {} } , "sort" : [ { "@timestamp" : { "order" : "desc" } } ] } # 只查询某些字段 GET /tmpl-word-log*/_search { "size" : 2 , "query" : { "match_all" : {} } , "_source" : ["@timestamp" , "index" ] }
全文索引查询,这意外着首先会对待查字符串(查询条件)进行分词,然后再去匹配,返回结果中会待上本次匹配的关联度分数。
例如存在这样一条数据:
"_source" :{ "post_date" :"2009-11-16T14:12:12" , "message" :"trying out Elasticsearch" , "user" :"dingw2" }
查询字符串:
"query" : { "match" : { "message" : "this out Elasticsearch" } }
其JAVA代码对应:
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); sourceBuilder.query(QueryBuilders.matchQuery("message" , "this out elasticsearch" ));
其大体步骤如下:
首先对this out Elasticsearch分词,最终返回结果为 this、out、Elasticsearch,然后分别去库中进行匹配,默认只要一个匹配,就认为匹配,但会加入一个匹配程度(关联度),用scoce分数表示。
match query常用参数详解:
operator(操作类型) 可选值为:Operator.OR 和 Operator.AND。表示对查询字符串分词后,返回的词根列表,OR只需一个满足及认为匹配,而AND则需要全部词根都能匹配,默认值为:Operator.OR。 minimum_should_match 最少需要匹配个数。在操作类型为Operator.OR时生效,指明分词后的词根,至少minimum_should_match 个词根匹配,则命中。 "match" : { "message" : "this out Elasticsearch" , “minimum_should_match ”:“3 ” }
此时由于this词根并不在原始数据”trying out Elasticsearch”中,又要求必须匹配的词根个数为3,故本次查询,无法命中。minimum_should_match 可选值如下:
Type Example Description Integer 3 直接数字,不考虑查询字符串分词后的个数。如果分词的个数小于3个,则无法匹配到任何条目。 Negative integer -2 负数表示最多不允许不匹配的个数。也就是需要匹配的个数为(total-2)。 Percentage 75% 百分比,表示需要匹配的词根占总数的百分比。 Negative percentage -25% 允许不匹配的个数占总数的百分比。 Combination 3<90% 如果查询字符串分词的个数小于等于3(前面的整数),则只要全部匹配则返回,如果分词的个数大于3个,则只要90%的匹配即可。 Multiple combinations 2<-25% 9<-3 支持多条件表达式,中间用空格分开。该表达式的意义如下:1、如果分词的个数小于等于2,则必须全部匹配;如果大于2小于9,则除了25%(注意负号)之外都需要满足。2、如果大于9个,则只允许其中3个不满足。
设置分词器,默认使用字段映射中定义的分词器或elasticsearch默认的分词器。
是否忽略由于数据类型不匹配引起的异常,默认为false。例如尝试用文本查询字符串查询数值字段,默认会抛出错误。
模糊匹配。
默认情况下,如果分词器会过滤查询字句中的停用词,可能会造成查询字符串分词后变成空字符串,此时默认的行为是无法匹配到任何文档,如果想改变该默认情况,可以设置zero_terms_query=all,类似于match_all,默认值为none。
match查询支持cutoff_frequency,允许指定绝对或相对的文档频率:
OR:高频单词被放入“或许有”的类别,仅在至少有一个低频(低于cutoff_frequency)单词满足条件时才积分; AND:高频单词被放入“或许有”的类别,仅在所有低频(低于cutoff_frequency)单词满足条件时才积分。该查询允许在运行时动态处理停用词而不需要使用停用词文件。它阻止了对高频短语(停用词)的评分/迭代,并且只在更重要/更低频率的短语与文档匹配时才会考虑这些文档。然而,如果所有查询条件都高于给定的cutoff_frequency,则查询将自动转换为纯连接(and)查询,以确保快速执行。 cutoff_frequency取值是相对于文档的总数的小数[0..1),也可以是绝对值[1, +∞)。
可在分词器中定义同义词,具体同义词将在后续章节中会单独介绍。
match query示例:
public static void testMatchQuery ( ) { RestHighLevelClient client = EsClient.getClient(); try { SearchRequest searchRequest = new SearchRequest(); searchRequest.indices("twitter" ); SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); sourceBuilder.query( QueryBuilders.matchQuery("message" , "is out Elasticsearch" ) .zeroTermsQuery(ZeroTermsQuery.ALL) .operator(Operator.OR) .minimumShouldMatch("4<90%" ) ).sort(new FieldSortBuilder("post_date" ).order(SortOrder.DESC)) .docValueField("post_date" , "epoch_millis" ); searchRequest.source(sourceBuilder); SearchResponse result = client.search(searchRequest, RequestOptions.DEFAULT); System.out.println(result); } catch (Throwable e) { e.printStackTrace(); } finally { EsClient.close(client); } }
(2)match_phrase 与match query类似,但只是用来精确匹配的短语。
其主要工作流程:
首先,Elasticsearch(lucene)会使用分词器对全文本进行分词(返回一个一个的词根(顺序排列)),然后同样使用分词器对查询字符串进行分析,返回一个一个的词根(顺序性)。如果能在全字段中能够精确找到与查询字符串通用的词根序列,则认为匹配,否则认为不匹配。
举例如下:
如果原文字段message:”quick brown fox test we will like to you”,则使用标准分词器(analyzer=standard)返回的结果如下:
使用命令:
curl -X GET "192.168.1.10:9200/_analyze" -H 'Content-Type: application/json' -d' { "tokenizer" : "standard", "text" : "quick brown fox test we will like to you", "attributes" : ["keyword"] }'
得出如下结果:
{ "tokens" :[ { "token" :"quick" , "start_offset" :0 , "end_offset" :5 , "type" :"<ALPHANUM>" , "position" :0 }, { "token" :"brown" , "start_offset" :6 , "end_offset" :11 , "type" :"<ALPHANUM>" , "position" :1 }, { "token" :"fox" , "start_offset" :12 , "end_offset" :15 , "type" :"<ALPHANUM>" , "position" :2 }, { "token" :"test" , "start_offset" :16 , "end_offset" :20 , "type" :"<ALPHANUM>" , "position" :3 }, { "token" :"we" , "start_offset" :21 , "end_offset" :23 , "type" :"<ALPHANUM>" , "position" :4 }, { "token" :"will" , "start_offset" :24 , "end_offset" :28 , "type" :"<ALPHANUM>" , "position" :5 }, { "token" :"like" , "start_offset" :29 , "end_offset" :33 , "type" :"<ALPHANUM>" , "position" :6 }, { "token" :"to" , "start_offset" :34 , "end_offset" :36 , "type" :"<ALPHANUM>" , "position" :7 }, { "token" :"you" , "start_offset" :37 , "end_offset" :40 , "type" :"<ALPHANUM>" , "position" :8 } ] }
其词根具有顺序性(词根序列)为quick、brown、fox、test 、we 、will、 like、 to 、you
如果查询字符串为 quick brown,分词后的词根序列为 quick brown,则是原词根序列的子集,则匹配。
如果查询字符串为 quick fox,分词后的词根序列为 quick fox,与原词根序列不匹配。如果指定slop属性,设置为1,则匹配,其表示每一个词根直接跳过一个词根形成新的序列,与搜索词根进行比较,是否匹配。
如果查询字符串为quick fox test,其特点是quick与原序列跳过一个词brown,但fox后面不跳过任何次,与test紧挨着,如果指定slop=1,同样能匹配到文档,但查询字符串quick fox test will,却匹配不到文档,说明slop表示整个搜索词根中为了匹配流,能跳过的最大次数。
按照match_phrase的定义,与match query的区别一个在与精确匹配,一个在于词组term(理解为词根序列),故match_phrase与match相比,不会有如下参数:fuzziness、cutoff_frequency、operator、minimum_should_match 这些参数。
(3)match_phrase_prefix 与match phrase基本相同,只是该查询模式会对最后一个词根进行前缀匹配。
GET /_search { "query" : { "match_phrase_prefix" : { "message" : { "query" : "quick brown f" , "max_expansions" : 10 } } } }
其工作流程如下:首先先对除最后一个词进行分词,得到词根序列 quick brown,然后遍历整个elasticsearch倒排索引,查找以f开头的词根,依次组成多个词根流,例如(quick brown fox) (quick brown foot),默认查找50组,受参数max_expansions控制,在使用时请设置合理的max_expansions,该值越大,查询速度将会变的更慢。该技术主要完成及时搜索,指用户在输入过程中,就根据前缀返回查询结果,随着用户输入的字符越多,查询的结果越接近用户的需求。
(4)multi_match multi_match查询建立在match查询之上,允许多字段查询。
GET /_search { "query" : { "multi_match" : { "query" : "this is a test" , "fields" : [ "subject" , "message" ] } } }
@1执行作用(查询)的字段,有如下几种用法:
[ “subject”, “message” ] ,表示针对查询自动对subject,message字段进行查询匹配。
[ “title”, “*name” ],支持通配符,表示对title,以 name结尾的字段进行查询匹配。
[ “subject^3”, “message” ],表示subject字段是message的重要性的3倍,类似于字段权重
2. 布尔查询 # 匹配word包含中秋(两个字中有一个就能匹配)且index=2 的数据 GET /tmpl-word-log*/_search { "query" : { "bool" : { "must" : [ {"match" :{"word" : "中秋" }}, {"match" : {"index" : 2 }} ] } } } # 匹配word包含中秋(两个字中有一个就能匹配)或index=2 的数据 GET /tmpl-word-log*/_search { "query" : { "bool" : { "should" : [ {"match" :{"word" : "中秋" }}, {"match" : {"index" : 2 }} ] } } } # 匹配word不包含中秋(两个字中有一个就能匹配)且index=2 的数据 GET /tmpl-word-log*/_search { "query" : { "bool" : { "must_not" : [ {"match" :{"word" : "中秋" }}, {"match" : {"index" : 2 }} ] } } } # 在bool中must,must_not,should等关键字可以混用,表示多条件同时满足 # 匹配word包含中秋(两个字中有一个就能匹配)且index!=2 的数据 GET /tmpl-word-log*/_search { "query" : { "bool" : { "must" : [ {"match" :{"word" : "中秋" }} ] , "must_not" : [ {"match" : {"index" : 2 }} ] } } }
3. 范围查询 # 查询索引在11 -20 之间的数据 GET /tmpl-word-log*/_search { "query" : { "range" : { "index" : { "gte" : 10 , "lte" : 20 } } } }
4. 过滤查询(filters) # 在Elasticsearch5.x版本中filtered方法弃用改用bool中的filter # 匹配日期在9 -12 到9 -24 日的数据 GET /tmpl-word-log*/_search { "query" : { "bool" :{ "must" : {"match_all" : {}}, "filter" :{ "range" :{ "@timestamp" :{ "gte" :"2018-09-12T00:00:00.000+0800" , "lte" :"2018-09-24T23:59:59.000+0800" } } } } } }
5. 分页查询 (1)from / size 分页 from - 表示起始位置,size - 表示每页数量;类似与 MySQL 的 limit + offset。 示例:
GET /_search { "from" : 10, "size" : 10, "query" : { "term" : { "user" : "kimchy" } } }
需要注意的是,from + size 不能超过10000,也就是说在前10000条之内,可以随意翻页,10000条之后就不行了。
实际上,通过设置 index.max_result_window 可以修改这个限制,但是不建议这么做,因为这种方式翻页越深效率越低。
原理:
Query阶段:
当一个请求发送到某个ES节点时,该节点(Node1)会根据from和size,建立一个结果集窗口,窗口大小为from+size。假如from=10000,size=100,则窗口大小为10100。 此节点将请求广播到其他相关节点上,从每个Shards中取Top 10100条的score和id。 所有Shards获取的结果汇聚到Node1,假如有5个Shards,则一共会取到5 * 10100 = 50500条数据。 Node1进行归并排序,并选择Top 10100条,存入结果集窗口。 Fetch阶段: 根据Query阶段得到的排序结果,从 from 位置取 size 条数据,抓取文档详细内容返回。
从此过程中可以看出,翻页越靠后,需要参与排序的文档就越多,效率也就越低。所以,如果结果集很大,不建议用这种分页方式。
使用scroll就像传统数据库中的游标一样,方式如下:
第一步
POST /twitter/_search?scroll=1m { "size" : 100, "query" : { "match" : { "title" : "elasticsearch" } } }
scroll=1m,表示“search context”存活时间1分钟。返回结果中会带有一个“_scroll_id”,这个值在后续的翻页过程中使用。
第二步
POST /_search/scroll { "scroll" : "1m" , "scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==" }
不用指定index和type,也不用其他查询条件,只要把上一步的_scroll_id即可。
之后翻页一直如此,每次执行会自动滚动100条数据,直到返回的结果为空为止。
每次执行间隔不要超过1分钟,否则“search context”会释放掉。
第三步
DELETE /_search/scroll { "scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==" }
结果遍历完成后,删除scroll_id。这一步也可以不做,等1分钟后没有继续翻页请求,“search context”会自动释放掉,不过建议还是手动清除,节省资源。
优化: 如果目的是为了遍历所有结果,而不关心结果的顺序,那么可以按“_doc”排序来提高性能
POST /twitter/_search?scroll=1m { "size" : 100, "query" : { "match" : { "title" : "elasticsearch" } }, "sort" : ["_doc" ] }
与 from/size 分页方式不同,使用 scroll 分页只能单向顺序翻页,不能随机翻页,适用于遍历结果集的场景。
scroll 翻页能够深度翻页,但是翻页期间需要维护“search context”,这是需要占用一定资源的。
所以对于用户高并发访问的场景,不推荐用这种方式,scroll 更适用于批处理类的后台任务。
scroll API 保持了那些已经返回记录结果,所以能更加高效地返回排序的结果。但是,按照默认设定排序结果仍然需要代价。
一般来说,你仅仅想要找到结果,不关心顺序。你可以通过组合 scroll 和 scan 来关闭任何打分或者排序,以最高效的方式返回结果。你需要做的就是将 search_type=scan 加入到查询的字符串中:
POST /my_index/my_type/_search?scroll=1m&search_type=scan { "query" : { "match" : { "cityName" : "杭州" } } }
设置 search_type 为 scan 可以关闭打分,让滚动更加高效。 扫描式的滚动请求和标准的滚动请求有四处不同:
(1)不算分,关闭排序。结果会按照在索引中出现的顺序返回; (2)不支持聚合; (3)初始 search 请求的响应不会在 hits 数组中包含任何结果。第一批结果就会按照第一个 scroll 请求返回。 (4)参数 size 控制了每个分片上而非每个请求的结果数目,所以 size 为 10 的情况下,如果命中了 5 个分片,那么每个 scroll 请求最多会返回 50 个结果。
如果你想支持打分,即使不进行排序,将 track_scores 设置为 true。
(4)使用 search after 分页 这种方式同样可以深度翻页,但是弥补了 scroll 方式的不足。其思想是:用前一次的查询结果作为下一次的查询条件。
示例:
首次查询
GET /user_model/_search { "size" : 10, "query" : {"match_all" : {}}, "sort" : [ {"_id" : "asc" } ] }
返回结果:
{ "took" : 4379 , "timed_out" : false , "_shards" : { "total" : 6 , "successful" : 6 , "skipped" : 0 , "failed" : 0 }, "hits" : { "total" : 38213940 , "max_score" : null , "hits" : [ ... { "_index" : "user_model" , "_type" : "_doc" , "_id" : "00000f78f59644b1967783986c35496c" , "_score" : null , "_source" : { ... }, "sort" : [ "00000f78f59644b1967783986c35496c" ] } ] } }
后续查询
GET /user_model/_search { "size" : 10, "query" : {"match_all" : {}}, "sort" : [ {"_id" : "asc" } ], "search_after" : ["00000f78f59644b1967783986c35496c" ] }
其中,search_after 为上次查询结果中最后一条记录的 sort 值。
(5)总结: 如果数据量小(10000条内),或者只关注结果集的TopN数据,可以使用from / size 分页,简单粗暴 数据量大,深度翻页,后台批处理任务(数据迁移)之类的任务,使用 scroll 方式 数据量大,深度翻页,用户实时、高并发查询需求,使用 search after 方式 6. 批量查询 Elasticsearch 的速度已经很快了,但甚至能更快。 将多个请求合并成一个,避免单独处理每个请求花费的网络延时和开销。 如果你需要从 Elasticsearch 检索很多文档,那么使用 multi-get 或者 mget
API 来将这些检索请求放在一个请求中,将比逐个文档请求更快地检索到全部文档。
mget
API 要求有一个 docs
数组作为参数,每个元素包含需要检索文档的元数据, 包括 _index
、 _type
和 _id
。如果你想检索一个或者多个特定的字段,那么你可以通过 _source
参数来指定这些字段的名字:
GET /_mget { "docs" : [ { "_index" : "website" , "_type" : "blog" , "_id" : 2 }, { "_index" : "website" , "_type" : "pageviews" , "_id" : 1 , "_source" : "views" } ] }
该响应体也包含一个 docs
数组, 对于每一个在请求中指定的文档,这个数组中都包含有一个对应的响应,且顺序与请求中的顺序相同。 其中的每一个响应都和使用单个 get
request 请求所得到的响应体相同:
{ "docs" : [ { "_index" : "website" , "_id" : "2" , "_type" : "blog" , "found" : true , "_source" : { "text" : "This is a piece of cake..." , "title" : "My first external blog entry" }, "_version" : 10 }, { "_index" : "website" , "_id" : "1" , "_type" : "pageviews" , "found" : true , "_version" : 2 , "_source" : { "views" : 2 } } ] }
如果想检索的数据都在相同的 _index
中(甚至相同的 _type
中),则可以在 URL 中指定默认的 /_index
或者默认的 /_index/_type
。
你仍然可以通过单独请求覆盖这些值:
GET /website/blog/_mget { "docs" : [ { "_id" : 2 }, { "_type" : "pageviews" , "_id" : 1 } ] }
事实上,如果所有文档的 _index
和 _type
都是相同的,你可以只传一个 ids
数组,而不是整个 docs
数组:
GET /website/blog/_mget { "ids" : [ "2" , "1" ] }
注意,我们请求的第二个文档是不存在的。我们指定类型为 blog
,但是文档 ID 1
的类型是 pageviews
,这个不存在的情况将在响应体中被报告:
{ "docs" : [ { "_index" : "website" , "_type" : "blog" , "_id" : "2" , "_version" : 10 , "found" : true , "_source" : { "title" : "My first external blog entry" , "text" : "This is a piece of cake..." } }, { "_index" : "website" , "_type" : "blog" , "_id" : "1" , "found" : false } ] }
2.4-批量操作 与 mget
可以使我们一次取回多个文档同样的方式, bulk
API 允许在单个步骤中进行多次 create
、 index
、 update
或 delete
请求。 如果你需要索引一个数据流比如日志事件,它可以排队和索引数百或数千批次。
bulk
与其他的请求体格式稍有不同,如下所示:
{ action : { metadata }}\n { request body }\n { action : { metadata }}\n { request body }\n ...
这种格式类似一个有效的单行 JSON 文档 流 ,它通过换行符(\n
)连接到一起。注意两个要点:
每行一定要以换行符(\n
)结尾, 包括最后一行 。这些换行符被用作一个标记,可以有效分隔行。 这些行不能包含未转义的换行符,因为他们将会对解析造成干扰。这意味着这个 JSON 不 能使用 pretty 参数打印。 bulk的请求模板
分成action、metadata和doc三部份
action : 必须是以下4种选项之一
index(最常用) : 如果文档不存在就创建他,如果文档存在就更新他
create : 如果文档不存在就创建他,但如果文档存在就返回错误
使用时一定要在metadata设置_id值,他才能去判断这个文档是否存在
update : 更新一个文档,如果文档不存在就返回错误
使用时也要给_id值,且后面文档的格式和其他人不一样
delete : 删除一个文档,如果要删除的文档id不存在,就返回错误
使用时也必须在metadata中设置文档_id,且后面不能带一个doc,因为没意义,他是用_id去删除文档的
metadata : 设置这个文档的metadata,像是_id、_index、_type…
doc : 就是一般的文档格式
metadata
应该指定被索引、创建、更新或者删除的文档的 _index
、 _type
和 _id
。
例如,一个 delete
请求看起来是这样的:
{ "delete" : { "_index" : "website" , "_type" : "blog" , "_id" : "123" }}
request body
行由文档的 _source
本身组成—文档包含的字段和值。它是 index
和 create
操作所必需的,这是有道理的:你必须提供文档以索引。
它也是 update
操作所必需的,并且应该包含你传递给 update
API 的相同请求体: doc
、 upsert
、 script
等等。 删除操作不需要 request body
行。
{ "create" : { "_index" : "website" , "_type" : "blog" , "_id" : "123" }} { "title" : "My first blog post" }
如果不指定 _id
,将会自动生成一个 ID :
{ "index" : { "_index" : "website" , "_type" : "blog" }} { "title" : "My second blog post" }
为了把所有的操作组合在一起,一个完整的 bulk
请求 有以下形式:
POST /_bulk { "delete" : { "_index" : "website" , "_type" : "blog" , "_id" : "123" }} { "create" : { "_index" : "website" , "_type" : "blog" , "_id" : "123" }} { "title" : "My first blog post" } { "index" : { "_index" : "website" , "_type" : "blog" }} { "title" : "My second blog post" } { "update" : { "_index" : "website" , "_type" : "blog" , "_id" : "123" , "_retry_on_conflict" : 3 } } { "doc" : {"title" : "My updated blog post" } }
3-ES整合SpringBoot 面介绍下 SpringBoot 如何通过 elasticsearch-rest-high-level-client 工具操作 ElasticSearch,这里需要说一下,为什么没有使用 Spring 家族封装的 spring-data-elasticsearch。
主要原因是灵活性和更新速度,Spring 将 ElasticSearch 过度封装,让开发者很难跟 ES 的 DSL 查询语句进行关联。再者就是更新速度,ES 的更新速度是非常快,但是 spring-data-elasticsearch 更新速度比较缓慢。
由于上面两点,所以选择了官方推出的 Java 客户端 elasticsearch-rest-high-level-client,它的代码写法跟 DSL 语句很相似,懂 ES 查询的使用其上手很快。
3.1-Maven 引入相关依赖 lombok :lombok 工具依赖。
fastjson:用于将 JSON 转换对象的依赖。
spring-boot-starter-web: SpringBoot 的 Web 依赖。
elasticsearch:ElasticSearch:依赖,需要和 ES 版本保持一致。
elasticsearch-rest-high-level-client:用于操作 ES 的 Java 客户端。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 2.2.4.RELEASE</version > <relativePath /> </parent > <groupId > club.mydlq</groupId > <artifactId > springboot-elasticsearch-example</artifactId > <version > 0.0.1-SNAPSHOT</version > <name > springboot-elasticsearch-example</name > <description > Demo project for Spring Boot ElasticSearch</description > <properties > <java.version > 1.8</java.version > </properties > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <optional > true</optional > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > fastjson</artifactId > <version > 1.2.61</version > </dependency > <dependency > <groupId > org.elasticsearch.client</groupId > <artifactId > elasticsearch-rest-high-level-client</artifactId > <version > 6.5.4</version > </dependency > <dependency > <groupId > org.elasticsearch</groupId > <artifactId > elasticsearch</artifactId > <version > 6.5.4</version > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > </plugin > </plugins > </build > </project >
3.2-ElasticSearch 连接配置 1. application.yml 配置文件 为了方便更改连接 ES 的连接配置,所以我们将配置信息放置于 application.yaml 中:
server: port: 8080 spring: application: name: springboot-elasticsearch-example elasticsearch: schema: http address: 127.0 .0 .1 :9200 connectTimeout: 5000 socketTimeout: 5000 connectionRequestTimeout: 5000 maxConnectNum: 100 maxConnectPerRoute: 100
2. java 连接配置类 这里需要写一个 Java 配置类读取 application 中的配置信息:
import org.apache.http.HttpHost;import org.elasticsearch.client.RestClient;import org.elasticsearch.client.RestClientBuilder;import org.elasticsearch.client.RestHighLevelClient;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.util.ArrayList;import java.util.List;@Configuration public class ElasticSearchConfig { @Value("${elasticsearch.schema:http}") private String schema; @Value("${elasticsearch.address}") private String address; @Value("${elasticsearch.connectTimeout:5000}") private int connectTimeout; @Value("${elasticsearch.socketTimeout:10000}") private int socketTimeout; @Value("${elasticsearch.connectionRequestTimeout:5000}") private int connectionRequestTimeout; @Value("${elasticsearch.maxConnectNum:100}") private int maxConnectNum; @Value("${elasticsearch.maxConnectPerRoute:100}") private int maxConnectPerRoute; @Bean public RestHighLevelClient restHighLevelClient () { List<HttpHost> hostLists = new ArrayList<>(); String[] hostList = address.split("," ); for (String addr : hostList) { String host = addr.split(":" )[0 ]; String port = addr.split(":" )[1 ]; hostLists.add(new HttpHost(host, Integer.parseInt(port), schema)); } HttpHost[] httpHost = hostLists.toArray(new HttpHost[]{}); RestClientBuilder builder = RestClient.builder(httpHost); builder.setRequestConfigCallback(requestConfigBuilder -> { requestConfigBuilder.setConnectTimeout(connectTimeout); requestConfigBuilder.setSocketTimeout(socketTimeout); requestConfigBuilder.setConnectionRequestTimeout(connectionRequestTimeout); return requestConfigBuilder; }); builder.setHttpClientConfigCallback(httpClientBuilder -> { httpClientBuilder.setMaxConnTotal(maxConnectNum); httpClientBuilder.setMaxConnPerRoute(maxConnectPerRoute); return httpClientBuilder; }); return new RestHighLevelClient(builder); } }
3.3-索引操作示例 1. Restful 操作示例 创建索引
创建名为 mydlq-user 的索引与对应 Mapping。
PUT /mydlq-user { "mappings" : { "doc" : { "dynamic" : true , "properties" : { "name" : { "type" : "text" , "fields" : { "keyword" : { "type" : "keyword" } } }, "address" : { "type" : "text" , "fields" : { "keyword" : { "type" : "keyword" } } }, "remark" : { "type" : "text" , "fields" : { "keyword" : { "type" : "keyword" } } }, "age" : { "type" : "integer" }, "salary" : { "type" : "float" }, "birthDate" : { "type" : "date" , "format" : "yyyy-MM-dd" }, "createTime" : { "type" : "date" } } } } }
删除索引
删除 mydlq-user 索引。
2. Java 代码示例 import lombok.extern.slf4j.Slf4j;import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;import org.elasticsearch.action.support.master.AcknowledgedResponse;import org.elasticsearch.client.RequestOptions;import org.elasticsearch.client.RestHighLevelClient;import org.elasticsearch.common.settings.Settings;import org.elasticsearch.common.xcontent.XContentBuilder;import org.elasticsearch.common.xcontent.XContentFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.io.IOException;@Slf4j @Service public class IndexService2 { @Autowired private RestHighLevelClient restHighLevelClient; public void createIndex () { try { XContentBuilder mapping = XContentFactory.jsonBuilder() .startObject() .field("dynamic" , true ) .startObject("properties" ) .startObject("name" ) .field("type" ,"text" ) .startObject("fields" ) .startObject("keyword" ) .field("type" ,"keyword" ) .endObject() .endObject() .endObject() .startObject("address" ) .field("type" ,"text" ) .startObject("fields" ) .startObject("keyword" ) .field("type" ,"keyword" ) .endObject() .endObject() .endObject() .startObject("remark" ) .field("type" ,"text" ) .startObject("fields" ) .startObject("keyword" ) .field("type" ,"keyword" ) .endObject() .endObject() .endObject() .startObject("age" ) .field("type" ,"integer" ) .endObject() .startObject("salary" ) .field("type" ,"float" ) .endObject() .startObject("birthDate" ) .field("type" ,"date" ) .field("format" , "yyyy-MM-dd" ) .endObject() .startObject("createTime" ) .field("type" ,"date" ) .endObject() .endObject() .endObject(); Settings settings = Settings.builder() .put("index.number_of_shards" , 1 ) .put("index.number_of_replicas" , 0 ) .build(); CreateIndexRequest request = new CreateIndexRequest("mydlq-user" , settings); request.mapping("doc" , mapping); CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT); boolean isCreated = createIndexResponse.isAcknowledged(); log.info("是否创建成功:{}" , isCreated); } catch (IOException e) { log.error("" , e); } } public void deleteIndex () { try { DeleteIndexRequest request = new DeleteIndexRequest("mydlq-user" ); AcknowledgedResponse acknowledgedResponse = restHighLevelClient.indices().delete(request, RequestOptions.DEFAULT); boolean siDeleted = acknowledgedResponse.isAcknowledged(); log.info("是否删除成功:{}" , siDeleted); } catch (IOException e) { log.error("" , e); } } }
3.4-文档操作示例 1. Restful 操作示例 增加文档信息
在索引 mydlq-user 中增加一条文档信息。
POST /mydlq-user/doc { "address" : "北京市" , "age" : 29 , "birthDate" : "1990-01-10" , "createTime" : 1579530727699 , "name" : "张三" , "remark" : "来自北京市的张先生" , "salary" : 100 }
获取文档信息
获取 mydlq-user 的索引 id=1 的文档信息。
更新文档信息
更新之前创建的 id=1 的文档信息。
PUT /mydlq-user/doc/1 { "address" : "北京市海淀区" , "age" : 29 , "birthDate" : "1990-01-10" , "createTime" : 1579530727699 , "name" : "张三" , "remark" : "来自北京市的张先生" , "salary" : 100 }
删除文档信息
删除之前创建的 id=1 的文档信息。
2. Java 代码示例 import club.mydlq.elasticsearch.model.entity.UserInfo;import com.alibaba.fastjson.JSON;import lombok.extern.slf4j.Slf4j;import org.elasticsearch.action.delete.DeleteRequest;import org.elasticsearch.action.delete.DeleteResponse;import org.elasticsearch.action.get.GetRequest;import org.elasticsearch.action.get.GetResponse;import org.elasticsearch.action.index.IndexRequest;import org.elasticsearch.action.index.IndexResponse;import org.elasticsearch.action.update.UpdateRequest;import org.elasticsearch.action.update.UpdateResponse;import org.elasticsearch.client.RequestOptions;import org.elasticsearch.client.RestHighLevelClient;import org.elasticsearch.common.xcontent.XContentType;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.io.IOException;import java.util.Date;@Slf4j @Service public class IndexService { @Autowired private RestHighLevelClient restHighLevelClient; public void addDocument () { try { IndexRequest indexRequest = new IndexRequest("mydlq-user" , "doc" , "1" ); UserInfo userInfo = new UserInfo(); userInfo.setName("张三" ); userInfo.setAge(29 ); userInfo.setSalary(100.00f ); userInfo.setAddress("北京市" ); userInfo.setRemark("来自北京市的张先生" ); userInfo.setCreateTime(new Date()); userInfo.setBirthDate("1990-01-10" ); byte [] json = JSON.toJSONBytes(userInfo); indexRequest.source(json, XContentType.JSON); IndexResponse response = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT); log.info("创建状态:{}" , response.status()); } catch (Exception e) { log.error("" , e); } } public void getDocument () { try { GetRequest getRequest = new GetRequest("mydlq-user" , "doc" , "1" ); GetResponse getResponse = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT); if (getResponse.isExists()) { UserInfo userInfo = JSON.parseObject(getResponse.getSourceAsBytes(), UserInfo.class); log.info("员工信息:{}" , userInfo); } } catch (IOException e) { log.error("" , e); } } public void updateDocument () { try { UpdateRequest updateRequest = new UpdateRequest("mydlq-user" , "doc" , "1" ); UserInfo userInfo = new UserInfo(); userInfo.setSalary(200.00f ); userInfo.setAddress("北京市海淀区" ); byte [] json = JSON.toJSONBytes(userInfo); updateRequest.doc(json, XContentType.JSON); UpdateResponse response = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT); log.info("创建状态:{}" , response.status()); } catch (Exception e) { log.error("" , e); } } public void deleteDocument () { try { DeleteRequest deleteRequest = new DeleteRequest("mydlq-user" , "doc" , "1" ); DeleteResponse response = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT); log.info("删除状态:{}" , response.status()); } catch (IOException e) { log.error("" , e); } } }
3.5-插入初始化数据 1. 单条插入 POST mydlq-user/_doc
{ "name" :"零零" , "address" :"北京市丰台区" , "remark" :"低层员工" , "age" :29 , "salary" :3000 , "birthDate" :"1990-11-11" , "createTime" :"2019-11-11T08:18:00.000Z" }
2. 批量插入 POST _bulk
{"index" :{"_index" :"mydlq-user" ,"_type" :"doc" }} {"name" :"刘一" ,"address" :"北京市丰台区" ,"remark" :"低层员工" ,"age" :30 ,"salary" :3000 ,"birthDate" :"1989-11-11" ,"createTime" :"2019-03-15T08:18:00.000Z" } {"index" :{"_index" :"mydlq-user" ,"_type" :"doc" }} {"name" :"陈二" ,"address" :"北京市昌平区" ,"remark" :"中层员工" ,"age" :27 ,"salary" :7900 ,"birthDate" :"1992-01-25" ,"createTime" :"2019-11-08T11:15:00.000Z" } {"index" :{"_index" :"mydlq-user" ,"_type" :"doc" }} {"name" :"张三" ,"address" :"北京市房山区" ,"remark" :"中层员工" ,"age" :28 ,"salary" :8800 ,"birthDate" :"1991-10-05" ,"createTime" :"2019-07-22T13:22:00.000Z" } {"index" :{"_index" :"mydlq-user" ,"_type" :"doc" }} {"name" :"李四" ,"address" :"北京市大兴区" ,"remark" :"高层员工" ,"age" :26 ,"salary" :9000 ,"birthDate" :"1993-08-18" ,"createTime" :"2019-10-17T15:00:00.000Z" } {"index" :{"_index" :"mydlq-user" ,"_type" :"doc" }} {"name" :"王五" ,"address" :"北京市密云区" ,"remark" :"低层员工" ,"age" :31 ,"salary" :4800 ,"birthDate" :"1988-07-20" ,"createTime" :"2019-05-29T09:00:00.000Z" } {"index" :{"_index" :"mydlq-user" ,"_type" :"doc" }} {"name" :"赵六" ,"address" :"北京市通州区" ,"remark" :"中层员工" ,"age" :32 ,"salary" :6500 ,"birthDate" :"1987-06-02" ,"createTime" :"2019-12-10T18:00:00.000Z" } {"index" :{"_index" :"mydlq-user" ,"_type" :"doc" }} {"name" :"孙七" ,"address" :"北京市朝阳区" ,"remark" :"中层员工" ,"age" :33 ,"salary" :7000 ,"birthDate" :"1986-04-15" ,"createTime" :"2019-06-06T13:00:00.000Z" } {"index" :{"_index" :"mydlq-user" ,"_type" :"doc" }} {"name" :"周八" ,"address" :"北京市西城区" ,"remark" :"低层员工" ,"age" :32 ,"salary" :5000 ,"birthDate" :"1987-09-26" ,"createTime" :"2019-01-26T14:00:00.000Z" } {"index" :{"_index" :"mydlq-user" ,"_type" :"doc" }} {"name" :"吴九" ,"address" :"北京市海淀区" ,"remark" :"高层员工" ,"age" :30 ,"salary" :11000 ,"birthDate" :"1989-11-25" ,"createTime" :"2019-09-07T13:34:00.000Z" } {"index" :{"_index" :"mydlq-user" ,"_type" :"doc" }} {"name" :"郑十" ,"address" :"北京市东城区" ,"remark" :"低层员工" ,"age" :29 ,"salary" :5000 ,"birthDate" :"1990-12-25" ,"createTime" :"2019-03-06T12:08:00.000Z" } {"index" :{"_index" :"mydlq-user" ,"_type" :"doc" }} {"name" :"萧十一" ,"address" :"北京市平谷区" ,"remark" :"低层员工" ,"age" :29 ,"salary" :3300 ,"birthDate" :"1990-11-11" ,"createTime" :"2019-03-10T08:17:00.000Z" } {"index" :{"_index" :"mydlq-user" ,"_type" :"doc" }} {"name" :"曹十二" ,"address" :"北京市怀柔区" ,"remark" :"中层员工" ,"age" :27 ,"salary" :6800 ,"birthDate" :"1992-01-25" ,"createTime" :"2019-12-03T11:09:00.000Z" } {"index" :{"_index" :"mydlq-user" ,"_type" :"doc" }} {"name" :"吴十三" ,"address" :"北京市延庆区" ,"remark" :"中层员工" ,"age" :25 ,"salary" :7000 ,"birthDate" :"1994-10-05" ,"createTime" :"2019-07-27T14:22:00.000Z" } {"index" :{"_index" :"mydlq-user" ,"_type" :"doc" }} {"name" :"冯十四" ,"address" :"北京市密云区" ,"remark" :"低层员工" ,"age" :25 ,"salary" :3000 ,"birthDate" :"1994-08-18" ,"createTime" :"2019-04-22T15:00:00.000Z" } {"index" :{"_index" :"mydlq-user" ,"_type" :"doc" }} {"name" :"蒋十五" ,"address" :"北京市通州区" ,"remark" :"低层员工" ,"age" :31 ,"salary" :2800 ,"birthDate" :"1988-07-20" ,"createTime" :"2019-06-13T10:00:00.000Z" } {"index" :{"_index" :"mydlq-user" ,"_type" :"doc" }} {"name" :"苗十六" ,"address" :"北京市门头沟区" ,"remark" :"高层员工" ,"age" :32 ,"salary" :11500 ,"birthDate" :"1987-06-02" ,"createTime" :"2019-11-11T18:00:00.000Z" } {"index" :{"_index" :"mydlq-user" ,"_type" :"doc" }} {"name" :"鲁十七" ,"address" :"北京市石景山区" ,"remark" :"高员工" ,"age" :33 ,"salary" :9500 ,"birthDate" :"1986-04-15" ,"createTime" :"2019-06-06T14:00:00.000Z" } {"index" :{"_index" :"mydlq-user" ,"_type" :"doc" }} {"name" :"沈十八" ,"address" :"北京市朝阳区" ,"remark" :"中层员工" ,"age" :31 ,"salary" :8300 ,"birthDate" :"1988-09-26" ,"createTime" :"2019-09-25T14:00:00.000Z" } {"index" :{"_index" :"mydlq-user" ,"_type" :"doc" }} {"name" :"吕十九" ,"address" :"北京市西城区" ,"remark" :"低层员工" ,"age" :31 ,"salary" :4500 ,"birthDate" :"1988-11-25" ,"createTime" :"2019-09-22T13:34:00.000Z" } {"index" :{"_index" :"mydlq-user" ,"_type" :"doc" }} {"name" :"丁二十" ,"address" :"北京市东城区" ,"remark" :"低层员工" ,"age" :33 ,"salary" :2100 ,"birthDate" :"1986-12-25" ,"createTime" :"2019-03-07T12:08:00.000Z" }
3. 查询数据 插入完成后再查询数据,查看之前插入的数据是否存在:
执行后得到下面记录:
{ "took" : 2 , "timed_out" : false , "_shards" : { "total" : 1 , "successful" : 1 , "skipped" : 0 , "failed" : 0 }, "hits" : { "total" : 20 , "max_score" : 1 , "hits" : [ { "_index" : "mydlq-user" , "_type" : "_doc" , "_id" : "BeN0BW8B7BNodGwRFTRj" , "_score" : 1 , "_source" : { "name" : "刘一" , "address" : "北京市丰台区" , "remark" : "低层员工" , "age" : 30 , "salary" : 3000 , "birthDate" : "1989-11-11" , "createTime" : "2019-03-15T08:18:00.000Z" } }, { "_index" : "mydlq-user" , "_type" : "_doc" , "_id" : "BuN0BW8B7BNodGwRFTRj" , "_score" : 1 , "_source" : { "name" : "陈二" , "address" : "北京市昌平区" , "remark" : "中层员工" , "age" : 27 , "salary" : 7900 , "birthDate" : "1992-01-25" , "createTime" : "2019-11-08T11:15:00.000Z" } }, { "_index" : "mydlq-user" , "_type" : "_doc" , "_id" : "B-N0BW8B7BNodGwRFTRj" , "_score" : 1 , "_source" : { "name" : "张三" , "address" : "北京市房山区" , "remark" : "中层员工" , "age" : 28 , "salary" : 8800 , "birthDate" : "1991-10-05" , "createTime" : "2019-07-22T13:22:00.000Z" } }, { "_index" : "mydlq-user" , "_type" : "_doc" , "_id" : "CON0BW8B7BNodGwRFTRj" , "_score" : 1 , "_source" : { "name" : "李四" , "address" : "北京市大兴区" , "remark" : "高层员工" , "age" : 26 , "salary" : 9000 , "birthDate" : "1993-08-18" , "createTime" : "2019-10-17T15:00:00.000Z" } }, { "_index" : "mydlq-user" , "_type" : "_doc" , "_id" : "CeN0BW8B7BNodGwRFTRj" , "_score" : 1 , "_source" : { "name" : "王五" , "address" : "北京市密云区" , "remark" : "低层员工" , "age" : 31 , "salary" : 4800 , "birthDate" : "1988-07-20" , "createTime" : "2019-05-29T09:00:00.000Z" } }, { "_index" : "mydlq-user" , "_type" : "_doc" , "_id" : "CuN0BW8B7BNodGwRFTRj" , "_score" : 1 , "_source" : { "name" : "赵六" , "address" : "北京市通州区" , "remark" : "中层员工" , "age" : 32 , "salary" : 6500 , "birthDate" : "1987-06-02" , "createTime" : "2019-12-10T18:00:00.000Z" } }, { "_index" : "mydlq-user" , "_type" : "_doc" , "_id" : "C-N0BW8B7BNodGwRFTRj" , "_score" : 1 , "_source" : { "name" : "孙七" , "address" : "北京市朝阳区" , "remark" : "中层员工" , "age" : 33 , "salary" : 7000 , "birthDate" : "1986-04-15" , "createTime" : "2019-06-06T13:00:00.000Z" } }, { "_index" : "mydlq-user" , "_type" : "_doc" , "_id" : "DON0BW8B7BNodGwRFTRj" , "_score" : 1 , "_source" : { "name" : "周八" , "address" : "北京市西城区" , "remark" : "低层员工" , "age" : 32 , "salary" : 5000 , "birthDate" : "1987-09-26" , "createTime" : "2019-01-26T14:00:00.000Z" } }, { "_index" : "mydlq-user" , "_type" : "_doc" , "_id" : "DeN0BW8B7BNodGwRFTRj" , "_score" : 1 , "_source" : { "name" : "吴九" , "address" : "北京市海淀区" , "remark" : "高层员工" , "age" : 30 , "salary" : 11000 , "birthDate" : "1989-11-25" , "createTime" : "2019-09-07T13:34:00.000Z" } }, { "_index" : "mydlq-user" , "_type" : "_doc" , "_id" : "DuN0BW8B7BNodGwRFTRj" , "_score" : 1 , "_source" : { "name" : "郑十" , "address" : "北京市东城区" , "remark" : "低层员工" , "age" : 29 , "salary" : 5000 , "birthDate" : "1990-12-25" , "createTime" : "2019-03-06T12:08:00.000Z" } } ] } }
3.6-查询操作示例 1. 精确查询(term) (1)Restful 操作示例 精确查询
精确查询,查询地址为 北京市通州区 的人员信息:
查询条件不会进行分词,但是查询内容可能会分词,导致查询不到。之前在创建索引时设置 Mapping 中 address 字段存在 keyword 字段是专门用于不分词查询的子字段。
GET mydlq-user/_search { "query" : { "term" : { "address.keyword" : { "value" : "北京市通州区" } } } }
精确查询-多内容查询
精确查询,查询地址为 北京市丰台区、北京市昌平区 或 北京市大兴区 的人员信息:
GET mydlq-user/_search { "query" : { "terms" : { "address.keyword" : [ "北京市丰台区" , "北京市昌平区" , "北京市大兴区" ] } } }
(2)Java 代码示例 import club.mydlq.elasticsearch.model.entity.UserInfo;import com.alibaba.fastjson.JSON;import lombok.extern.slf4j.Slf4j;import org.elasticsearch.action.search.SearchRequest;import org.elasticsearch.action.search.SearchResponse;import org.elasticsearch.client.RequestOptions;import org.elasticsearch.client.RestHighLevelClient;import org.elasticsearch.index.query.QueryBuilders;import org.elasticsearch.rest.RestStatus;import org.elasticsearch.search.SearchHit;import org.elasticsearch.search.SearchHits;import org.elasticsearch.search.builder.SearchSourceBuilder;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.io.IOException;@Slf4j @Service public class TermQueryService { @Autowired private RestHighLevelClient restHighLevelClient; public void termQuery () { try { SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(QueryBuilders.termQuery("address.keyword" , "北京市通州区" )); SearchRequest searchRequest = new SearchRequest("mydlq-user" ); searchRequest.source(searchSourceBuilder); SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().totalHits > 0 ) { SearchHits hits = searchResponse.getHits(); for (SearchHit hit : hits) { UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class); log.info(userInfo.toString()); } } } catch (IOException e) { log.error("" , e); } } public void termsQuery () { try { SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(QueryBuilders.termsQuery("address.keyword" , "北京市丰台区" , "北京市昌平区" , "北京市大兴区" )); SearchRequest searchRequest = new SearchRequest("mydlq-user" ); searchRequest.source(searchSourceBuilder); SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().totalHits > 0 ) { SearchHits hits = searchResponse.getHits(); for (SearchHit hit : hits) { UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class); log.info(userInfo.toString()); } } } catch (IOException e) { log.error("" , e); } } }
2. 匹配查询(match) (1)Restful 操作示例 匹配查询全部数据与分页
匹配查询符合条件的所有数据,并且设置以 salary 字段升序排序,并设置分页:
GET mydlq-user/_search { "query" : { "match_all" : {} }, "from" : 0 , "size" : 10 , "sort" : [ { "salary" : { "order" : "asc" } } ] }
匹配查询数据
匹配查询地址为 通州区 的数据:
GET mydlq-user/_search { "query" : { "match" : { "address" : "通州区" } } }
词语匹配查询
词语匹配进行查询,匹配 address 中为 北京市通州区 的员工信息:
GET mydlq-user/_search { "query" : { "match_phrase" : { "address" : "北京市通州区" } } }
内容多字段查询
查询在字段 address、remark 中存在 北京 内容的员工信息:
GET mydlq-user/_search { "query" : { "multi_match" : { "query" : "北京" , "fields" : ["address" ,"remark" ] } } }
(2)Java 代码示例 import club.mydlq.elasticsearch.model.entity.UserInfo;import com.alibaba.fastjson.JSON;import lombok.extern.slf4j.Slf4j;import org.elasticsearch.action.search.SearchRequest;import org.elasticsearch.action.search.SearchResponse;import org.elasticsearch.client.RequestOptions;import org.elasticsearch.client.RestHighLevelClient;import org.elasticsearch.index.query.MatchAllQueryBuilder;import org.elasticsearch.index.query.QueryBuilders;import org.elasticsearch.rest.RestStatus;import org.elasticsearch.search.SearchHit;import org.elasticsearch.search.SearchHits;import org.elasticsearch.search.builder.SearchSourceBuilder;import org.elasticsearch.search.sort.SortOrder;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.io.IOException;@Slf4j @Service public class MatchQueryService { @Autowired private RestHighLevelClient restHighLevelClient; public Object matchAllQuery () { try { MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery(); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(matchAllQueryBuilder); searchSourceBuilder.from(0 ); searchSourceBuilder.size(3 ); searchSourceBuilder.sort("salary" , SortOrder.ASC); SearchRequest searchRequest = new SearchRequest("mydlq-user" ); searchRequest.source(searchSourceBuilder); SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().totalHits > 0 ) { SearchHits hits = searchResponse.getHits(); for (SearchHit hit : hits) { UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class); log.info(userInfo.toString()); } } } catch (IOException e) { log.error("" , e); } } public Object matchQuery () { try { SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(QueryBuilders.matchQuery("address" , "*通州区" )); SearchRequest searchRequest = new SearchRequest("mydlq-user" ); searchRequest.source(searchSourceBuilder); SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().totalHits > 0 ) { SearchHits hits = searchResponse.getHits(); for (SearchHit hit : hits) { UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class); log.info(userInfo.toString()); } } } catch (IOException e) { log.error("" , e); } } public Object matchPhraseQuery () { try { SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(QueryBuilders.matchPhraseQuery("address" , "北京市通州区" )); SearchRequest searchRequest = new SearchRequest("mydlq-user" ); searchRequest.source(searchSourceBuilder); SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().totalHits > 0 ) { SearchHits hits = searchResponse.getHits(); for (SearchHit hit : hits) { UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class); log.info(userInfo.toString()); } } } catch (IOException e) { log.error("" , e); } } public Object matchMultiQuery () { try { SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(QueryBuilders.multiMatchQuery("北京市" , "address" , "remark" )); SearchRequest searchRequest = new SearchRequest("mydlq-user" ); searchRequest.source(searchSourceBuilder); SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().totalHits > 0 ) { SearchHits hits = searchResponse.getHits(); for (SearchHit hit : hits) { UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class); log.info(userInfo.toString()); } } } catch (IOException e) { log.error("" , e); } } }
3. 模糊查询(fuzzy) (1)Restful 操作示例 模糊查询所有以 三 结尾的姓名
GET mydlq-user/_search { "query" : { "fuzzy" : { "name" : "三" } } }
(2)Java 代码示例 import club.mydlq.elasticsearch.model.entity.UserInfo;import com.alibaba.fastjson.JSON;import lombok.extern.slf4j.Slf4j;import org.elasticsearch.action.search.SearchRequest;import org.elasticsearch.action.search.SearchResponse;import org.elasticsearch.client.RequestOptions;import org.elasticsearch.client.RestHighLevelClient;import org.elasticsearch.common.unit.Fuzziness;import org.elasticsearch.index.query.QueryBuilders;import org.elasticsearch.rest.RestStatus;import org.elasticsearch.search.SearchHit;import org.elasticsearch.search.SearchHits;import org.elasticsearch.search.builder.SearchSourceBuilder;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.io.IOException;@Slf4j @Service public class FuzzyQueryService { @Autowired private RestHighLevelClient restHighLevelClient; public Object fuzzyQuery () { try { SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(QueryBuilders.fuzzyQuery("name" , "三" ).fuzziness(Fuzziness.AUTO)); SearchRequest searchRequest = new SearchRequest("mydlq-user" ); searchRequest.source(searchSourceBuilder); SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().totalHits > 0 ) { SearchHits hits = searchResponse.getHits(); for (SearchHit hit : hits) { UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class); log.info(userInfo.toString()); } } } catch (IOException e) { log.error("" , e); } } }
4. 范围查询(range) (1)Restful 操作示例 查询岁数 ≥ 30 岁的员工数据:
GET /mydlq-user/_search { "query" : { "range" : { "age" : { "gte" : 30 } } } }
查询生日距离现在 30 年间的员工数据:
GET mydlq-user/_search { "query" : { "range" : { "birthDate" : { "gte" : "now-30y" } } } }
(2)Java 代码示例 import club.mydlq.elasticsearch.model.entity.UserInfo;import com.alibaba.fastjson.JSON;import lombok.extern.slf4j.Slf4j;import org.elasticsearch.action.search.SearchRequest;import org.elasticsearch.action.search.SearchResponse;import org.elasticsearch.client.RequestOptions;import org.elasticsearch.client.RestHighLevelClient;import org.elasticsearch.index.query.QueryBuilders;import org.elasticsearch.rest.RestStatus;import org.elasticsearch.search.SearchHit;import org.elasticsearch.search.SearchHits;import org.elasticsearch.search.builder.SearchSourceBuilder;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.io.IOException;@Slf4j @Service public class RangeQueryService { @Autowired private RestHighLevelClient restHighLevelClient; public void rangeQuery () { try { SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(QueryBuilders.rangeQuery("age" ).gte(30 )); SearchRequest searchRequest = new SearchRequest("mydlq-user" ); searchRequest.source(searchSourceBuilder); SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().totalHits > 0 ) { SearchHits hits = searchResponse.getHits(); for (SearchHit hit : hits) { UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class); log.info(userInfo.toString()); } } } catch (IOException e) { log.error("" , e); } } public void dateRangeQuery () { try { SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(QueryBuilders.rangeQuery("birthDate" ) .gte("now-30y" ).includeLower(true ).includeUpper(true )); SearchRequest searchRequest = new SearchRequest("mydlq-user" ); searchRequest.source(searchSourceBuilder); SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().totalHits > 0 ) { SearchHits hits = searchResponse.getHits(); for (SearchHit hit : hits) { UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class); log.info(userInfo.toString()); } } } catch (IOException e) { log.error("" , e); } } }
5. 通配符查询(wildcard) (1)Restful 操作示例 查询所有以 “三” 结尾的姓名:
GET mydlq-user/_search { "query" : { "wildcard" : { "name.keyword" : { "value" : "*三" } } } }
(2)Java 代码示例 import club.mydlq.elasticsearch.model.entity.UserInfo;import com.alibaba.fastjson.JSON;import lombok.extern.slf4j.Slf4j;import org.elasticsearch.action.search.SearchRequest;import org.elasticsearch.action.search.SearchResponse;import org.elasticsearch.client.RequestOptions;import org.elasticsearch.client.RestHighLevelClient;import org.elasticsearch.index.query.QueryBuilders;import org.elasticsearch.rest.RestStatus;import org.elasticsearch.search.SearchHit;import org.elasticsearch.search.SearchHits;import org.elasticsearch.search.builder.SearchSourceBuilder;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.io.IOException;@Slf4j @Service public class WildcardQueryService { @Autowired private RestHighLevelClient restHighLevelClient; public Object wildcardQuery () { try { SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(QueryBuilders.wildcardQuery("name.keyword" , "*三" )); SearchRequest searchRequest = new SearchRequest("mydlq-user" ); searchRequest.source(searchSourceBuilder); SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().totalHits > 0 ) { SearchHits hits = searchResponse.getHits(); for (SearchHit hit : hits) { UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class); log.info(userInfo.toString()); } } } catch (IOException e) { log.error("" , e); } } }
6. 布尔查询(bool) (1)Restful 操作示例 查询出生在 1990-1995 年期间,且地址在 北京市昌平区、北京市大兴区、北京市房山区 的员工信息:
GET /mydlq-user/_search { "query" : { "bool" : { "filter" : { "range" : { "birthDate" : { "format" : "yyyy" , "gte" : 1990 , "lte" : 1995 } } }, "must" : [ { "terms" : { "address.keyword" : [ "北京市昌平区" , "北京市大兴区" , "北京市房山区" ] } } ] } } }
(2)Java 代码示例 import club.mydlq.elasticsearch.model.entity.UserInfo;import com.alibaba.fastjson.JSON;import lombok.extern.slf4j.Slf4j;import org.elasticsearch.action.search.SearchRequest;import org.elasticsearch.action.search.SearchResponse;import org.elasticsearch.client.RequestOptions;import org.elasticsearch.client.RestHighLevelClient;import org.elasticsearch.index.query.BoolQueryBuilder;import org.elasticsearch.index.query.QueryBuilders;import org.elasticsearch.rest.RestStatus;import org.elasticsearch.search.SearchHit;import org.elasticsearch.search.SearchHits;import org.elasticsearch.search.builder.SearchSourceBuilder;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.io.IOException;@Slf4j @Service public class BoolQueryService { @Autowired private RestHighLevelClient restHighLevelClient; public Object boolQuery () { try { BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); boolQueryBuilder.must(QueryBuilders.termsQuery("address.keyword" , "北京市昌平区" , "北京市大兴区" , "北京市房山区" )) .filter().add(QueryBuilders.rangeQuery("birthDate" ).format("yyyy" ).gte("1990" ).lte("1995" )); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(boolQueryBuilder); SearchRequest searchRequest = new SearchRequest("mydlq-user" ); searchRequest.source(searchSourceBuilder); SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().totalHits > 0 ) { SearchHits hits = searchResponse.getHits(); for (SearchHit hit : hits) { UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class); log.info(userInfo.toString()); } } }catch (IOException e){ log.error("" ,e); } } }
3.7-聚合查询操作示例 1. Metric 聚合分析 (1)Restful 操作示例 统计员工总数、工资最高值、工资最低值、工资平均工资、工资总和:
GET /mydlq-user/_search { "size" : 0 , "aggs" : { "salary_stats" : { "stats" : { "field" : "salary" } } } }
统计员工工资最低值:
GET /mydlq-user/_search { "size" : 0 , "aggs" : { "salary_min" : { "min" : { "field" : "salary" } } } }
统计员工工资最高值:
GET /mydlq-user/_search { "size" : 0 , "aggs" : { "salary_max" : { "max" : { "field" : "salary" } } } }
统计员工工资平均值:
GET /mydlq-user/_search { "size" : 0 , "aggs" : { "salary_avg" : { "avg" : { "field" : "salary" } } } }
统计员工工资总值:
GET /mydlq-user/_search { "size" : 0 , "aggs" : { "salary_sum" : { "sum" : { "field" : "salary" } } } }
统计员工总数:
GET /mydlq-user/_search { "size" : 0 , "aggs" : { "employee_count" : { "value_count" : { "field" : "salary" } } } }
统计员工工资百分位:
GET /mydlq-user/_search { "size" : 0 , "aggs" : { "salary_percentiles" : { "percentiles" : { "field" : "salary" } } } }
(2)Java 代码示例 import lombok.extern.slf4j.Slf4j;import org.elasticsearch.action.search.SearchRequest;import org.elasticsearch.action.search.SearchResponse;import org.elasticsearch.client.RequestOptions;import org.elasticsearch.client.RestHighLevelClient;import org.elasticsearch.rest.RestStatus;import org.elasticsearch.search.aggregations.AggregationBuilder;import org.elasticsearch.search.aggregations.AggregationBuilders;import org.elasticsearch.search.aggregations.Aggregations;import org.elasticsearch.search.aggregations.metrics.avg.ParsedAvg;import org.elasticsearch.search.aggregations.metrics.max.ParsedMax;import org.elasticsearch.search.aggregations.metrics.min.ParsedMin;import org.elasticsearch.search.aggregations.metrics.percentiles.ParsedPercentiles;import org.elasticsearch.search.aggregations.metrics.percentiles.Percentile;import org.elasticsearch.search.aggregations.metrics.stats.ParsedStats;import org.elasticsearch.search.aggregations.metrics.sum.ParsedSum;import org.elasticsearch.search.aggregations.metrics.sum.SumAggregationBuilder;import org.elasticsearch.search.aggregations.metrics.valuecount.ParsedValueCount;import org.elasticsearch.search.builder.SearchSourceBuilder;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.io.IOException;@Slf4j @Service public class AggrMetricService { @Autowired private RestHighLevelClient restHighLevelClient; public Object aggregationStats () { String responseResult = "" ; try { AggregationBuilder aggr = AggregationBuilders.stats("salary_stats" ).field("salary" ); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.aggregation(aggr); searchSourceBuilder.size(0 ); SearchRequest request = new SearchRequest("mydlq-user" ); request.source(searchSourceBuilder); SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT); Aggregations aggregations = response.getAggregations(); if (RestStatus.OK.equals(response.status()) || aggregations != null ) { ParsedStats aggregation = aggregations.get("salary_stats" ); log.info("-------------------------------------------" ); log.info("聚合信息:" ); log.info("count:{}" , aggregation.getCount()); log.info("avg:{}" , aggregation.getAvg()); log.info("max:{}" , aggregation.getMax()); log.info("min:{}" , aggregation.getMin()); log.info("sum:{}" , aggregation.getSum()); log.info("-------------------------------------------" ); } responseResult = response.toString(); } catch (IOException e) { log.error("" , e); } return responseResult; } public Object aggregationMin () { String responseResult = "" ; try { AggregationBuilder aggr = AggregationBuilders.min("salary_min" ).field("salary" ); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.aggregation(aggr); searchSourceBuilder.size(0 ); SearchRequest request = new SearchRequest("mydlq-user" ); request.source(searchSourceBuilder); SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT); Aggregations aggregations = response.getAggregations(); if (RestStatus.OK.equals(response.status()) || aggregations != null ) { ParsedMin aggregation = aggregations.get("salary_min" ); log.info("-------------------------------------------" ); log.info("聚合信息:" ); log.info("min:{}" , aggregation.getValue()); log.info("-------------------------------------------" ); } responseResult = response.toString(); } catch (IOException e) { log.error("" , e); } return responseResult; } public Object aggregationMax () { String responseResult = "" ; try { AggregationBuilder aggr = AggregationBuilders.max("salary_max" ).field("salary" ); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.aggregation(aggr); searchSourceBuilder.size(0 ); SearchRequest request = new SearchRequest("mydlq-user" ); request.source(searchSourceBuilder); SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT); Aggregations aggregations = response.getAggregations(); if (RestStatus.OK.equals(response.status()) || aggregations != null ) { ParsedMax aggregation = aggregations.get("salary_max" ); log.info("-------------------------------------------" ); log.info("聚合信息:" ); log.info("max:{}" , aggregation.getValue()); log.info("-------------------------------------------" ); } responseResult = response.toString(); } catch (IOException e) { log.error("" , e); } return responseResult; } public Object aggregationAvg () { String responseResult = "" ; try { AggregationBuilder aggr = AggregationBuilders.avg("salary_avg" ).field("salary" ); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.aggregation(aggr); searchSourceBuilder.size(0 ); SearchRequest request = new SearchRequest("mydlq-user" ); request.source(searchSourceBuilder); SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT); Aggregations aggregations = response.getAggregations(); if (RestStatus.OK.equals(response.status()) || aggregations != null ) { ParsedAvg aggregation = aggregations.get("salary_avg" ); log.info("-------------------------------------------" ); log.info("聚合信息:" ); log.info("avg:{}" , aggregation.getValue()); log.info("-------------------------------------------" ); } responseResult = response.toString(); } catch (IOException e) { log.error("" , e); } return responseResult; } public Object aggregationSum () { String responseResult = "" ; try { SumAggregationBuilder aggr = AggregationBuilders.sum("salary_sum" ).field("salary" ); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.aggregation(aggr); searchSourceBuilder.size(0 ); SearchRequest request = new SearchRequest("mydlq-user" ); request.source(searchSourceBuilder); SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT); Aggregations aggregations = response.getAggregations(); if (RestStatus.OK.equals(response.status()) || aggregations != null ) { ParsedSum aggregation = aggregations.get("salary_sum" ); log.info("-------------------------------------------" ); log.info("聚合信息:" ); log.info("sum:{}" , String.valueOf((aggregation.getValue()))); log.info("-------------------------------------------" ); } responseResult = response.toString(); } catch (IOException e) { log.error("" , e); } return responseResult; } public Object aggregationCount () { String responseResult = "" ; try { AggregationBuilder aggr = AggregationBuilders.count("employee_count" ).field("salary" ); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.aggregation(aggr); searchSourceBuilder.size(0 ); SearchRequest request = new SearchRequest("mydlq-user" ); request.source(searchSourceBuilder); SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT); Aggregations aggregations = response.getAggregations(); if (RestStatus.OK.equals(response.status()) || aggregations != null ) { ParsedValueCount aggregation = aggregations.get("employee_count" ); log.info("-------------------------------------------" ); log.info("聚合信息:" ); log.info("count:{}" , aggregation.getValue()); log.info("-------------------------------------------" ); } responseResult = response.toString(); } catch (IOException e) { log.error("" , e); } return responseResult; } public Object aggregationPercentiles () { String responseResult = "" ; try { AggregationBuilder aggr = AggregationBuilders.percentiles("salary_percentiles" ).field("salary" ); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.aggregation(aggr); searchSourceBuilder.size(0 ); SearchRequest request = new SearchRequest("mydlq-user" ); request.source(searchSourceBuilder); SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT); Aggregations aggregations = response.getAggregations(); if (RestStatus.OK.equals(response.status()) || aggregations != null ) { ParsedPercentiles aggregation = aggregations.get("salary_percentiles" ); log.info("-------------------------------------------" ); log.info("聚合信息:" ); for (Percentile percentile : aggregation) { log.info("百分位:{}:{}" , percentile.getPercent(), percentile.getValue()); } log.info("-------------------------------------------" ); } responseResult = response.toString(); } catch (IOException e) { log.error("" , e); } return responseResult; } }
2. Bucket 聚合分析 (1)Restful 操作示例 按岁数进行聚合分桶,统计各个岁数员工的人数:
GET mydlq-user/_search { "size" : 0 , "aggs" : { "age_bucket" : { "terms" : { "field" : "age" , "size" : "10" } } } }
按工资范围进行聚合分桶,统计工资在 3000-5000、5000-9000 和 9000 以上的员工信息:
GET mydlq-user/_search { "aggs" : { "salary_range_bucket" : { "range" : { "field" : "salary" , "ranges" : [ { "key" : "低级员工" , "to" : 3000 },{ "key" : "中级员工" , "from" : 5000 , "to" : 9000 },{ "key" : "高级员工" , "from" : 9000 } ] } } } }
按照时间范围进行分桶,统计 1985-1990 年和 1990-1995 年出生的员工信息:
GET mydlq-user/_search { "size" : 10 , "aggs" : { "date_range_bucket" : { "date_range" : { "field" : "birthDate" , "format" : "yyyy" , "ranges" : [ { "key" : "出生日期1985-1990的员工" , "from" : "1985" , "to" : "1990" },{ "key" : "出生日期1990-1995的员工" , "from" : "1990" , "to" : "1995" } ] } } } }
按工资多少进行聚合分桶,设置统计的最小值为 0,最大值为 12000,区段间隔为 3000:
GET mydlq-user/_search { "size" : 0 , "aggs" : { "salary_histogram" : { "histogram" : { "field" : "salary" , "extended_bounds" : { "min" : 0 , "max" : 12000 }, "interval" : 3000 } } } }
按出生日期进行分桶:
GET mydlq-user/_search { "size" : 0 , "aggs" : { "birthday_histogram" : { "date_histogram" : { "format" : "yyyy" , "field" : "birthDate" , "interval" : "year" } } } }
(2)Java 代码示例 import lombok.extern.slf4j.Slf4j;import org.elasticsearch.action.search.SearchRequest;import org.elasticsearch.action.search.SearchResponse;import org.elasticsearch.client.RequestOptions;import org.elasticsearch.client.RestHighLevelClient;import org.elasticsearch.rest.RestStatus;import org.elasticsearch.search.aggregations.AggregationBuilder;import org.elasticsearch.search.aggregations.AggregationBuilders;import org.elasticsearch.search.aggregations.Aggregations;import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval;import org.elasticsearch.search.aggregations.bucket.histogram.Histogram;import org.elasticsearch.search.aggregations.bucket.range.Range;import org.elasticsearch.search.aggregations.bucket.terms.Terms;import org.elasticsearch.search.builder.SearchSourceBuilder;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.io.IOException;import java.util.List;@Slf4j @Service public class AggrBucketService { @Autowired private RestHighLevelClient restHighLevelClient; public Object aggrBucketTerms () { try { AggregationBuilder aggr = AggregationBuilders.terms("age_bucket" ).field("age" ); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.size(10 ); searchSourceBuilder.aggregation(aggr); SearchRequest request = new SearchRequest("mydlq-user" ); request.source(searchSourceBuilder); SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT); Aggregations aggregations = response.getAggregations(); if (RestStatus.OK.equals(response.status())) { Terms byCompanyAggregation = aggregations.get("age_bucket" ); List<? extends Terms.Bucket> buckets = byCompanyAggregation.getBuckets(); log.info("-------------------------------------------" ); log.info("聚合信息:" ); for (Terms.Bucket bucket : buckets) { log.info("桶名:{} | 总数:{}" , bucket.getKeyAsString(), bucket.getDocCount()); } log.info("-------------------------------------------" ); } } catch (IOException e) { log.error("" , e); } } public Object aggrBucketRange () { try { AggregationBuilder aggr = AggregationBuilders.range("salary_range_bucket" ) .field("salary" ) .addUnboundedTo("低级员工" , 3000 ) .addRange("中级员工" , 5000 , 9000 ) .addUnboundedFrom("高级员工" , 9000 ); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.size(0 ); searchSourceBuilder.aggregation(aggr); SearchRequest request = new SearchRequest("mydlq-user" ); request.source(searchSourceBuilder); SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT); Aggregations aggregations = response.getAggregations(); if (RestStatus.OK.equals(response.status())) { Range byCompanyAggregation = aggregations.get("salary_range_bucket" ); List<? extends Range.Bucket> buckets = byCompanyAggregation.getBuckets(); log.info("-------------------------------------------" ); log.info("聚合信息:" ); for (Range.Bucket bucket : buckets) { log.info("桶名:{} | 总数:{}" , bucket.getKeyAsString(), bucket.getDocCount()); } log.info("-------------------------------------------" ); } } catch (IOException e) { log.error("" , e); } } public Object aggrBucketDateRange () { try { AggregationBuilder aggr = AggregationBuilders.dateRange("date_range_bucket" ) .field("birthDate" ) .format("yyyy" ) .addRange("1985-1990" , "1985" , "1990" ) .addRange("1990-1995" , "1990" , "1995" ); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.size(0 ); searchSourceBuilder.aggregation(aggr); SearchRequest request = new SearchRequest("mydlq-user" ); request.source(searchSourceBuilder); SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT); Aggregations aggregations = response.getAggregations(); if (RestStatus.OK.equals(response.status())) { Range byCompanyAggregation = aggregations.get("date_range_bucket" ); List<? extends Range.Bucket> buckets = byCompanyAggregation.getBuckets(); log.info("-------------------------------------------" ); log.info("聚合信息:" ); for (Range.Bucket bucket : buckets) { log.info("桶名:{} | 总数:{}" , bucket.getKeyAsString(), bucket.getDocCount()); } log.info("-------------------------------------------" ); } } catch (IOException e) { log.error("" , e); } } public Object aggrBucketHistogram () { try { AggregationBuilder aggr = AggregationBuilders.histogram("salary_histogram" ) .field("salary" ) .extendedBounds(0 , 12000 ) .interval(3000 ); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.size(0 ); searchSourceBuilder.aggregation(aggr); SearchRequest request = new SearchRequest("mydlq-user" ); request.source(searchSourceBuilder); SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT); Aggregations aggregations = response.getAggregations(); if (RestStatus.OK.equals(response.status())) { Histogram byCompanyAggregation = aggregations.get("salary_histogram" ); List<? extends Histogram.Bucket> buckets = byCompanyAggregation.getBuckets(); log.info("-------------------------------------------" ); log.info("聚合信息:" ); for (Histogram.Bucket bucket : buckets) { log.info("桶名:{} | 总数:{}" , bucket.getKeyAsString(), bucket.getDocCount()); } log.info("-------------------------------------------" ); } } catch (IOException e) { log.error("" , e); } } public Object aggrBucketDateHistogram () { try { AggregationBuilder aggr = AggregationBuilders.dateHistogram("birthday_histogram" ) .field("birthDate" ) .interval(1 ) .dateHistogramInterval(DateHistogramInterval.YEAR) .format("yyyy" ); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.size(0 ); searchSourceBuilder.aggregation(aggr); SearchRequest request = new SearchRequest("mydlq-user" ); request.source(searchSourceBuilder); SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT); Aggregations aggregations = response.getAggregations(); if (RestStatus.OK.equals(response.status())) { Histogram byCompanyAggregation = aggregations.get("birthday_histogram" ); List<? extends Histogram.Bucket> buckets = byCompanyAggregation.getBuckets(); log.info("-------------------------------------------" ); log.info("聚合信息:" ); for (Histogram.Bucket bucket : buckets) { log.info("桶名:{} | 总数:{}" , bucket.getKeyAsString(), bucket.getDocCount()); } log.info("-------------------------------------------" ); } } catch (IOException e) { log.error("" , e); } } }
3. Metric 与 Bucket 聚合分析 (1)Restful 操作示例 按照员工岁数分桶、然后统计每个岁数员工工资最高值:
GET mydlq-user/_search { "size" : 0 , "aggs" : { "salary_bucket" : { "terms" : { "field" : "age" , "size" : "10" }, "aggs" : { "salary_max_user" : { "top_hits" : { "size" : 1 , "sort" : [ { "salary" : { "order" : "desc" } } ] } } } } } }
(2)Java 代码示例 import lombok.extern.slf4j.Slf4j;import org.elasticsearch.action.search.SearchRequest;import org.elasticsearch.action.search.SearchResponse;import org.elasticsearch.client.RequestOptions;import org.elasticsearch.client.RestHighLevelClient;import org.elasticsearch.rest.RestStatus;import org.elasticsearch.search.SearchHit;import org.elasticsearch.search.aggregations.AggregationBuilder;import org.elasticsearch.search.aggregations.AggregationBuilders;import org.elasticsearch.search.aggregations.Aggregations;import org.elasticsearch.search.aggregations.bucket.terms.Terms;import org.elasticsearch.search.aggregations.metrics.tophits.ParsedTopHits;import org.elasticsearch.search.builder.SearchSourceBuilder;import org.elasticsearch.search.sort.SortOrder;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.io.IOException;import java.util.List;@Slf4j @Service public class AggrBucketMetricService { @Autowired private RestHighLevelClient restHighLevelClient; public Object aggregationTopHits () { try { AggregationBuilder testTop = AggregationBuilders.topHits("salary_max_user" ) .size(1 ) .sort("salary" , SortOrder.DESC); AggregationBuilder salaryBucket = AggregationBuilders.terms("salary_bucket" ) .field("age" ) .size(10 ); salaryBucket.subAggregation(testTop); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.size(0 ); searchSourceBuilder.aggregation(salaryBucket); SearchRequest request = new SearchRequest("mydlq-user" ); request.source(searchSourceBuilder); SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT); Aggregations aggregations = response.getAggregations(); if (RestStatus.OK.equals(response.status())) { Terms byCompanyAggregation = aggregations.get("salary_bucket" ); List<? extends Terms.Bucket> buckets = byCompanyAggregation.getBuckets(); log.info("-------------------------------------------" ); log.info("聚合信息:" ); for (Terms.Bucket bucket : buckets) { log.info("桶名:{}" , bucket.getKeyAsString()); ParsedTopHits topHits = bucket.getAggregations().get("salary_max_user" ); for (SearchHit hit:topHits.getHits()){ log.info(hit.getSourceAsString()); } } log.info("-------------------------------------------" ); } } catch (IOException e) { log.error("" , e); } } }
参考:
——https://segmentfault.com/a/1190000020205776
——https://www.jianshu.com/p/60b242cbd8b4
——https://segmentfault.com/a/1190000020022504
——https://mp.weixin.qq.com/s/Lz5Gq1Lwkn8GgMUozqUVjQ