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

ElasticSearch自动创建索引的机制

2021-02-262.3k 阅读

ElasticSearch 自动创建索引的机制

ElasticSearch 索引基础概念

在深入探讨 ElasticSearch 自动创建索引机制之前,我们先来回顾一下索引的基本概念。在 ElasticSearch 中,索引是文档的集合,类似于传统关系型数据库中的数据库概念。每个索引都有一个唯一的名称,通过这个名称可以对索引中的文档进行各种操作,如添加、查询、更新和删除。

ElasticSearch 使用倒排索引结构来存储和检索数据。倒排索引是一种基于单词的索引结构,它将每个单词映射到包含该单词的文档列表。这种结构使得 ElasticSearch 能够快速地根据关键词找到相关的文档,从而实现高效的搜索功能。

自动创建索引的触发条件

  1. 文档写入操作
    • 当向 ElasticSearch 发送一个文档写入请求时,如果目标索引不存在,ElasticSearch 会自动创建该索引。例如,使用 ElasticSearch 的 REST API 发送如下的 PUT 请求:
    PUT /my_index/_doc/1
    {
        "title": "示例文档",
        "content": "这是一个自动创建索引的示例文档"
    }
    
    在上述请求中,如果 my_index 索引不存在,ElasticSearch 会自动创建 my_index 索引,并将指定的文档添加到该索引中。
    • 从代码层面看,在 Java 中使用 Elasticsearch High - Level REST Client 也可以实现类似的操作:
    import org.apache.http.HttpHost;
    import org.elasticsearch.action.index.IndexRequest;
    import org.elasticsearch.action.index.IndexResponse;
    import org.elasticsearch.client.RequestOptions;
    import org.elasticsearch.client.RestClient;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.elasticsearch.common.xcontent.XContentType;
    import java.io.IOException;
    
    public class AutoCreateIndexExample {
        public static void main(String[] args) throws IOException {
            RestHighLevelClient client = new RestHighLevelClient(
                    RestClient.builder(
                            new HttpHost("localhost", 9200, "http")));
    
            IndexRequest request = new IndexRequest("my_index")
                   .id("1")
                   .source("{\"title\":\"示例文档\",\"content\":\"这是一个自动创建索引的示例文档\"}", XContentType.JSON);
    
            IndexResponse response = client.index(request, RequestOptions.DEFAULT);
    
            client.close();
        }
    }
    
    这段 Java 代码同样会在 my_index 索引不存在时自动创建该索引并添加文档。
  2. Mapping 定义操作
    • 虽然文档写入是触发自动创建索引最常见的方式,但在某些情况下,直接定义 Mapping 也可能触发索引的自动创建。例如,通过 PUT Mapping API 为一个不存在的索引定义 Mapping:
    PUT /new_index/_mapping
    {
        "properties": {
            "name": {
                "type": "text"
            },
            "age": {
                "type": "integer"
            }
        }
    }
    
    在上述请求中,如果 new_index 索引不存在,ElasticSearch 会自动创建该索引,并应用定义的 Mapping。
    • 在 Python 中使用 Elasticsearch 库可以这样实现:
    from elasticsearch import Elasticsearch
    
    es = Elasticsearch([{'host': 'localhost', 'port': 9200}])
    
    mapping = {
        "properties": {
            "name": {
                "type": "text"
            },
            "age": {
                "type": "integer"
            }
        }
    }
    
    es.indices.put_mapping(index='new_index', body=mapping)
    
    此 Python 代码同样会在 new_index 索引不存在时自动创建索引并设置 Mapping。

