上一篇解决了 Multiple Pipelines 的隔离与路由问题。这一篇进入运维调优阶段的第一个主题:怎么从指标判断管道卡在 input、filter 还是 output,以及 hot threads API 的读法。

核心问题:一条管道吞吐下降时,靠什么指标定位瓶颈在哪一段?plugin 级别的耗时从哪里拿到?hot threads 输出能读出什么信息?

监控数据的来源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
      Logstash 进程
┌──────────────────────────────────────────────┐
│ │
│ input plugins │
│ └─ events.in counter │
│ │
│ [queue](仅 queue.type: persisted 时注册) │
│ └─ queue.events │
│ queue.capacity.queue_size_in_bytes │
│ │
│ pipeline workers(同时跑 filter 和 output) │
│ └─ per-plugin duration_in_millis │
│ events.out / flow.worker_utilization │
│ │
│ JVM / OS metrics │
│ └─ heap_used / gc / open_file_descriptors │
└──────────────────────────────────────────────┘

▼ HTTP API (默认端口范围 9600..9700)
GET /_node/stats
GET /_node/hot_threads
GET /_health_report

Logstash 自带 HTTP API,进程启动后立即可用,不需要额外安装任何组件,也不依赖 X-Pack 授权。官方目前列出五个监控 API:

1
2
3
4
5
6
GET /                    根资源:host、version、http_address
GET /_node 节点信息:pipeline 设置、OS、JVM
GET /_node/plugins 已安装插件清单
GET /_node/stats 节点统计:JVM、process、event、pipeline 运行时
GET /_node/hot_threads 热点线程栈快照
GET /_health_report 健康报告:直接给出健康状态与问题诊断

最后一个是专做健康与瓶颈判定的,本篇后面的判断树是手工版本,_health_report 是官方版本,两者可以互相印证。

接监控时第一批会撞上的是端口和绑定地址这两件事,它们的默认值都和"硬编码 localhost:9600"的直觉不一致:

1
2
3
4
5
6
7
8
9
10
11
api.http.port    默认不是单个端口,而是范围 9600..9700。
端口被占用时顺延,同一台机器上的第二个实例
会落在 9601。官方为此专门提示:需要确定性
端口时用 --api.http.port 显式指定

api.http.host 默认 127.0.0.1,只绑回环地址。从别的机器
curl 不通不是防火墙问题,是没改这个设置。
跨机采集必须先改成 0.0.0.0 或指定网卡地址

api.auth.type 默认 none
api.ssl.enabled 默认 false

后两行是改 api.http.host 之前要一起看的:这个 API 默认零鉴权零 TLS。"进程启动后立即可用"的另一半,是一个默认对外无鉴权的运维端口。对外暴露前要配上:

1
2
3
4
5
6
7
8
# logstash.yml
api.http.host: 0.0.0.0
api.ssl.enabled: true
api.ssl.keystore.path: /path/to/keystore.jks
api.ssl.keystore.password: "${API_KEYSTORE_PASS}"
api.auth.type: basic
api.auth.basic.username: "logstash"
api.auth.basic.password: "${API_BASIC_PASS}"

密码类字段用 keystore 或环境变量占位,避免明文落在配置文件里。

指标送出去有三条路线,本篇后面只展开第一条:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
路线                        适用场景
──────────────────────────────────────────────────────────
内置 HTTP API(本篇) 自研采集脚本、临时排障、
接入自己的 Prometheus exporter

OpenTelemetry (OTLP) 导出 把内部指标推给任意 OTLP 后端
(Elastic、Prometheus 等),
靠 otel.metrics.enabled 与
otel.exporter.otlp.endpoint 配置。
方向是 Logstash 作为遥测数据源导出,
它不是 OTLP 接收端。
9.5.0 起提供,目前标注为技术预览

legacy Stack monitoring 发往独立监控集群,
由 Kibana 的 Stack Monitoring 呈现

只需要临时看一眼或者写脚本告警,用第一条。要把指标接进既有的可观测性栈,第二条是方向正确的那条路,但它还在预览阶段,生产上要先确认版本够新、并接受接口可能变动。

关键对象与数据结构

Node Stats API 的响应按几个顶层 section 组织:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
GET http://localhost:9600/_node/stats

