Elasticsearch使用

Getting Started

基本概念

Near Realtime(近实时)

Elasticsearch 是一个near-realtime(接近于实时)的搜索平台。从索引文档到它被搜索到只用轻微的延迟(通常是1秒)

Cluster(集群)

集群是一个或多个节点的集合,这些节点被用来保存数据或提供索引和跨节点搜索服务。集群有唯一的名称标识(cluster name),通常是elasticsearch。一个node只有通过这个唯一的名称才可以加入这个集群。

不同的环境应该使用不同的cluster name。以免节点加错集群。

单节点集群是有效的,你也可以建多个有不同cluster name的集群。

Node(节点)

节点是集群的一个服务,被用来存储数据,并参与集群的索引和提供搜索功能。和集群一样,节点通过名字区分,默认情况下这个名字是一个随机的UUID。如果你不希望使用默认的节点名,可以重新定义。如果你希望识别出哪些服务与那些节点有关系,这个名称很关键。

每个节点可以通过cluster name加入到集群。每个节点都会默认的加入到名为elasticsearch的集群。这意味着,如果你启动多个node,并且他们在网络上互相可见,他们将会自动组成一个名为elasticsearch的集群。

一个集群中可以有多个节点。如果网络上没有其他节点,启动单个节点将默认形成一个名为Elasticsearch的单节点集群。

Index(索引)

索引是一个有相似特征的文档的集合,例如,你可以有一个用户数据索引,一个产品目录索引,和其他数据索引。通过一个全小写的名称定义一个索引,此名称用于在对其中的文档执行索引(动词)、搜索、更新和删除操作时用到。

在一个集群中可以定义多个索引。

Type(类型)

类型是索引的逻辑分区,你可以在同一个索引下为不同的文档建立类型(type)。比如一个用户类型,一个文章类型。现在已经不可能在索引中创建多个类型,而且类型的整个概念将在以后的版本中被删除。

在Elasticsearch 6.0.0或更高版本中创建的索引可能只包含一个映射类型。5.x中创建的具有多种映射类型的索引将继续在Elasticsearch中发挥作用。映射类型将在Elasticsearch 7.0.0中完全删除。

Document(文档)

文档是可以被索引的基础单元。比如单个用户作为一个文档、单个产品作为一个文档。文档用json表示。

在同一个索引或类型中,可以保存多个文档。虽然文档物理上保存在索引中,但是一个文档必须被分配给索引中的类型。

Shards&Replicas(分片和副本)

索引可能存储超过单个节点硬件资源限制的大量数据。例如,一个包含10亿个文档、占用1TB磁盘空间的索引可能不适用于单个节点的磁盘,或者只有一个几点可能处理搜索请求的速度太慢。

为了解决这个问题,elasticsearch提供了可以将一个索引分为多个部分的能力。每个部分称为一个分片。当你创建索引的时候,你可以简单的定义你想要的分片的数量。每个分片本身就是一个功能完整且独立的”索引”,可以驻留在集群中的任何节点上。

分片重要的两个原因:

它允许你水平扩展和缩放你的数据卷。

它允许你跨分片(可能在多个节点上)分布和并行化操作,从而提高性能/吞吐量。

分片如何分布以及如何将其文档聚合回搜索请求的机制完全由Elasticsearch管理,对于用户来说是透明的。

在随时可能出现故障的云环境中,当节点或分片由于某种原因脱机或消失时,故障转移机制是非常有用的。所以强烈建议开启故障转移机制。为此,Elasticsearch允许您将索引分片的一个或多个副本复制为所谓的复制分片,或简称为副本。

副本重要的两个原因

当分片或节点出现问题的时候,它提供了高可用性。因此,一定要注意的是副本不能与它被复制的原始/主分片分配在同一个节点上。

因为搜索可以并行地在所有副本上执行,所以它允许您扩展搜索量/吞吐量。

总而言之,每个索引可以被分成多个分片。索引还可以复制0次(即没有副本)或更多次。复制之后,每个索引都将拥有主分片(从中复制的原始碎片)和副本分片(主碎片的副本)。

可以在创建索引时为每个索引定义分片和副本的数量。创建索引之后,还可以随时动态更改副本的数量。您可以使用_shrink和_split api更改现有索引的分片数量,但是这不是一项简单的任务,预先计划正确的分片数量是最优的方法。

