8925 字
45 分钟
ELK系列(三):DSL查询、索引模板与集群运维API实战

引言与背景#

ELK 平台部署完成、日志数据开始流转之后,真正的考验才刚刚开始。很多运维同学的日常困境是:查日志只会在 Kibana Discover 里”点点点”,一旦遇到统计某接口近 1 小时平均响应时间、排查非 200 且耗时超 1 秒的请求这类需求就无从下手;索引按天滚动后分片、字段类型逐渐混乱;集群扩容、换机时数据迁移没有标准姿势;集群变黄、变红时更不知道该用哪些 API 快速定位根因。

本篇作为 ELK 实战系列的第三篇,将一次性补齐这四块运维高频短板,文中所有示例均可在 Kibana Dev Tools 中直接复制执行并复现结果。

系列文章导航
  • 第一篇:ELK 平台部署实战
  • 第二篇:日志采集与索引基础概念
  • 第三篇(本篇):DSL 查询、索引模板与集群运维 API 实战

目标读者:已完成 ELK 部署(本系列前两篇)、了解索引 / 文档 / 字段基础概念,但不会写查询语句、缺乏集群运维经验的运维工程师。

本文实战目标与收益:

  • 掌握运维够用的 DSL 查询 / 过滤 / 聚合技能
  • 学会用索引模板统一管控日志索引,根治分片与字段类型混乱
  • 掌握同集群与跨集群两套数据迁移标准方案
  • 熟记一套 ES 集群故障排查 API 组合拳,看完即可在生产环境落地

DSL语句#

DSL 是 ES 运维的”基本功”——日志排查、慢请求定位、指标统计,最终都要落到一条条 DSL 上。本模块从测试数据准备开始,逐条拆解运维最常用的查询、过滤、聚合语法,每个示例都附预期返回,确保你照着做就能得到一致的结果。

本模块前置知识#

  • 已完成 Elasticsearch 集群部署并能正常访问(本系列前两篇内容)
  • 会使用 Kibana Dev Tools 执行 REST 请求(GET / PUT / POST 基本写法)
  • 能看懂 JSON 格式,了解索引、文档、字段的基本概念

本模块核心术语#

DSL : Domain Specific Language,ES 提供的基于 JSON 的查询语言,所有查询 / 过滤 / 聚合都通过它表达。

Query 与 Filter 上下文 : Query 上下文会计算相关性得分(_score);Filter 上下文只做”是 / 否”过滤、不计分且可走缓存,过滤场景优先用 Filter,性能更好。

相关性评分 _score : ES 对每条命中文档计算的相关性得分,默认按得分降序返回。

倒排索引 : ES 的核心数据结构,按”词条 → 文档列表”组织,是全文检索毫秒级响应的基础。

分词器 Analyzer : 写入和检索时将文本拆分为词条的组件,决定 match 的匹配粒度。

聚合 Aggregation : 对查询结果做分组、统计计算的能力,相当于 SQL 的 GROUP BY + 统计函数。

测试数据准备:Dev Tools 批量写入模拟日志#

后续所有示例统一复用一份模拟 Nginx 访问日志,包含 @timestamp、client_ip、url、status、response_time、message 六个字段。在 Dev Tools 执行以下 _bulk 请求一次性写入:

Kibana Dev Tools
POST logs-demo/_bulk
{"index":{"_id":"1"}}
{"@timestamp":"2024-11-29T09:00:01.000Z","client_ip":"192.168.1.10","url":"/api/users","status":200,"response_time":0.12,"message":"request completed successfully"}
{"index":{"_id":"2"}}
{"@timestamp":"2024-11-29T09:05:12.000Z","client_ip":"10.0.0.8","url":"/api/orders","status":500,"response_time":2.35,"message":"error timeout while calling upstream service"}
{"index":{"_id":"3"}}
{"@timestamp":"2024-11-29T09:11:30.000Z","client_ip":"192.168.1.11","url":"/static/logo.png","status":404,"response_time":0.05,"message":"file not found"}
{"index":{"_id":"4"}}
{"@timestamp":"2024-11-29T09:20:45.000Z","client_ip":"172.16.0.20","url":"/api/users","status":200,"response_time":0.35,"message":"request completed successfully"}
{"index":{"_id":"5"}}
{"@timestamp":"2024-11-29T09:32:08.000Z","client_ip":"10.0.0.9","url":"/api/pay","status":502,"response_time":3.80,"message":"error upstream connection refused"}
{"index":{"_id":"6"}}
{"@timestamp":"2024-11-29T09:40:00.000Z","client_ip":"127.0.0.1","url":"/health","status":200,"response_time":0.01,"message":"health check ok"}
{"index":{"_id":"7"}}
{"@timestamp":"2024-11-29T09:47:55.000Z","client_ip":"10.0.0.8","url":"/api/orders","status":500,"response_time":1.56,"message":"error timeout while writing to database"}
{"index":{"_id":"8"}}
{"@timestamp":"2024-11-29T09:55:21.000Z","client_ip":"192.168.1.12","url":"/old-page","status":301,"response_time":0.02,"message":"redirect to new page"}

预期返回中 "errors": false 表示全部写入成功。随后用 _count 验证写入条数:

Kibana Dev Tools
GET logs-demo/_count
预期返回
{ "count": 8, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 } }
关于 _bulk 的小细节

_bulk 的 action 行与数据行必须交替排列,且每一行都以换行符结尾(包括最后一行),否则会抛出 parse_exception。