{
"events": {
"in": <total events ingested>,
"filtered": <total events passed through filters>,
"out": <total events successfully sent to outputs>,
"duration_in_millis": <cumulative filter+output processing time>,
"queue_push_duration_in_millis": <time spent pushing into queue>
},
"pipelines": {
"<pipeline_id>": {
"events": { ... }, // per-pipeline counters
"plugins": {
"inputs": [ { "id": "...", "events": { "out": N }, ... } ],
"filters": [ { "id": "...", "events": { "in": N, "out": N,
"duration_in_millis": N } } ],
"outputs": [ { "id": "...", "events": { "in": N, "out": N,
"duration_in_millis": N } } ]
},
"flow": {
"input_throughput": { "current": N, "lifetime": N },
"filter_throughput": { "current": N, "lifetime": N },
"output_throughput": { "current": N, "lifetime": N },
"queue_backpressure": { "current": N, "lifetime": N },
"worker_concurrency": { "current": N, "lifetime": N },
"worker_utilization": { "current": N, "lifetime": N }
},
"queue": { // 只在 queue.type: persisted 时注册
"type": "persisted",
"events": <events currently in queue>,
"capacity": {
"page_capacity_in_bytes": N,
"max_queue_size_in_bytes": N,
"max_unread_events": N,
"queue_size_in_bytes": N
},
"data": { "free_space_in_bytes": N, "storage_type": "ext4" }
}
}
},
"queue": {
"events_count": <节点级 rollup,见下文>
},
"jvm": {
"heap_used_in_bytes": N,
"heap_used_percent": N,
"gc": { "collectors": { "old": { "collection_time_in_millis": N } } }
},
"process": {
"open_file_descriptors": N,
"cpu": { "percent": N }
}
}

queue 这一段的层级最容易记错,而后面的判断树全靠它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
字段                                      在哪一层、是什么
──────────────────────────────────────────────────────────
pipelines.<id>.queue.events 该 pipeline 当前的事件数。
没有 _count 后缀

pipelines.<id>.queue.capacity
.queue_size_in_bytes 当前字节占用
.max_queue_size_in_bytes 配置上限
字节量都嵌在 capacity 下面

queue.events_count 只在节点顶层,而且是 rollup:
遍历所有 pipeline、跳过 system
pipeline、只累加 type 为
persisted 的那些。既不是某条
pipeline 的深度,也不覆盖
内存队列的 pipeline

比字段名更要紧的是一条前提:整个 queue 段只在 queue.type: persisted 时才注册。默认配置是内存队列,照上面的路径去读会直接拿不到字段,这既不是版本问题也不是权限问题。

内存队列的积压得换两个间接信号来看:

1
2
3
4
5
6
7
8
9
10
11
内存队列的积压判读
──────────────────────────────────────────────────────────
events.queue_push_duration_in_millis 增长快
→ input 线程在"往队列里塞"这一步上耗时变长,
说明队列常满、下游消化不过来

flow.queue_backpressure.current 抬升
→ 同一件事的速率版本,不用自己做差

在途上界 = pipeline.workers × pipeline.batch.size
→ 内存队列没有字节记账,超过这个量就只能靠背压

events.inevents.filteredevents.out 和各插件的 duration_in_millis 都是自启动以来的累计值,不是实时速率。算速率要么两次采样做差除时间间隔,要么直接读 flow——后者不只是省掉手工差分,它多出来的 worker_utilization 是现在判读瓶颈的主线,下一节就从它开始。flow metrics 从 8.5.0 起进入 Node Stats API,PQ 的两个增长率指标和 worker_utilization 更晚。

瓶颈定位逻辑

先说清一件决定后面所有读法的事:pipeline.workers 控制的那组线程同时执行 filter 和 output 两个阶段,不存在独立的 output 线程池。WorkerLoop 的类注释写得很直白——它负责为每个 batch 执行 filter 和 output 插件。

这一条直接解释了几个否则很难串起来的现象:output 阻塞时 filter 也一起停(同一个线程卡在 output 里,回不到 filter);给 pipeline.workers 加值同时抬高 filter 并发和 output 并发;以及第 12 篇里 output isolator 拓扑为什么必要——想让两个 output 互不牵连,只能把它们放进不同 pipeline,因为同一条 pipeline 里它们连线程都是共用的。

