MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

ElasticSearch GET API的存储字段管理

2024-06-073.2k 阅读

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 文档中的 namedepartment 字段,可以这样请求:

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.compresstrue 来启用压缩:

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 为 12 的文档,并只获取 namedepartment 字段,可以这样请求:

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 相关的功能。

例如,假设我们有两个索引 employeescontractors,它们都有相似的文档结构,我们要从这两个索引中获取 ID 为 1 的文档,并只获取 name 字段,可以这样请求:

POST /employees,contractors/_mget {
    "docs": [
        {
            "_id": "1",
            "_source": "name"
        }
    ]
}

响应结果将包含来自两个索引的符合条件的文档的 name 字段。通过这种方式,我们可以在多个索引之间灵活地获取存储字段,满足复杂的业务需求。

在跨索引获取存储字段时,需要注意索引之间的兼容性,确保字段名称和数据类型的一致性,以避免获取到不准确或错误的数据。

存储字段管理的高级技巧

使用别名管理存储字段相关操作

Elasticsearch 的别名是一个指向一个或多个索引的可切换的“指针”。我们可以利用别名来简化存储字段管理相关的操作。

例如,我们可以创建一个别名 all_staff 指向 employeescontractors 索引:

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 索引中的 namedepartment 字段:

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 索引文档时,只能获取 namedepartment 字段,实现了基于角色的存储字段访问控制,增强了数据的安全性和隐私性。

通过上述各种方法和技巧,我们可以更加灵活、高效地管理 Elasticsearch GET API 中的存储字段,满足不同场景下的业务需求,同时优化系统的性能和资源使用。在实际应用中,需要根据具体的业务场景和数据特点,综合运用这些方法,以达到最佳的效果。