match:全文检索#

match 会先对查询词分词,再与倒排索引中的词条逐一匹配,适合检索 message 这类长文本。例如检索 message 中含 “error timeout” 的日志:

Kibana Dev Tools
GET logs-demo/_search
{
"query": {
"match": { "message": "error timeout" }
}
}

预期返回(节选)——total.value 为命中总数,文档默认按 _score 降序排列:

预期返回(节选)
{
"hits": {
"total": { "value": 3, "relation": "eq" },
"hits": [
{ "_id": "2", "_score": 1.68 },
{ "_id": "7", "_score": 1.62 },
{ "_id": "5", "_score": 0.83 }
]
}
}

注意 _id: 5 的文档只含 “error” 不含 “timeout”,依然被命中——因为 match 分词后默认是 OR 逻辑,任一词条命中即算匹配。

match_phrase:短语精确匹配#

match_phrase 将查询词作为整体短语、按顺序精确匹配,适合检索固定报错串:

Kibana Dev Tools
GET logs-demo/_search
{
"query": {
"match_phrase": { "message": "connection refused" }
}
}
预期返回(节选)
{
"hits": {
"total": { "value": 1, "relation": "eq" },
"hits": [ { "_id": "5", "_source": { "message": "error upstream connection refused" } } ]
}
}

两者差异一目了然:match "error timeout" 命中 3 条(分词后任一词条命中即可),而 match_phrase "connection refused" 只命中短语连续出现的 1 条。排查固定报错串时优先用 match_phrase,避免分词带来的”误命中”。

match_all:全局匹配#

match_all 返回索引内全部文档,常配合 _source、size 做数据抽样查看:

Kibana Dev Tools
GET logs-demo/_search
{
"query": { "match_all": {} }
}

预期返回中 total.value 为 8;需要注意 hits 数组默认最多返回 10 条(由 size 参数控制,本例数据量小故全部返回)。

_source:只查看指定字段#

线上日志的 message 字段可能很大,全量返回会拖慢查询、占用带宽。用 _source 裁剪返回字段,只保留关心的列:

Kibana Dev Tools
GET logs-demo/_search
{
"_source": ["url", "status", "response_time"],
"query": { "match_all": {} }
}

预期返回中每个文档的 _source 仅含指定的三个字段:

预期返回(节选)
{ "_id": "1", "_source": { "url": "/api/users", "status": 200, "response_time": 0.12 } }

语法高亮 highlight#

对命中关键字自动包裹 <em> 标签,便于在结果列表中快速定位关键词:

Kibana Dev Tools
GET logs-demo/_search
{
"query": { "match": { "message": "timeout" } },
"highlight": {
"fields": { "message": {} }
}
}

预期返回中每个命中文档会多出 highlight 节点:

预期返回(节选)
{
"_id": "2",
"highlight": { "message": [ "error <em>timeout</em> while calling upstream service" ] }
}
自定义高亮标签

默认标签为 <em>,可通过 pre_tags / post_tags 替换为自定义标签(如 <span class="hl">),方便与前端页面样式集成。


分页 from/size#

from + size 是最直观的翻页方式:from 指定起始偏移量,size 指定每页条数。例如查看第 2 页、每页 3 条:

Kibana Dev Tools
GET logs-demo/_search
{
"from": 3,
"size": 3,
"query": { "match_all": {} }
}

预期返回 hits 数组中只包含第 4~6 条文档。

深分页陷阱:max_result_window

from + size 的值超过 max_result_window(默认 10000) 时会直接报错 Result window is too large。因为深分页需要协调节点对每个分片拉取 from + size 条数据再全局排序,内存开销随页深度线性暴涨。需要遍历大批量数据时请改用 search_after + PIT,禁止在脚本里无脑翻大页。

排序 sort#

通过 sort 按 @timestamp 或 response_time 排序,例如按响应时间降序找最慢的请求:

Kibana Dev Tools
GET logs-demo/_search
{
"sort": [
{ "response_time": { "order": "desc" } }
],
"query": { "match_all": {} }
}

预期返回中 _id: 5(3.80s)排在最前,_id: 6(0.01s)排在最后。注意排序后文档的 _score 变为 null(排序场景下不再计算相关性,省一笔开销)。

text 字段不可直接排序

对 message 这类 text 类型字段排序会报错 Fielddata is disabled on text fields。正确姿势是使用其 keyword 子字段:"sort": [{ "url.keyword": { "order": "asc" } }]。

判断文档是否存在#

运维中有两类”存在性”判断,对应两种姿势:

姿势一:按文档 ID 判断——HEAD 请求零开销,存在返回 200,不存在返回 404:

Kibana Dev Tools
HEAD logs-demo/_doc/1
HEAD logs-demo/_doc/999

姿势二:按字段判断——exists 查询筛查缺失某字段的异常文档,例如找出没有 response_time 的脏数据:

Kibana Dev Tools
GET logs-demo/_search
{
"query": {
"bool": {
"must_not": { "exists": { "field": "response_time" } }
}
}
}

当前测试数据字段齐全,预期返回 total.value 为 0;一旦写入异常文档,该查询能第一时间把它们捞出来。

range 范围过滤(lt/gt/lte/gte)#

range 用于数值、日期的区间过滤,四个边界参数含义如下:

参数含义记忆口诀
lt小于(less than)严格小于,不含边界
gt大于(greater than)严格大于,不含边界
lte小于等于含边界
gte大于等于含边界

实战示例:在 filter 上下文中筛选响应时间大于 1 秒的慢请求:

Kibana Dev Tools
GET logs-demo/_search
{
"query": {
"bool": {
"filter": [
{ "range": { "response_time": { "gt": 1 } } }
]
}
}
}

预期返回命中 _id: 2(2.35s)、_id: 5(3.80s)、_id: 7(1.56s)三条慢请求。时间过滤同理,"@timestamp": { "gte": "now-1h" } 即可圈定近 1 小时的日志。

为什么过滤要放在 filter 上下文

filter 只做”是 / 否”判断、不计算 _score,且结果可被 ES 自动缓存复用。同样的 range 条件放在 match 系查询里既慢又浪费内存,过滤场景一律优先 filter。

聚合统计(terms/max/min/avg/sum)#

聚合相当于 SQL 的 GROUP BY + 统计函数。加上 "size": 0 只取聚合结果、不返回文档明细,是统计场景的标准写法。

terms 聚合:按 status 状态码分组计数,一眼看出 5xx 占比:

Kibana Dev Tools
GET logs-demo/_search
{
"size": 0,
"aggs": {
"status_count": {
"terms": { "field": "status" }
}
}
}
预期返回(节选)
{
"aggregations": {
"status_count": {
"buckets": [
{ "key": 200, "doc_count": 3 },
{ "key": 500, "doc_count": 2 },
{ "key": 404, "doc_count": 1 },
{ "key": 502, "doc_count": 1 },
{ "key": 301, "doc_count": 1 }
]
}
}
}

max / min / avg / sum:一条请求同时统计 response_time 四项指标(等价于 stats 聚合拆开写):

Kibana Dev Tools
GET logs-demo/_search
{
"size": 0,
"aggs": {
"rt_max": { "max": { "field": "response_time" } },
"rt_min": { "min": { "field": "response_time" } },
"rt_avg": { "avg": { "field": "response_time" } },
"rt_sum": { "sum": { "field": "response_time" } }
}
}

预期返回各聚合节点分别给出 3.8、0.01、1.02、8.21 左右的统计值。聚合还可以嵌套(如先按 status 分组、再组内算 avg),这是接口级性能分析的常用手段。

权重 boost#

多字段联合检索时,用 字段^数值 提升关键字段的权重,让重要命中排在前面:

Kibana Dev Tools
GET logs-demo/_search
{
"query": {
"multi_match": {
"query": "error",
"fields": [ "url^3", "message" ]
}
}
}

url^3 表示 url 字段命中的权重放大 3 倍。对比不加 boost 的执行结果,可观察到:url 中含关键词的文档 _score 显著提升、排名前移。boost 数值没有标准答案,按业务对字段重要性的判断调试即可。

bool 多条件组合查询(must/must_not/should)#

bool 是 DSL 的”总装车间”,四个子句各司其职:

子句语义是否计分
must必须满足计分
filter必须满足不计分,可缓存
must_not必须不满足不计分
should应该满足计分,可提升排名

运维高频综合实战:排查”近 1 小时、响应超 1 秒、排除内部健康检查 IP(127.0.0.1)“的慢请求:

Kibana Dev Tools
GET logs-demo/_search
{
"query": {
"bool": {
"must": [
{ "match": { "message": "error" } }
],
"filter": [
{ "range": { "@timestamp": { "gte": "now-1h" } } },
{ "range": { "response_time": { "gt": 1 } } }
],
"must_not": [
{ "term": { "client_ip.keyword": "127.0.0.1" } }
]
}
}
}

预期返回命中 _id: 2、_id: 5、_id: 7 三条真实慢请求——must 保证日志含 error 语义,两个 filter 圈定时间与耗时,must_not 剔除健康检查流量。

should 与 minimum_should_match 的搭配

当 bool 中只有 should 子句时,默认至少满足 1 个 should 条件;一旦同时存在 must / filter,should 默认只影响评分、不影响命中。此时需显式设置 "minimum_should_match": 1 才能保证 should 条件必中其一,这是生产环境最常见的 bool 踩坑点。


索引模板#

日志索引按天滚动生成,如果放任 ES 自由发挥,每个新索引的分片数、字段类型都可能”开盲盒”。本模块用索引模板把 settings、mappings、aliases 三件事一次性管起来,让每一天的日志索引从出生起就符合统一规范。

本模块前置知识#

  • 已掌握上一模块的 DSL 基础操作
  • 了解分片(Shard)、副本(Replica)、Mapping 的基本概念

本模块核心术语#

索引模板 Index Template : 预先定义的规则,新建索引名称匹配规则时自动套用 settings / mappings / aliases。

index_patterns : 模板的索引名匹配模式,如 logs-*。

优先级 priority : 多个模板同时匹配时,priority 数值大的模板优先生效。

动态映射 Dynamic Mapping : ES 根据写入数据自动推断字段类型的机制,模板可提前约束推断结果。

为什么需要索引模板:日志按天滚动的管控痛点#

不设模板时,每天自动生成的 logs-2024-11-29、logs-2024-11-30 等索引会完全依赖 ES 的默认行为:

  • 分片数随机:不同版本默认值不同,小索引也占 1 主 1 副,分片越积越多拖垮集群
  • 字段类型漂移:某天 status 被推断成 long,某天混入字符串后被推断成 text,聚合直接报错
  • 无统一别名:跨天查询要手写一串索引名,无法用一个别名收口

