开始学习
本文为翻译官网“Getting Started”部分但有一定改动之后的文章,版本为6.0,因为一些基础的东西6.0才有,当然学习此版本的基础知识和其他版本知识相同。后续关于一些实际的应用将会更新更高版本的。当然我认为这对学习ES有很大帮助。学完这些我们会对ES有一个大概的了解,同时也会使用一些基本的查询以及学习ES的工作模式。这对与使用ES有很大的帮助。
关键字:Es elasticsearch官网中文 Es(elasticsearch)快速入门
官网地址:https://www.elastic.co/guide/en/elasticsearch/reference/6.0/getting-started.html
Elasticsearch是一个高度可扩展的开源全文搜索和分析引擎。它允许您快速、近乎实时地存储、搜索和分析大量数据。它通常被用作底层引擎/技术,为具有复杂搜索功能和需求的应用程序提供动力。
下面是一些Elasticsearch可以用于的示例用例:
- 你经营一家网上商店,允许你的顾客搜索你出售的产品。在这种情况下,您可以使用Elasticsearch来存储您的整个产品目录和库存,并为它们提供搜索和自动完成建议。
- 您希望收集日志或事务数据,并希望分析和挖掘该数据,以查找趋势、统计信息、摘要或异常情况。在这种情况下,您可以使用Logstash (Elasticsearch/Logstash/Kibana堆栈的一部分)来收集、聚合和解析您的数据,然后让Logstash将这些数据提供给Elasticsearch。一旦数据在Elasticsearch中,您就可以运行搜索和聚合来挖掘您感兴趣的任何信息。
- 你运行一个价格警报平台,允许价格敏感的客户指定一个规则,如“我有兴趣购买一个特定的电子产品,如果在下个月任何供应商的产品价格低于X美元,我希望得到通知”。在这种情况下,您可以抓取供应商价格,将它们推入Elasticsearch,并使用它的反向搜索(Percolator)功能根据客户查询匹配价格变化,最终在找到匹配的情况下将警报推送给客户。
- 您有分析/商业智能需求,希望快速调查、分析、可视化并对大量数据(考虑数百万或数十亿条记录)提出特别的问题。在这种情况下,您可以使用Elasticsearch来存储您的数据,然后使用Kibana (Elasticsearch/Logstash/Kibana堆栈的一部分)来构建定制的仪表板,可以可视化对您重要的数据方面。此外,您可以使用Elasticsearch聚合功能对您的数据执行复杂的业务智能查询。
重要概念
Elasticsearch有几个核心概念。从一开始就理解这些概念将极大地帮助简化学习过程。
Near Realtime (NRT)接近实时的
Elasticsearch是一个近乎实时的搜索平台。这意味着,从您为文档建立索引到它变得可搜索有一个轻微的延迟(通常是1秒)。
Cluster(集群)
集群是一个或多个节点(服务器)的集合,它们共同保存整个数据,并提供跨所有节点的联合索引和搜索功能。集群由唯一名称标识,默认情况下为“elasticsearch”。这个名称很重要,因为如果将节点设置为通过其名称加入集群,则该节点只能是集群的一部分。
请确保不要在不同的环境中重用相同的集群名称,否则可能导致节点加入了错误的集群。例如,您可以对开发、登台和生产集群使用logging-dev、logging-stage和logging-prod。
请注意,只有一个节点的集群是有效的,也是完全可以的。此外,还可以有多个独立的集群,每个集群都有自己唯一的集群名称。
Node(节点)
节点是集群的一部分,存储数据并参与集群的索引和搜索功能的单个服务器。就像集群一样,节点由名称标识,默认情况下,该名称是在启动时分配给节点的随机通用唯一标识符(UUID)。如果不需要默认的节点名,可以定义任何节点名。这个名称对于管理目的非常重要,因为您需要确定网络中的哪些服务器对应于Elasticsearch集群中的哪些节点。
可以通过集群名称配置节点加入特定的集群。默认情况下,每个节点都被设置为加入一个名为elasticsearch的集群,这意味着如果您启动网络上的许多节点,并且假设它们可以相互发现,那么它们都将自动形成并加入一个名为elasticsearch的集群。
在单个集群中,您可以拥有任意数量的节点。此外,如果您的网络中目前没有运行其他Elasticsearch节点,启动单个节点将默认形成名为Elasticsearch的新的单节点集群。
Index(索引)
索引是具有某种相似特征的文档的集合。例如,您可以为客户数据提供一个索引,为产品目录提供另一个索引,为订单数据提供另一个索引。索引由名称(必须全小写)标识,在对其中的文档执行索引、搜索、更新和删除操作时,使用该名称引用索引。
在单个集群中,可以定义任意数量的索引。
Type(类型)
类型曾经是索引的一个逻辑类别/分区,允许您在同一个索引中存储不同类型的文档,例如一种类型用于用户,另一种类型用于博客文章。在一个索引中不再可能创建多个类型,类型的整个概念将在以后的版本(6.0.0)中被删除。
Document(文档)
文档是可以被索引的基本信息单位。例如,可以为单个客户拥有一个文档,为单个产品拥有另一个文档,以及为单个订单拥有另一个文档。本文档用JSON (JavaScript对象表示法)表示,这是一种普遍存在的互联网数据交换格式。
在一个索引/类型中,可以存储任意数量的文档。注意,尽管文档物理上驻留在索引中,但文档实际上必须被索引/分配给索引中的类型。
Shards & Replicas(分片和副本)
索引可以潜在地存储大量数据,这些数据可能超过单个节点的硬件限制。例如,10亿个文档的一个索引占用1TB的磁盘空间,可能无法装入单个节点的磁盘,或者可能太慢,无法单独处理来自单个节点的搜索请求。
为了解决这个问题,Elasticsearch提供了将索引细分为多个分片的能力。在创建索引时,可以简单地定义所需的分片数量。每个分片本身就是一个功能齐全、独立的“索引”,可以托管在集群中的任何节点上。
分片之所以重要,主要有两个原因:
1,它允许您水平分割/缩放您的内容卷
2,它允许您跨分片(可能在多个节点上)分布和并行化操作,从而提高性能/吞吐量
分片如何分布以及它的文档如何聚合回搜索请求的机制完全由Elasticsearch管理,并且对作为用户的您是透明的。
在随时可能发生故障的网络/云环境中,如果碎片/节点因某种原因脱机或消失,那么强烈建议使用故障转移机制,这是非常有用的。为此,Elasticsearch允许您将索引分片的一个或多个副本制作为所谓的副本分片,或简称为副本。
副本之所以重要,主要有两个原因:
1,它在分片/节点故障时提供高可用性。因此,重要的是要注意,副本分片永远不会被分配到与其被复制的原始/主分片相同的节点上。
2,它允许您扩展搜索量/吞吐量,因为搜索可以在所有副本上并行执行。
总的来说,每个索引可以被分割成多个分片。索引也可以被零次复制(意味着没有副本)或多次复制。复制完成后,每个索引将拥有主分片(被复制的原始分片)和副本分片(主碎片的副本)。可以在创建索引时定义每个索引的分片和副本的数量。在创建索引之后,您可以随时动态更改副本的数量,但不能在事后更改分片的数量。
默认情况下,Elasticsearch中的每个索引分配5个主碎片和1个副本,这意味着如果您的集群中至少有两个节点,那么您的索引将有5个主分片和另外5个副本分片(1个完整副本),每个索引总共有10个分片。
每个Elasticsearch分片都是一个Lucene索引。在一个Lucene索引中可以拥有一个最大的文档数量。截至LUCENE-5843,限制是2,147,483,519(=Integer.MAX_VALUE - 128)文档。你可以使用_cat/shards API来监视分片的大小。
安装
Elasticsearch至少需要Java 8。在安装之前请检查JDK版本。
具体安装步骤不做详细介绍。
探索你的集群
REST API
既然我们已经启动并运行了节点(和集群),下一步就是了解如何与它通信。幸运的是,Elasticsearch提供了一个非常全面和强大的REST API,您可以使用它与您的集群进行交互。以下是一些可以用API完成的事情:
- 检查集群、节点和索引运行状况、状态和统计信息
- 管理集群、节点和索引数据和元数据
- 对索引执行CRUD(创建、读取、更新和删除)和搜索操作
- 执行高级搜索操作,如分页、排序、筛选、脚本编制、聚合等
集群健康值
让我们从一个基本的运行状况检查开始,我们可以使用它来查看集群的运行情况。我们将使用curl来实现这一点,但您可以使用任何允许您进行HTTP/REST调用的工具。让我们假设我们仍然在启动Elasticsearch的同一节点上,并打开另一个命令shell窗口。
为了检查集群运行状况,我们将使用_cat API。你可以在Kibana的控制台中通过点击“VIEW in Console”来运行下面的命令,或者通过浏览器http或者curl查看
GET /_cat/health?v
curl -XGET http://localhost:9200/_cat/health?v
?v
:显示列表表头
响应:
epoch timestamp cluster status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent
1669863314 02:55:14 Hes7.3 green 2 2 52 26 0 0 0 0 - 100.0%
epoch
: 时间戳
timestamp
:时间戳
cluster
: 集群名称
status
: 集群状态
node
.total: 在线的节点总数量
node.data
: 在线的数据节点总数量
shards
即active_shards: 存活的分片数量
pri
即active_primary_shards : 存活的主分片数量,正常情况下shards是pri的两倍
relo
即relocating_shards迁移中的分片数量,正常情况下为0
init
即initalizing_shards: 初始化中的分片数量 正常情况下为0
unassign
即unassign_shards: 未分配的分片,正常情况下为0
pending_tasks
: 准备中的任务包括迁移分片等任务,正常情况下为0
max_task_wait_time
: 任务最长等待时间
active_shards_percent
: 正常分片百分比,正常情况下为100%
集群有3种健康值分别为:GRENN
,YELLOW
以及RED
GRENN:一切正常(集群功能齐全)
YELLOW:所有数据都可用,但一些副本尚未分配(集群功能完全)
RED:某些数据由于某种原因不可用(集群部分可用)
注意:当集群为红色时,它将继续提供来自可用碎片的搜索请求,但您可能需要尽快修复它,因为有未分配的分片。
注意,如果我们使用的是默认的集群名称(elasticsearch),而且elasticsearch默认使用单播网络发现来查找同一机器上的其他节点,因此您可能会意外地启动计算机上的多个节点,并将它们全部加入到一个集群中。在这种情况下,您可能会在上面的响应中看到多个节点。
我们还可以获得集群中的节点列表,如下所示:
GET /_cat/nodes?v
curl -XGET http://localhost:9200/_cat/nodes?v
响应:
ip heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name
192.168.0.158 69 95 2 0.00 0.01 0.05 cdfhilmrstw - node-1
192.168.0.158 65 95 2 0.00 0.01 0.05 cdfhilmrstw * node-2
ip : 结点所处主机IP
heap.percent: 堆内存占用百分比
ram.percent: 内存占用百分比
cpu: cpu占用百分比
load_1m: 一分钟负载
load_5m: 五分钟负载
load_15m: 十五分钟负载
node.role: 结点类型
master: 是否为主节点,*表示主节点
name: 节点名称
m:master eligible node(候选主节点)
d:data node(数据节点)
i:ingest node(摄取节点)
查看所有索引
GET /_cat/indices?v
curl -XGET http://localhost:9200/_cat/indices?v
响应:
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
green open index1 Y6AwC_OuQ721bRgYfqfM7g 2 1 0 0 832b 416b
green open index2 ZwTo0Hh3RgWxldWBt5I6AQ 2 1 0 0 832b 416b
green open index3 DRjVVW2AQhOKaYYDfxArwQ 5 1 0 0 2kb 1kb
创建一个索引
PUT /new_index
curl -XPUT http://localhost:9200/new_index
响应:
{
"acknowledged" : true,
"shards_acknowledged" : true,
"index" : "new_index"
}
如果集群为一个单节点那么索引上标记了一个黄色的运行状况。回想一下我们前面的讨论,黄色表示有些副本(还)没有分配。这是因为Elasticsearch在默认情况下为该索引创建了一个副本。因为我们目前只有一个节点在运行,所以这个副本还不能被分配(以实现高可用性),直到稍后另一个节点加入集群时才会分配。将该副本分配到第二个节点后,此索引的运行状况状态将变为绿色。
索引和查询文档
首先插入数据到索引
PUT /new_index/doc/1?pretty
curl -XPUT http://localhost:9200/new_index/doc/1?pretty -d '{"name":"zhangsan"}'
响应:
{
"_index" : "new_index",
"_type" : "doc",
"_id" : "1",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 2,
"failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 2
}
从上面,我们可以看到在new_index索引中成功创建了一个新的客户文档。文档还有一个内部id 1,这是我们在创建索引时指定的。需要注意的是,Elasticsearch不要求您在将文档索引到其中之前先显式地创建索引。在前面的例子中,如果new_index索引之前不存在,Elasticsearch将自动创建它。
现在让我们检索刚刚索引的文档:
curl -XGET localhost:9200/new_index/doc/1?pretty
响应:
{
"_index" : "new_index",
"_type" : "doc",
"_id" : "1",
"_version" : 1,
"_seq_no" : 0,
"_primary_term" : 2,
"found" : true,
"_source" : {
"name" : "zhangsan"
}
}
这里没有什么不同寻常的,除了一个字段,found,说明我们找到了一个具有请求ID 1的文档和另一个字段,_source,它返回我们在上一步中建立索引的完整JSON文档。
删除一个索引
curl localhost:9200/new_index
通过以上命令我们可以总结出es的API访问格式为
<REST verb>/<index>/<Type>/<ID>
修改数据
Elasticsearch提供了近乎实时的数据操作和搜索功能。默认情况下,从索引/更新/删除数据到它出现在搜索结果中,您可以预期有一秒钟的延迟(刷新间隔)。这是与SQL等其他平台的一个重要区别,在SQL中,数据在事务完成后立即可用。
索引/替换文档
我们之前已经了解了如何为单个文档建立索引:
PUT /new_index/doc/1?pretty
{
"name":"zhangsan"
}
同样,上面的操作将把指定的文档索引到ID为1的new_index索引中。如果我们用一个不同的(或相同的)文档再次执行上面的命令,Elasticsearch将用ID为1的现有文档替换(即重新索引)一个新文档:
PUT /new_index/doc/1?pretty
{
"name":"lisi"
}
上述操作将ID为1的文档的名称从“zhangsan”更改为“lisi”。另一方面,如果使用不同的ID,则会创建一个新文档的索引,而索引中已经存在的文档保持不变。
索引时,ID部分是可选的。如果没有指定,Elasticsearch将生成一个随机ID,然后使用它为文档建立索引。Elasticsearch生成的实际ID(或我们在前面的示例中显式指定的任何ID)作为索引API调用的一部分返回。
这个例子展示了如何索引一个没有显式ID的文档:
POST /new_index/doc?pretty
{
"name": "zhangsan"
}
响应:
{
"_index" : "new_index",
"_type" : "doc",
"_id" : "TPrqzIQBPxZUAZnBSaxz",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 2,
"failed" : 0
},
"_seq_no" : 6,
"_primary_term" : 2
}
注意,在上面的例子中,我们使用POST动词而不是PUT,因为我们没有指定ID。"TPrqzIQBPxZUAZnBSaxz"为Elasticsearch为我们生成的新ID。
更新文档
除了能够索引和替换文档之外,我们还可以更新文档。不过请注意,Elasticsearch实际上并不在底层进行就地更新。每当我们进行更新时,Elasticsearch都会删除旧文档,然后将更新应用到新文档的索引中。
例如:我们原来的索引中有一条数据:
{
"_index" : "new_index",
"_type" : "doc",
"_id" : "1",
"_version" : 1,
"_seq_no" : 0,
"_primary_term" : 2,
"found" : true,
"_source" : {
"name" : "zhangsan"
}
}
现将id为1的文档中的name
改为"lisi"
curl -XPOST http://localhost:9200/new_index/doc/1/_update -H "Content-Type:application/json" -d '{"doc":{"name":"lisi"}}'
为文档增加一个字段为age的属性
curl -XPOST http://localhost:9200/new_index/doc/1/_update -H "Content-Type:application/json" -d '{"doc":{"name":"lisi","age",10}}'
更新后的值:
{
"_index" : "new_index",
"_type" : "doc",
"_id" : "1",
"_version" : 3,
"_seq_no" : 2,
"_primary_term" : 2,
"found" : true,
"_source" : {
"name" : "lisi",
"age" : 10
}
}
我们也可以使用简单的脚本把age的值增加
curl -XPOST http://localhost:9200/new_index/doc/1/_update -H "Content-Type:application/json" -d '{"script":"ctx._source.age+=5"}'
执行更新后的值:
{
"_index" : "new_index",
"_type" : "doc",
"_id" : "1",
"_version" : 4,
"_seq_no" : 3,
"_primary_term" : 2,
"found" : true,
"_source" : {
"name" : "lisi",
"age" : 15
}
}
在上面的例子中,ctx._source指即将更新的当前源文档
Elasticsearch提供了在给定查询条件(如SQL update - where语句)下更新多个文档的能力。详情可看_update_by_query用法
删除文档
curl -XDELETE http://localhost:9200/new_index/doc/1
即删除new_index索引下,doc下id为1的文档
Elasticsearch也提供了给定查询条件下(如SQL的delete-where语句)删除多个文档的能力。详情可查看_delete_by_query用法
批量处理
除了能够索引、更新和删除单个文档之外,Elasticsearch还提供了使用_bulk API
批量执行上述任何操作的能力。这个功能非常重要,因为它提供了一种非常有效的机制,可以用尽可能少的网络往返以尽可能快的速度执行多个操作。
批量索引数据使用方式:
curl -XPOST http://localhost:9200/new_index/doc/_bulk?pretty -H "Content-Type:application/json" -d '
> {"index":{"_id":1}}
> {"name":"zhangsan"}
> {"index":{"_id":2}}
> {"name":"lisi"}
> '
以上操作为同时索引id为1和2且姓名为"zhangsan","lisi"的文档
响应:
{
"took" : 23,
"errors" : false,
"items" : [
{
"index" : {
"_index" : "new_index",
"_type" : "doc",
"_id" : "1",
"_version" : 5,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 2,
"failed" : 0
},
"_seq_no" : 4,
"_primary_term" : 2,
"status" : 200
}
},
{
"index" : {
"_index" : "new_index",
"_type" : "doc",
"_id" : "2",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 2,
"failed" : 0
},
"_seq_no" : 5,
"_primary_term" : 2,
"status" : 201
}
}
]
}
更新一个文档同时删除另一个文档:
curl -XPOST http://localhost:9200/new_index/doc/_bulk?pretty -H "Content-Type:application/json" -d '
>{"update":{"_id":"1"}}
>{"doc": { "name": "zhangsan" } }
>{"delete":{"_id":"2"}}
> '
请注意,对于删除操作,在它之后没有相应的源文档,因为删除只需要删除文档的ID。
Bulk API不会因为某个操作的失败而失败。如果单个操作由于任何原因失败,它将继续处理其后的其余操作。当批量API返回时,它将为每个操作提供一个状态(按照发送的顺序),以便您可以检查特定操作是否失败。
探索你的数据
现在让我们从一些简单的搜索开始。运行搜索有两种基本方法:一种是通过REST请求URI发送搜索参数,另一种是通过REST请求主体发送搜索参数。请求体方法允许您更有表现力,并以更可读的JSON格式定义搜索。
样本数据集
首先在你的集群中添加一些如下格式的数据:
{
"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"
}
您可以从这里下载样例数据集(accounts.json)。将它解压缩到当前目录中,然后按如下方法将其加载到集群中:
curl -H "Content-Type: application/json" -XPOST 'localhost:9200/bank/account/_bulk?pretty&refresh' --data-binary "@accounts.json"
curl 'localhost:9200/_cat/indices?v'
这意味着我们刚刚成功地将1000个文档批量索引到银行索引中(在帐户类型下)。
搜索API
可以从_search端点访问用于搜索的REST API。这个示例返回bank索引中的所有文档:
curl -X GET "localhost:9200/bank/_search?q=*&sort=account_number:asc&pretty&pretty"
让我们首先分析一下搜索调用。我们在银行索引中搜索(_search端点),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" : "account",
"_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" : "account",
"_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执行搜索的时间(以毫秒为单位)time_out
: 告诉我们搜索是否超时了_shards
: 告诉我们搜索了多少个碎片,以及成功/失败搜索碎片的计数hits
: 搜索结果hits.total
: 符合我们搜索条件的文档总数hits.hits
: 实际的搜索结果数组(默认为前10个文档)hits.sort
: 结果的排序键(如果按分数排序,则丢失)
下面是上面使用替代请求体方法的相同搜索:
GET /bank/_search
{
"query": { "match_all": {} },
"sort": [
{ "account_number": "asc" }
]
}
这里的区别在于,我们没有在URI中传递q=*,而是向_search API提供了json样式的查询请求体。
重要的是要理解,一旦您得到您的搜索结果,Elasticsearch就完全完成了请求,并且不维护任何类型的服务器端资源或打开游标到您的结果中。这与许多其他平台(如SQL)形成鲜明对比,在这些平台中,您最初可能预先获得查询结果的部分子集,然后如果您想使用某种有状态的服务器端游标获取(或翻页)其余结果,则必须不断返回服务器。
搜索语言
Elasticsearch提供了一种json风格的领域特定语言,您可以使用它来执行查询。这被称为Query DSL。查询语言非常全面,乍一看可能令人生畏,但真正学习它的最好方法是从一些基本示例开始。
GET /bank/_search
{
"query": { "match_all": {} }
}
通过分析上面的内容,查询部分告诉我们查询定义是什么,而match_all部分只是我们想要运行的查询类型。match_all查询只是对指定索引中的所有文档进行搜索。
除了查询参数,我们还可以传递其他参数来影响搜索结果。在上面的例子中,我们传递的是排序,这里我们传递的是大小:
GET /bank/_search
{
"query": { "match_all": {} },
"size": 1
}
注意,如果没有指定size,则默认值为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" } }
}
执行搜索
现在我们已经了解了一些基本的搜索参数,让我们进一步研究Query DSL。让我们首先看一下返回的文档字段。默认情况下,完整的JSON文档作为所有搜索的一部分返回。这被称为source(搜索命中的_source字段)。如果我们不希望返回整个源文档,我们可以只从源文档中请求返回几个字段。
这个例子展示了如何从搜索中返回两个字段,account_number和balance(在_source中):
GET /bank/_search
{
"query": { "match_all": {} },
"_source": ["account_number", "balance"]
}
注意,上面的示例只是减少了_source字段。它仍然只返回一个名为_source的字段,但在该字段中,只包括字段account_number和balance。
现在让我们转到查询部分。在前面,我们已经看到了如何使用match_all查询来匹配所有文档。现在让我们引入一个名为match查询的新查询,它可以被认为是一个基本的字段搜索查询(即针对特定字段或一组字段进行的搜索)。
这个示例返回编号为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子句指定了所有的查询,这些查询必须为真,文档才能被认为是匹配的。
相反,这个示例包含两个匹配查询,并返回地址中包含"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子句中组合bool查询,以模拟任何复杂的多级布尔逻辑。
这个例子返回任何40岁但不居住在ID(aho)中的人的所有账户:
GET /bank/_search
{
"query": {
"bool": {
"must": [
{ "match": { "age": "40" } }
],
"must_not": [
{ "match": { "state": "ID" } }
]
}
}
}
执行过滤
在上一节中,我们跳过了一个称为文档评分(搜索结果中的_score字段)的小细节。分数是一个数值,它是文档与我们指定的搜索查询匹配程度的相对度量。分数越高,说明文档越相关,分数越低,说明文档越不相关。
但是查询并不总是需要产生分数,特别是当它们仅用于“过滤”文档集时。Elasticsearch检测这些情况,并自动优化查询执行,以避免计算无用的分数。
我们在上一节中介绍的bool查询还支持筛选子句,它允许使用查询来限制将被其他子句匹配的文档,而不改变分数的计算方式。作为一个例子,让我们引入范围查询,它允许我们根据值的范围来筛选文档。这通常用于数字或日期筛选。
此示例使用bool查询返回余额在20000到30000之间的所有帐户(包括20000和30000)。换句话说,我们希望找到余额大于等于20000小于等于30000的账户。
GET /bank/_search
{
"query": {
"bool": {
"must": { "match_all": {} },
"filter": {
"range": {
"balance": {
"gte": 20000,
"lte": 30000
}
}
}
}
}
}
仔细分析上面的内容,bool查询包含一个match_all查询(查询部分)和一个范围查询(过滤器部分)。我们可以将任何其他查询替换到查询和过滤器部分。在上面的例子中,范围查询非常有意义,因为范围内的文档都匹配“相等”,也就是说,没有一个文档比另一个文档更相关。
除了match_all、match、bool和range查询之外,还有许多其他可用的查询类型,我们在这里不详细介绍。由于我们已经对它们的工作方式有了基本的了解,因此将这些知识应用于学习和试验其他查询类型应该不会太难。
执行聚合
聚合提供了从数据中分组和提取统计信息的能力。考虑聚合的最简单方法是将其大致等同于SQL GROUP by和SQL聚合函数。在Elasticsearch中,您能够执行返回命中的搜索,同时在一个响应中返回从命中分离出来的聚合结果。这是非常强大和有效的,因为您可以运行查询和多个聚合,并一次性获得两个(或其中一个)操作的结果,使用简洁和简化的API避免网络往返。
首先,这个示例按状态对所有帐户进行分组,然后返回按计数降序排序的前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(爱达荷州)中有27个帐户,TX(德克萨斯州)中有27个帐户,AL(阿拉巴马州)中有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"
}
}
}
}
}
}
}
}
总结
Elasticsearch是一个既简单又复杂的产品。到目前为止,我们已经了解了它是什么、如何查看它的内部以及如何使用一些REST api来处理它的基本知识。希望本教程能让您更好地理解Elasticsearch是什么,更重要的是,激发您进一步体验它的其他伟大特性!