默认情况下,Elasticsearch中的每个索引分配5个主分片和1个副本,这意味着如果您的集群中至少有两个节点,那么您的索引将拥有5个主分片和5个副本分片(1个完整副本),每个索引总共有10个分片。

每个Elasticsearch分片都是Lucene索引,在一个Lucene索引中可以拥有的文档数量

有一个最大值。截至LUCENE-5843,该限制为2,147,483,519个(=Integer.MAX_VALUE – 128)文档。可以使用_cat/shards API监视分片规模和大小。

安装

Helm安装:

Es: http://songjxin.cn/?p=246

Kibana: http://songjxin.cn/?p=267

其他安装:

https://www.elastic.co/guide/en/elasticsearch/reference/6.5/_installation.html

探索集群

现在我们已经启动并运行了节点(和集群),下一步是了解如何与它通信。Elasticsearch提供了一个非常全面和强大的REST API,您可以使用它与集群交互。使用API可以做的事情如下:

检查集群、节点和索引健康状态、状态和统计信息

管理集群、节点和索引数据和元数据

对索引执行CRUD(创建、读取、更新和删除)和搜索操作

执行高级搜索操作,如分页、排序、筛选

集群健康

GET /_cat/health?v

Status:

Green 集群正常

Yellow 所有主分片正常,副分片存在异常

Red 部分主分片异常

当集群为红色时,它将继续为来自可用碎片的搜索请求提供服务,但是您可能需要尽快修复它,因为存在未分配的碎片。

GET /_cat/nodes?v

列出所有目录

GET /_cat/indices?v

创建一个索引

PUT /customer?pretty

GET /_cat/indices?v

第一条命令是用PUT命令创建一个名为customerd的索引。我们只是在调用的末尾追加pretty,告诉它打印JSON响应(如果有的话)。

响应中红色的提示es版本7.0之后分片的默认数是从5变到1 可以忽略。

第二个命令的结果告诉我们,现在有一个名为customer的索引,它有5个主碎片和1个副本(默认值),其中包含0个文档。

索引并查询文档

现在让我们把一些东西放入我们的客户索引中。我们将一个简单的客户文档编入客户索引,ID为1,如下所示:

PUT /customer/_doc/1?pretty

{

“name”: “John Doe”

}

从上面,我们可以看到在customer索引中成功地创建了一个新的客户文档。文档的内部id也为1,这是我们在索引时指定的。

需要注意的是,Elasticsearch不要求您先显式地创建索引,然后才能将文档索引到其中。在前面的例子中,Elasticsearch将自动创建客户索引,如果它之前不存在的话。

GET /customer/_doc/1?pretty

found字段 表明我们找到了一个带有请求ID 1的文档

_source字段 返回我们从上一步索引的完整JSON文档

删除一个索引

DELETE /customer?pretty

GET /_cat/indices?v

我们可以看到在Elasticsearch中访问数据的模式:

<HTTP Verb>
/<Index>/<Type>/<ID>

修改数据

Elasticsearch提供近实时的数据处理和搜索功能。默认情况下,从索引/更新/删除数据到出现在搜索结果中,可能会有一秒的延迟(刷新间隔)。这是与其他平台(如SQL)的一个重要区别,在SQL中,事务完成后数据立即可用。

索引和替换文档

我们添加一条数据

PUT /customer/_doc/1?pretty

{

“name”: “John Doe”

}

上面将指定的文档索引到customer索引中,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”

}

上面的代码索引了ID2的新文档。

索引时,ID部分是可选的。如果没有指定,Elasticsearch将生成一个随机ID,然后使用它来索引文档。实际的ID Elasticsearch生成(或者我们在前面的示例中明确指定的任何内容)作为索引API调用的一部分返回。

这个例子展示了如何索引一个没有显式ID的文档:

POST /customer/_doc?pretty

{

“name”: “Jane Doe”

}

更新文档

除了能够索引和替换文档之外,我们还可以更新文档。请注意,Elasticsearch实际上并没有在底层进行就地更新。每当我们进行更新时,Elasticsearch都会删除旧文档,然后将更新应用到的新文档一次性索引到新文档。

