一、索引的动态设置、静态设置
- 索引设置包含两部分核心内容:
- 静态设置(static index settings),只允许在创建索引时或者针对已关闭的索引进行设置。
- 指动态设置(dynamic index settings),可以借助更新设置(update settings)的方式进行动态更新,更新后立即生效。
1. 静态设置
静态设置实战场景举例如下:
设置主分片大小的参数是index.number_of_shards,只在创建索引时生效,不支持动态修改。
默认主分片大小为1,且每个索引的分片数量上限默认为1024。此限制是一个安全限制,可防止索引分片数过多导致集群不稳定。
如果在业务层面扩充节点后确实需要扩展主分片数,该怎么办?
答案:在非业务核心时间通过reindex操作迁移实现。
2. 动态设置
动态设置的实战场景举例如下:
- 设置副本数参数为index.number_of_replicas,可以动态修改:
PUT news_index/_settings
{
"number_of_replicas":3
}
- 设置刷新频率参数为index.refresh_interval,可以动态修改:
PUT news_index/_settings
{
"refresh_interval":"1s"
}
默认刷新频率参数值为1s,即每秒刷新一次。这1s决定了Elasticsearch是近实时的搜索引擎,而非准实时搜索引擎。如果业务层面对实时性的要求不高,可以考虑将该值调大。因为如果采用1s,则每秒都会生成一个新的分段(关于分段的概念可以参考最后一章),会影响写入性能。
- max_result_window是Elasticsearch中的一个设置参数,用于控制搜索结果的最大窗口的大小:
默认情况下,max_result_window的值为10000,这意味着在分页搜索时最多可以返回10000条数据。如果每页可显示10条数据,那么最多可以翻到1000页。在某些情况下,可能需要处理比默认值更大的数据集。
在这种情况下,可以通过更新索引设置来动态修改max_result_window的值:
PUT news_index/_settings
{
"max_result_window":50000
}
上述命令将max_result_window的值设置为50000。此时如果每页显示10条数据,则可以最多翻到5000页。
增大max_result_window的值可能会对Elasticsearch集群的性能产生影响,尤其是在处理大量数据时。因此,在根据实际需求调整此参数时,要权衡性能和查询范围之间的关系。如果需要遍历大量数据,则建议使用scroll API或search_after参数,以更高效地进行处理。
在 Elasticsearch 中,max_result_window参数(默认值为 10000)主要限制的是基于from + size的深度分页查询(即通过from指定偏移量,size指定每页大小)。这种查询方式在偏移量(from)较大时,会导致 Elasticsearch 在每个分片上生成大量中间结果并合并,消耗大量内存和 CPU,甚至引发 OOM(内存溢出)。
而 scroll API 和 search_after 这两种分页方式,设计上避免了max_result_window的直接限制。
二、索引别名
- 索引别名常见的使用场景:
- 当需要定期创建新索引(如日志按天 / 月分割),同时保持应用端无需感知索引名称变化时,通过别名指向 “当前有效索引”,实现无缝切换。
- 索引重建或结构升级:旧索引 users_v1 需升级到 users_v2(如新增字段、调整映射),先通过别名 users 指向 users_v1,应用正常访问,重建完成后,通过原子操作切换别名指向 users_v2,实现无感知迁移。
索引别名只是物理索引的软链接的名称而已,一个索引可以创建多个别名,一个别名也可以指向多个索引。
实战中,很多工程师在开发中后期才发现索引别名的妙处。正如前文所说,别名能进行高效的索引管理,能进行索引数据修改或更新操作并确保用户无感知。
-
示例场景:
线上索引users_v1
需要新增一个分词器为ik_max_word
的字段address
,但直接修改映射会导致集群分片重建,且旧数据无法应用新分词器(需重建索引)。 -
传统方案(无别名)的痛点:
- 重建新索引
users_v2
并迁移数据,需修改所有客户端代码/配置中的索引名,易遗漏导致线上故障; - 切换期间需停机或双写,用户体验差。
- 别名实现无感知升级(步骤):
-
创建新索引并绑定临时别名:
PUT /users_v2 { "mappings": { "properties": { "address": { "type": "text", "analyzer": "ik_max_word" } } } } POST /_aliases { "actions": [ { "add": { "index": "users_v2", "alias": "users_tmp" } } ] }
- 开发/测试环境通过
users_tmp
验证新索引逻辑,不影响线上users_v1
。
- 开发/测试环境通过
-
生产环境双写验证:
- 应用端同时写入
users_v1
和users_v2
(通过别名解耦,代码无需硬编码索引名), - 读取时通过别名
users
暂指向users_v1
,确保线上流量无影响。
- 应用端同时写入
-
原子切换别名指向:
POST /_aliases { "actions": [ { "remove": { "index": "users_v1", "alias": "users" } }, { "add": { "index": "users_v2", "alias": "users" } } // 可同时删除旧索引(需确保数据迁移完成) ] }
- 切换瞬间完成,客户端无感知,无需重启服务或修改配置。
- 核心价值:
- 风险隔离:通过临时别名
users_tmp
验证新索引,避免直接操作线上索引; - 零停机迁移:利用别名的原子操作,实现“热切换”,用户请求始终路由到有效索引。
- 从别名检索:
在 Elasticsearch 中,检索时使用索引别名与使用真实索引名的操作完全一致,别名会被透明解析为实际指向的索引(单个或多个)。
# 简单查询
GET /my_alias/_search
{
"query": { "match_all": {} }
}
# 带过滤的查询
GET /my_alias/_search
{
"query": { "term": { "status": "active" } }
}
若别名指向多个索引(如 logs_2025_q1 指向 logs-2025-01、logs-2025-02、logs-2025-03),检索时会同时查询所有关联索引:
GET /logs_2025_q1/_search
{
"query": { "range": { "timestamp": { "gte": "2025-01-01" } } }
}
等价于 GET /logs-2025-01,logs-2025-02,logs-2025-03/_search,但别名简化了索引列表的维护。
别名支持通配符模式(如 logs-*),检索时自动匹配所有符合模式的索引:
# 创建别名匹配2025年4月的所有日志索引
POST /_aliases
{
"actions": [
{ "add": { "index": "logs-2025-04*", "alias": "april_logs" } }
]
}
# 检索时使用别名
GET /april_logs/_search
{ "query": { "term": { "service": "user-center" } } }
避免在 DSL 中硬编码索引模式(如 logs-2025-04*),通过别名统一管理匹配规则。
三、索引模板
- 两个常见的业务场景问题:
- 问题1:数据量非常大,需要进行索引生命周期管理,具体要按日期划分索引,且要求多个索引的Mapping一致,而每次手动创建或者脚本创建都很麻烦,怎么办?
- 问题2:实际业务中应用了多个索引,想让这些索引中相同名字的字段类型完全一致,以便实现跨索引检索,怎么办?
我们会发现传统方式不能解决多索引的快速定义和高效管理等问题。因此,索引模板应运而生。
1. 索引模板的定义
Elasticsearch 7.8及之后版本支持两种定义模板的方式,可简记为普通模板定义方式和组件模板新增/创建方式。
- 普通模板定义方式如下所示:
PUT _index_template/<template_name> # 模板名称(唯一)
{
"index_patterns": ["logs-*", "metrics-*"], # 匹配的索引名模式(支持通配符)
"priority": 100, # 模板优先级(高优先级覆盖低优先级)
"template": { # 新索引的配置内容
"settings": { # 索引设置(分片、副本、刷新间隔等)
"number_of_shards": 3,
"number_of_replicas": 1,
"refresh_interval": "30s"
},
"mappings": { # 字段映射(类型、分词器、动态模板等)
"dynamic": "strict", # 严格模式(禁止自动添加未定义字段)
"properties": {
"@timestamp": { "type": "date" }, # 时间字段(必选,用于时序数据)
"message": {
"type": "text",
"analyzer": "ik_max_word", # 中文分词器(需提前安装)
"fields": { "keyword": { "type": "keyword" } } # 同时存储keyword子字段
}
}
},
"aliases": { # 为新索引自动绑定别名
"current_logs": {} # 别名指向新索引(无额外配置)
}
},
"composed_of": ["ilm_policy_template"] # 组合其他组件模板(可选,8.x+ 支持)
}
而组件模板的核心在于将原有普通模板定义的mappings、settings等以组件的方式分隔,以便最小化更新模板。
- 组件模板定义方式如下所示:
由上可知,模板名称为mydata_template,包含两个核心组件——component_mapping_template、component_settings_template。component_mapping_template组件模板实现了映射的定义,component_settings_template实现了设置和别名的定义。
当业务层面需要更新映射时,只需要更新component_mapping_template组件模板即可,改动范围更小、操作更精细化。
2. 索引模板应用的常见问题
模板和索引在应用上的区别是什么?
索引针对的是单一索引,类似MySQL中的一个表。而模板针对一个或多个索引,或者说是针对具有相同表结构的一类索引。
如果想更新映射,那么可以通过更新模板来实现吗?
首先需要建立这样一个认知前提:一旦创建了映射,除几个特定的类型以外,其他类型都不支持更新,除非进行reindex操作。
所以,一旦创建了索引,对索引模板的更新将不会影响该索引。
更新模板仅适用于新创建的索引。更新为动态模板仅会影响索引中的新字段。
附
分段
在Elasticsearch(基于Lucene实现)中,“分段”(Segment)是底层存储和处理数据的基本单元,本质上是一个不可变的倒排索引文件。以下是具体解释:
分段的本质与作用
- 倒排索引载体:每个分段存储一组文档的倒排索引(关键词到文档的映射),是Lucene实现快速搜索的核心数据结构。
- 独立搜索单元:分段一旦生成就不可修改,可独立被搜索,多个分段的搜索结果会在查询时合并。
- 写入过程的中间产物:文档写入时不会直接写入磁盘上的主索引,而是先存入内存缓冲区,通过定期刷新(
refresh
)生成新分段。
分段与“近实时”机制的关系
-
默认1秒刷新(
refresh_interval
):
Elasticsearch默认每1秒将内存缓冲区中的文档写入一个新分段(并开放搜索),这使得数据在写入后1秒内可见,实现“近实时”(Near Real-Time)。- 若调大该值(如
30s
),则每30秒生成一个分段,数据可见延迟增加,但减少分段生成频率。
- 若调大该值(如
-
“准实时”与“近实时”的区别:
- 准实时:数据可见延迟较长(如分钟级),分段生成频率低。
- 近实时:通过高频刷新(1秒)缩短延迟,但代价是分段数量增加。
分段对写入性能的影响
-
频繁生成分段的代价:
每次refresh
会:- 将内存数据写入分段文件(磁盘I/O);
- 生成新的分段元数据(如文件句柄、索引结构);
- 可能触发后续的分段合并(
merge
)操作(长期分段过多时,Elasticsearch会自动合并小分段为大分段,减少搜索时的开销)。
这些操作在高写入负载下会消耗CPU、磁盘I/O和内存资源,降低写入吞吐量。
-
调大刷新间隔的优势:
减少分段生成频率,降低I/O和元数据管理开销,提升写入性能,适合对实时性要求不高的场景(如日志分析、离线报表)。
分段的生命周期
- 生成:通过
refresh
操作将内存数据写入新分段(默认1秒一次)。 - 存在:分段不可变,可被搜索,直到被合并或删除。
- 合并:Elasticsearch后台定期合并小分段为大分段,减少分段数量,提升搜索效率(合并过程会释放旧分段资源)。
- 删除:当文档被删除时,分段不会立即修改,而是记录“删除标记”,合并时才真正移除被删除的文档。