索引模板正是解决这类”批量索引统一管控”问题的标准入口。

创建索引模板:settings/mappings/aliases 三要素#

执行以下请求创建可组合索引模板(ES 7.8+ 推荐的新版 _index_template):

Kibana Dev Tools
PUT _index_template/logs-template
{
"index_patterns": ["logs-*"],
"priority": 100,
"template": {
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1
},
"mappings": {
"properties": {
"@timestamp": { "type": "date" },
"client_ip": { "type": "ip" },
"url": { "type": "text", "fields": { "keyword": { "type": "keyword" } } },
"status": { "type": "integer" },
"response_time": { "type": "float" },
"message": { "type": "text" }
}
},
"aliases": {
"logs-all": {}
}
}
}

预期返回:

预期返回
{ "acknowledged": true }

三要素各司其职:settings 锁定分片与副本数,mappings 把六个字段的类型一次性钉死(含 url 的 keyword 子字段,排序聚合全靠它),aliases 让所有 logs-* 索引自动挂上 logs-all 统一别名,跨天查询直接查别名即可。


分页 from/size#

from + size 是最直观的翻页方式:from 指定起始偏移量,size 指定每页条数。例如查看第 2 页、每页 3 条:

Kibana Dev Tools
GET logs-demo/_search
{
"from": 3,
"size": 3,
"query": { "match_all": {} }
}

预期返回 hits 数组中只包含第 4~6 条文档。

深分页陷阱:max_result_window

from + size 的值超过 max_result_window(默认 10000) 时会直接报错 Result window is too large。因为深分页需要协调节点对每个分片拉取 from + size 条数据再全局排序,内存开销随页深度线性暴涨。需要遍历大批量数据时请改用 search_after + PIT,禁止在脚本里无脑翻大页。

排序 sort#

通过 sort 按 @timestamp 或 response_time 排序,例如按响应时间降序找最慢的请求:

Kibana Dev Tools
GET logs-demo/_search
{
"sort": [
{ "response_time": { "order": "desc" } }
],
"query": { "match_all": {} }
}

预期返回中 _id: 5(3.80s)排在最前,_id: 6(0.01s)排在最后。注意排序后文档的 _score 变为 null(排序场景下不再计算相关性,省一笔开销)。

text 字段不可直接排序

对 message 这类 text 类型字段排序会报错 Fielddata is disabled on text fields。正确姿势是使用其 keyword 子字段:"sort": [{ "url.keyword": { "order": "asc" } }]。

判断文档是否存在#

运维中有两类”存在性”判断,对应两种姿势:

姿势一:按文档 ID 判断——HEAD 请求零开销,存在返回 200,不存在返回 404:

Kibana Dev Tools
HEAD logs-demo/_doc/1
HEAD logs-demo/_doc/999

姿势二:按字段判断——exists 查询筛查缺失某字段的异常文档,例如找出没有 response_time 的脏数据:

Kibana Dev Tools
GET logs-demo/_search
{
"query": {
"bool": {
"must_not": { "exists": { "field": "response_time" } }
}
}
}

当前测试数据字段齐全,预期返回 total.value 为 0;一旦写入异常文档,该查询能第一时间把它们捞出来。

range 范围过滤(lt/gt/lte/gte)#

range 用于数值、日期的区间过滤,四个边界参数含义如下:

参数含义记忆口诀
lt小于(less than)严格小于,不含边界
gt大于(greater than)严格大于,不含边界
lte小于等于含边界
gte大于等于含边界

实战示例:在 filter 上下文中筛选响应时间大于 1 秒的慢请求:

Kibana Dev Tools
GET logs-demo/_search
{
"query": {
"bool": {
"filter": [
{ "range": { "response_time": { "gt": 1 } } }
]
}
}
}

预期返回命中 _id: 2(2.35s)、_id: 5(3.80s)、_id: 7(1.56s)三条慢请求。时间过滤同理,"@timestamp": { "gte": "now-1h" } 即可圈定近 1 小时的日志。

为什么过滤要放在 filter 上下文

filter 只做”是 / 否”判断、不计算 _score,且结果可被 ES 自动缓存复用。同样的 range 条件放在 match 系查询里既慢又浪费内存,过滤场景一律优先 filter。

聚合统计(terms/max/min/avg/sum)#

聚合相当于 SQL 的 GROUP BY + 统计函数。加上 "size": 0 只取聚合结果、不返回文档明细,是统计场景的标准写法。

terms 聚合:按 status 状态码分组计数,一眼看出 5xx 占比:

Kibana Dev Tools
GET logs-demo/_search
{
"size": 0,
"aggs": {
"status_count": {
"terms": { "field": "status" }
}
}
}
预期返回(节选)
{
"aggregations": {
"status_count": {
"buckets": [
{ "key": 200, "doc_count": 3 },
{ "key": 500, "doc_count": 2 },
{ "key": 404, "doc_count": 1 },
{ "key": 502, "doc_count": 1 },
{ "key": 301, "doc_count": 1 }
]
}
}
}

max / min / avg / sum:一条请求同时统计 response_time 四项指标(等价于 stats 聚合拆开写):