自动创建索引的配置参数

  1. index.mapper.dynamic
    • 这个参数控制了在文档写入时,ElasticSearch 如何动态地为新字段添加 Mapping。它有三个取值:truefalsestrict
    • true(默认值):当文档中出现新字段时,ElasticSearch 会自动为该字段添加 Mapping。例如,有如下文档写入请求:
    PUT /dynamic_index/_doc/1
    {
        "new_field": "新字段的值"
    }
    
    如果 dynamic_index 索引已经存在,且 index.mapper.dynamictrue,ElasticSearch 会自动为 new_field 添加 Mapping,根据字段值的类型推断其数据类型,这里 new_field 会被推断为 text 类型。
    • false:当文档中出现新字段时,ElasticSearch 不会自动为该字段添加 Mapping。新字段的数据不会被索引,但是会在 _source 字段中保留。例如:
    PUT /no_dynamic_index/_doc/1
    {
        "new_field": "新字段的值"
    }
    
    假设 no_dynamic_index 索引存在且 index.mapper.dynamicfalsenew_field 不会被索引,在搜索时无法通过 new_field 进行查询,但在获取文档的 _source 时可以看到 new_field 及其值。
    • strict:当文档中出现新字段时,ElasticSearch 会抛出异常,拒绝写入该文档。例如:
    PUT /strict_index/_doc/1
    {
        "new_field": "新字段的值"
    }
    
    如果 strict_index 索引存在且 index.mapper.dynamicstrict,上述请求会失败,因为 new_field 是新出现的字段。
  2. action.auto_create_index
    • 这个参数控制是否允许自动创建索引。它可以接受一个逗号分隔的索引名称模式列表。例如,设置 action.auto_create_index: my_index*,other_index,表示只有以 my_index 开头的索引和 other_index 索引可以自动创建。
    • 如果设置为 false,则完全禁用自动创建索引功能。任何针对不存在索引的写入或 Mapping 操作都会失败。例如,尝试写入一个不存在的索引:
    PUT /disabled_auto_index/_doc/1
    {
        "data": "一些数据"
    }
    
    action.auto_create_indexfalse 时,上述请求会返回错误,提示索引不存在且自动创建被禁用。

自动创建索引的流程

  1. 索引名称验证
    • 当触发自动创建索引的操作时,ElasticSearch 首先会验证索引名称是否符合命名规则。索引名称必须小写,不能包含空格、逗号、冒号等特殊字符,且长度不能超过 255 个字符。例如,my_index 是一个合法的索引名称,而 MyIndex(大写开头)、my,index(包含逗号)都是不合法的。
    • 如果索引名称不合法,ElasticSearch 会立即返回错误,不会进行自动创建索引的后续操作。
  2. 索引模板匹配
    • ElasticSearch 会尝试查找与要创建的索引名称匹配的索引模板。索引模板可以定义索引的各种设置和 Mapping 规则。例如,可以定义一个索引模板 my_template
    PUT _template/my_template
    {
        "index_patterns": ["my_index*"],
        "settings": {
            "number_of_shards": 3,
            "number_of_replicas": 1
        },
        "mappings": {
            "properties": {
                "common_field": {
                    "type": "text"
                }
            }
        }
    }
    
    当尝试自动创建 my_index1 索引时,由于 my_index1 匹配 my_index* 模式,ElasticSearch 会应用 my_template 模板中的设置和 Mapping 来创建索引。
    • 如果没有匹配的索引模板,ElasticSearch 会使用默认的设置和 Mapping 来创建索引。默认情况下,索引会有 1 个主分片和 1 个副本分片,并且文档中的字段会根据数据类型自动推断 Mapping。
  3. 索引创建与初始化
    • 一旦确定了索引的设置和 Mapping(无论是通过模板还是默认值),ElasticSearch 就会在集群中创建索引。它会在每个相关的节点上分配主分片和副本分片。
    • 例如,对于一个有 3 个节点的集群,当创建一个具有 3 个主分片和 1 个副本分片的索引时,ElasticSearch 会将 3 个主分片分配到不同的节点上,并为每个主分片创建一个副本分片,副本分片会分配到与主分片不同的节点上,以实现数据的冗余和高可用性。
    • 在索引创建完成后,ElasticSearch 会初始化相关的数据结构,如倒排索引,为后续的文档写入和搜索操作做好准备。

