Elasticsearch 6.3官方教程翻译系列(1):入门
最近在看这个Elasticsearch 6.3最新版本,苦于没有最新中文资料,遂自己翻译记录备用,拿出来分享,翻译得一般般,莫怪咯!
1. 入门
Elasticsearch是一个高度可扩展的开源全文搜索和分析引擎。它允许您快速,近实时地存储,搜索和分析大量数据。它通常用作支持具有复杂搜索功能和需求的应用程序的底层引擎/技术。
以下是Elasticsearch可用于的几个示例用例:
- 您运营一家在线网上商店,让您的客户可以搜索您销售的产品。在这种情况下,您可以使用Elasticsearch来存储您的整个产品目录和库存,并为其提供搜索和自动填充建议。
- 您希望收集日志或交易数据,并且想要分析和挖掘此数据以查找趋势,统计数据,汇总或异常情况。在这种情况下,您可以使用Logstash(Elasticsearch / Logstash / Kibana堆栈的一部分)来收集,汇总和分析数据,然后使用Logstash将此数据提供给Elasticsearch。一旦数据在Elasticsearch中,您就可以运行搜索和聚合来挖掘您感兴趣的任何信息。
- 你运行一个价格提醒平台,它允许精明的顾客指定一条规则,例如“我有兴趣购买特定的电子产品,并且如果在下个月内,任何供应商的产品价格低于$ X,我都会收到通知”。在这种情况下,您可以缩减供应商价格,将其推入Elasticsearch,并使用其反向搜索(Percolator)功能将价格变动与客户查询进行匹配,并最终在发现匹配时将警报推送给客户。
您需要分析/商业智能需求,并且希望快速调查,分析,可视化并针对大量数据提出临时问题(可以考虑数百万或数十亿条记录)。
在这种情况下,您可以使用Elasticsearch存储数据,然后使用Kibana(Elasticsearch / Logstash / Kibana堆栈的一部分)来构建自定义仪表板,以便可视化数据对您很重要的各个方面。
另外,您可以使用Elasticsearch聚合功能对数据执行复杂的商业智能查询。在本教程的其余部分中,您将通过获取并运行Elasticsearch,浏览它并执行基本操作(如索引,搜索和修改数据)的过程来指导您。
在本教程的最后,您应该对Elasticsearch是什么以及它的工作原理有一个很好的了解,并希望能够启发您,以了解如何使用它来构建复杂的搜索应用程序或从数据中挖掘情报。
1.1 基本概念(Basic Concepts)
有一些概念是Elasticsearch的核心。
从一开始就理解这些概念将极大地帮助简化学习过程。
近实时(Near Realtime)
Elasticsearch是一个接近实时的搜索平台。
这意味着从索引文档的时间到可搜索的时间之间存在轻微的延迟(通常为一秒)。
集群(Cluster)
群集是一个或多个节点(服务器)的集合,它们一起保存整个数据,并提供跨所有节点的联合索引和搜索功能。
集群由默认名称为“elasticsearch”的唯一名称标识。
此名称很重要,因为如果节点设置为通过名称加入群集,则节点只能成为群集的一部分。
确保不要在不同的环境中重复使用相同的群集名称,否则可能会导致节点加入错误的群集。
例如,您可以使用logging-dev,logging-stage和logging-prod开发,分段和生产集群。
请注意,有一个只有一个节点的集群是完全正确的。
此外,您还可能拥有多个独立的群集,每个群集都有自己的唯一群集名称。
节点(Node)
节点是属于集群一部分的单个服务器,存储数据并参与集群的索引和搜索功能。
就像一个集群一样,一个节点由一个名称来标识,默认情况下该名称是在启动时分配给节点的随机通用唯一标识符(UUID)。
如果您不需要默认值,您可以定义任何您想要的节点名称。
此名称对于管理目的非常重要,您需要确定网络中的哪些服务器与Elasticsearch群集中的哪些节点相对应。
可以将节点配置为按集群名称加入特定集群。
默认情况下,每个节点都设置为加入名为elasticsearch的集群,这意味着如果启动网络中的多个节点并假设他们可以发现其他节点,它们将自动形成并加入名为elasticsearch的单个集群。
在单个群集中,您可以拥有任意数量的节点。
此外,如果网络上当前没有其他Elasticsearch节点正在运行,则默认情况下启动单个节点会形成一个名为elasticsearch的新单节点群集。
索引(Index)
索引是一些具有相似特征的文档集合。
例如,您可以拥有客户数据的索引,产品目录的另一个索引以及订单数据的另一个索引。
索引由名称(必须全部为小写)标识,并且此名称用于在对其中的文档执行索引,搜索,更新和删除操作时引用索引。
在单个群集中,您可以根据需要定义多个索引。
类型(type)已经弃用
注意:在6.0版本已经弃用
一种类型曾经是您的索引的逻辑类别/分区,允许您在同一索引中存储不同类型的文档,例如一种类型的用户,另一种类型的博客文章。
不再可能在索引中创建多个类型,并且在更高版本中将删除整个类型的概念。
有关更多信息,请参阅移除映射类型。
文档(Document)
文档是可被索引的基本信息单位。
例如,您可以为单个客户提供文档,为单个产品提供另一个文档,为单个订单提供另一个文档。
本文档以JSON(JavaScript Object Notation)表示,它是一种无处不在的互联网数据交换格式。
在索引/类型中,您可以根据需要存储任意数量的文档。
请注意,尽管文档实际上驻留在索引中,但实际上文档必须被索引/分配给索引内的类型。
分片和副本(Shards & Replicas)
索引可能潜在地存储大量数据,这些数据可能会超出单个节点的硬件限制。
例如,占用1TB磁盘空间的十亿份文档的单个索引可能不适合单个节点的磁盘,或者可能太慢而无法单独向单个节点提供搜索请求。
为了解决这个问题,Elasticsearch提供了将索引细分为多个碎片的能力。
当您创建索引时,您可以简单地定义所需的碎片数量。
每个分片本身都是一个功能齐全且独立的“索引”,可以在集群中的任何节点上进行托管。
分片很重要,主要有两个原因:
- 它允许您水平分割/缩放内容量
- 它允许您跨越分片(可能在多个节点上)分发和并行化操作,从而提高性能/吞吐量
分片如何分布的机制以及其文档如何聚合回搜索请求完全由Elasticsearch管理,并且对作为用户的您透明。
在任何时候都可能出现故障的网络/云环境中,非常有用并且强烈建议拥有故障切换机制,以防碎片/节点以某种方式脱机或因任何原因而消失。为此,Elasticsearch允许您将索引碎片的一个或多个副本制作为简称为副本碎片或副本。
复制很重要,主要有两个原因:
- 它在分片/节点失败的情况下提供高可用性。由于这个原因,需要注意的是,副本分片永远不会分配到与从中复制的原始/主分片相同的节点上。
- 它允许您扩展搜索量/吞吐量,因为搜索可以在所有副本上并行执行。
总而言之,每个索引可以分成多个分片。
索引也可以被复制为零(意味着没有副本)或更多次。一旦复制,每个索引将具有主分片(从中复制的原始分片)和副本分片(主分片的副本)。
在创建索引时,可以为每个索引定义分片和副本的数量。在创建索引之后,您可以随时更改动态副本的数量,但您无法在事后更改碎片的数量。
默认情况下,Elasticsearch中的每个索引都分配了5个主分片和1个副本,这意味着如果群集中至少有两个节点,则索引将包含5个主分片和另外5个副本分片(1个完整副本),总共
每个索引10个碎片。
注意: 每个Elasticsearch分片都是一个Lucene索引。
您可以在单个Lucene索引中拥有最大数量的文档。
截至LUCENE-5843,限制为2,147,483,519(= Integer.MAX_VALUE - 128)文件。
您可以使用_cat / shards API监视分片大小。
随着这一点,让我们开始有趣的部分…
1.2 安装
Elasticsearch至少需要Java 8.在撰写本文时,建议您使用Oracle JDK 1.8.0_131版。
Java安装因平台而异,因此我们不会在这里详细介绍这些细节。
Oracle的推荐安装文档可以在Oracle网站上找到。
只需要说,在安装Elasticsearch之前,请先运行(然后根据需要安装/升级)来检查您的Java版本:
java -version
echo $JAVA_HOME
一旦我们建立了Java,我们就可以下载并运行Elasticsearch。这些二进制文件可以从 www.elastic.co/downloads以及过去发布的所有版本中获得。
对于每个版本,您可以在zip或tar归档文件,DEB或RPM软件包或Windows MSI安装软件包中进行选择。
1.2.1 用tar安装例子
为了简单起见,我们使用tar文件。
我们下载Elasticsearch 6.3.0 tar,如下所示:
curl -L -O https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.3.0.tar.gz
然后解压缩如下
tar -xvf elasticsearch-6.3.0.tar.gz
它会在当前目录中创建一堆文件和文件夹。
然后我们进入bin目录,如下所示:
cd elasticsearch-6.3.0/bin
现在我们准备开始我们的节点和单个集群:
./elasticsearch
1.2.2 用自制软件安装
在macOS上,Elasticsearch也可以通过Homebrew安装:
brew install elasticsearch
1.2.3 MSI Windows安装程序的安装示例
对于Windows用户,我们推荐使用MSI Installer软件包 。该软件包包含一个图形用户界面(GUI),可引导您完成安装过程。
首先,从https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.3.0.msi下载Elasticsearch 6.3.0 MSI。
然后双击下载的文件启动GUI。
在第一个屏幕中,选择部署目录:
然后选择是作为服务安装还是根据需要手动启动Elasticsearch。要与tar示例对齐,请选择不安装为服务:
对于配置,只需保留默认值即可:
再次,为了与tar示例一致,请取消选中所有插件以不安装任何插件:
点击安装按钮后,将安装Elasticsearch:
默认情况下,Elasticsearch将安装在%PROGRAMFILES%\ Elastic \ Elasticsearch。在这里导航并进入bin目录,如下所示:
用命令提示符:
cd %PROGRAMFILES%\Elastic\Elasticsearch\bin
使用PowerShell:
cd $env:PROGRAMFILES\Elastic\Elasticsearch\bin
现在我们准备开始我们的节点和单个集群:
.\elasticsearch.exe
1.2.4 成功运行节点
如果安装过程中一切顺利,您应该看到如下所示的一堆消息:
[2016-09-16T14:17:51,251][INFO ][o.e.n.Node ] [] initializing ...
[2016-09-16T14:17:51,329][INFO ][o.e.e.NodeEnvironment ] [6-bjhwl] using [1] data paths, mounts [[/ (/dev/sda1)]], net usable_space [317.7gb], net total_space [453.6gb], spins? [no], types [ext4]
[2016-09-16T14:17:51,330][INFO ][o.e.e.NodeEnvironment ] [6-bjhwl] heap size [1.9gb], compressed ordinary object pointers [true]
[2016-09-16T14:17:51,333][INFO ][o.e.n.Node ] [6-bjhwl] node name [6-bjhwl] derived from node ID; set [node.name] to override
[2016-09-16T14:17:51,334][INFO ][o.e.n.Node ] [6-bjhwl] version[6.3.0], pid[21261], build[f5daa16/2016-09-16T09:12:24.346Z], OS[Linux/4.4.0-36-generic/amd64], JVM[Oracle Corporation/Java HotSpot(TM) 64-Bit Server VM/1.8.0_60/25.60-b23]
[2016-09-16T14:17:51,967][INFO ][o.e.p.PluginsService ] [6-bjhwl] loaded module [aggs-matrix-stats]
[2016-09-16T14:17:51,967][INFO ][o.e.p.PluginsService ] [6-bjhwl] loaded module [ingest-common]
[2016-09-16T14:17:51,967][INFO ][o.e.p.PluginsService ] [6-bjhwl] loaded module [lang-expression]
[2016-09-16T14:17:51,967][INFO ][o.e.p.PluginsService ] [6-bjhwl] loaded module [lang-mustache]
[2016-09-16T14:17:51,967][INFO ][o.e.p.PluginsService ] [6-bjhwl] loaded module [lang-painless]
[2016-09-16T14:17:51,967][INFO ][o.e.p.PluginsService ] [6-bjhwl] loaded module [percolator]
[2016-09-16T14:17:51,968][INFO ][o.e.p.PluginsService ] [6-bjhwl] loaded module [reindex]
[2016-09-16T14:17:51,968][INFO ][o.e.p.PluginsService ] [6-bjhwl] loaded module [transport-netty3]
[2016-09-16T14:17:51,968][INFO ][o.e.p.PluginsService ] [6-bjhwl] loaded module [transport-netty4]
[2016-09-16T14:17:51,968][INFO ][o.e.p.PluginsService ] [6-bjhwl] loaded plugin [mapper-murmur3]
[2016-09-16T14:17:53,521][INFO ][o.e.n.Node ] [6-bjhwl] initialized
[2016-09-16T14:17:53,521][INFO ][o.e.n.Node ] [6-bjhwl] starting ...
[2016-09-16T14:17:53,671][INFO ][o.e.t.TransportService ] [6-bjhwl] publish_address {192.168.8.112:9300}, bound_addresses {{192.168.8.112:9300}
[2016-09-16T14:17:53,676][WARN ][o.e.b.BootstrapCheck ] [6-bjhwl] max virtual memory areas vm.max_map_count [65530] likely too low, increase to at least [262144]
[2016-09-16T14:17:56,731][INFO ][o.e.h.HttpServer ] [6-bjhwl] publish_address {192.168.8.112:9200}, bound_addresses {[::1]:9200}, {192.168.8.112:9200}
[2016-09-16T14:17:56,732][INFO ][o.e.g.GatewayService ] [6-bjhwl] recovered [0] indices into cluster_state
[2016-09-16T14:17:56,748][INFO ][o.e.n.Node ] [6-bjhwl] started
没有太多细节,我们可以看到我们的节点名为“6-bjhwl”(在你的案例中将是一组不同的字符)已经开始,并且将它自己选为一个集群中的主节点。
现在不要担心master的意思。
这里最重要的是我们在一个集群内启动了一个节点。
如前所述,我们可以覆盖集群或节点名称。
这可以在启动Elasticsearch时从命令行完成,如下所示:
./elasticsearch -Ecluster.name=my_cluster_name -Enode.name=my_node_name
还请注意标记为http的行,并提供关于我们的节点可到达的HTTP地址(192.168.8.112)和端口(9200)的信息。
默认情况下,Elasticsearch使用端口9200来提供对其REST API的访问。
如有必要,此端口可配置。
1.3 探索你的群集
THE REST API
现在我们已经启动并运行了节点(和集群),下一步就是了解如何与它进行通信。
幸运的是,Elasticsearch提供了一个非常全面和强大的REST API,您可以使用它与群集进行交互。
使用API 可以完成的几件事情如下:
- 检查您的群集,节点和索引运行状况,状态和统计信息
- 管理您的群集,节点和索引数据和元数据
- 根据索引执行CRUD(创建,读取,更新和删除)和搜索操作
- 执行高级搜索操作,例如分页,排序,过滤,脚本,聚合等等
1.3.1 集群健康(Cluster Health)
让我们从一个基本的健康检查开始,我们可以用它来看看我们的集群是如何做的。
我们将使用curl来执行此操作,但您可以使用允许您进行HTTP / REST调用的任何工具。
假设我们仍然在开启Elasticsearch的同一节点上,并打开另一个命令外壳窗口。
为了检查集群健康状况,我们将使用_cat API。
您可以在Kibana的控制台中运行以下命令,方法是单击“VIEW IN CONSOLE”或单击curl,然后单击下面的“COPY AS CURL”链接并粘贴到终端中。
GET /_cat/health?v
结果是:
epoch timestamp cluster status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent
1475247709 17:01:49 elasticsearch green 1 1 0 0 0 0 0 0 - 100.0%
我们可以看到名为“elasticsearch”的集群处于绿色状态。
无论何时我们要求群集健康,我们要么获得绿色,黄色或红色。
- 绿色 - 一切都很好(群集功能齐全)
- 黄色 - 所有数据都可用,但一些副本尚未分配(群集完全可用)
- 红色 - 某些数据因任何原因不可用(群集部分功能)
注意:群集为红色时,它将继续提供来自可用碎片的搜索请求,但您可能需要尽快修复它,因为存在未分配的碎片。
同样从上面的回应中,我们可以看到总共有1个节点,并且我们有0个碎片,因为我们还没有数据。
请注意,由于我们使用默认集群名称(elasticsearch),并且由于Elasticsearch默认使用单播网络发现来查找同一台计算机上的其他节点,因此您可能会意外启动计算机上的多个节点并使它们
全部加入一个集群。
在这种情况下,您可能会在上述响应中看到多个节点。
我们也可以得到我们集群中的节点列表,如下所示:
GET /_cat/nodes?v
然后响应是:
ip heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name
127.0.0.1 10 5 5 4.46 mdi * PB2SGZY
在这里,我们可以看到我们的一个名为“PB2SGZY”的节点,它是当前在我们集群中的单个节点。
1.3.2 列出所有索引(List All Indicese)
现在让我们来看看我们的索引:
GET /_cat/indices?v
然后响应是:
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
这仅仅意味着我们在集群中还没有索引。
1.3.3 创建一个索引(Create an index)
现在让我们创建一个名为“customer”的索引,然后再次列出所有索引:
PUT /customer?pretty
GET /_cat/indices?v
第一个命令使用PUT动词创建名为“customer”的索引。
我们只需在调用结束时添加漂亮的内容,就可以让它显示JSON响应(如果有的话)。
响应结果是:
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
yellow open customer 95SQ4TSUT7mWBT7VNHH67A 5 1 0 0 260b 260b
第二条命令的结果告诉我们,我们现在有一个名为customer的索引,它有5个主分片和1个副本(默认值),它包含0个文档。
您可能还会注意到客户索引标有黄色健康状况。回想一下我们之前的讨论,黄色意味着一些副本没有(尚未)分配。这个索引发生这种情况的原因是因为Elasticsearch默认为这个索引创建了一个副本。由于此刻我们只有一个节点正在运行,因此只有在其他节点加入群集的时间点之后才能分配一个副本(以获得高可用性)。一旦该副本被分配到第二个节点上,该索引的健康状态将变为绿色。
1.3.4 索引和查询文档(Index and Query a Document)
现在让我们把一些东西放入我们的客户索引。
我们会将一个简单的客户文档编入客户索引,ID为1,如下所示:
PUT /customer/_doc/1?pretty
{
"name": "John Doe"
}
响应:
{
"_index" : "customer",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 1
}
从上面,我们可以看到客户索引中成功创建了新的客户文档。
该文档的内部ID为1,我们在索引时指定。
重要的是要注意,Elasticsearch并不要求您在将文档编入索引之前先显式创建索引。
在前面的例子中,Elasticsearch会自动创建客户索引,如果事先不存在的话。
现在让我们检索我们刚编入索引的文档:
GET /customer/_doc/1?pretty
响应:
{
"_index" : "customer",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"found" : true,
"_source" : { "name": "John Doe" }
}
除了找到的字段外,没有什么不寻常的,说明我们找到了一个带有所需ID 1的文档,另一个字段_source返回了我们从上一步编入索引的完整JSON文档。
1.3.5 删除索引(Delete index)
现在让我们删除刚刚创建的索引,然后再次列出所有索引:
DELETE /customer?pretty
GET /_cat/indices?v
响应:
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
这意味着索引已成功删除,现在我们回到我们在集群中没有任何内容的地方。
在我们继续之前,让我们再仔细看看迄今为止学到的一些API命令:
PUT /customer
PUT /customer/_doc/1
{
"name": "John Doe"
}
GET /customer/_doc/1
DELETE /customer
如果我们仔细研究上述命令,我们实际上可以看到我们如何在Elasticsearch中访问数据的模式。该模式可以概括如下:
<REST Verb> /<Index>/<Type>/<ID>
这种REST访问模式在所有API命令中都非常普遍,如果你可以简单地记住它,你将在掌握Elasticsearch方面有一个良好的开端。
1.4 修改您的数据
Elasticsearch几乎实时提供数据操作和搜索功能。
默认情况下,从索引/更新/删除数据开始,直到它出现在搜索结果中的时间,您可以预计会有一秒的延迟(刷新间隔)。
这是与SQL等其他平台的重要区别,其中数据在事务完成后立即可用。
索引/替换Documents 文档
我们以前见过我们如何索引单个文档。
让我们再次回忆起那个命令:
PUT /customer/_doc/1?pretty
{
"name": "John Doe"
}
再次,上面将索引指定的文件到客户索引中,ID为1.如果我们接着用不同的(或相同的)文件再次执行上述命令,Elasticsearch将(即重新索引)一个新文档覆盖
现有的ID为1的文档:
PUT /customer/_doc/1?pretty
{
"name": "Jane Doe"
}
以上内容将ID为1的文档的名称从“John Doe”更改为“Jane Doe”。
另一方面,如果我们使用不同的ID,则新文档将被编入索引,并且索引中已存在的文档将保持不变。
PUT /customer/_doc/2?pretty
{
"name": "Jane Doe"
}
以上索引ID为2的新文档。
索引时,ID部分是可选的。
如果未指定,Elasticsearch将生成一个随机ID,然后用它来索引文档。
作为索引API调用的一部分,将返回Elasticsearch生成实际的ID(或我们在前面的示例中明确指定的内容)。
POST /customer/_doc?pretty
{
"name": "Jane Doe"
}
请注意,在上述情况下,我们使用POST动词而不是PUT,因为我们没有指定ID。
1.5 探索您的数据
1.5.1 搜索API(The Search API)
现在让我们开始一些简单的搜索。有两种运行搜索的基本方法:一种是通过REST request URI 发送搜索参数,另一种是通过REST request body 发送搜索参数。
request body 方法允许您更具表现力,并以更具可读性的JSON格式定义您的搜索。
我们将尝试请求URI方法的一个示例,但在本教程的其余部分中,我们将专门使用请求主体方法。
搜索的REST API可从_search端点访问。本示例返回银行索引中的所有文档:
GET /bank/_search?q=*&sort=account_number:asc&pretty
首先解析搜索调用。我们在银行索引中搜索(_search endpoint),并且q = *参数指示Elasticsearch匹配索引中的所有文档。
sort = account_number:asc参数指示使用每个文档的account_number字段对结果按升序排序。pretty参数告诉Elasticsearch返回格式良好的JSON结果。
响应(部分显示):
{
"took" : 63,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 1000,
"max_score" : null,
"hits" : [ {
"_index" : "bank",
"_type" : "_doc",
"_id" : "0",
"sort": [0],
"_score" : null,
"_source" : {"account_number":0,"balance":16623,"firstname":"Bradshaw","lastname":"Mckenzie","age":29,"gender":"F","address":"244 Columbus Place","employer":"Euron","email":"bradshawmckenzie@euron.com","city":"Hobucken","state":"CO"}
}, {
"_index" : "bank",
"_type" : "_doc",
"_id" : "1",
"sort": [1],
"_score" : null,
"_source" : {"account_number":1,"balance":39225,"firstname":"Amber","lastname":"Duke","age":32,"gender":"M","address":"880 Holmes Lane","employer":"Pyrami","email":"amberduke@pyrami.com","city":"Brogan","state":"IL"}
}, ...
]
}
}
至于回应,我们看到以下部分:
- took- Elasticsearch执行搜索的时间(以毫秒为单位)
- timed_out - 告诉我们搜索是否超时
- _shards - 告诉我们搜索了多少碎片,以及搜索碎片成功/失败的次数
- hits - 搜索结果
- hits.total - 符合我们搜索条件的文档总数
- hits.hits - 实际搜索结果数组(默认为前10个文档)
- hits.sort - 结果排序键(按分数排序时缺失)
- hits._score和max_score - 暂时忽略这些字段
下面是跟上面完全相同的搜索,使用的是请求主体方法替代。
GET /bank/_search
{
"query": { "match_all": {} },
"sort": [
{ "account_number": "asc" }
]
}
这里的区别在于,不是在URI中传递q = *,而是为_search API提供JSON风格的查询请求主体。
我们将在下一节讨论这个JSON查询。
重要的是要明白,一旦你得到你的搜索结果,Elasticsearch完全完成了请求,并没有维护任何种类的服务器端资源或打开游标到你的结果。这与许多其他平台(例如SQL)形成了鲜明对比,其中您可能最初会首先获得查询结果的部分子集,然后如果您想要获取(或翻阅)其余部分,则必须不断返回服务器
的结果使用某种有状态的服务器端游标。
1.5.2 介绍查询语言(Introducing the Query Language)
Elasticsearch提供了一种可用于执行查询的JSON式特定于领域的语言。
这被称为查询DSL。查询语言非常全面,乍一看可能会让人感到恐慌,但实际学习它的最好方法是从几个基本示例开始。
回到我们的最后一个例子,我们执行了这个查询:
GET /bank/_search
{
"query": { "match_all": {} }
}
解析上述内容,<code>query</code>查询部分告诉我们我们的查询定义是什么,<code>match_all</code>部分仅仅是我们想要运行的查询类型。
match_all查询只是搜索指定索引中的所有文档。
除query查询参数外,我们还可以传递其他参数来影响搜索结果。
在上面的例子中,我们通过sort排序,在这里我们通过<code>size</code>传递大小:
GET /bank/_search
{
"query": { "match_all": {} },
"size": 1
}
请注意,如果未指定大小,则默认为10。
此示例执行match_all并返回10到19号文档:
GET /bank/_search
{
"query": { "match_all": {} },
"from": 10,
"size": 10
}
from参数(从0开始)指定从哪个文档索引开始,size参数指定从from参数开始返回多少个文档。此功能在实现分页搜索结果时非常有用。
请注意,如果from未指定,则默认为0。
此示例执行match_all并按帐户余额按降序对结果进行排序,并返回前10个(默认大小)文档。
GET /bank/_search
{
"query": { "match_all": {} },
"sort": { "balance": { "order": "desc" } }
}
1.5.3 执行搜索(Executing Searches)
现在我们已经看到了一些基本的搜索参数,让我们进一步深入查询DSL。
我们先来看看返回的文档字段。
默认情况下,完整的JSON文档作为所有搜索的一部分返回。
这被称为源(搜索匹配中的_source字段)。
如果我们不希望整个源文档被返回,我们可以只需要返回源内的几个字段。
此示例显示如何从搜索中返回两个字段account_number和balance(在_source之内):
GET /bank/_search
{
"query": { "match_all": {} },
"_source": ["account_number", "balance"]
}
请注意,上面的例子简单地减少了_source字段。
它只会返回一个名为_source的字段,但其中只包含字段account_number和余额。
如果您来自SQL背景,则上述内容在概念上与SQL SELECT FROM字段列表有些相似。
现在让我们继续查询部分。
以前,我们已经看到match_all查询是如何用来匹配所有文档的。
现在我们来介绍一个称为匹配查询(match query)的新查询,它可以被认为是基本的搜索查询(即针对特定字段或字段集合进行的搜索)。
此示例返回编号为20的帐户:
GET /bank/_search
{
"query": { "match": { "account_number": 20 } }
}
此示例返回地址中包含词“mill”的所有帐户:
GET /bank/_search
{
"query": { "match": { "address": "mill" } }
}
本示例返回地址中包含词“mill”或“lane”的所有帐户:
GET /bank/_search
{
"query": { "match": { "address": "mill lane" } }
}
这个例子是match(match_phrase)的一个变体,返回地址中包含短语“mill lane”的所有帐户:
GET /bank/_search
{
"query": { "match_phrase": { "address": "mill lane" } }
}
现在让我们来介绍一下bool查询。
bool查询允许我们使用布尔逻辑将更小的查询组合成更大的查询。
此示例组成两个匹配查询并返回地址中包含“mill”和“lane”的所有帐户:
GET /bank/_search
{
"query": {
"bool": {
"must": [
{ "match": { "address": "mill" } },
{ "match": { "address": "lane" } }
]
}
}
}
在上面的例子中,bool must子句指定了一个文档必须匹配所有match查询。
相反,下面示例组成两个匹配查询并返回地址中包含“mill”或“lane”的所有帐户:
GET /bank/_search
{
"query": {
"bool": {
"should": [
{ "match": { "address": "mill" } },
{ "match": { "address": "lane" } }
]
}
}
}
在上面的例子中,bool should 子句指定一个查询列表,其中有任何一个查询条件是真的就可以视为匹配的文档。
下面示例组成两个匹配查询并返回地址中既不包含“mill”也不包含“lane”的所有帐户:
GET /bank/_search
{
"query": {
"bool": {
"must_not": [
{ "match": { "address": "mill" } },
{ "match": { "address": "lane" } }
]
}
}
}
bool must_not 部分指定一个查询列表,其中任何一个查询都不应该为真,这样才能被视为匹配的文档。
我们可以在一个bool查询中同时结合must,should和must_not子句。
此外,我们可以在任何这些bool子句中编写布尔查询来模拟任何复杂的多级布尔逻辑。
此示例返回任何40岁但未居住在ID(aho)的人的所有帐户:
GET /bank/_search
{
"query": {
"bool": {
"must": [
{ "match": { "age": "40" } }
],
"must_not": [
{ "match": { "state": "ID" } }
]
}
}
}
1.5.4 执行过滤器(Executing Filters)
在前一节中,我们跳过了一个称为文档分数(搜索结果中的_score字段)的细节。
分数是一个数值,它是文档与我们指定的搜索查询匹配度的相对度量。
分数越高,文档越相关,分数越低,文档的相关性越低。
但查询并不总是需要生成分数,特别是当它们仅用于“过滤”文档集时。
Elasticsearch检测这些情况并自动优化查询执行,以便不计算无用分数。
我们在前一节介绍的bool查询还支持筛选子句,它允许使用查询来限制将由其他子句匹配的文档,而不会更改计算分数的方式。
作为一个例子,我们介绍一下范围查询,它允许我们通过一系列值来过滤文档。
这通常用于数字或日期过滤。
此示例使用bool查询返回余额介于20000和30000之间的所有帐户。
换句话说,我们希望查找余额大于或等于20000且小于等于30000的帐户。
GET /bank/_search
{
"query": {
"bool": {
"must": { "match_all": {} },
"filter": {
"range": {
"balance": {
"gte": 20000,
"lte": 30000
}
}
}
}
}
}
解析上述内容,bool查询包含一个match_all查询(查询部分)和一个range 范围查询(过滤器部分)。我们可以将任何其他查询替换为查询和过滤器部分。在上述情况下,范围查询非常有意义,因为落入该范围的文档全部匹配“平等”,即没有文档比另一个更重要。
除了match_all,match,bool和range查询之外,还有很多其他可用的查询类型,我们在这里不会涉及它们。
由于我们已经对其工作原理有了基本的了解,因此将这些知识应用于学习和试用其他查询类型应该不会太困难。
1.5.5 执行聚合(Executing Aggregations)
聚合提供了从数据中分组和提取统计数据的功能。
理解聚合的最简单方法是将其大致等同于SQL GROUP BY和SQL聚合函数。
在Elasticsearch中,您可以执行返回匹配的搜索,同时还可以在一个响应中返回与匹配的聚合结果。
这是非常强大和高效的,因为您可以运行查询和多个聚合,并且一次性获得两个(或两个)操作的结果,避免使用简洁和简化的API来避免网络往返。
首先,此示例按状态对所有帐户进行分组,然后返回按每组的count账户数 降序(也是默认值)排序的前10个(默认)状态:
GET /bank/_search
{
"size": 0,
"aggs": {
"group_by_state": {
"terms": {
"field": "state.keyword"
}
}
}
}
在SQL中,上面的聚合在概念上类似于:
SELECT state, COUNT(*) FROM bank GROUP BY state ORDER BY COUNT(*) DESC
响应(部分显示):
{
"took": 29,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped" : 0,
"failed": 0
},
"hits" : {
"total" : 1000,
"max_score" : 0.0,
"hits" : [ ]
},
"aggregations" : {
"group_by_state" : {
"doc_count_error_upper_bound": 20,
"sum_other_doc_count": 770,
"buckets" : [ {
"key" : "ID",
"doc_count" : 27
}, {
"key" : "TX",
"doc_count" : 27
}, {
"key" : "AL",
"doc_count" : 25
}, {
"key" : "MD",
"doc_count" : 25
}, {
"key" : "TN",
"doc_count" : 23
}, {
"key" : "MA",
"doc_count" : 21
}, {
"key" : "NC",
"doc_count" : 21
}, {
"key" : "ND",
"doc_count" : 21
}, {
"key" : "ME",
"doc_count" : 20
}, {
"key" : "MO",
"doc_count" : 20
} ]
}
}
}
我们可以看到ID(Idaho)有27个账户,其次是TX (Texas)的27个账户,其次是AL (Alabama)阿拉巴马州的25个账户,等等。
请注意,我们将size = 0设置为不显示搜索匹配,因为我们只想查看响应中的聚合结果。
基于以前的汇总,本示例按状态计算平均账户余额(再次仅按按降序顺序排列的前10个州):
GET /bank/_search
{
"size": 0,
"aggs": {
"group_by_state": {
"terms": {
"field": "state.keyword"
},
"aggs": {
"average_balance": {
"avg": {
"field": "balance"
}
}
}
}
}
}
注意我们如何在group_by_state聚合内嵌套average_balance聚合。
这是所有聚合的通用模式。
您可以任意嵌套聚合内的聚合,以便从数据中提取所需的枢纽总结。
基于以前的汇总,现在让我们按降序对平均余额进行排序:
GET /bank/_search
{
"size": 0,
"aggs": {
"group_by_state": {
"terms": {
"field": "state.keyword",
"order": {
"average_balance": "desc"
}
},
"aggs": {
"average_balance": {
"avg": {
"field": "balance"
}
}
}
}
}
}
这个例子演示了我们如何按年龄段(20-29岁,30-39岁和40-49岁)进行分组,然后按性别进行分组,然后最终得出每个性别的年龄段平均账户余额:
GET /bank/_search
{
"size": 0,
"aggs": {
"group_by_age": {
"range": {
"field": "age",
"ranges": [
{
"from": 20,
"to": 30
},
{
"from": 30,
"to": 40
},
{
"from": 40,
"to": 50
}
]
},
"aggs": {
"group_by_gender": {
"terms": {
"field": "gender.keyword"
},
"aggs": {
"average_balance": {
"avg": {
"field": "balance"
}
}
}
}
}
}
}
}
还有其他许多聚合功能,我们在这里不会详细介绍。
如果你想做进一步的实验,那么聚合参考指南是一个很好的起点。
1.6 总结
Elasticsearch既是一个简单又复杂的产品。
到目前为止,我们已经了解了它的基本原理,如何看待它,以及如何使用一些REST API来处理它。
希望本教程能够让您更好地理解Elasticsearch是什么,更重要的是,启发您进一步试验其其他伟大功能!