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

ElasticSearch创建索引的最佳实践

2021-10-145.8k 阅读

ElasticSearch 索引基础概念

在深入探讨 ElasticSearch 创建索引的最佳实践之前,我们先来了解一下 ElasticSearch 索引的基本概念。

在 ElasticSearch 中,索引是一个存储数据的逻辑容器,类似于关系型数据库中的数据库概念。然而,与关系型数据库不同的是,ElasticSearch 中的索引更加灵活和动态。它可以包含多个类型(在 ElasticSearch 7.0 及以上版本,类型的概念逐渐被弱化并最终在 8.0 版本移除),每个类型可以有不同的文档结构,但在实际使用中,尽量保持文档结构的一致性会更有利于查询和管理。

索引中的每个文档都有一个唯一的标识符,即 _id。文档以 JSON 格式进行存储,这种格式的灵活性使得 ElasticSearch 能够轻松适应各种数据结构。例如,以下是一个简单的文档示例:

{
    "title": "Sample Document",
    "content": "This is a sample document for ElasticSearch.",
    "timestamp": "2023-10-01T12:00:00Z"
}

选择合适的索引名称

在创建索引时,索引名称的选择至关重要。一个好的索引名称应该遵循以下原则:

  1. 描述性:索引名称应该能够清晰地描述存储在其中的数据。例如,如果索引用于存储用户信息,那么 usersuser - data 就是比 index1 更好的名称选择。这样可以使其他开发人员更容易理解索引的用途,同时也便于维护和管理。
  2. 遵循命名规范:ElasticSearch 对索引名称有一些限制。索引名称必须全部为小写字母,不能包含空格,且长度不能超过 255 个字符。例如,user_data 是合规的,而 User Data(包含空格且非全小写)和 a * 256(长度超过限制)则是不允许的。
  3. 避免特殊字符:除了 -_. 之外,尽量避免在索引名称中使用其他特殊字符。例如,不要使用 @$ 等字符,因为它们可能会在某些情况下导致问题,特别是在使用命令行工具或编写脚本时。

规划索引设置

在创建索引时,合理设置索引的各种参数对于性能和功能至关重要。下面我们来详细讨论一些关键的索引设置。

  1. 分片和副本
    • 分片:ElasticSearch 将索引数据分割成多个分片(shard),每个分片是一个独立的 Lucene 索引。分片的主要作用是实现数据的分布式存储和并行处理,从而提高索引和搜索的性能。例如,如果有大量的数据需要存储在一个索引中,将其分成多个分片可以使每个分片处理的数据量相对较小,提高处理效率。
    • 副本:副本(replica)是分片的拷贝,主要用于提高数据的可用性和搜索性能。当某个分片所在的节点出现故障时,副本可以接替其工作,保证数据不丢失且服务不中断。同时,副本也可以用于分担搜索请求,提高搜索的并发处理能力。
    • 最佳实践:对于分片数量的选择,需要根据数据量和集群规模来确定。一般来说,对于小型数据集(例如,小于 10GB),可以使用较少的分片,如 1 - 3 个。对于大型数据集,可以按照每个分片 15GB - 50GB 的数据量来估算分片数量。例如,如果有 500GB 的数据,大约可以设置 10 - 33 个分片。副本数量通常设置为 1 - 2 个,以平衡数据可用性和存储成本。
  2. 索引映射
    • 概念:索引映射(mapping)定义了文档中各个字段的类型、索引方式等属性。例如,一个 title 字段可以定义为 text 类型,用于全文搜索;而一个 user_id 字段可以定义为 keyword 类型,用于精确匹配。
    • 动态映射与静态映射
      • 动态映射:ElasticSearch 默认使用动态映射,即当插入一个新文档时,如果文档中的字段在映射中不存在,ElasticSearch 会自动根据字段值推断其类型并添加到映射中。例如,插入一个包含 age 字段且值为数字的文档,ElasticSearch 会自动将 age 字段映射为 long 类型。虽然动态映射非常方便,但在某些情况下可能会导致映射不准确,例如将日期字符串错误地推断为 text 类型。
      • 静态映射:静态映射则是在创建索引时手动定义所有字段的映射。这可以确保字段类型和索引方式的准确性。例如:
{
    "mappings": {
        "properties": {
            "title": {
                "type": "text",
                "analyzer": "standard"
            },
            "user_id": {
                "type": "keyword"
            },
            "birth_date": {
                "type": "date",
                "format": "yyyy - MM - dd"
            }
        }
    }
}
- **最佳实践**:对于生产环境,建议尽量使用静态映射,以确保数据的一致性和查询的准确性。在定义映射时,要仔细考虑每个字段的用途,选择合适的类型和索引选项。例如,对于需要进行全文搜索的字段,选择 `text` 类型并配置合适的分析器;对于需要精确匹配的字段,选择 `keyword` 类型。