Kibana Dev Tools
GET logs-demo/_search
{
"size": 0,
"aggs": {
"rt_max": { "max": { "field": "response_time" } },
"rt_min": { "min": { "field": "response_time" } },
"rt_avg": { "avg": { "field": "response_time" } },
"rt_sum": { "sum": { "field": "response_time" } }
}
}

预期返回各聚合节点分别给出 3.8、0.01、1.02、8.21 左右的统计值。聚合还可以嵌套(如先按 status 分组、再组内算 avg),这是接口级性能分析的常用手段。

权重 boost#

多字段联合检索时,用 字段^数值 提升关键字段的权重,让重要命中排在前面:

Kibana Dev Tools
GET logs-demo/_search
{
"query": {
"multi_match": {
"query": "error",
"fields": [ "url^3", "message" ]
}
}
}

url^3 表示 url 字段命中的权重放大 3 倍。对比不加 boost 的执行结果,可观察到:url 中含关键词的文档 _score 显著提升、排名前移。boost 数值没有标准答案,按业务对字段重要性的判断调试即可。

bool 多条件组合查询(must/must_not/should)#

bool 是 DSL 的”总装车间”,四个子句各司其职:

子句语义是否计分
must必须满足计分
filter必须满足不计分,可缓存
must_not必须不满足不计分
should应该满足计分,可提升排名

运维高频综合实战:排查”近 1 小时、响应超 1 秒、排除内部健康检查 IP(127.0.0.1)“的慢请求:

Kibana Dev Tools
GET logs-demo/_search
{
"query": {
"bool": {
"must": [
{ "match": { "message": "error" } }
],
"filter": [
{ "range": { "@timestamp": { "gte": "now-1h" } } },
{ "range": { "response_time": { "gt": 1 } } }
],
"must_not": [
{ "term": { "client_ip.keyword": "127.0.0.1" } }
]
}
}
}

预期返回命中 _id: 2、_id: 5、_id: 7 三条真实慢请求——must 保证日志含 error 语义,两个 filter 圈定时间与耗时,must_not 剔除健康检查流量。

should 与 minimum_should_match 的搭配

当 bool 中只有 should 子句时,默认至少满足 1 个 should 条件;一旦同时存在 must / filter,should 默认只影响评分、不影响命中。此时需显式设置 "minimum_should_match": 1 才能保证 should 条件必中其一,这是生产环境最常见的 bool 踩坑点。


索引模板#

日志索引按天滚动生成,如果放任 ES 自由发挥,每个新索引的分片数、字段类型都可能”开盲盒”。本模块用索引模板把 settings、mappings、aliases 三件事一次性管起来,让每一天的日志索引从出生起就符合统一规范。

本模块前置知识#

  • 已掌握上一模块的 DSL 基础操作
  • 了解分片(Shard)、副本(Replica)、Mapping 的基本概念

本模块核心术语#

索引模板 Index Template : 预先定义的规则,新建索引名称匹配规则时自动套用 settings / mappings / aliases。

index_patterns : 模板的索引名匹配模式,如 logs-*。

优先级 priority : 多个模板同时匹配时,priority 数值大的模板优先生效。

动态映射 Dynamic Mapping : ES 根据写入数据自动推断字段类型的机制,模板可提前约束推断结果。

为什么需要索引模板:日志按天滚动的管控痛点#

不设模板时,每天自动生成的 logs-2024-11-29、logs-2024-11-30 等索引会完全依赖 ES 的默认行为:

  • 分片数随机:不同版本默认值不同,小索引也占 1 主 1 副,分片越积越多拖垮集群
  • 字段类型漂移:某天 status 被推断成 long,某天混入字符串后被推断成 text,聚合直接报错
  • 无统一别名:跨天查询要手写一串索引名,无法用一个别名收口

索引模板正是解决这类”批量索引统一管控”问题的标准入口。

创建索引模板:settings/mappings/aliases 三要素#

执行以下请求创建可组合索引模板(ES 7.8+ 推荐的新版 _index_template):

Kibana Dev Tools
PUT _index_template/logs-template
{
"index_patterns": ["logs-*"],
"priority": 100,
"template": {
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1
},
"mappings": {
"properties": {
"@timestamp": { "type": "date" },
"client_ip": { "type": "ip" },
"url": { "type": "text", "fields": { "keyword": { "type": "keyword" } } },
"status": { "type": "integer" },
"response_time": { "type": "float" },
"message": { "type": "text" }
}
},
"aliases": {
"logs-all": {}
}
}
}

预期返回:

预期返回
{ "acknowledged": true }

三要素各司其职:settings 锁定分片与副本数,mappings 把六个字段的类型一次性钉死(含 url 的 keyword 子字段,排序聚合全靠它),aliases 让所有 logs-* 索引自动挂上 logs-all 统一别名,跨天查询直接查别名即可。


模板验证与优先级#

模板创建后不必等真实索引生成就能验证效果——用 _simulate 模拟一个假想索引名,直接预览模板会套用出什么配置:

Kibana Dev Tools
POST _index_template/_simulate/logs-2024-11-30

预期返回中会完整列出该假想索引将继承的 settings、mappings、aliases,以及所有参与匹配的模板清单(overlapping 节点)。

当多个模板的 index_patterns 同时命中时(例如 logs-* 与 logs-nginx-* 都匹配 logs-nginx-2024-11-30),priority 数值大者胜出。注意新版可组合模板不会叠加多个模板的配置,而是由胜出者独占生效。因此给模板定 priority 时要留出梯度(如通用模板 100、业务专用模板 200),避免裁决结果出乎意料。

