索引模板决定「新索引长什么样」:分片数、副本数、分析器、字段映射、生命周期策略。7.8 之前只有一种模板,所有配置塞在一个大 JSON 里,多个业务共用时靠复制粘贴维护,改一处要同步改十处。组合模板(composable template)把配置拆成可复用的组件模板,再用组合模板按顺序拼装,配合 ILM 形成「模板定结构、ILM 管生命周期」的分工。本文讲清这套机制的合并规则、与 ILM 的配合方式,以及升级迁移时的注意事项。
1. 索引模板的演进
1.1 旧式模板的问题
旧式模板(legacy template)用 PUT _template/<name> 定义,把所有配置写在一个对象里。它有三个硬伤:一是无法复用,两个业务都要「中文分析器」只能各写一份;二是没有组合能力,公共配置与私有配置混在一起;三是冲突时只有「后定义者覆盖」,没有显式的优先级表达。团队规模一大,模板就变成没人敢动的巨石。
1.2 组件模板与组合模板
组合模板体系把配置拆成两层:
- 组件模板(component template):一段可复用的配置片段,包含
settings、mappings、aliases中的任意组合。 - 组合模板(index template):声明
index_patterns与composed_of列表,把若干组件模板按顺序组装,并可再附加自己的settings、mappings、aliases。
一个索引创建时,Elasticsearch 找出匹配它的所有组合模板,按 priority 选优先级最高的,再按 composed_of 顺序合并组件模板。
1.3 与相关概念的分工
| 概念 | 作用 | 生效时机 |
|---|---|---|
| 组合模板 | 定义新索引的结构 | 索引创建时 |
| ILM 策略 | 定义索引的生命周期动作 | 索引存在期间 |
| 数据流 | 管理按时间滚动的写索引 | rollover 时 |
| 搜索模板 | 复用查询语句 | 查询时 |
| 动态映射 | 自动推断字段类型 | 写入新字段时 |
一个常见混淆是把「组合模板」与「搜索模板」搞混。前者管索引结构,后者管查询语句(用 Mustache 语法),两者毫无关系,只是中文名都带「模板」二字。
2. 组件模板
2.1 定义组件模板
curl -X PUT "localhost:9200/_component_template/base_settings" \
-H "Content-Type: application/json" -d'
{
"template": {
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1,
"refresh_interval": "30s"
}
},
"_meta": {
"description": "通用分片与刷新设置",
"version": 3
}
}'
template 里可以放 settings、mappings、aliases 三者任意组合;_meta 是自由元数据,用来记录用途与版本,不参与合并。
2.2 分析器组件
把分析器定义抽成独立组件,供多个业务复用:
curl -X PUT "localhost:9200/_component_template/zh_analyzer" \
-H "Content-Type: application/json" -d'
{
"template": {
"settings": {
"analysis": {
"analyzer": {
"zh_index_analyzer": {
"type": "custom",
"tokenizer": "ik_max_word",
"filter": ["lowercase", "stop"]
}
}
}
}
}
}'
分析器属于 settings,因此可以在多个组合模板间共享。注意分析器变更需要重建索引,组件模板只解决「定义复用」,不解决「重建」。
2.3 映射组件
字段映射是最适合拆分的部分:把 ECS 公共字段、时间字段、业务字段分开定义。
curl -X PUT "localhost:9200/_component_template/ecs_base_mapping" \
-H "Content-Type: application/json" -d'
{
"template": {
"mappings": {
"properties": {
"@timestamp": { "type": "date" },
"host": {
"properties": { "name": { "type": "keyword" } }
},
"message": { "type": "text" }
}
}
}
}'
properties 的合并是逐字段深度合并:多个组件定义了不同字段会叠加;定义了同名字段则以靠后的组件为准。
2.4 组件模板的版本化
组件模板支持 version 字段做版本标记,但它只是元数据,不影响合并:
{ "_meta": { "version": 3, "updated_by": "platform-team" } }
真正需要版本化的是组件模板本身。推荐做法是每次变更新建一个带版本号的组件(如 base_settings_v2),组合模板引用新组件,观察一段时间后再删除旧组件。这样回滚只需改引用,不用重建旧定义。
3. 组合模板
3.1 声明索引匹配
curl -X PUT "localhost:9200/_index_template/logs_template" \
-H "Content-Type: application/json" -d'
{
"index_patterns": ["logs-*"],
"priority": 200,
"composed_of": ["base_settings", "ecs_base_mapping", "zh_analyzer"],
"template": {
"settings": { "number_of_replicas": 2 },
"aliases": { "logs": {} }
}
}'
index_patterns 支持通配符,一个索引可以匹配多个组合模板,靠 priority 决胜。
3.2 composed_of 的顺序与合并规则
合并顺序是:组件模板按 composed_of 数组顺序依次合并,最后合并组合模板自身的 template。后合并的覆盖先合并的。因此把「通用配置」放前面、「特化配置」放后面是基本纪律。
base_settings (shards=3, replicas=1)
→ zh_analyzer (analysis)
→ ecs_base_mapping (mappings)
→ template 自身 (replicas=2) ← 最终 replicas=2
3.3 priority 与模板冲突
当多个组合模板匹配同一索引:
curl -X PUT "localhost:9200/_index_template/logs_special" \
-H "Content-Type: application/json" -d'
{
"index_patterns": ["logs-nginx-*"],
"priority": 300,
"composed_of": ["base_settings", "nginx_mapping"]
}'
logs-nginx-2026.01 同时匹配 logs-*(priority 200)与 logs-nginx-*(priority 300),后者胜出。注意不会合并两个组合模板:只有优先级最高的那个生效,其余被完全忽略。这是最容易踩的坑——以为「更具体的模板会叠加,只覆盖冲突部分」,实际是整体替换。
3.4 优先级相同的情况
若两个组合模板 priority 相同且都匹配,Elasticsearch 无法确定用哪个,会拒绝创建索引并报错。因此同一 pattern 空间内必须保证优先级唯一。
3.5 模板与别名的关系
组合模板里的 aliases 会在索引创建时自动挂上:
{
"aliases": {
"logs": {},
"logs-write": { "is_write_index": true }
}
}
is_write_index: true 用于数据流或 rollover 场景,标记哪个索引是当前写入索引。多个索引挂同一别名时,只有一个是 write index。
4. 与 ILM 的配合
4.1 在模板里挂 ILM
ILM 策略通过 index.lifecycle.name 绑定,模板是绑定的标准位置:
{
"template": {
"settings": {
"index.lifecycle.name": "logs_30d_policy",
"index.lifecycle.rollover_alias": "logs"
}
}
}
rollover_alias 告诉 ILM 在 rollover 时操作哪个别名。两者必须成对出现,只配 name 不配 alias 会导致 rollover 动作失败。
4.2 数据流与模板
数据流(data stream)的模板必须声明 data_stream:
curl -X PUT "localhost:9200/_index_template/logs_ds_template" \
-H "Content-Type: application/json" -d'
{
"index_patterns": ["logs-app-*"],
"data_stream": {},
"priority": 500,
"composed_of": ["base_settings", "ecs_base_mapping"]
}'
data_stream: {} 表示匹配到的名称会创建为数据流而非普通索引。数据流自带 rollover 语义,index.lifecycle.rollover_alias 不再需要,ILM 会自动处理。
4.3 生命周期与模板的职责边界
| 配置 | 归属 | 原因 |
|---|---|---|
| 分片数 | 模板 | 创建时固定,不可改 |
| 副本数 | 模板(ILM 可调) | 可动态调整 |
| 分析器 | 模板 | 影响索引结构 |
| 字段映射 | 模板 | 影响索引结构 |
| 段合并 | ILM | 生命周期动作 |
| 转冷/删除 | ILM | 生命周期动作 |
| 分片分配 | ILM | 随阶段变化 |
原则是:结构性的、创建后不可改的放模板;运行期的、随时间变化的放 ILM。把副本数写在模板里作为默认值,ILM 在 warm 阶段再调低,是常见组合。
4.4 rollover 别名与模板的一致性
ILM 的 rollover 会创建新索引,新索引继承的配置来自模板而非旧索引。因此改了模板后,只有新滚动的索引会生效,已存在的索引保持原状。这一点常导致「改了模板但没效果」的困惑——要生效必须等下一次 rollover,或手工重建索引。
5. 版本化与迁移
5.1 模板变更的生效时机
| 变更 | 对已有索引 | 对新建索引 |
|---|---|---|
| 组件模板内容 | 无影响 | 生效 |
| 组合模板 composed_of | 无影响 | 生效 |
| 组合模板 priority | 无影响 | 生效 |
| 动态 mapping 参数 | 部分可改 | 生效 |
结论:模板是「创建时快照」,改模板不会追溯修改已有索引。需要追溯时只能 _reindex 或重建。
5.2 迁移旧式模板
旧式模板用 _template API,迁移到组合模板要分三步:
# 第一步:查看旧模板
curl -s "localhost:9200/_template/legacy_logs?pretty"
# 第二步:拆分成组件模板
curl -X PUT "localhost:9200/_component_template/legacy_logs_settings" -d @settings.json
curl -X PUT "localhost:9200/_component_template/legacy_logs_mapping" -d @mapping.json
# 第三步:建组合模板并验证
curl -X PUT "localhost:9200/_index_template/legacy_logs" -d @composed.json
拆分时注意:旧模板的 order 字段对应新体系的 priority,数值语义一致但取值范围不同(旧 order 默认 0,新 priority 默认 100),迁移后要重新校准优先级。
5.3 新旧模板共存
组合模板与旧式模板可以共存,但组合模板优先:若一个索引同时匹配两类模板,只有组合模板生效。因此迁移期间要避免新旧同时匹配同一 pattern,否则会出现「旧模板改了没反应」的情况。稳妥做法是迁移时给新组合模板用稍不同的 pattern 做灰度,验证后再切换。
5.4 版本化发布流程
平台团队维护模板时,建议走这样的流程:
- 新增组件模板
xxx_v2,不动xxx_v1; - 新建组合模板
yyy_v2,composed_of引用 v2 组件,priority略高于 v1; - 用
_simulate验证新旧组合的最终配置差异; - 观察一段时间,确认无异常后删除 v1 组件与组合模板。
这样任何一步都可回退,不会出现「改坏了没有旧版本可用」的局面。
6. 实践与排错
6.1 用 _simulate 预览
创建索引前先模拟,看最终生效的配置:
curl -X POST "localhost:9200/_index_template/_simulate_index/logs-2026.01?pretty"
返回 settings、mappings、aliases 的最终合并结果,以及 overlapping 字段说明有哪些模板匹配了。这是排查「为什么分片数是 5 不是 3」的最快手段。
6.2 查看索引实际继承的配置
curl -s "localhost:9200/logs-2026.01/_settings?pretty"
curl -s "localhost:9200/logs-2026.01/_mapping?pretty"
若与预期不符,先确认索引是哪个模板创建的(index.template 或 index.provided_name),再回看该模板的 composed_of 顺序。
6.3 常见问题
| 现象 | 原因 | 对策 |
|---|---|---|
| 模板不生效 | priority 被更高者压制 | 用 _simulate 看 overlapping |
| 配置被覆盖 | composed_of 顺序反了 | 通用在前、特化在后 |
| 报优先级冲突 | 两个模板 priority 相同 | 保证 pattern 空间内唯一 |
| rollover 失败 | 缺 rollover_alias | 补上并检查别名 write index |
| 改了模板没反应 | 已有索引不追溯 | 等 rollover 或重建索引 |
| 组件模板未定义 | 引用名拼写错 | 先 _component_template/<name> 确认 |
6.4 多租户下的模板组织
多租户场景建议按「平台层 + 租户层」拆分:
平台层组件:base_settings、ecs_base_mapping、common_analyzer
租户层组合:tenant_a_template(priority 400, composed_of 平台层 + tenant_a_mapping)
租户层只定义差异部分,平台层统一维护公共配置。这样新增租户只需建一个映射组件与一个组合模板,不必复制全部配置。租户隔离的完整方案可阅读《多租户隔离》,字段建模细节可阅读《数据建模与 Mapping 设计》。
7. 总结
| 环节 | 要点 |
|---|---|
| 分层 | 组件模板复用配置,组合模板按顺序拼装 |
| 合并顺序 | composed_of 顺序 + 组合模板自身最后合并 |
| 优先级 | 多个匹配时只取 priority 最高者,不叠加 |
| 与 ILM | 模板定结构,ILM 管生命周期,rollover_alias 成对配 |
| 生效时机 | 模板是创建时快照,不追溯已有索引 |
| 迁移 | 旧 order 对应新 priority,注意取值范围差异 |
| 版本化 | 新组件新组合,灰度验证后再删旧版 |
| 排错 | _simulate 看最终配置与 overlapping |
组合模板的价值在于把「配置」变成「可组装的构件」,让平台团队维护公共部分、业务团队只关心差异。用好它的前提是理解合并顺序与优先级语义——这两点搞错,模板体系会从「减少重复」变成「制造困惑」。索引生命周期策略的细节可阅读《索引生命周期与 rollover》。
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。