3. 分析器 - 作用:分析器(analyzer)用于在索引和搜索时对文本进行处理。它通常包括字符过滤器、分词器和标记过滤器三个部分。字符过滤器用于在分词前对文本进行预处理,例如去除 HTML 标签;分词器将文本分割成一个个单词(token);标记过滤器则对分词后的单词进行进一步处理,例如转换为小写、去除停用词等。 - 内置分析器:ElasticSearch 提供了多种内置分析器,如 standard 分析器、simple 分析器、whitespace 分析器等。standard 分析器是默认的分析器,它适用于大多数语言,会将文本按词边界进行分词,并转换为小写。simple 分析器则更简单,它只根据非字母字符进行分词,并转换为小写。whitespace 分析器则根据空白字符进行分词,不进行其他处理。 - 自定义分析器:在某些情况下,内置分析器可能无法满足需求,这时可以创建自定义分析器。例如,如果需要处理中文文本,可以创建一个使用 IK 分词器的自定义分析器。以下是创建自定义分析器的示例:

{
    "settings": {
        "analysis": {
            "analyzer": {
                "my_analyzer": {
                    "type": "custom",
                    "tokenizer": "ik_max_word",
                    "filter": ["lowercase"]
                }
            }
        }
    }
}
- **最佳实践**:根据文本的语言和具体需求选择合适的分析器。对于英文文本,`standard` 分析器通常能满足大多数需求;对于中文文本,使用 IK 分词器等专门的中文分词器可以获得更好的分词效果。在创建自定义分析器时,要仔细配置各个组件,以达到最佳的索引和搜索性能。

4. 索引生命周期管理 - 概念:索引生命周期管理(ILM)允许根据预定义的策略自动管理索引的整个生命周期,包括创建、滚动、收缩、冻结和删除等操作。例如,可以设置一个策略,当索引的文档数量达到一定阈值或者索引创建时间超过一定期限时,将其滚动到新的索引中,同时对旧索引进行删除或归档操作。 - 策略定义:ILM 策略通过 JSON 格式进行定义,包含多个阶段,如 hotwarmcolddelete 等。在 hot 阶段,可以设置索引的读写优先级较高,并且可以根据需要调整副本数量;在 warm 阶段,可以减少副本数量以节省存储资源;在 cold 阶段,可以将索引冻结以进一步降低资源消耗;最后在 delete 阶段,删除不再需要的索引。以下是一个简单的 ILM 策略示例:

{
    "policy": {
        "phases": {
            "hot": {
                "min_age": "0ms",
                "actions": {
                    "rollover": {
                        "max_docs": 1000000,
                        "max_size": "50gb"
                    }
                }
            },
            "delete": {
                "min_age": "30d",
                "actions": {
                    "delete": {}
                }
            }
        }
    }
}
- **最佳实践**:合理使用 ILM 可以有效地管理索引资源,提高集群的性能和可用性。在定义 ILM 策略时,要根据业务需求和数据增长模式来设置各个阶段的参数。例如,对于数据更新频繁且重要性较高的索引,可以适当延长 `hot` 阶段的时间;对于历史数据,可以尽快将其转移到 `cold` 或 `delete` 阶段。

创建索引的 API 操作

  1. 使用 REST API 创建索引
    • 基本语法:通过发送 PUT 请求到 ElasticSearch 的 REST API 来创建索引。例如,创建一个名为 my_index 的索引:
PUT http://localhost:9200/my_index
- **设置索引参数**:可以在请求体中设置索引的各种参数,如分片、副本、映射等。以下是一个创建索引并设置分片、副本和映射的示例:
PUT http://localhost:9200/my_index
{
    "settings": {
        "number_of_shards": 3,
        "number_of_replicas": 1
    },
    "mappings": {
        "properties": {
            "title": {
                "type": "text",
                "analyzer": "standard"
            },
            "user_id": {
                "type": "keyword"
            }
        }
    }
}
  1. 使用 Elasticsearch - Python 客户端创建索引
    • 安装客户端:首先需要安装 elasticsearch 库,可以使用 pip install elasticsearch 命令进行安装。
    • 代码示例:以下是使用 Python 客户端创建索引的示例代码:
from elasticsearch import Elasticsearch

es = Elasticsearch([{'host': 'localhost', 'port': 9200}])

index_name ='my_index'
body = {
   "settings": {
       "number_of_shards": 3,
       "number_of_replicas": 1
   },
   "mappings": {
       "properties": {
           "title": {
               "type": "text",
               "analyzer": "standard"
           },
           "user_id": {
               "type": "keyword"
           }
       }
   }
}

response = es.indices.create(index=index_name, body=body)
print(response)
  1. 使用 Kibana Dev Tools 创建索引
    • 打开 Dev Tools:在 Kibana 界面中,点击左侧菜单栏的 Dev Tools 选项卡。
    • 执行创建索引命令:在 Dev Tools 的控制台中输入创建索引的命令,例如:
PUT my_index
{
    "settings": {
        "number_of_shards": 3,
        "number_of_replicas": 1
    },
    "mappings": {
        "properties": {
            "title": {
                "type": "text",
                "analyzer": "standard"
            },
            "user_id": {
                "type": "keyword"
            }
        }
    }
}

创建索引时的性能优化

  1. 批量操作
    • 原理:在向 ElasticSearch 中插入数据时,尽量使用批量操作。通过一次请求插入多个文档,可以减少网络开销和索引操作的次数,从而提高性能。例如,假设有 1000 个文档需要插入,如果逐个插入,需要发送 1000 次请求;而使用批量操作,只需要发送一次请求。
    • 代码示例
      • 使用 REST API 批量插入
POST http://localhost:9200/_bulk
{"index":{"_index":"my_index","_id":"1"}}
{"title":"Document 1","content":"Content of document 1"}
{"index":{"_index":"my_index","_id":"2"}}
{"title":"Document 2","content":"Content of document 2"}
    - **使用 Elasticsearch - Python 客户端批量插入**:
from elasticsearch import Elasticsearch, helpers

es = Elasticsearch([{'host': 'localhost', 'port': 9200}])

actions = [
    {
        "_index": "my_index",
        "_id": 1,
        "title": "Document 1",
        "content": "Content of document 1"
    },
    {
        "_index": "my_index",
        "_id": 2,
        "title": "Document 2",
        "content": "Content of document 2"
    }
]

helpers.bulk(es, actions)
  1. 控制索引刷新频率
    • 概念:ElasticSearch 会定期将内存中的数据刷新到磁盘上,这个过程称为刷新(refresh)。默认情况下,ElasticSearch 每秒自动刷新一次索引,这虽然可以保证数据的近实时可见性,但也会带来一定的性能开销。
    • 调整刷新频率:在创建索引或插入大量数据时,可以适当降低刷新频率,例如将其设置为手动刷新或延长自动刷新的间隔时间。在插入完数据后,再手动执行一次刷新操作,使数据可见。以下是在创建索引时设置刷新间隔的示例:
PUT http://localhost:9200/my_index
{
    "settings": {
        "refresh_interval": "30s"
    },
    "mappings": {
        "properties": {
            "title": {
                "type": "text",
                "analyzer": "standard"
            }
        }
    }
}
- **手动刷新**:可以使用以下命令手动刷新索引:
POST http://localhost:9200/my_index/_refresh
  1. 预热索引
    • 作用:在索引创建后,首次查询可能会比较慢,因为 ElasticSearch 需要加载相关的数据和索引结构到内存中。预热索引(warm - up)可以在索引创建后立即执行一些常见的查询操作,将数据和索引结构预先加载到内存中,从而提高后续查询的性能。
    • 实现方式:可以编写脚本,在索引创建后自动执行一些代表性的查询。例如,使用 Elasticsearch - Python 客户端执行一个简单的查询来预热索引:
from elasticsearch import Elasticsearch

es = Elasticsearch([{'host': 'localhost', 'port': 9200}])

index_name ='my_index'
query = {
    "query": {
        "match_all": {}
    }
}

response = es.search(index=index_name, body=query)
print(response)