模板日常管理与生效验证#

日常查看与删除模板只需两条命令:

Kibana Dev Tools
GET _index_template/logs-template
DELETE _index_template/logs-template

实战验证模板是否生效:直接向一个不存在的索引写入数据,让 ES 自动建索引:

Kibana Dev Tools
POST logs-2024-11-30/_doc
{
"@timestamp": "2024-11-30T00:00:01.000Z",
"client_ip": "192.168.1.100",
"url": "/api/test",
"status": 200,
"response_time": 0.5,
"message": "template verify"
}

随后检查该索引的 mapping:

Kibana Dev Tools
GET logs-2024-11-30/_mapping

预期返回中六个字段类型与模板定义完全一致(status 为 integer、client_ip 为 ip、url 带 keyword 子字段),说明模板已被自动套用;再用 GET logs-2024-11-30/_settings 确认分片副本数同样是模板指定的 1 主 1 副,验证闭环完成。

模板只对新索引生效

索引模板不具备回溯能力:模板创建之前已存在的索引不会被套用。存量索引如需统一配置,必须走下一模块的 _reindex 重建流程。


集群API迁移#

集群扩容、换机、版本升级,最终都会落到”数据怎么搬”这个问题上。本模块给出两套标准姿势:同集群用 _reindex 原地重建;跨集群则按数据量大小,在跨集群 reindex 与快照恢复之间选型,最后补齐迁移后的一致性校验闭环。

本模块前置知识#

  • 已掌握 DSL 基础与索引模板的使用
  • 了解分片与副本机制,具备 Linux 共享存储(NFS)或对象存储(MinIO)的基础使用能力

本模块核心术语#

_reindex : ES 内置的数据重建 API,将数据从一个索引复制到另一个索引。

快照 Snapshot : 索引数据在某个时间点的备份,是跨集群迁移与灾备的核心手段。

仓库 Repository : 快照的存储位置,支持共享文件系统(NFS)、S3 / MinIO 等。

remote reindex : 跨集群 reindex,直接从远端集群读取数据写入本地。

同集群迁移:_reindex 重建索引#

适用场景:修改 mapping、调整分片数、按新模板重建旧索引。ES 中已创建的字段类型不可修改,唯一出路就是建新索引、把旧数据搬过去:

Kibana Dev Tools
POST _reindex
{
"source": { "index": "logs-demo" },
"dest": { "index": "logs-demo-v2" }
}

预期返回(节选)——created 为实际写入文档数,took 为耗时毫秒数:

预期返回(节选)
{ "took": 120, "total": 8, "created": 8, "updated": 0, "failures": [] }

数据量大时同步等待容易超时,改为异步执行并拿回任务 ID:

Kibana Dev Tools
POST _reindex?wait_for_completion=false
{
"source": { "index": "logs-2024-11-01" },
"dest": { "index": "logs-2024-11-01-v2" }
}
预期返回
{ "task": "node01:12345" }

之后用 GET _tasks/node01:12345 随时查看进度与预估剩余时间。

reindex 不搬 mapping

_reindex 只复制文档数据,目标索引的 settings / mappings 必须提前备好(手动创建或由模板自动生成)。否则 ES 会按动态映射现场推断字段类型,迁移前功尽弃。

跨集群迁移方案一:跨集群 _reindex#

前置配置:在目标集群各节点的 elasticsearch.yml 中放行远端集群地址,重启后生效:

elasticsearch.yml
reindex.remote.whitelist: "192.168.10.11:9200,192.168.10.12:9200"

随后发起 remote reindex,source.remote.host 直接指向远端集群:

Kibana Dev Tools
POST _reindex
{
"source": {
"remote": { "host": "http://192.168.10.11:9200" },
"index": "logs-2024-11-29",
"size": 1000
},
"dest": { "index": "logs-2024-11-29" }
}

预期返回中 created 即为成功迁移的文档数。

控制迁移速率

生产环境务必用 slices(并发切片数)与 size(单批文档数)控制速率,并避开业务高峰。remote reindex 会持续占用源集群的读 IO 与网络带宽,放任全速跑很容易把源集群打满。

跨集群迁移方案二:快照恢复(Snapshot/Restore)#

快照恢复走”备份 → 共享 → 恢复”路线,适合全量、大批量迁移,整体流程如下:

graph LR
  A[源集群] -->|创建快照| B[共享仓库 NFS/MinIO]
  B -->|注册同一仓库| C[目标集群]
  C -->|执行 _restore| D[目标索引]

第一步:源集群注册仓库(以共享文件系统为例,location 路径需预先加入各节点 path.repo 白名单):

Kibana Dev Tools
PUT _snapshot/my_backup
{
"type": "fs",
"settings": { "location": "/mnt/es-backup" }
}

第二步:创建快照,wait_for_completion=true 同步等待完成:

Kibana Dev Tools
PUT _snapshot/my_backup/snap_1?wait_for_completion=true
{
"indices": "logs-*",
"include_global_state": false
}

预期返回中快照状态为 "state": "SUCCESS"。

第三步:目标集群注册同一仓库(重复第一步的 PUT 请求),随后发起恢复,可用 rename_pattern 重命名避免与现有索引冲突:

Kibana Dev Tools
POST _snapshot/my_backup/snap_1/_restore
{
"indices": "logs-*",
"rename_pattern": "logs-(.+)",
"rename_replacement": "restored-logs-$1"
}