自动创建索引的注意事项

  1. 性能影响
    • 频繁的自动创建索引操作可能会对 ElasticSearch 集群的性能产生影响。每次创建索引时,ElasticSearch 需要进行一系列的操作,如分配分片、初始化数据结构等,这些操作会消耗集群的资源,包括 CPU、内存和网络带宽。
    • 例如,如果在短时间内大量自动创建索引,可能会导致集群负载过高,从而影响其他正常的搜索和写入操作的性能。为了避免这种情况,可以尽量提前规划好索引结构,减少不必要的自动创建索引操作。
  2. 数据一致性
    • 在自动创建索引的过程中,如果多个节点同时尝试创建同一个索引,可能会出现数据一致性问题。虽然 ElasticSearch 有一些机制来处理这种情况,但在极端情况下,可能会导致索引创建失败或出现不一致的状态。
    • 为了确保数据一致性,可以在创建索引之前先检查索引是否存在。例如,在 Java 中可以使用如下代码检查索引是否存在:
    import org.apache.http.HttpHost;
    import org.elasticsearch.client.RequestOptions;
    import org.elasticsearch.client.RestClient;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.elasticsearch.client.indices.GetIndexRequest;
    import java.io.IOException;
    
    public class IndexExistsExample {
        public static void main(String[] args) throws IOException {
            RestHighLevelClient client = new RestHighLevelClient(
                    RestClient.builder(
                            new HttpHost("localhost", 9200, "http")));
    
            GetIndexRequest request = new GetIndexRequest("my_index");
            boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
    
            if (!exists) {
                // 执行创建索引操作
            }
    
            client.close();
        }
    }
    
    这样可以避免多个节点同时创建同一个索引带来的数据一致性问题。
  3. 安全风险
    • 自动创建索引可能带来一定的安全风险。如果恶意用户能够向 ElasticSearch 发送写入请求,他们可能会利用自动创建索引的功能创建大量无用的索引,从而消耗集群资源,甚至进行数据泄露等恶意行为。
    • 为了防范这种风险,需要对 ElasticSearch 进行严格的访问控制,如使用身份验证和授权机制。可以通过 Elasticsearch 的 X - Pack 插件启用身份验证和授权,只有经过授权的用户才能进行索引创建和文档写入操作。例如,配置基本身份验证后,客户端在发送请求时需要提供用户名和密码:
    import org.apache.http.HttpHost;
    import org.apache.http.auth.AuthScope;
    import org.apache.http.auth.UsernamePasswordCredentials;
    import org.apache.http.client.CredentialsProvider;
    import org.apache.http.impl.client.BasicCredentialsProvider;
    import org.elasticsearch.client.RestClient;
    import org.elasticsearch.client.RestHighLevelClient;
    
    public class SecuredClientExample {
        public static void main(String[] args) {
            final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
            credentialsProvider.setCredentials(
                    AuthScope.ANY,
                    new UsernamePasswordCredentials("username", "password"));
    
            RestHighLevelClient client = new RestHighLevelClient(
                    RestClient.builder(
                            new HttpHost("localhost", 9200, "http"))
                           .setHttpClientConfigCallback(httpClientBuilder ->
                                    httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)));
    
            // 使用 client 进行操作
    
            try {
                client.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    这样可以有效防止未经授权的自动创建索引等恶意操作。

自动创建索引与动态 Mapping 的关系

  1. 动态 Mapping 依赖自动创建索引
    • 动态 Mapping 是在自动创建索引过程中,或者在已存在索引的文档写入时,为新字段自动生成 Mapping 的机制。当自动创建索引时,如果 index.mapper.dynamic 参数为 true(默认值),ElasticSearch 不仅会创建索引,还会根据文档中的字段自动生成 Mapping。
    • 例如,当自动创建一个索引并写入如下文档:
    PUT /dynamic_mapping_index/_doc/1
    {
        "new_text_field": "文本值",
        "new_number_field": 123
    }
    
    ElasticSearch 会自动为 new_text_field 生成 text 类型的 Mapping,为 new_number_field 生成 integer 类型的 Mapping。
  2. 动态 Mapping 对自动创建索引的影响
    • 动态 Mapping 的设置会影响自动创建索引的最终形态。如果将 index.mapper.dynamic 设置为 falsestrict,在自动创建索引时,即使文档中包含新字段,也不会为这些新字段生成 Mapping(false 时新字段不索引但保留在 _sourcestrict 时拒绝写入)。
    • 这意味着,动态 Mapping 的设置决定了自动创建索引时对文档字段的处理方式,进而影响索引的结构和后续的搜索功能。例如,在 index.mapper.dynamicfalse 的情况下创建索引并写入文档后,后续无法通过新字段进行搜索,因为新字段没有被索引。

实际应用场景中的自动创建索引

  1. 日志管理系统
    • 在日志管理系统中,自动创建索引非常实用。通常,日志数据会按照时间进行分区存储,每天可能会产生一个新的日志索引。例如,每天凌晨,日志收集系统会将当天的日志数据发送到 ElasticSearch。
    • 如果使用自动创建索引功能,日志收集系统只需要将日志文档发送到形如 log - yyyy - MM - dd 的索引中,ElasticSearch 会自动创建不存在的日期索引。例如,在 Python 中可以这样实现:
    from elasticsearch import Elasticsearch
    from datetime import datetime
    
    es = Elasticsearch([{'host': 'localhost', 'port': 9200}])
    today = datetime.now().strftime('%Y-%m-%d')
    index_name = f'log - {today}'
    
    log_data = {
        "timestamp": datetime.now().isoformat(),
        "message": "这是一条示例日志"
    }
    
    es.index(index=index_name, body=log_data)
    
    这种方式简化了日志索引的管理,无需手动提前创建每个日期的索引。
  2. 物联网数据收集
    • 物联网环境中,大量的设备会实时上传数据。每个设备可能会产生不同类型的数据,而且设备数量可能动态变化。自动创建索引可以很好地适应这种场景。
    • 例如,每个物联网设备可以将数据发送到以设备 ID 命名的索引中。当新设备开始上传数据时,ElasticSearch 会自动创建对应的索引。在 Java 中可以这样模拟:
    import org.apache.http.HttpHost;
    import org.elasticsearch.action.index.IndexRequest;
    import org.elasticsearch.action.index.IndexResponse;
    import org.elasticsearch.client.RequestOptions;
    import org.elasticsearch.client.RestClient;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.elasticsearch.common.xcontent.XContentType;
    import java.io.IOException;
    
    public class IoTDataIndexing {
        public static void main(String[] args) throws IOException {
            RestHighLevelClient client = new RestHighLevelClient(
                    RestClient.builder(
                            new HttpHost("localhost", 9200, "http")));
    
            String deviceId = "device_123";
            IndexRequest request = new IndexRequest(deviceId)
                   .source("{\"temperature\":25,\"humidity\":60}", XContentType.JSON);
    
            IndexResponse response = client.index(request, RequestOptions.DEFAULT);
    
            client.close();
        }
    }
    
    当有新的设备加入并发送数据时,ElasticSearch 会自动为其创建索引,方便对不同设备的数据进行存储和管理。

总结自动创建索引机制的优缺点

  1. 优点
    • 便捷性:自动创建索引极大地简化了开发和运维流程。开发人员无需手动提前创建每个可能用到的索引,只需要专注于数据的写入和业务逻辑的实现。例如,在快速迭代的 Web 应用开发中,新的业务需求可能会导致数据结构的频繁变化,自动创建索引可以轻松应对这种情况,减少了开发过程中的索引管理工作量。
    • 灵活性:能够适应动态变化的数据环境。如在物联网场景中,新设备的不断加入和数据类型的动态变化,自动创建索引可以实时为新数据创建合适的存储结构,保证数据的及时存储和处理。
  2. 缺点
    • 性能开销:如前文所述,频繁的自动创建索引操作会消耗集群资源,影响性能。在大规模集群中,如果大量索引同时自动创建,可能会导致集群负载过高,影响正常的搜索和写入操作。
    • 潜在风险:存在安全风险和数据一致性问题。恶意用户可能利用自动创建索引进行恶意操作,而且多个节点同时创建索引可能导致数据不一致。同时,自动生成的 Mapping 可能不符合业务需求,需要后续进行调整。

通过深入理解 ElasticSearch 的自动创建索引机制,开发人员和运维人员可以更好地利用这一功能,同时避免其带来的潜在问题,从而构建高效、稳定的 ElasticSearch 应用。在实际应用中,应根据具体的业务场景和需求,合理配置自动创建索引的相关参数,确保 ElasticSearch 集群的性能和数据的安全性。