• 进入"运维那点事"后,希望您第一件事就是阅读“关于”栏目,仔细阅读“关于Ctrl+c问题”,不希望误会!

Elasticsearch索引和文档操作

ELK 彭东稳 6年前 (2018-11-14) 21803次浏览 已收录 0个评论

一、索引与文档

  • 索引(Index)

一个索引就是含有某些相似特性的文档的集合。例如,你可以有一个用户数据的索引,一个产品目录的索引,还有其他的有规则数据的索引。一个索引被一个名称(必须都是小写)唯一标识,并且这个名称被用于索引通过文档去执行索引,搜索,更新和删除操作。

在一个集群中,你可以根据自己的需求定义任意多的索引。

类型(Type)[Deprecated in 6.0.0.]

警告!Type在6.0.0版本中已经不赞成使用

一个类型是你的索引中的一个分类或者说是一个分区,它可以让你在同一索引中存储不同类型的文档,例如,为用户建一个类型,为博客文章建另一个类型。现在已不可能在同一个索引中创建多个类型,并且整个类型的概念将会在未来的版本中移除。查看“映射类型的移除 [https://www.elastic.co/guide/en/elasticsearch/reference/current/removal-of-types.html] ”了解更多。

  • 文档(Document)

一个文档是一个可被索引的数据的基础单元。例如,你可以给一个单独的用户创建一个文档,给单个产品创建一个文档,以及其他的单独的规则。这个文档用JSON格式表现,JSON是一种普遍的网络数据交换格式。

在一个索引或类型中,你可以根据自己的需求存储任意多的文档。注意,虽然一个文档在物理存储上属于一个索引,但是文档实际上必须指定一个在索引中的类型。

二、创建索引

为了将数据添加到Elasticsearch,我们需要索引(index),一个存储关联数据的地方。实际上,索引只是一个用来指向一个或多个分片(shards)的“逻辑命名空间(logical namespace)”。

一个分片(shard)是一个最小级别“工作单元(worker unit)”,它只是保存了索引中所有数据的一部分。其实分片就是一个Lucene实例,并且它本身就是一个完整的搜索引擎。我们的文档(document)存储在分片中,并且在分片中被索引,但是我们的应用程序不会直接与它们通信,取而代之的是,直接与索引通信。

分片是Elasticsearch在集群中分发数据的关键,把分片想象成数据的容器,文档存储在分片中,然后分片分配到你集群中的节点上。当你的集群扩容或缩小,Elasticsearch将会自动在你的节点间迁移分片,以使集群保持平衡。

分片又可以是主分片(primary shard)或者是复制分片(replica shard)。你索引中的每个文档属于一个单独的主分片,所以主分片的数量决定了索引最多能存储多少数据。理论上主分片能存储的数据大小是没有限制的,限制取决于你实际的使用情况。分片的最大容量完全取决于你的使用状况:硬件存储的大小、文档的大小和复杂度、如何索引和查询你的文档,以及你期望的响应时间。

复制分片只是主分片的一个副本,它可以防止硬件故障导致的数据丢失,同时可以提供读请求,比如搜索或者从别的shard取回文档。

当索引创建完成的时候,主分片的数量就固定了,但是复制分片的数量可以随时调整。对于一个索引的主分片个数,默认是5个,复制分片默认是1个;当然,分片个数及复制分片的个数也可以在创建索引时指定,每个索引都可以创建不同的指标。

现在让我们在集群中创建一个“customer”索引,我们简单的在请求后面追加pretty参数来使返回值以格式化过美观的JSON输出(如果返回值是JSON格式的话)。

默认情况下,创建索引会在必要主分片已经创建成功并运行或请求超时后返回HTTP响应。创建索引的返回的响应:

其中 acknowledged 表示是否在集群成功创建索引,同时 shards_acknowledged 表示是否在超时之前成功创建运行必要的分片。

注意 acknowledged 和 shards_acknowledged 可能返回 false,但是索引创建成功。这些值只是表明是否操作在超时之前完成。如果 acknowledged 是 false,表示我们对集群更新新创建的索引操作超时,但它可能很快将被创建。如果 shards_acknowledged 是 false,表示我们必要的分片启动操作超时(默认只为主分片),即使集群成功更新新创建的索引。

现在我们现在检查集群健康(cluster-health),我们将见到以下信息:

结果告诉我们,我们现在有一个名为customer的索引,它有5个主分片和1个副本(默认值),并且它包含0个文档。

集群的健康状态green表示所有的主分片(primary shards)和复制分片(replica shards)启动并且正常运行了,集群已经可以正常处理任何请求。如果你看到的集群的健康状态为yellow表示所有的主分片(primary shards)启动并且正常运行了,集群已经可以正常处理任何请求——但是复制分片(replica shards)还没有全部可用,此时你应该看到unassign有一些未分配的复制分片。

三、获取索引

我们可以列出集群所有索引:

返回结果如下:

根据结果,我们可以看出列出了每个索引的健康状态,以及分片&副本和大小信息。

也可以查看当个索引的详细信息:

API必须指定一个索引,别名或通配符表达式。API也可以应用于多个索引,通过使用 _all 或者 *作为索引名。

返回的信息可以筛选,通过在URL后指定一个逗号分隔的列表:

上述命令返回 customer 的设置和映射,可用设置为 _settings, _mappings 和 _aliases。

四、索引和文档查询

现在让我们往 customer 索引中放点东西,如下请求将一个简单的顾客文档放入 customer 索引中,这个文档有一个 ID 为 1:

返回结果为:

从上面我们可以看到,一个新的文档已经在 customer 索引中成功创建,result 返回类型为 created,证明我们这是一个新建的文档(如果再次执行这个语句这个 result 返回的就是 updated)。另外还返回了操作成功的 shards。同时这个文档有一个自己的 id,这个 id 就是我们在将文档加入索引时指定的。

Note

这里有一个重要的注意点,你不需要在将一个文档加入一个索引前明确的将这个索引预先创建好。在上面我们创建文档的例子中,如果这个 customer 索引事先不存在,Elasticsearch 会自动创建 customer 索引。

另外,_doc 是文档类型(type),可以认为是一个默认文档类型。如果指定其他以 “_” 开头的文档类型是不被允许的,但可以指定非 “_” 开头的其他类型,一个索引只能由一个类型。

现在让我们获取刚刚加入索引的文档:

这里没有什么不寻常的,除了一个属性found,这个found属性表示我们通过请求ID为1发现了一个文档,还有另一个属性_source,_source属性返回我们在上一步中加入索引的完整JSON文档内容。

五、删除一个索引

现在让我们删除刚刚创建的索引并且再次列出所有的索引:

返回结果为:

以上结果意味着我们的索引已经被删除,并且我们回到了刚开始集群中什么都没有的地方。

如果我们在学习上面的命令时非常仔细的话,我们一定会发现在Elasticsearch中访问数据的模式。这个模式可以总结为以下形式:<REST Verb> /<Index>/<Type>/<ID>。这种REST访问模式遍布所有的API命令,如果简单的记住它,你将会在掌握Elasticsearch的过程中有一个很好的开端。

六、修改数据

Elasticsearch提供了近实时的数据操作和搜索能力。默认情况下,从你开始索引/更新/删除你的数据到出现搜索结果的时间会有一秒的延时(刷新间隔)。这个与其它的SQL数据库平台在一个事务完成后立即获取到数据这一点上有很大的优势。

  • 将文档放入索引/替换索引中的文档

我们之前已经看过如何将一个文档放入索引中,让我们再次回忆一下那个命令:

再次补充说明一下,上面的请求将会将一个 ID 为 1 的文档加入customer索引。如果我们再次执行上面的请求,以相同的文档内容或者是不同的,Elasticsearch 将会用这个新文档替换之前的文档(就是以相同的 ID 重新加入索引)。

上述操作将 ID 为 1 的文档的 name 属性从 “John Doe” 改成了 “Jane Doe”。设想另一种场景,我们使用一个不同的 ID,这样的话将会创建一个新的文档,而之前的文档还是保持原样。

当将文档加入索引时,ID 部分并不是必须的。如果没有指定,Elasticsearch 将会生产一个随机的 ID,然后使用它去索引文档。实际 Elasticsearch 生成的 ID(或者是我们明确指定的)将会在 API 调用成功后返回。

如下这个例子演示如何使用隐式的 ID 将一个文档加入索引:

注意在上面的例子中,当我们没有明确指定 ID 的时候,我们需要使用POST方法代替PUT来发送请求。

返回结果

除了 _id 是 Elasticsearch 自动生成的,响应的其他部分和前面的类似。

自动生成的 ID 是 URL-safe、 基于 Base64 编码且长度为 20 个字符的 GUID 字符串。 这些 GUID 字符串由可修改的 FlakeID 模式生成,这种模式允许多个节点并行生成唯一 ID ,且互相之间的冲突概率几乎为零。

  • 更新文档

除了能够新增和替换文档,我们也可以更新文档。注意虽然Elasticsearch在底层并没有真正更新文档,而是当我们更新文档时,Elasticsearch首先去删除旧的文档,然后加入新的文档。

如下的例子演示如何去更新我们的之前ID为1的文档,在这里将name属性改为“Jane Doe”,并同时添加新的age属性:

此时,如果再去更新name属性为”dkey“,其age属性并不会改变了。

更新操作也可以使用简单的脚本来执行。如下的示例使用一个脚本将age增加了5:

在上面的示例中,ctx._source指代的是当前需要被更新的source文档。

Elasticsearch提供了一次更新多个文档的功能,通过使用查询条件(比如SQL的UPDATE-WHERE语句)。详情查看Update By Query API

  • 删除文档

删除一个文档操作相当的直截了当。如下的示例演示了如何删除我们之前ID为2的文档:

查看Delete By Query API去删除匹配特定条件的所有的文档。有一个值得注意的的地方是,直接删除整个索引比通过Query API删除索引中的所有文档更高效。

  • 批处理

除了在单个文档上执行索引,更新和删除操作外,Elasticsearch还提供了批操作的功能,通过使用_bulk完成。这个功能非常重要,因为它提供了一种非常高效的机制去通过更少的网络切换尽可能快的执行多个操作。

作为一个快速入门示例,如下请求在一个批操作中创建了两个文档:

如下的示例在一个批操作中首先更新ID为1的文档,然后删除ID为2的文档:

注意上面的删除操作,删除时只需要指定被删除的文档的ID即可,不需要指定对应的source内容。

批处理API不会因为单条指令失败而全部失败。如果里面有单条指令因为一些原因失败了,那么整个批处理还会继续执行它后面剩余的指令。当批处理API返回时,它会提供每条指令的执行状态(以发送时的顺序),以便你可以检查一个特定的指令是否失败。

七、查询数据

  • 样本数据集

既然我们已经了解了基础知识,让我们来尝试操作一些更真实的数据集。我已经预先准备好了一些虚拟的顾客银行账户信息JSON文档样本。每一个文档都有如下的机构:

这些数据是在http://www.json-generator.com生成的。所有请忽略这些数据值的实际意义,因为它们都是随机生成的。

你可以从这里下载样本数据集(accounts.json)。把它放到我们当前的目录下,然后使用如下的命令把它加载到我们得集群中:

  • 搜索API

现在,让我们从一些简单的搜索指令开始。执行搜索有两种基础的方式,一种是在请求的URL中加入参数来实现,另一种方式是将请求内容放到请求体中。使用请求体可以让你的JSON数据以一种更加可读和更加富有展现力的方式发送。我们将会在一开始演示一次使用请求URI的方式,然后在本教程剩余的部分,我们将统一使用请求体的方式发送。

REST API可以使用_search端点来实现搜索。如下的示例将返回bank索引的所有的文档:

让我们首先来详细分析一下这个搜索请求。这个请求在bank索引中进行搜索(使用 _search 端点),然后 q=* 参数命令Elasticsearch匹配索引中的全部文档。sort=account_number:asc 参数表示按 account_number 属性升序排列返回的结果。pretty 参数之前已经提到过,就是将返回结果以美观的格式返回。

返回结果为(展示部分):

关于返回结果,我们看到了如下的部分:

took – Elasticsearch执行此次搜索所用的时间(单位:毫秒)。

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

_shards – 告诉我们搜索了多少分片,还有搜索成功和搜索失败的分片数量。

hits – 搜索结果。

hits.total – 符合搜索条件的文档数量。

hits.hits – 实际返回的搜索结果对象数组(默认只返回前10条)。

hits.sort – 返回结果的排序字段值(如果是按score进行排序,则没有)。

hits._score 和 max_score – 目前先忽略这两个字段。

如下是相同效果的另一种将数据放入请求体的方式:

这里的不同点在于我们使用一个JSON格式的请求体代替了在URI中的 q=* 参数。我们将在下一节详细讨论这种JSON格式的查询方式。

一旦你得到了返回结果,Elasticsearch就完全执行结束,不会保持任何的服务器资源或者往你的结果里加入开放的游标,理解这一点是非常重要的。这同很多其他的平台,比如SQL数据库的一些特性形成了鲜明的对比,比如在SQL数据库中你可能在查询时,会首先得到查询结果的一部分,然后你需要通过一些有状态的服务端游标不断地去请求服务端来取得剩余的查询结果。

  • 介绍查询语言

Elasticsearch提供了一种JSON格式的领域特定语言,你可以使用它来执行查询。这个通常叫做Query DSL。这门查询语言相当的全面以至于你第一次看到它时会被它吓住,不过学习它最好的方式就是从一些简单的示例程序开始。

回到我们上个例子,我们执行了这个查询:

分析以上查询,query 部分告诉我们我们的查询定义是什么,match_all 部分简单指定了我们想去执行的查询类型,意思就是在索引中搜索所有的文档。

除了query参数,我们还可以通过其他的参数影响搜索结果。在上一节的示例中我们使用了sort来指定搜索结果的顺序,这里我们指定size来指定返回的结果数量:

注意如果size没有指定,它默认为10。

如下的示例使用match_all并返回了11到20的文档:

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

如下示例使用match_all并且按账户的balance值进行倒序排列后返回前10条文档:

  • 执行搜索

既然我们已经了解了一些基础的搜索参数,那就让我们来深入学习一下Query DSL吧。首先,我们来关注一下返回的文档属性。默认情况下,文档会作为搜索结果的一部分返回所有的属性值。这个文档的JSON内容被称为source(返回结果中的hits的_source属性值)。如果我们不需要返回所有的source文档属性,我们可以在请求体中加入我们需要返回的属性名。

如下的示例演示了如何返回两个属性,account_number 和 balance (在_source中):

注意上面的例子仅仅只是减少了_source里的属性。它仍然会返回_source属性,只不过_source属性中之包含account_numberbalance两个属性。如果你之前学过SQL,上面的示例有点像SQL中的SELECT FROM中指定返回的字段列表。

现在,让我们的视线转到查询部分。之前我们已经看到如何使用match_all来匹配所有的文档。现在让我们介绍一个新的查询叫做match 查询,它可以被认为是基本的属性搜索查询(就是通过特定的一个或多个属性来搜索)。

如下的示例返回account_number为20的文档:

如下示例返回所有的address字段中包含“mill”这个单词的账户文档:

如下示例返回所有的address字段中包含“mill”或者是“lane”的账户文档:

如下示例是match的一种变体(match_phrase),这个将返回所有address中包含“mill lane”这个短语的账户文档:

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

如下的示例组合两个match查询并且返回所有address属性中包含 “mill”  “lane” 的账户文档:

在上述示例中,bool must 子句指定了所有匹配文档必须满足的条件。

相比之下,如下的示例组合两个match查询并且返回所有address属性中包含 “mill”  “lane” 的账户文档:

在上述的例子中,bool should 子句指定了匹配文档只要满足其中的任何一个条件即可匹配。

如下示例组合两个match查询并且返回所有address属性中既不包含 “mill” 也不包含 “lane” 的账户文档:

在上述例子中,bool must_not 子句指定了其中的任何一个条件都不满足时即可匹配。

我们可以在一个bool查询中同时指定mustshouldmust_not子句。此外,我们也可以在一个bool子句中组合另一个bool来模拟任何复杂的多重布尔逻辑。

如下的示例返回所有age属性为40,并且state属性不为ID的账户文档:

bool 嵌套 bool

  • 执行过滤

在之前的章节中,我们跳过了一个叫做文档得分(在搜索结果中的_score属性)的小细节。这个得分是一个数值,它是一个相对量,用来衡量搜索结果跟我们指定的关键字的相关程度。分数越高,说明这个文档的相关性越大,分数越低,说明这个文档的相关性越小。

但是一些查询结果并不总是需要产生得分,尤其是当他们仅仅被用来过滤文档集的时候。Elasticsearch会检测这种情况并自动优化查询以免计算无用的分数。

我们在前面章节介绍的 bool 查询也支持 filter 子句,它允许我们可以在不改变得分计算逻辑的的情况下限制其他子句匹配的查询结果。为了示例说明,让我们介绍一下range 查询,它允许我们通过一个值区间来过滤文档。这个通常用在数值和日期过滤上。

如下的示例使用bool查询返回所有余额在20000到30000之间的账户(包含边界)。换句话说,我们想查询账户余额大于等于20000并且小于等于30000的用户。

仔细分析一下上面的例子,bool查询在查询部分使用match_all,在过滤部分使用range。我们可以使用任何的查询来代替查询部分和过滤部分。在上面的例子中,range查询让结果更加合乎情理,因为文档在这个区间中一定是符合的,就是说,没有比这些相关性更大的了。

除了match_allmatchbool,和range查询之外,还有很多其他的查询类型,在这里我们就不一一介绍了。当我们对这些基础的理解了之后,再去学习和使用其他的查询类型应该是不会太难了。

  • 执行聚合

聚合提供了功能可以分组并统计你的数据。理解聚合最简单的方式就是可以把它粗略的看做SQL的GROUP BY操作和SQL的聚合函数。在Elasticsearch中,你可以在执行搜索后在一个返回结果中同时返回搜索结果和聚合结果。你可以使用简洁的API执行搜索和多个聚合操作,并且可以一次拿到所有的结果,避免网络切换,就此而言,这是一个非常强大和高效功能。

作为开始,如下的例子将账户按state进行分组,然后按count降序(默认)返回前10组(默认)states。

上面的聚合的例子跟如下的SQL类似:

返回结果为(展示部分):

我们可以看到有27个账户在ID(爱达荷州),然后27个在TX(得克萨斯州),还有25个在AL(亚拉巴马州),等等。

注意我们设置了size=0来不显示hits搜索结果,因为我们这里只关心聚合结果。

如下示例我们在上一个聚合的基础上构建,这个示例计算每个state分组的平均账户余额(还是使用默认按count倒序返回前10个):

注意我们是怎么嵌套average_balance聚合到group_by_state聚合中的。

这是一个适用于所有聚合操作的通用模式。你可以任意嵌套聚合,从你的数据中提取你需要的主题汇总。

如下例子依然是在之前的聚合上构建,我们现在来按平均余额倒序排列:

如下示例演示我们如何按年龄区间分组(20-29,30-39,40-49),然后按性别,最后获取每个年龄区间,每个性别的平均账户余额:

还有很多其它的聚合功能在这里我们就不去详细介绍了。如果你想了解更多,可以参考聚合参考手册

八、结论

Elasticsearch是一个既简单又复杂的产品。我们到目前为止已经学习了基础的知识,知道了它是什么,它内部的实现原理,以及如何使用REST API去操作它。希望此教程能帮助你理解Elasticsearch以及更重要的东西,鼓励你去实践它剩余的更多的特性!

<转载>

https://blog.csdn.net/HoloLens/article/details/78932628


如果您觉得本站对你有帮助,那么可以支付宝扫码捐助以帮助本站更好地发展,在此谢过。
喜欢 (0)
[资助本站您就扫码 谢谢]
分享 (0)

您必须 登录 才能发表评论!