首选路径:flow metrics

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
flow 判断树
─────────────────────────────────────────
worker_utilization 接近 100
→ worker 已被占满,pipeline 层是瓶颈
→ 再看各插件的 flow.worker_utilization,
谁占的比例最大,谁就是具体的瓶颈插件
→ 配合插件级 worker_millis_per_event 看它单条 event 有多贵

worker_utilization 明显低于 100
且 input_throughput 低
→ 瓶颈在 input 侧或数据源,加 worker 无用

queue_backpressure 抬升
→ 下游消化不过来,队列在施加背压

queue_persisted_growth_events 持续为正(仅 PQ)
→ 队列在涨,写入快于消费;这个值应该趋近于零,
负值代表队列正在收缩,是积压在恢复
─────────────────────────────────────────

插件级的 worker_utilization 是这条路径的关键:pipeline 级说明 worker 满了,插件级说明满在谁身上。因为 filter 和 output 共用 worker,这张表不需要事先区分是 filter 慢还是 output 慢——占比最大的那个插件是谁就是谁。

兜底路径:手工差分累计值

flow metrics 之前的版本,或者需要更细的自定义口径时,回到累计值做差:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
瓶颈判断树(累计值版本)
─────────────────────────────────────────
events.in >> events.out
且 queue.events 持续增长(PQ)
或 queue_push_duration_in_millis 增长快(内存队列)
→ 消费端跟不上生产端

events.out 稳定
且某个 output plugin 的 duration_in_millis 独占大头
→ 该 output 是瓶颈(下游慢,如 ES bulk 延迟)
→ 注意它会连带拖住同一 worker 上的 filter

events.out 稳定
且某个 filter plugin duration_in_millis 独占大头
→ 该 filter 是瓶颈(通常是复杂 Grok 或 sleep)

events.in 低
且队列不涨
→ input 是瓶颈(数据源慢,或 input plugin 限速)
─────────────────────────────────────────

单个插件的耗时从 plugins.filters[i].events.duration_in_millis 读取。把同一插件两次采样的 duration_in_millis 差值除以对应事件数差值,得到该插件的平均每事件处理时间(毫秒/event)——这是插件级 worker_millis_per_event 的手工版本。

实验:用 curl 读 Node Stats

启动一个本地 Logstash 实例,执行以下命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 查看整体 events 计数
curl -s http://localhost:9600/_node/stats | python3 -m json.tool | grep -A6 '"events"'

# 查看单条 pipeline 的 plugin 级别 duration
curl -s 'http://localhost:9600/_node/stats?pretty' \
| python3 -c "
import json, sys
data = json.load(sys.stdin)
for pid, pdata in data['pipelines'].items():
print(f'pipeline: {pid}')
for f in pdata['plugins']['filters']:
ms = f['events'].get('duration_in_millis', 0)
cnt = f['events'].get('in', 1)
print(f' filter {f[\"id\"]}: {ms}ms total, {ms/max(cnt,1):.2f}ms/event')
for o in pdata['plugins']['outputs']:
ms = o['events'].get('duration_in_millis', 0)
cnt = o['events'].get('in', 1)
print(f' output {o[\"id\"]}: {ms}ms total, {ms/max(cnt,1):.2f}ms/event')
"

两次采样、做差,得到速率:

1
2
3
4
5
# 采样间隔 10 秒,计算事件吞吐速率
T1=$(curl -s http://localhost:9600/_node/stats | python3 -c "import json,sys; d=json.load(sys.stdin); print(d['events']['out'])")
sleep 10
T2=$(curl -s http://localhost:9600/_node/stats | python3 -c "import json,sys; d=json.load(sys.stdin); print(d['events']['out'])")
echo "events/sec = $(( (T2 - T1) / 10 ))"

如果版本支持 flow metrics,上面这套差分都不用写,一次请求就能拿到速率和饱和度:

1
2
3
4
5
6
7
8
curl -s http://localhost:9600/_node/stats | python3 -c "
import json, sys
for pid, p in json.load(sys.stdin)['pipelines'].items():
fl = p.get('flow', {})
def cur(k): return fl.get(k, {}).get('current')
print(f'{pid}: in={cur(\"input_throughput\")} out={cur(\"output_throughput\")} '
f'util={cur(\"worker_utilization\")} bp={cur(\"queue_backpressure\")}')
"

worker_utilization 接近 100 就说明 worker 已经饱和,这是下一节判断树的入口。

hot threads API

_node/hot_threads 返回当前 CPU 占用最高的 Java 线程的堆栈快照。默认返回 JSON,加上 human=true 才是纯文本——human 这个通用参数在 Logstash 里只对 hot threads API 生效:

1
curl -s 'http://localhost:9600/_node/hot_threads?human=true'

输出模板写死在 logstash-core/locales/en.yml 里,只有两行结构:主机名标题行 + Hot threads at ... 一行,然后每个线程一行摘要加它的栈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
::: {my-host}
Hot threads at 2026-08-06T07:10:00+00:00, busiestThreads=3:

55.3 % of cpu usage, state: timed_waiting, thread name: '[main]>worker0', thread id: 42
java.lang.Thread.sleep(Native Method)
org.jruby.RubyKernel.sleep(RubyKernel.java:820)
...

21.7 % of cpu usage, state: runnable, thread name: '[main]>worker1', thread id: 43
org.apache.http.impl.io.SessionInputBufferImpl.streamRead(...)
org.elasticsearch.client.RestClient.performRequest(...)
...

3.2 % of cpu usage, state: runnable, thread name: '[main]<beats', thread id: 39
io.netty.channel.epoll.Native.epollWait(Native Method)
...

每行摘要有四项:CPU 百分比、线程状态、线程名、线程 id。没有 interval= 字段,也没有 “(Xms out of Yms)” 这种窗口占比——原因见下面对百分比语义的说明。

线程名不是随手起的,Linux 平台上 Logstash 给线程打的标签有固定规律:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
线程名                含义
───────────────────────────────────────────────────────────
[<pipeline.id>]>workerN
pipeline worker,N 从 0 开始编号。
它同时跑 filter 和 output,所以栈顶在
grok/mutate 里 → CPU 花在 filter;
栈顶在 HTTP/网络调用里 → 卡在 output,
此时该线程上的 filter 也一并停着。
数量与 pipeline.workers 配置一致

[<pipeline.id>]<inputname
input 线程,如 [main]<beats、[main]<file。
卡在 epollWait / socket read 是正常的空闲态;
若 CPU 高且栈在 JSON/正则解析 → codec 是瓶颈

Api Webserver 监控 API 自己的 HTTP 线程。
采集频率过高时它会自己爬上榜

Ruby-0-Thread-N 插件在 Ruby 层另起的线程(如某些
input 的后台线程),栈里能看到具体插件路径

多条 pipeline 时,方括号里就是各自的 pipeline.id,这是把热点线程归属到具体管道的唯一线索。

合法查询参数只有四个(外加通用的 human):

1
2
3
4
threads              返回几个线程,默认 3
ordered_by 排序依据:cpu / wait / block
stacktrace_size 每个线程打印几层栈帧
ignore_idle_threads 是否过滤空闲线程,默认 true

没有 interval 参数。写 ?interval=1000 不会报错,也不会有任何效果——参数被直接忽略,读者以为自己调过了采样窗口,实际什么都没发生。

之所以没有采样窗口,是因为百分比根本不是窗口采样算出来的。它的算法是线程累计 cpu.time 除以进程 uptime,也就是"这个线程自 Logstash 启动以来占掉了多少 CPU"。

这个区别会直接改变结论的可信度。累计值意味着一个只在启动阶段疯狂跑过的线程,会长期挂在榜首;而一个刚刚开始变慢的线程,要很久才能爬上来。所以"多次调用、看同一个线程是否持续出现"这种做法在这里是无效的——它区分不了"现在正热"和"开机时热过",因为两者在累计视角下都表现为持续出现。

要判断当下的热点,得自己取两次快照做差。JSON 响应里给的是 percent_of_cpu_time,把它乘回同一时刻的 jvm.uptime_in_millis 就还原成累计 CPU 毫秒数,两次相减即为窗口内的真实消耗:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
snap() {
ht=$(curl -s "http://localhost:9600/_node/hot_threads?threads=20")
up=$(curl -s http://localhost:9600/_node/stats \
| python3 -c "import json,sys; print(json.load(sys.stdin)['jvm']['uptime_in_millis'])")
python3 -c "
import json, sys
d = json.loads(sys.argv[1]); up = float(sys.argv[2])
for t in d['hot_threads']['threads']:
print(f\"{t['name']}\t{t['percent_of_cpu_time'] / 100.0 * up}\")
" "$ht" "$up"
}

snap > /tmp/ht1.tsv
sleep 30
snap > /tmp/ht2.tsv

join -t $'\t' <(sort /tmp/ht1.tsv) <(sort /tmp/ht2.tsv) \
| awk -F'\t' '{printf "%10.1f ms %s\n", $3 - $2, $1}' \
| sort -rn | head -5

30 秒窗口内消耗 CPU 最多的线程才是当前的热点。这一步是 hot threads 从"看个大概"变成可用证据的分界线。

将实验结果映射回内部对象

Node Stats 的数字直接对应管道内部的对象。这些类名都能在 elastic/logstash 仓库里打开对应文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
API 路径                                    内部对象
───────────────────────────────────────────────────────────────────
pipelines.<id>.events.* org.logstash.execution
.AbstractPipelineExt
(pipeline 级指标的注册与聚合都在这里)

pipelines.<id>.plugins.filters[i]
.events.duration_in_millis org.logstash.execution.WorkerLoop
对该插件的累计调用时间

pipelines.<id>.plugins.outputs[i]
.events.duration_in_millis AbstractOutputDelegatorExt /
OutputDelegatorExt
(由同一个 WorkerLoop 线程调用)

pipelines.<id>.queue.events org.logstash.ackedqueue.Queue
的当前深度(仅 PQ)

内存队列(无 queue 段) org.logstash.ext
.JrubyWrappedSynchronousQueueExt,
batch.delay 的等待逻辑在
QueueReadClientBase

jvm.heap_used_percent JVM 堆占用率
process.cpu.percent 进程级 CPU,包含所有线程

hot threads 里的 [<id>]>workerN 线程就是 WorkerLoopThread,循环体是 WorkerLoop,数量与该 pipeline 的 pipeline.workers 一一对应。它在一次循环里读一个 batch、跑完 filter、再交给 output delegator,所以 filter 和 output 的耗时都记在同一个线程的账上。

output 侧的并发因此不是"再开一组线程",而是插件自己的连接池。以 ES output 为例,真正的旋钮是 pool_max(默认 1000)和 pool_max_per_route(默认 100)。

这里有一个值得单独记住的陷阱:output plugin 有一个 workers 参数,写进配置不会报错,但它在 logstash-core/lib/logstash/outputs/base.rb 里的声明是 :deprecated => "This parameter will be ignored."。参数存在、管道正常启动、只在日志里留一条 deprecation,然后被完全忽略。这比一个不存在的参数更难发现——不存在的参数会让管道起不来,读者立刻知道;这个会让读者以为自己已经调过了 output 并发,实际什么都没发生。

模式提炼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
模式:三段分层指标 + 差分速率 + 栈快照三角定位

- 第一层(全局饱和度):用 flow.worker_utilization 判断
worker 是否已被占满;无 flow 时退回 events.in 与
events.out 的差分看有没有积压

- 第二层(分段归因):用插件级 worker_utilization 与
worker_millis_per_event 定位到具体插件;手工版本是
per-plugin duration_in_millis 的两次采样差分

- 第三层(线程状态):用 hot_threads 判断是 CPU 密集还是 I/O 等待
CPU 密集 → 算法层面优化该插件;I/O 等待 → 调整插件连接池/批量
注意百分比是累计值,要自己对两次快照做差才能看当下

三层合用,排除方向是由粗到细:先看全局,再看分段,再看线程。
每层都记得 filter 与 output 共用 worker,不要按两组线程去归因。

这套由粗到细的路径不依赖 Logstash 特有的实现。当全局饱和度、分段耗时、线程栈这三类数据齐备时,都可以这样排查。

工程迁移表

Logstash 监控概念 Kafka 生态对应 Flink 对应 通用 ETL 对应
flow.*_throughput 预计算速率 consumer/producer rate(JMX) records-in-rate / records-out-rate 行/记录吞吐量
flow.worker_utilization consumer 线程忙闲比 Task busyTimeMsPerSecond Worker 池饱和度
per-plugin duration_in_millis UDF 执行时间(JMX metrics) operator latency histogram Transform 阶段耗时
queue.events 增长(PQ) consumer group lag 增长 checkpoint 间隔内积压量 staging 表行数增长
queue_push_duration_in_millis(内存队列) producer send 阻塞时间 反压时的 buffer 等待 写入阻塞耗时
hot threads [main]>workerN 栈位置 consumer poll / producer send 线程栈 TaskManager thread dump Worker 线程栈
jvm.heap_used_percent + GC 曲线形态 broker/consumer heap OOM 风险 TaskManager GC 压力 JVM 服务 GC 压力

常见误解

误解一:“events.out 低就是 output 的问题”。它只说明管道整体输出慢。因为 filter 和 output 跑在同一组 worker 线程上,这两种情况在 events.out 上表现完全一样:filter 自己慢,或者 output 卡住把整个 worker 一起拖住。区分它们要看插件级的 worker_utilization(或两次采样的 per-plugin duration_in_millis),再加上 input 本身是否就慢(源数据量小)。

误解二:“hot threads 里 worker 线程 CPU 高就该加 worker”。CPU 高可能是单个 worker 执行效率低(比如灾难性回溯的 Grok 正则),加 worker 只是把同样的浪费摊到更多线程上,不解决根因。而且 hot threads 的百分比是自启动以来的累计占比,看到某个线程排在前面并不等于它现在正忙。先对两次快照做差确认它当下确实在烧 CPU,再用 Grok Debugger 确认正则是否有指数级回溯,最后才轮到加 worker。

误解三:“Node Stats 的 queue 字段拿不到值是版本或权限问题”。queue 段只在 queue.type: persisted 时注册。默认是内存队列,此时这条 pipeline 下面根本没有 queue 对象,读到的是空。内存队列要看 events.queue_push_duration_in_millisflow.queue_backpressure。同理,节点顶层的 queue.events_count 是个只统计 PQ pipeline 的 rollup,全用内存队列时它恒为 0。

练习

  1. 本地启动一条带慢 filter 的 Logstash 管道(用 sleep { time => 0.1 } 模拟),向它发送事件,每隔 5 秒采集一次 _node/stats,计算 events.out 的速率,确认与 sleep 设置的延迟吻合(约 10 events/s)。再把 pipeline.workers 从 1 调到 2,重复实验,观察速率变化。

  2. 对同一个带慢 filter 的管道调用 _node/hot_threads?human=true,确认线程名形如 [main]>worker0(多条 pipeline 时方括号里是各自的 pipeline.id),并确认堆栈指向 sleep 调用。把 sleep 换成 CPU 密集型操作(比如循环 Grok),重复观察堆栈结构的变化。顺便试一次 ?interval=1000,对比加与不加的输出,验证这个参数被静默忽略。

  3. 用上面那段两次快照做差的脚本,在管道空转 5 分钟后再制造一次短促的 CPU 尖峰。对比"累计百分比排名"和"30 秒窗口内 CPU 增量排名"两个榜单,找出两者给出不同答案的具体线程,解释为什么单看累计值会把结论带偏。

  4. 构造一个 output 慢的场景(用 http output 指向一个延迟高的端点)。先用默认内存队列跑一遍,确认 pipelines.<id>.queue 整段不存在,只能靠 events.queue_push_duration_in_millis 判断积压;再把 queue.type 改成 persisted 重跑,此时 queue.eventsqueue.capacity.queue_size_in_bytes 才有值。结合插件级 worker_utilization 确认是 output 而非 filter 造成的积压。

系列导航

序号 主题
00 导读:核心对象是 event,骨架是三段管道
01 架构:JRuby、JVM 与 pipeline 的运行形态
02 event 模型:@timestamp、@metadata 与字段引用
03 codec:字节流与 event 的边界转换
04 input 插件:拉取、监听与 Beats 接入
05 Grok 的本质:命名正则加预定义 pattern
06 dissect 与结构化 filter:放弃回溯换吞吐
07 常用 filter 组合:mutate、date、geoip 与条件
08 output 插件:Elasticsearch output 与批量写入
09 pipeline 执行模型:worker、batch 与背压
10 内存队列 vs 持久队列:可靠性的分界线
11 死信队列(DLQ):无法处理的 event 去哪
12 Multiple Pipelines 与 pipeline-to-pipeline
13 监控:Node Stats API、hot threads 与瓶颈定位(本篇)
14 性能调优:JVM heap、批处理与持久队列磁盘
15 Logstash vs Beats vs Ingest Pipeline:该用谁
16 Logstash vs Fluentd vs Vector:日志管道的三种取舍
17 Logstash 的演进与 Elastic Agent 的冲击

参考资料