这个例子展示了如何通过将name字段更改为”Jane Doe”来更新我们之前的文档(ID为1):

POST /customer/_doc/1/_update?pretty

{

“doc”: { “name”: “Jane Doe” }

}

这个和之前的替换文档没有区别。

这个例子展示了如何更新我们之前的文档(ID为1),方法是将name字段更改为”Jane Doe”,同时添加一个age字段:

POST /customer/_doc/1/_update?pretty

{

“doc”: { “name”: “Jane Doe”, “age”: 20 }

}

还可以使用简单的脚本执行更新。本例使用脚本将年龄增加5岁:

POST /customer/_doc/1/_update?pretty

{

“script” : “ctx._source.age += 5”

}

上面例子中,ctx._source指的是将要升级的文档的source

Elasticsearch提供了在给定查询条件(如SQL update – where语句)下更新多个文档的能力。详情查看docs-update-by-query API。

删除文档

删除文档相当简单。这个例子展示了如何删除我们之前ID为2的客户:

DELETE /customer/_doc/2?pretty

查看_delete_by_query API来删除与特定查询匹配的所有文档。删除整个索引要比使用delete By Query API删除所有文档有效得多。

批量处理

除了能够索引、更新和删除单个文档外,Elasticsearch还提供了使用_bulk API批量执行上述任何操作的能力。这个功能非常重要,因为它提供了一种非常有效的机制,可以以尽可能少的网络往返尽可能快地执行多个操作。

作为一个快速示例,以下命令在一个批量操作中索引两个文档(ID 1 – John Doe和ID 2 – Jane Doe):

POST /customer/_doc/_bulk?pretty

{“index”:{“_id”:”1″}}

{“name”: “John Doe” }

{“index”:{“_id”:”2″}}

{“name”: “Jane Doe” }

探索数据

准备数据

下载随机生成的数据,并上传到es

wget “https://github.com/elastic/elasticsearch/blob/master/docs/src/test/resources/accounts.json”

curl -H “Content-Type: application/json” -XPOST “10.110.25.74:54206/bank/_doc/_bulk?pretty&refresh” –data-binary “@accounts.json”

curl “10.110.25.74:54206/_cat/indices?v”

搜索API(search API)

运行搜索有两种基本方法:一种是通过REST request URI发送搜索参数,另一种是通过REST request body。Request body的方法更直观,并且可以使用可读性更强的json来格式化。下面是一个request URI方法的示例,但是在之后的部分所有的实例将只使用request body的方法:

返回银行索引中所有文档:

GET /bank/_search?q=*&sort=account_number:asc&pretty