处理索引创建错误

  1. 常见错误类型
    • 索引已存在错误:当尝试创建一个已经存在的索引时,会收到 resource_already_exists_exception 错误。例如,再次执行 PUT http://localhost:9200/my_index 时,如果 my_index 已经存在,就会出现这个错误。
    • 映射冲突错误:如果在创建索引时定义的映射与已有的映射不兼容,会出现映射冲突错误。例如,试图将一个已经定义为 text 类型的字段重新定义为 keyword 类型,就会引发错误。
    • 参数错误:如果设置的索引参数不合法,如设置的分片数量为负数,或者索引名称不符合命名规范,都会导致参数错误。
  2. 错误处理方法
    • 索引已存在错误处理:在创建索引之前,可以先检查索引是否存在。例如,使用 Elasticsearch - Python 客户端:
from elasticsearch import Elasticsearch

es = Elasticsearch([{'host': 'localhost', 'port': 9200}])

index_name ='my_index'
if not es.indices.exists(index=index_name):
    body = {
        "settings": {
            "number_of_shards": 3,
            "number_of_replicas": 1
        },
        "mappings": {
            "properties": {
                "title": {
                    "type": "text",
                    "analyzer": "standard"
                }
            }
        }
    }
    es.indices.create(index=index_name, body=body)
else:
    print(f"Index {index_name} already exists.")
- **映射冲突错误处理**:在更新映射时,要确保新的映射与旧的映射兼容。如果需要进行不兼容的更改,可以考虑创建一个新的索引,将数据迁移到新索引中。例如,可以使用 ElasticSearch 的 `reindex` API 来迁移数据:
POST _reindex
{
    "source": {
        "index": "old_index"
    },
    "dest": {
        "index": "new_index"
    }
}
- **参数错误处理**:仔细检查设置的参数,确保其符合 ElasticSearch 的规范。在使用命令行工具或编写代码时,要注意参数的格式和取值范围。例如,确保分片数量为正整数,索引名称符合命名规则等。

与其他系统集成时创建索引的考虑

  1. 与日志管理系统集成
    • 需求:当与日志管理系统(如 Logstash、Filebeat 等)集成时,需要根据日志数据的特点来创建索引。日志数据通常具有时间序列的特点,可能包含大量的文本信息,并且需要支持快速的搜索和聚合操作。
    • 索引设置
      • 时间字段:可以将日志中的时间字段映射为 date 类型,并设置合适的日期格式。例如,如果日志中的时间格式为 yyyy - MM - dd HH:mm:ss,可以在映射中设置:
{
    "mappings": {
        "properties": {
            "timestamp": {
                "type": "date",
                "format": "yyyy - MM - dd HH:mm:ss"
            }
        }
    }
}
    - **文本字段**:对于日志中的文本字段,选择合适的分析器。如果日志主要是英文,可以使用 `standard` 分析器;如果包含多种语言,可以考虑使用 `icu` 分析器等更通用的分析器。
    - **索引生命周期**:根据日志数据的保留策略设置 ILM 策略。例如,如果只需要保留最近一周的日志,可以设置 `delete` 阶段在日志创建一周后删除索引。

2. 与业务系统集成 - 数据一致性:与业务系统集成时,要确保 ElasticSearch 中的索引数据与业务系统中的数据保持一致性。这可能需要在业务系统进行数据更新、插入或删除操作时,同步更新 ElasticSearch 中的索引。可以使用消息队列(如 Kafka)来实现异步的数据同步,以减少对业务系统性能的影响。 - 索引设计与业务需求匹配:根据业务系统的查询需求来设计索引。例如,如果业务系统经常需要根据用户 ID 和时间范围进行查询,可以在索引映射中对 user_id 字段使用 keyword 类型,对时间字段使用 date 类型,并考虑创建复合索引来提高查询性能。 3. 与 BI 工具集成 - 数据聚合与可视化需求:当与商业智能(BI)工具(如 Tableau、PowerBI 等)集成时,需要根据 BI 工具的聚合和可视化需求来创建索引。例如,如果 BI 工具需要对某些字段进行分组统计,这些字段应该设置为合适的类型,并且在创建索引时考虑是否需要启用 fielddata 来支持聚合操作。 - 性能优化:为了提高 BI 工具的查询性能,可以对索引进行预热,并且调整索引的刷新频率和副本数量等参数,以平衡性能和资源消耗。同时,要确保 ElasticSearch 的集群配置能够满足 BI 工具的并发查询需求。

通过以上全面深入的介绍,涵盖了 ElasticSearch 创建索引的各个方面,从基础概念到最佳实践,从性能优化到错误处理以及与其他系统的集成,希望能帮助读者在实际应用中创建出高效、稳定且符合业务需求的 ElasticSearch 索引。