ElasticSearch GET API的存储字段管理
ElasticSearch GET API 基础概述
Elasticsearch 是一个分布式、RESTful 风格的搜索和数据分析引擎,被广泛应用于全文搜索、结构化搜索、分析以及这三个功能的组合。GET API 在 Elasticsearch 中扮演着至关重要的角色,它允许用户从 Elasticsearch 集群中检索文档。
简单 GET 请求获取文档
在 Elasticsearch 中,使用 GET API 最简单的形式就是根据文档的索引、类型(在 Elasticsearch 7.0 之后类型逐渐被弃用)和 ID 来获取文档。例如,假设我们有一个名为 employees
的索引,在早期版本可能有一个 employee
类型(现在可忽略类型概念),并且有一个 ID 为 1
的文档。我们可以通过以下的 RESTful 请求来获取这个文档:
GET /employees/_doc/1
上述请求会返回 employees
索引中 ID 为 1
的文档内容。Elasticsearch 的响应会包含文档的元数据以及文档的实际内容。例如,响应可能如下:
{
"_index": "employees",
"_type": "_doc",
"_id": "1",
"_version": 1,
"_seq_no": 0,
"_primary_term": 1,
"found": true,
"_source": {
"name": "John Doe",
"age": 30,
"department": "Engineering"
}
}
在这个响应中,_index
表示文档所在的索引,_type
目前多为 _doc
,_id
是文档的唯一标识符,_version
表示文档的版本号,_seq_no
和 _primary_term
用于乐观并发控制,found
表示是否找到了该文档,_source
则包含了文档的实际内容。
源字段过滤
在很多情况下,我们可能并不需要获取文档的全部内容,而只是其中的部分字段。Elasticsearch 的 GET API 提供了源字段过滤的功能,允许我们指定想要获取的字段。例如,如果我们只想获取 employees
文档中的 name
和 department
字段,可以这样请求:
GET /employees/_doc/1?_source=name,department
响应结果将只包含我们指定的字段:
{
"_index": "employees",
"_type": "_doc",
"_id": "1",
"_version": 1,
"_seq_no": 0,
"_primary_term": 1,
"found": true,
"_source": {
"name": "John Doe",
"department": "Engineering"
}
}
我们还可以通过排除某些字段的方式来实现类似的效果。比如,不想获取 age
字段,可以这样写:
GET /employees/_doc/1?_source_exclude=age
这样响应结果就不会包含 age
字段。
ElasticSearch 存储字段的概念
在 Elasticsearch 中,存储字段是指文档中实际存储的数据部分,也就是 _source
中的内容。理解存储字段的管理对于有效地使用 Elasticsearch 非常重要。
存储字段与索引字段的区别
Elasticsearch 中的字段可以分为存储字段和索引字段。索引字段主要用于搜索,Elasticsearch 会对这些字段进行分词、倒排索引等操作,以便能够快速地根据这些字段进行搜索。而存储字段则是文档的原始数据部分,主要用于在需要时完整地获取文档内容。
例如,对于一个包含文章内容的文档,文章的标题字段可能既被索引用于搜索,也被存储以便在搜索结果中展示完整标题。而文章的正文内容可能主要是被索引用于全文搜索,在某些场景下并不需要每次都完整地存储和返回,此时可以选择只索引正文而不存储。
存储字段的存储方式
Elasticsearch 默认会将整个文档存储在 _source
字段中。这意味着当我们使用 GET API 获取文档时,能够得到完整的原始文档内容。_source
字段以 JSON 格式存储,这使得它非常灵活,可以包含各种复杂的数据结构,如对象、数组等。
然而,存储整个文档可能会占用较多的磁盘空间,尤其是对于大文档或者有大量文档的索引。在一些情况下,我们可以选择只存储部分字段,以减少存储空间的占用。同时,Elasticsearch 也提供了压缩 _source
字段的选项,以进一步节省空间。
ElasticSearch GET API 的存储字段管理
获取特定存储字段
通过 GET API 的源字段过滤功能,我们可以精准地获取文档中的特定存储字段,这在前面已经有所提及。但这里需要进一步强调的是,对于复杂的文档结构,我们可以通过点表示法来获取嵌套字段。
例如,假设我们的 employees
文档中有一个嵌套的 address
对象,结构如下:
{
"name": "John Doe",
"age": 30,
"department": "Engineering",
"address": {
"city": "New York",
"country": "USA"
}
}
如果我们只想获取地址中的城市字段,可以这样请求:
GET /employees/_doc/1?_source=address.city
响应结果将只包含城市字段:
{
"_index": "employees",
"_type": "_doc",
"_id": "1",
"_version": 1,
"_seq_no": 0,
"_primary_term": 1,
"found": true,
"_source": {
"address": {
"city": "New York"
}
}
}
控制存储字段的返回格式
有时候,我们可能希望对返回的存储字段进行一些格式化处理。虽然 Elasticsearch 本身并不像一些专门的数据处理工具那样提供丰富的格式化功能,但我们可以通过一些简单的手段来实现基本的格式化需求。
例如,对于日期类型的字段,Elasticsearch 默认会以 ISO 8601 格式存储和返回。但如果我们希望以更友好的格式展示,可以在应用层进行转换。不过,在某些情况下,我们可以通过脚本字段来实现一定程度的格式化。
假设我们有一个 timestamp
字段,存储的是时间戳,我们想将其转换为日期格式返回。我们可以使用如下的脚本字段:
GET /my_index/_doc/1 {
"script_fields": {
"formatted_date": {
"script": {
"source": "doc['timestamp'].date.format('yyyy - MM - dd')",
"lang": "painless"
}
}
}
}
在这个例子中,我们使用 Painless 脚本语言,通过 doc['timestamp'].date.format('yyyy - MM - dd')
来将 timestamp
字段的值格式化为 yyyy - MM - dd
的日期格式,并以 formatted_date
作为新的字段返回。
处理缺失存储字段
当我们使用 GET API 获取文档时,如果请求的存储字段不存在,Elasticsearch 的默认行为是在响应中不包含该字段。例如,如果我们请求获取一个不存在的 phone_number
字段:
GET /employees/_doc/1?_source=phone_number
响应结果中 _source
将为空:
{
"_index": "employees",
"_type": "_doc",
"_id": "1",
"_version": 1,
"_seq_no": 0,
"_primary_term": 1,
"found": true,
"_source": {}
}
然而,在某些情况下,我们可能希望在字段缺失时返回一个默认值。这可以通过脚本字段来实现。例如,我们希望在 phone_number
字段缺失时返回 N/A
:
GET /employees/_doc/1 {
"script_fields": {
"phone_number": {
"script": {
"source": "doc.containsKey('phone_number')? doc['phone_number'].value : 'N/A'",
"lang": "painless"
}
}
}
}
在这个脚本中,我们使用 doc.containsKey('phone_number')
来检查 phone_number
字段是否存在,如果存在则返回其值,否则返回 N/A
。
存储字段的更新与管理
虽然 GET API 主要用于获取文档,但在 Elasticsearch 中,存储字段的更新也间接与 GET API 相关。当我们更新文档时,实际上是对存储在 _source
中的字段进行修改。
例如,我们要更新 employees
文档中 ID 为 1
的员工年龄,可以使用如下的 POST
请求:
POST /employees/_update/1 {
"doc": {
"age": 31
}
}
更新完成后,如果我们再次使用 GET API 获取该文档:
GET /employees/_doc/1
响应结果中的 age
字段将显示为 31
。
此外,我们还可以通过 upsert
操作来实现如果文档不存在则创建,存在则更新的功能。例如:
POST /employees/_update/1 {
"doc": {
"age": 31
},
"upsert": {
"name": "John Doe",
"age": 31,
"department": "Engineering"
}
}
在这个例子中,如果 ID 为 1
的文档不存在,Elasticsearch 将使用 upsert
中的内容创建一个新文档;如果文档存在,则使用 doc
中的内容更新文档。
性能与存储字段管理
存储字段的管理对 Elasticsearch 的性能有重要影响。过多的存储字段或者大的 _source
字段会增加磁盘 I/O 和内存使用,从而影响查询性能。
减少不必要的存储字段
为了提高性能,我们应该尽量只存储必要的字段。例如,如果某些字段只用于搜索而不需要在获取文档时展示,就可以选择不存储这些字段。通过源字段过滤,我们可以在查询时灵活地选择需要返回的字段,进一步减少网络传输和处理开销。
优化 _source
存储
Elasticsearch 提供了压缩 _source
字段的选项,可以在一定程度上减少磁盘空间的占用。我们可以在创建索引时通过设置 index.mapping.source.compress
为 true
来启用压缩:
PUT /my_index {
"settings": {
"index.mapping.source.compress": true
},
"mappings": {
"properties": {
"name": {
"type": "text"
},
"age": {
"type": "integer"
}
}
}
}
启用压缩后,虽然会增加一些压缩和解压缩的 CPU 开销,但可以显著减少磁盘空间的使用,尤其对于大文档或者大量文档的索引,这有助于提高整体性能。
缓存与存储字段
Elasticsearch 的节点缓存(如分片请求缓存)可以缓存 GET API 的响应结果。合理利用缓存可以减少重复获取存储字段的开销,提高查询性能。当文档的存储字段很少发生变化时,启用缓存可以带来明显的性能提升。我们可以通过在查询请求中设置 preference
参数来控制缓存行为,例如:
GET /employees/_doc/1?preference=_local
preference=_local
表示优先从本地节点的缓存中获取数据,如果缓存中没有则从磁盘读取并更新缓存。
复杂场景下的存储字段管理
多文档获取时的存储字段管理
在实际应用中,我们经常需要一次性获取多个文档。Elasticsearch 提供了 mget
API 来满足这一需求。在使用 mget
API 时,同样可以对存储字段进行管理。
例如,我们要从 employees
索引中获取 ID 为 1
和 2
的文档,并只获取 name
和 department
字段,可以这样请求:
POST /_mget {
"docs": [
{
"_index": "employees",
"_id": "1",
"_source": "name,department"
},
{
"_index": "employees",
"_id": "2",
"_source": "name,department"
}
]
}
响应结果将包含两个文档的指定字段:
{
"docs": [
{
"_index": "employees",
"_type": "_doc",
"_id": "1",
"_version": 1,
"_seq_no": 0,
"_primary_term": 1,
"found": true,
"_source": {
"name": "John Doe",
"department": "Engineering"
}
},
{
"_index": "employees",
"_type": "_doc",
"_id": "2",
"_version": 1,
"_seq_no": 0,
"_primary_term": 1,
"found": true,
"_source": {
"name": "Jane Smith",
"department": "Marketing"
}
}
]
}
通过这种方式,我们可以在一次请求中高效地获取多个文档的特定存储字段,减少网络请求次数和数据传输量。
与聚合功能结合的存储字段管理
Elasticsearch 的聚合功能允许我们对文档集合进行统计分析。在进行聚合操作时,有时也需要结合存储字段进行处理。
例如,我们要对 employees
索引中的员工按部门进行分组,并统计每个部门的平均年龄。同时,我们希望在聚合结果中包含每个部门的名称(存储字段)。可以这样请求:
GET /employees/_search {
"aggs": {
"by_department": {
"terms": {
"field": "department"
},
"aggs": {
"avg_age": {
"avg": {
"field": "age"
}
}
}
}
},
"_source": "department"
}
在这个请求中,我们通过 _source
只获取 department
字段,同时在聚合中按 department
字段进行分组并计算平均年龄。响应结果将包含聚合结果以及每个分组对应的部门名称:
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": null,
"hits": []
},
"aggregations": {
"by_department": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "Engineering",
"doc_count": 1,
"avg_age": {
"value": 30.0
}
},
{
"key": "Marketing",
"doc_count": 1,
"avg_age": 25.0
}
]
}
}
}
这样,我们可以在聚合分析的同时有效地管理存储字段,确保获取到有价值的信息且减少不必要的数据传输。
跨索引获取存储字段
Elasticsearch 支持在多个索引中同时搜索和获取文档。当我们需要跨索引获取存储字段时,同样可以使用 GET API 相关的功能。
例如,假设我们有两个索引 employees
和 contractors
,它们都有相似的文档结构,我们要从这两个索引中获取 ID 为 1
的文档,并只获取 name
字段,可以这样请求:
POST /employees,contractors/_mget {
"docs": [
{
"_id": "1",
"_source": "name"
}
]
}
响应结果将包含来自两个索引的符合条件的文档的 name
字段。通过这种方式,我们可以在多个索引之间灵活地获取存储字段,满足复杂的业务需求。
在跨索引获取存储字段时,需要注意索引之间的兼容性,确保字段名称和数据类型的一致性,以避免获取到不准确或错误的数据。
存储字段管理的高级技巧
使用别名管理存储字段相关操作
Elasticsearch 的别名是一个指向一个或多个索引的可切换的“指针”。我们可以利用别名来简化存储字段管理相关的操作。
例如,我们可以创建一个别名 all_staff
指向 employees
和 contractors
索引:
POST /_aliases {
"actions": [
{
"add": {
"index": "employees",
"alias": "all_staff"
}
},
{
"add": {
"index": "contractors",
"alias": "all_staff"
}
}
]
}
之后,当我们要对这些索引进行存储字段相关的获取操作时,就可以通过别名来操作,例如:
POST /all_staff/_mget {
"docs": [
{
"_id": "1",
"_source": "name"
}
]
}
这样,如果后续索引结构发生变化,比如添加或删除索引,我们只需要更新别名的指向,而不需要修改大量的获取存储字段的请求代码,提高了系统的可维护性。
动态映射与存储字段管理
Elasticsearch 支持动态映射,即当文档被索引时,如果字段不存在于映射中,Elasticsearch 会自动为该字段添加映射。在存储字段管理方面,动态映射也有一些需要注意的地方。
当新字段通过动态映射被添加时,默认情况下该字段会被索引和存储。但在某些场景下,我们可能不希望某些动态添加的字段被存储,以节省空间和提高性能。我们可以通过在索引模板中设置动态映射的规则来控制这一行为。
例如,我们可以创建一个索引模板,设置新的字符串类型字段不存储:
PUT _template/my_template {
"index_patterns": ["my_index*"],
"mappings": {
"dynamic_templates": [
{
"strings": {
"match_mapping_type": "string",
"mapping": {
"type": "text",
"store": false
}
}
}
]
}
}
这样,当在以 my_index
开头的索引中通过动态映射添加新的字符串类型字段时,该字段将不会被存储,从而有效地管理了存储字段的数量和空间占用。
基于角色的存储字段访问控制
在多用户或多租户的 Elasticsearch 环境中,我们可能需要根据用户角色来控制对存储字段的访问。这可以通过 Elasticsearch 的 X-Pack 安全功能来实现。
例如,我们可以定义一个角色 readonly_staff
,该角色只能读取 employees
索引中的 name
和 department
字段:
PUT /_security/role/readonly_staff {
"cluster": [],
"indices": [
{
"names": ["employees"],
"privileges": ["read"],
"field_security": {
"grant": ["name", "department"]
}
}
]
}
然后,将该角色分配给特定的用户:
PUT /_security/user/john {
"password": "password",
"roles": ["readonly_staff"]
}
这样,用户 john
在使用 GET API 获取 employees
索引文档时,只能获取 name
和 department
字段,实现了基于角色的存储字段访问控制,增强了数据的安全性和隐私性。
通过上述各种方法和技巧,我们可以更加灵活、高效地管理 Elasticsearch GET API 中的存储字段,满足不同场景下的业务需求,同时优化系统的性能和资源使用。在实际应用中,需要根据具体的业务场景和数据特点,综合运用这些方法,以达到最佳的效果。