我们在 blank (/bank 索引中执行 search (_search)操作。参数q=*
指示Elasticsearch匹配索引中的所有文档。sort=account_number:asc参数指示使用每个文档的account_number字段按升序对结果进行排序。Pretty参数只是告诉Elasticsearch返回json格式的结果。

在本次返回结果中我们可以看到以下部分:

took – Elasticsearch执行搜索的毫秒时间

timed_out – 告诉我们搜索是否超时

_shards – 告诉我们搜索了多少分片,以及成功/失败搜索分片的个数。

Hits – 搜索结果。

hits.total – 符合我们搜索条件的文档总数

hits.hits – 实际搜索结果数组(默认为前10个文档)

下面是上面使用request body方法进行的相同搜索:

GET /bank/_search

{

“query”: { “match_all”: {} },

“sort”: [

{ “account_number”: “asc” }

]

}

这里的不同之处在于,我们没有在URI中传递q=*,而是向_search API提供了json风格的查询请求体。

一旦您获得了搜索结果,Elasticsearch就完全完成了请求,不会维护任何类型的服务器端资源,也不会在结果中打开游标。和sql等其他平台不同的是,sql可以动态的获取结果,比如预先获取其中一部分,然后通过不停的请求服务器获取其他数据。

查询语言

Elasticsearch提供了一种json风格的特定于领域的语言——Query DSL,您可以使用它来执行查询。

继续查看上一个例子

GET /bank/_search

{

“query”: { “match_all”: {} }

}

SQL:select * from bank limit 0 10;

query部分告诉我们我们查询定义是什么,match_all部分只是我们想要查询的类型,match_all

查询只是搜索指定索引中的所有文除了查询参数,我们还可以传递其他参数来影响搜索结果。在上面我们以sort传递的例子中,这里我们以size传递:

GET /bank/_search

{

“query”: { “match_all”: {} },

“size”: 1

}

SQL: select * from bank limit 0,1;


如果size没有定义,它的默认值是0。

这个例子执行match_all并返回文档10到19:

GET /bank/_search

{

“query”: { “match_all”: {} },

“from”: 10,

“size”: 10

}

SQL:select * from bank limit 10,10;


from参数(从0开始)指定从哪个文档索引开始,size参数指定从from参数开始返回多少文档。该特性在实现搜索结果分页时非常有用。注意,如果没有指定from,则默认为0。

这个示例执行match_all并按帐户余额降序排序结果,并返回前10个(默认大小)文档:

GET /bank/_search

{

“query”: { “match_all”: {} },

“sort”: { “balance”: { “order”: “desc” } }

}

SQL:select * from bank order by balance desc limit 0,10;

搜索

让我们首先看看返回的文档字段。默认情况下,完整的JSON文档作为所有搜索的一部分返回。这称为源(搜索命中的_source字段)。如果不希望返回整个源文档,则可以只从源中请求返回几个字段。

这个例子展示了如何从搜索中返回两个字段account_numberbalance(在_source内部):

GET /bank/_search

{

“query”: { “match_all”: {} },

“_source”: [“account_number”, “balance”]

}

SQL:select account_number,balance from bank;

注意,上面的示例只是简化了_source字段。它仍然只返回一个名为_source的字段,但是其中只包含account_numberbalance字段。

现在让我们转到查询部分。在前面,我们已经看到了如何使用match_all查询来匹配所有文档。现在我们引入一个新的查询,称为match查询,它可以看作是一个基本的字段搜索查询(即针对特定字段或字段集进行的搜索)。

这个例子返回编号为20的帐户:

GET /bank/_search

{

“query”: { “match”: { “account_number”: 20 } }

}

SQL: select * from bank where account_number=20;

本例返回地址中包含术语”mill”的所有帐户:

GET /bank/_search

{

“query”: { “match”: { “address”: “mill” } }

}

SQL: select * from bank where address REGEXP ‘\\bmill\\b’ limit 0,10;


            

本例返回地址中包含术语”mill”或”lane”的所有帐户:

GET /bank/_search

{

“query”: { “match”: { “address”: “mill lane” } }

}

select * from bank where address REGEXP ‘((\\bmill\\b)|(\\blane\\b))’ limit 0,10;

这个例子是match (match_phrase)的变体,它返回地址中包含短语”mill lane”的所有帐户:

GET /bank/_search

{

“query”: { “match_phrase”: { “address”: “mill lane” } }

}

SQL:select * from bank where address REGEXP ‘\\bmill lane\\b’;

现在让我们介绍bool查询。bool查询允许我们使用布尔逻辑将较小的查询组合成较大的查询。

本例由两个匹配查询组成,返回地址中包含”mill”和”lane”的所有帐户:

GET /bank/_search

{

“query”: {

“bool”: {

“must”: [

{ “match”: { “address”: “mill” } },

{ “match”: { “address”: “lane” } }

]

}

}

}

SQL: select * from bank where address REGEXP ‘\\bmill\\b’ and address regexp ‘\\blane\\b’ ;

在上面的例子中,bool must子句指定了所有必须为true的查询,以便将文档视为匹配。

与此相反,本例由两个匹配查询组成,返回地址中包含”mill”或”lane”的所有帐户:

GET /bank/_search

{

“query”: {

“bool”: {

“should”: [

{ “match”: { “address”: “mill” } },

{ “match”: { “address”: “lane” } }

]

}

}

}

SQL:select * from bank where address REGEXP ‘\\bmill\\b’ or address regexp ‘\\blane\\b’ limit 0,10 ;

在上面的例子中,bool should子句指定了一个查询列表,如果文档被认为是匹配的,那么其中任何一个查询都必须为true。

本例由两个匹配查询组成,返回地址中既不包含”mill”也不包含”lane”的所有帐户:

GET /bank/_search{

“query”: {

“bool”: {

“must_not”: [

{ “match”: { “address”: “mill” } },

{ “match”: { “address”: “lane” } }

]

}

}

}

SQL:select * from bank where address not REGEXP ‘\\bmill\\b’ and address not regexp ‘\\blane\\b’ ;

在上面的例子中,bool must_not子句指定了一个查询列表,其中没有一个查询必须为true,文档才能被认为是匹配的。

我们可以在bool查询中同时组合must、should和must_not子句。此外,我们可以在这些bool子句中组合bool查询来模拟任何复杂的多层布尔逻辑。

这个例子返回所有40岁但不居住在ID中的人的帐户:

GET /bank/_search

{

“query”: {

“bool”: {

“must”: [

{ “match”: { “age”: “40” } }

],

“must_not”: [

{ “match”: { “state”: “ID” } }

]

}

}

}

SQL: select * from bank where age = 40 and state != ‘ID’ limit 0,10;

过滤

在上一节中,我们跳过了一个称为文档得分(搜索结果中的_score字段)的小细节。分数是一个数值,它是文档与我们指定的搜索查询匹配程度的一个相对度量。分数越高,文档越相关,分数越低,文档越不相关。

但是查询并不总是需要生成分数,特别是当它们只用于”过滤”时。Elasticsearch能够发现这种情况并自动优化查询执行,以避免计算无用的分数。

我们在上一节中介绍的bool查询还支持筛选子句,这些子句允许我们不更改计算分数方式的情况下过滤文档。 例如,让我们引入范围查询,它允许我们通过一系列值过滤文档。这通常用于数字或日期筛选。

本例使用bool查询返回所有余额在20000到30000之间的帐户(包括在内)。换句话说,我们希望找到余额大于等于20000和小于等于30000的账户。

GET /bank/_search

{

“query”: {

“bool”: {

“must”: { “match_all”: {} },

“filter”: {

“range”: {

“balance”: {

“gte”: 20000,

“lte”: 30000

}

}

}

}

}

}

SQL: select * from bank where balance >= 20000 and balance <= 30000 ;

仔细分析上述内容,bool查询包含一个match_all查询(查询部分)和一个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 LIMIT 10;

存在误差。

我们可以看到ID 有27个账号,TX 有27个账号,AL有25个账号,依此类推。

注意,我们将size=0设置为不显示搜索结果(hits),因为我们只想在响应中看到聚合结果。

在上一个聚合的基础上,这个例子按状态计算平均账户余额(同样只针对按计数降序排序的前10个状态):

GET /bank/_search

{

“size”: 0,

“aggs”: {

“group_by_state”: {

“terms”: {

“field”: “state.keyword”

},

“aggs”: {

“average_balance”: {

“avg”: {

“field”: “balance”

}

}

}

}

}

}

SQL:SELECT state, COUNT(*),AVG(balance) FROM bank GROUP BY state ORDER BY COUNT(*) DESC limit 10;

注意我们如何将average_balance聚合嵌套在group_by_state聚合中。这是所有聚合的常见模式。您可以在聚合中任意嵌套聚合,以从数据中提取所需的旋转摘要。

在前面的聚合的基础上,我们现在按平均余额降序排序:

GET /bank/_search

{

“size”: 0,

“aggs”: {

“group_by_state”: {

“terms”: {

“field”: “state.keyword”,

“order”: {

“average_balance”: “desc”

}

},

“aggs”: {

“average_balance”: {

“avg”: {

“field”: “balance”

}

}

}

}

}

}

SQL:SELECT state, COUNT(*),AVG(balance) FROM bank GROUP BY state ORDER BY AVG(balance) DESC limit 10;

这个例子演示了我们如何根据年龄(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”

}

}

}

}

}

}

}

}

还有许多其他聚合功能,我们在这里不详细介绍。如果您想做进一步的实验,聚合参考指南是一个很好的起点。

结语

弹性搜索是一个简单而复杂的乘积。到目前为止,我们已经了解了它是什么,如何查看它的内部,以及如何使用一些REST api使用它。希望本教程能让您更好地理解什么是Elasticsearch,更重要的是,它还能激发您进一步试验它的其他伟大特性!

Author: jxin

发表评论

电子邮件地址不会被公开。 必填项已用*标注