两套方案选型结论:reindex 适合小批量、精准迁移(可在 source 中附加查询条件过滤数据);快照恢复适合全量、大批量迁移,速度快、对源集群压力小,但依赖共享存储。

迁移后数据一致性校验#

迁移完成不等于迁移正确,两道校验缺一不可:

  • 文档数比对:源、目标两端分别执行 GET logs-2024-11-29/_count,数值必须完全一致
  • 抽样比对:两端执行完全相同的 DSL 查询(如上一模块的 bool 慢请求排查语句),比对 total.value 与抽样文档内容是否一致

任何一项对不上,都说明迁移过程存在丢数据或写冲突,必须在切换业务流量前查清根因。


ES集群故障排查API#

集群变黄变红不可怕,可怕的是不知道从哪里看起。ES 自带一整套排查 API,本模块按”整体 → 索引/分片 → 节点 → 任务”的排查动线,把每个 API 的用法与关键输出讲透,形成可复用的故障定位闭环。

本模块前置知识#

  • 已掌握前面模块的 DSL 与集群基本操作
  • 了解集群由多节点组成、索引分片分布在不同节点上的基本架构

本模块核心术语#

集群健康状态 : green 表示所有主副分片正常;yellow 表示主分片正常但存在未分配副本;red 表示存在未分配主分片,部分数据已不可用。

磁盘水位 : ES 按磁盘使用率触发保护机制的阈值(默认 85% / 90% / 95%),超限会拒绝分片分配,甚至将索引置为只读。

_cluster/health:一眼定位集群整体状态#

排查第一步永远是看集群健康度:

Kibana Dev Tools
GET _cluster/health?pretty
预期返回(节选)
{
"cluster_name": "es-cluster",
"status": "yellow",
"number_of_nodes": 3,
"active_shards_percent_as_number": 96.5,
"unassigned_shards": 4
}

关键字段逐项解读:

字段含义关注点
status集群整体状态green / yellow / red
number_of_nodes总节点数是否符合预期拓扑,掉节点会立刻暴露
active_shards_percent_as_number活跃分片百分比非 100% 即有分片未分配
unassigned_shards未分配分片数yellow / red 的直接线索
initializing_shards / relocating_shards初始化 / 迁移中的分片长期不为 0 说明集群持续繁忙

unassigned_shards 大于 0 就是下一步诊断的入口——分片为什么分不出去,交给 _cat 与 allocation/explain 来回答。

_cat 系列:日常巡检三板斧#

_cat API 以紧凑的表格形式返回,加 ?v 显示表头,是每日巡检的标配三件套:

Kibana Dev Tools
GET _cat/nodes?v
GET _cat/indices?v
GET _cat/shards?v

返回样例(_cat/nodes 节选):

终端输出
ip heap.percent ram.percent cpu load_1m node.role master name
192.168.1.11 45 78 3 0.12 dimr * node01
192.168.1.12 38 65 1 0.05 dimr - node02

三条命令的关键列解读:

命令关键列关注点
_cat/nodesheap.percent、cpu、load_1m堆内存持续 >75% 或 CPU 高位需警惕
_cat/indicesdocs.count、store.size找出规模膨胀最快的索引
_cat/shardsindex、shard、prirep、state揪出 UNASSIGNED 状态的分片

_cat/shards 返回中所有 state 为 UNASSIGNED 的行,就是集群 yellow/red 的”当事人”。拿着这些索引名与分片号,即可交给下一小节的 _cluster/allocation/explain 追问根因。


_cluster/allocation/explain:分片未分配根因诊断#

集群 yellow / red 时,不必猜原因——该 API 会直接告诉你某个分片为什么分不出去。不带参数时 ES 自动挑选一个未分配分片诊断;也可以精准指定索引与分片:

Kibana Dev Tools
GET _cluster/allocation/explain
{
"index": "logs-2024-11-29",
"shard": 0,
"primary": false
}

典型返回样例(节选)——磁盘超水位导致分片被拒分配:

预期返回(节选)
{
"index": "logs-2024-11-29",
"current_state": "unassigned",
"node_allocation_decisions": [
{
"node_name": "node02",
"deciders": [
{
"decider": "disk_threshold",
"decision": "NO",
"explanation": "the node is above the high watermark cluster setting [cluster.routing.allocation.disk.watermark.high=90%], having less than the required free disk"
}
]
}
]
}

排查的核心是读懂 deciders 数组——每个 decider 是一位”裁判”,decision: "NO" 即投出反对票。常见裁判与处置对照:

decider根因处置思路
disk_threshold节点磁盘超水位清理磁盘 / 扩容后执行 POST _cluster/reroute?retry_failed=true
same_shard主副分片不能同节点副本数超过节点承载,调低 number_of_replicas
awareness机架感知属性不满足补齐节点 node.attr 感知属性
max_retry分配重试次数耗尽手动 POST _cluster/reroute?retry_failed=true 重置重试
node_version目标节点版本过低统一集群各节点版本

_nodes/stats 与 _nodes/hot_threads:节点级深度排查#

分片层面没问题、但集群就是慢时,视线要下沉到节点资源:

Kibana Dev Tools
GET _nodes/stats/jvm,fs,thread_pool

关键输出逐项解读:

  • jvm.mem.heap_used_percent:堆内存使用百分比,长期高于 75% 说明 GC 压力山大,需排查是否存在大聚合、深分页等内存杀手
  • fs.total.available_in_bytes:各节点剩余磁盘,结合默认 85% / 90% / 95% 三档水位提前预警
  • thread_pool.write.rejected / thread_pool.search.rejected:线程池拒绝数。write 拒绝持续增长说明写入洪峰超出节点处理能力;search 拒绝说明查询并发过高——拒绝数不为 0 的线程池就是瓶颈现场

CPU 飙高的节点上正在忙什么?hot_threads 直接给出答案:

Kibana Dev Tools
GET _nodes/hot_threads

返回各节点最繁忙线程的堆栈信息:堆栈反复落在 Lucene merge 相关类,说明段合并压力大(索引写入过猛);集中在 search 线程栈,则是某个重量级查询在反复执行,可结合慢日志揪出元凶。

_tasks:任务管理与慢任务定位#

集群里此刻正在跑什么任务、哪个任务卡死了,一问便知:

Kibana Dev Tools
GET _tasks?detailed=true&actions=*reindex

重点关注 running_time_in_nanos 超长且 cancellable: true 的任务。定位到卡死的 reindex 或失控聚合后,直接终止:

Kibana Dev Tools
POST _tasks/node01:12345/_cancel
故障排查标准动线

_cluster/health 看整体 → _cat/shards 锁定 UNASSIGNED 分片 → allocation/explain 追问根因 → _nodes/stats / hot_threads 查节点水位 → _tasks 清理堵点。按此动线执行,90% 的集群告警可在 10 分钟内定位到根因。

graph TD
  A[集群告警 yellow/red] --> B[_cluster/health 看整体状态]
  B --> C{unassigned_shards 大于 0?}
  C -->|是| D[_cat/shards 定位 UNASSIGNED 分片]
  D --> E[allocation/explain 追问根因]
  E --> G[按 decider 对症处置]
  C -->|否| F[_nodes/stats 查节点资源水位]
  F --> H{CPU 飙高?}
  H -->|是| I[_nodes/hot_threads 定位繁忙线程]
  H -->|否| J[_tasks 检查卡死任务并 _cancel]

常见问题与排错指南#

高危操作预警
  • 深分页 from + size 超过 10000 会触发 Result window is too large 报错,禁止在脚本中无脑翻大页,需改用 search_after
  • 对高基数字段(如 client_ip)做大范围 terms 聚合可能触发 circuit_breaking_exception 内存熔断,生产环境先小范围验证再放量
  • 跨集群 reindex 与快照恢复期间 IO / 带宽飙升,必须避开业务高峰并控制并发速率
  • DELETE /索引名 不可恢复,执行删除前必须确认已有可用快照

典型报错根因与解决方案#

FORBIDDEN/12/index read-only:磁盘超 95% 洪水位,ES 自动将索引置为只读以保护集群。先清理磁盘释放空间,再手动解除只读:

Kibana Dev Tools
PUT logs-demo/_settings
{
"index.blocks.read_only_allow_delete": null
}

集群长期 yellow:单节点集群默认副本数为 1,副本分片无处可分配。将副本数调整为 0 即可转绿(多节点集群无需此操作):

Kibana Dev Tools
PUT logs-demo/_settings
{
"number_of_replicas": 0
}

索引模板不生效:两种典型原因——index_patterns 与实际索引名不匹配;或已有更高 priority 的模板抢先命中。用 POST _index_template/_simulate/<索引名> 模拟验证后针对性调整。

跨集群 reindex 连接失败:按以下顺序逐项核对——

  • 目标集群 elasticsearch.yml 中 reindex.remote.whitelist 是否已配置并重启生效
  • 两端集群间网络是否互通(防火墙、安全组放行 9200 端口)
  • 两端 ES 版本跨度是否过大,对照官方版本兼容矩阵确认

总结与进阶建议#

核心内容复盘:本篇四大模块构成 ES 日常运维的完整能力闭环——

能力域核心武器解决什么问题
DSL 查询match / bool / range / 聚合日志检索、慢请求排查、指标统计
索引治理索引模板三要素分片数与字段类型的统一管控
数据迁移_reindex / 快照恢复同集群重建、跨集群搬迁
故障排查health / _cat / explain / _nodes / _tasks黄红集群快速定位根因

生产实践建议与进阶学习方向:

  • ILM 索引生命周期管理:替代”手动模板 + 定时任务”的原始组合,实现 hot → warm → cold → delete 的自动化流转1
  • CCR 跨集群复制:跨集群高频同步场景的进阶方案,主从索引实时跟随(需注意 License 等级要求)
  • search_after + PIT:深分页与大批量数据导出的标准姿势,彻底绕开 max_result_window 限制
  • Painless 脚本:script 查询、scripted metric 等复杂计算场景的瑞士军刀
  • 收藏官方 Cat API 与 Query DSL 文档作为日常速查工具书,比搜索引擎更快更准
写在最后

工具的价值在于肌肉记忆。建议把本篇的每一条命令都在测试集群亲手敲一遍——排查 API 的熟练度,决定了凌晨三点故障告警响起时你的睡眠时长。

Footnotes#

  1. ILM(Index Lifecycle Management)官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/index-lifecycle-management.html ↩

ELK系列(三):DSL查询、索引模板与集群运维API实战
https://www.6ixblog.site/posts/elk-3/
作者
Licwic
发布于
2025-07-12
许可协议
CC BY-NC-SA 4.0