跳转到内容

监控 Aeron®:面向你的可观测性平台的指标与告警

Aeron 在共享内存中暴露了一套丰富的无锁计数器——但它不提供 /metrics 端点。要把这些信号接入你公司的 Prometheus/Grafana/Datadog 平台,你要么写一个小 exporter(OSS),要么运行 Aeron Insights(Premium)。本页涵盖这两条路,然后把真正重要的计数器映射到它们所预示的故障模式,以及应当接入的告警条件。

每个 Aeron media driver 都会在其 Aeron 目录中维护一个 command-and-control 文件 cnc.dat,以内存映射方式存在。它的各个区域中包含 counters metadata buffer、counters values buffer 和 error-log buffer。计数器就是由 driver 无锁更新的普通 64 位值;任何外部进程都可以以只读方式内存映射该文件并实时读取它们,用 Aeron 自己的话说——「对性能无显著影响」。

这就是全部的接入面。没有 push、没有 agent 协议、没有 HTTP——你映射文件并遍历。参考读取器是 AeronStat(aeron-stat 脚本),它每秒循环一次并打印所有计数器。底层它做的事和任何 exporter 完全一样:

// 规范的读取模式——用 Agrona 的 CountersReader 读取映射后的 cnc.dat
final CountersReader counters = cncFileReader.countersReader();
counters.forEach((counterId, typeId, keyBuffer, label) -> {
final long value = counters.getCounterValue(counterId);
// → 把 {counterId, typeId, label, value} 发给你的 metrics 客户端
});

计数器按 type id 分组:0–99 client/driver,100–199 archive,200–299 cluster。当你把计数器转成指标时,这个 type id 就是你的路由键。

路径 1 —— OSS:自己写(或复用)一个计数器 exporter

Section titled “路径 1 —— OSS:自己写(或复用)一个计数器 exporter”

Aeron OSS 没有原生的 Prometheus exporter。这是有意为之,Aeron 自己的 cookbook 也建议你走自建这条路:「用 CountersReader 读取计数器数据」并喂给一个指标系统,「例如 DataDog 或 Prometheus」。模式是一个每节点 sidecar,它映射该节点的 cnc.dat,按固定周期遍历计数器,然后暴露 /metrics(供 Prometheus 抓取)或 push 给 StatsD/Datadog/CloudWatch。

你不必从零写起——已经有几个社区 exporter 实现了这一点:

Exporter是什么状态
singleplayer88/aeron-exporter通过 CncFileReader/CountersReader 读取 cnc.dat,用 Jetty 提供 /metrics。Apache-2.0。近期维护(2026)
fairtide/aeron-prometheus-stat同样的 CnC 读取模式,按 Prometheus 命名惯例重命名计数器;附带 Docker Compose。Apache-2.0。较旧(约 2023)
scalecube/scalecube-metrics构建在 Agrona counters + HdrHistogram 之上的遥测库,带一个 Prometheus-over-Aeron demo。活跃维护中

对于 Datadog、StatsD 或 CloudWatch,没有现成的集成——同样是那套 CountersReader.forEach 循环,只不过改成 push 给对应平台的客户端,而非暴露 /metrics。基于日志抓取的降级方案(解析每秒一次的 aeron-stat 控制台输出)也能用,但严格来说更差:更粗粒度、有损,而且是把你手头本来就是数值的数据,又当成文本重新解析了一遍。

Aeron Insights(Premium,当前为 beta)是路径 1 的开箱即用版本。它提供 Prometheus exporter、Grafana 示例仪表盘和 CLI。这些 exporter 靠「读取 Aeron 创建的文件」来采集数据——主要是 CnC 文件,外加 archive/cluster mark 文件。还是同一套只读做法,只是打包好了、也有人支持。它们提供两种每节点交付模式:

  • HTTP-server exporter(默认 :8080,localhost)—— Prometheus 直接抓取每个节点。这是常见做法。
  • Push-client exporter —— 按间隔(默认 1s)推送给一个 Prometheus Push Gateway。

回报是:Insights 开箱即可输出有名字、有文档的 Prometheus 指标(cluster_*、driver_*、archive_*),并带一个 Grafana「Cluster Overview」仪表盘,而不是一堆你得自己命名和维护的、只带 type id 的原始计数器。

OSS 自建 exporterPremium Aeron Insights
读取cnc.dat(靠你的代码)cnc.dat + mark 文件(已打包)
指标命名你自己定义有文档的 cluster_* / driver_* / archive_*
Grafana 仪表盘你自己搭附带「Cluster Overview」示例
交付方式你写什么就是什么HTTP 抓取或 Push Gateway
支持社区 / 自己Adaptive(beta)
成本免费(Apache-2.0)订阅

真正重要的计数器(以及「坏」长什么样)

Section titled “真正重要的计数器(以及「坏」长什么样)”

你不需要把 Aeron 的所有计数器都放上仪表盘。放到集群化的撮合引擎里看,下面这些才是承重的。OSS 列是 CnC 文件里的计数器标签 / type id;Insights 列是对应的 Prometheus 指标名(若存在)。

信号OSS 计数器 (type id)Insights 指标如何解读
Commit positionCluster commit-pos: (203)cluster_commit_position_total集群的心跳——被 quorum 复制到的 log 位置
Append positionlog recording 的 archive rec-pos(无专门指标)leader 已追加到哪里;与 commit 的差距 = follower 滞后
节点角色Cluster node role (201)cluster_node_role2 = Leader,0 = Follower,1 = Candidate
共识状态Consensus Module state (200)cluster_consensus_module_state1 = Active,3 = 正在打快照
选举状态 / 计数Cluster election state (207) / Cluster election count (238)cluster_election_state(计数未导出)稳态 = 17(Closed);计数不应上升
Leadership term idCluster leadership term id (239)(未导出)每次正当的 leader 更替才加一;比这更快就是churn
CM 周期时间Cluster max cycle time in ns (216) / exceeded (217)cluster_max_cycle_time_seconds、cluster_cycle_time_threshold_exceeded_totalconsensus module 的 duty-cycle 延迟
Service 周期时间Cluster container max cycle time in ns (218) / exceeded (219)cluster_service_max_cycle_time_seconds、cluster_service_cycle_time_threshold_exceeded_total你的服务代码的 duty-cycle 延迟
Driver 周期时间Conductor/Sender/Receiver max cycle time … in ns 及 work cycle exceeded threshold count(type 0)driver_max_cycle_time_seconds{agent}、driver_cycle_time_threshold_exceeded_total{agent}cluster 下面那一层——核隔离失效最先在这里露出来
错误Cluster Errors (212) / Cluster Container Errors (215)cluster_errors_total、cluster_service_errors_total单调累计——任何增量都值得一看
客户端超时Cluster timed out client count (213)cluster_timed_out_clients_total因不活跃而被踢掉的会话——网关卡住了,或者你的会话超时设得太紧
有效快照Cluster snapshot count (205)cluster_valid_snapshots可用快照的数量
快照年龄(由 recording-log 时间戳推算)cluster_last_snapshot_timestamp_seconds旧快照 ⇒ 恢复时回放漫长
快照时长Total max snapshot duration in ns (234) / exceeded (235);单个 service 为 (236/237)cluster_max_snapshot_duration_seconds、cluster_snapshot_duration_threshold_exceeded_total长快照会拖住热路径(OSS)
背压snd-bpe (13) / Sender flow control limitsdriver_sender_bpe_total(也按 streamId 分)「健康系统中应为零」
丢包 / NAKNAKs sent/received、Retransmits sent(type 0)driver_nak_messages_*_total、driver_retransmits_sent_total网络丢包 ⇒ 复制被拖慢
Loss gap fillsLoss gap fills(type 0)driver_loss_gap_fills_total被跳过而非被补回的丢包——只会出现在关掉了可靠投递的流上
畸形 / 丢弃Invalid packets、Short sends(type 0)driver_invalid_packets_total、driver_short_sends_total、driver_flow_control_over_runs_total三者都应为零;非零说明是 MTU、socket buffer,或某个跟不上的 subscriber
存活性(计数器更新的新鲜度)cluster_activity_age_seconds、driver_heartbeat_age_seconds距该组件上次活跃过去了多少秒

Consensus module 状态:哪些取值该叫人

Section titled “Consensus module 状态:哪些取值该叫人”

Consensus Module state (200) / cluster_consensus_module_state 是一个枚举,七个取值里只有两个是良性的。按「!= 1」告警会让你在每次打快照时都被叫起来。

取值状态如何处理
0INIT启动和恢复期间属正常。若长时间停留、超过你预期的回放时间就要叫人——卡在长 log 回放上的节点,就不在 quorum 里。
1ACTIVE健康稳态。
2SUSPENDED有人通过 ClusterControl/ClusterTool 挂起了集群,ingress 已不再被处理。叫人——生产环境里这几乎从来不是有意的。
3SNAPSHOT正常,且应当很短暂。持续不退就看快照时长那条告警。
4QUITTING节点正在退出。计划内停机属预期,否则叫人。
5TERMINATING正在关停——包括非自愿的 ClusterTerminationException 路径(例如 archive 存储耗尽)。叫人。
6CLOSED终态。节点已经没了,存活性/心跳年龄会印证这一点。

实操规则:2、4、5、6 立即叫人;0 只在超过一段按你的 log 回放时间设定的启动宽限期后才叫人;3 永远不要叫人——改为对它的持续时长告警。

下面每一项都把一个指标绑定到某个故障模式运维手册场景。Aeron 几乎不发布任何数值阈值——下表中的计时器是有文档的;其余都属于运维判断,因此要对照你自己的基线来校准,并从宽松阈值起步以避免告警疲劳。

告警条件依据 / 文档来源
Commit 停滞(严重)状态为 Active 时 commit-pos 增量 == 0 持续 > 约 10sAeron 自己的「No catchup progress」警告会在 commit 停止推进、超过 leader 心跳超时(默认 10s)时触发。成因:应用代码慢、机器负载、GC。
无 leader(严重)超过选举超时仍无节点报告角色 == Leader (2)选举进行中 = 集群没在提交。
选举频繁(高)election count (238) 在上升,或 election state 不回到 17(Closed)每一次自增都是一次 p99 悬崖。触发条件:心跳超时、丢失 quorum。(「Election storm」不是文档术语——自己定速率。)
Leadership churn(高)leadership term id (239) 的自增快于你计划中的切主频率term id 每次正当的 leader 更替才加一;没有维护动作却在爬,说明集群在反复重新选举 leader。
客户端在超时(中)cluster_timed_out_clients_total 增量 > 0会话因不活跃被踢——可能是网关/OMS 停止了轮询,也可能是会话超时比客户端最坏情况下的停顿还短。注意与 driver_client_timeouts_total 区分,后者是 media driver 的客户端,不是 cluster 会话。
Follower 滞后(中)rec-pos(log) − commit-pos 随时间增长leader 追加的速度快于 quorum 确认——正走向背压或 quorum 丢失。无文档化的差距阈值。
周期时间超限(中)cycle time exceeded 计数器(217/219)在自增CM 与 service 的 duty-cycle 阈值默认为 100ms。尖刺 = GC / CPU 饥饿;一次 10ms 的周期就是 p99 上一个 10ms 的坑。
Driver 周期时间超限(中)driver_cycle_time_threshold_exceeded_total{agent} 在自增driver 的 conductor、sender、receiver 同样默认 100ms。当成因是共享核争抢或 isolcpus 失效时,它会先于 cluster 那几个计数器触发——driver 线程最先感受到。见核隔离与绑核。
错误上升(高)Cluster Errors (212) 增量 > 0然后通过 ClusterTool errors 读文本。无文档化的数值速率。
快照过旧(中)当前时间 − cluster_last_snapshot_timestamp_seconds > 你的恢复预算Aeron 中快照是手动的——旧快照意味着重启时漫长的 log 回放。见快照失败与恢复。
快照过慢(低/中)快照时长 exceeded (235) 在自增阈值默认 1s。在 OSS 中快照会拖住热路径;在 leader 上则会体现为最大延迟的上升。
背压 / 丢包(中)snd-bpe、NAK 或重传速率高于基线subscriber/follower 跟不上,或网络丢包拖慢复制。无文档化的速率阈值。
数据被静默跳过(高)某条 cluster 流上 driver_loss_gap_fills_total 增量 > 0一次 gap fill 意味着丢包被跳过而不是补回。它只发生在非可靠流上,所以出现在 cluster 流量上就说明有人给一条不能丢数据的流配了 reliable=false。要查配置,不是查网络。
畸形帧(中)driver_invalid_packets_total、driver_short_sends_total 或 driver_flow_control_over_runs_total 增量 > 0文档明确这三个都「应为零」。invalid packets ⇒ MTU/路径问题,或有别的发送方占了这个端口;short sends ⇒ socket buffer 太小;over-runs ⇒ 同一条流里某个 receiver 落在了其他人后面。
存储耗尽(严重)archive/cluster 卷的可用空间低于你的下限archive 存储耗尽会抛 ClusterTerminationException 并终止节点——这是唯一一种总是终止性的快照/archive 故障。要在它发生之前告警。
节点失联(严重)cluster_activity_age_seconds / driver_heartbeat_age_seconds 在上升,或抓取目标掉线该组件停止更新计数器——进程已死或卡死。

散文式的告警条件最后都会烂掉,所以下面把同样的规则写成 PromQL。它们用的是 Insights 的指标名;自建 exporter 请替换成你自己的命名。另外注意 commit_position_total 是位置,不是计数器——对它做 rate() 得到的是每秒提交的 log 字节数,这正是你要的,但 increase() 遇到重置时会像是发生了重启。

每个 cluster 指标都带 clusterId 标签(service 还额外带 serviceId),所以凡是要归因到具体节点的表达式,都保留 by (instance, clusterId)。

groups:
- name: aeron-cluster-stability
rules:
# 1. Commit 停滞——集群活着,但没在提交。
# 窗口取得比 leader 心跳超时(10s)更长,避免一次正常选举把它触发。
- alert: AeronCommitStalled
expr: |
rate(cluster_commit_position_total[1m]) == 0
and on(instance, clusterId) cluster_consensus_module_state == 1
and on(instance, clusterId) cluster_node_role == 2
for: 15s
labels: { severity: critical }
annotations:
summary: "Leader 处于 ACTIVE 但 commit position 已平了 15s"
# 2. 整个集群没有 leader。
- alert: AeronNoLeader
expr: sum by (clusterId) (cluster_node_role == 2) == 0
for: 5s
labels: { severity: critical }
# 2b. 脑裂:多于一个节点自称 leader。
- alert: AeronMultipleLeaders
expr: sum by (clusterId) (cluster_node_role == 2) > 1
for: 5s
labels: { severity: critical }
# 3. Leadership churn。Insights 不导出选举计数指标(见上面的提示框),
# 所以退一步数角色切换。若你自己抓了计数器 238,就再加一条针对它的规则。
- alert: AeronLeadershipChurn
expr: sum by (clusterId) (changes(cluster_node_role[15m])) > 2
labels: { severity: warning }
annotations:
summary: "15 分钟内发生了不止一次切主"
# 3b. 选举收不回来:状态卡在 17(CLOSED)之外。
- alert: AeronElectionNotSettling
expr: cluster_consensus_module_state == 1 and cluster_election_state != 17
for: 30s
labels: { severity: warning }
# 4. Consensus module 处于需要人介入的状态。
# 2=SUSPENDED 4=QUITTING 5=TERMINATING 6=CLOSED。3=SNAPSHOT 是良性的。
- alert: AeronConsensusModuleBadState
expr: cluster_consensus_module_state == 2 or cluster_consensus_module_state >= 4
for: 10s
labels: { severity: critical }
# 0=INIT 只有在持续不退时才算问题——`for` 按你的 log 回放时间来定。
- alert: AeronStuckInitialising
expr: cluster_consensus_module_state == 0
for: 10m
labels: { severity: warning }
# 5. Duty-cycle 超限——GC、CPU 饥饿、绑核失效。
- alert: AeronConsensusCycleBreach
expr: increase(cluster_cycle_time_threshold_exceeded_total[5m]) > 0
labels: { severity: warning }
- alert: AeronServiceCycleBreach
expr: increase(cluster_service_cycle_time_threshold_exceeded_total[5m]) > 0
labels: { severity: warning }
- alert: AeronDriverCycleBreach
expr: increase(driver_cycle_time_threshold_exceeded_total[5m]) > 0
labels: { severity: warning }
annotations:
summary: "Driver {{ $labels.agent }} 的 duty cycle 超过了 100ms"
# 瓶颈在你的代码还是在 Raft?看比值,不看绝对值。
- alert: AeronServiceCodeIsBottleneck
expr: |
cluster_service_max_cycle_time_seconds
/ on(instance, clusterId) cluster_max_cycle_time_seconds > 5
for: 5m
labels: { severity: warning }
# 6. 错误——对计数告警,然后用 `ClusterTool errors` 读文本。
- alert: AeronErrors
expr: |
increase(cluster_errors_total[5m]) > 0
or increase(cluster_service_errors_total[5m]) > 0
or increase(driver_errors_total[5m]) > 0
or increase(archive_errors_total[5m]) > 0
labels: { severity: warning }
# 7. 恢复风险:快照年龄对照你的 RTO 预算,以及慢快照。
- alert: AeronSnapshotTooOld
expr: time() - cluster_last_snapshot_timestamp_seconds > 3600
labels: { severity: warning }
- alert: AeronSnapshotTooSlow
expr: |
increase(cluster_snapshot_duration_threshold_exceeded_total[30m]) > 0
or increase(cluster_service_snapshot_duration_threshold_exceeded_total[30m]) > 0
labels: { severity: info }
# 8. 下面那层传输。永远看变化率,不看累计总量。
- alert: AeronBackPressure
expr: rate(driver_sender_bpe_total[1m]) > 0
for: 2m
labels: { severity: warning }
- alert: AeronPacketLoss
expr: |
rate(driver_nak_messages_received_total[1m]) > 0
or rate(driver_retransmits_sent_total[1m]) > 0
for: 2m
labels: { severity: warning }
# 这几个文档写明「应为零」——任何增量都是真的。
- alert: AeronShouldBeZeroCounters
expr: |
increase(driver_invalid_packets_total[5m]) > 0
or increase(driver_short_sends_total[5m]) > 0
or increase(driver_loss_gap_fills_total[5m]) > 0
or increase(driver_flow_control_over_runs_total[5m]) > 0
or increase(cluster_timed_out_clients_total[5m]) > 0
labels: { severity: warning }
# 9. 节点失联。两个 age 都约每秒刷新,所以 >5s 说明进程卡死或已经没了。
- alert: AeronNodeDark
expr: cluster_activity_age_seconds > 5 or driver_heartbeat_age_seconds > 5
for: 10s
labels: { severity: critical }

按流监控:cluster 自己的 stream id

Section titled “按流监控:cluster 自己的 stream id”

cluster 流量跑的就是普通的 Aeron pub/sub,所以那些 driver 层计数器(snd-bpe、NAK、重传、publisher/subscriber 位置)全都可以按流拿到——而哪条流在丢包,就告诉你这是哪一类故障。下面是默认值;它们都可以用系统属性覆盖,所以请照你自己的配置读,不要想当然。

Stream id流配置项这里丢包意味着
100Cluster log(leader → follower)aeron.cluster.log.stream.id复制被拖慢——append−commit 差距扩大,接着丢 quorum
101Ingress(客户端 → leader)aeron.cluster.ingress.stream.id客户端到不了 leader;这里的背压会节流你的网关
102Egress(leader → 客户端)aeron.cluster.egress.stream.idack/响应在丢——客户端重试或超时
103Service replay(archive → service)aeron.cluster.replay.stream.id重启时恢复/回放变慢
104Service → consensus moduleaeron.cluster.service.stream.idservice 没法向 CM 汇报进度
105Consensus module → serviceaeron.cluster.consensus.module.stream.idservice 没有被驱动
106Service 快照aeron.cluster.snapshot.stream.id快照写入路径
107Consensus module 快照(同一个属性,默认值不同)快照写入路径
108Consensus(节点间:投票、心跳、canvass)aeron.cluster.consensus.stream.id这里丢包才是真正触发选举的原因——心跳和投票请求在丢

106/107 这个区分只是默认值造成的表象:consensus module 和 service container 读的是同一个 aeron.cluster.snapshot.stream.id 属性,只是默认值不同(107 和 106)。一旦显式配置该属性,两者就会塌到同一个 stream id 上——所以如果你的仪表盘靠 stream id 去区分这两条快照路径,只要有人配了这个属性,它立刻就失效。

要和操作系统一起看,否则会误诊

Section titled “要和操作系统一起看,否则会误诊”

「集群不稳」这类故障,只要真实成因是 GC、被抢走的核,或者打满的网卡,在 Aeron 的计数器里看起来都一模一样:周期时间超限加选举。计数器告诉你的是duty cycle 停滞了,它没法告诉你为什么。Aeron 自己的排障指南之所以要指向运行队列长度、swap 和线程调度,正是因为这个——所以请把下面这些放在同一条时间轴上一起采:

检查项命令你在找什么
运行队列 / 上下文切换vmstat 1r 列长期高于核数 ⇒ driver 和 CM 线程正在被调度走。cs 出现尖刺 ⇒ 有别的东西落到了它们的核上。si/so(swap)只要非零,这台机器就不该跑低延迟。
每核 steal / softirqmpstat -P ALL 1虚拟化宿主上的 %steal,或者你隔离出来的那些核上的 %soft/%irq——任何一个都说明你并没有真正独占你绑定的那个核。
UDP 数据报错误sar -n UDP 1 / netstat -suidgm_err、接收缓冲区错误和 noport 与 Aeron 的 NAK 同步上升,就确认了丢包发生在 Aeron 之下——是内核 socket buffer 或网卡 ring,不是协议。见接收路径缓冲区。
网卡丢弃ethtool -S <if> | grep -i drop、ip -s linkrx_dropped/rx_no_buffer ⇒ ring buffer 太小,或者接收路径跟不上。
GC 停顿JVM GC 日志(-Xlog:gc*,或 -Xlog:safepoint)任何接近 100ms duty-cycle 阈值的停顿都能解释一次周期超限;任何接近 10s 心跳超时的停顿都能解释一次选举。真正重要的数字是 safepoint 时间,不是 GC 时间。
磁盘延迟(follower 也要看)iostat -x 1archive 卷上的 await——一块慢的 follower 磁盘会拖住整个集群,因为 commit 要等 quorum,而 quorum 要等最慢的那个确认者。

如果你只搭一组面板,就照这个来——从上到下按你排障的顺序排列:

  1. 它还活着吗? 每个节点的角色(恰好一个 Leader)、存活/心跳年龄、抓取目标是否在线。
  2. 它在推进吗? commit-position 速率(你真正的吞吐量)以及 append−commit 差距。
  3. 它稳定吗? 选举计数(一条平线 = 健康)以及 consensus-module 状态。
  4. 瓶颈是你的代码吗? service 周期时间 vs. consensus 周期时间——若 service ≫ consensus,问题在你的 handler,而非 Raft。
  5. 底层健康吗? 背压、NAK/重传速率、archive/磁盘可用空间——并且按 stream id 拆开,让 consensus 流丢包(选举)和 log 流丢包(滞后)一眼就能分开。
  6. 你能快速恢复吗? 快照年龄、有效快照数、快照时长超限。
  7. 该怪机器吗? 运行队列、%steal、safepoint 停顿、UDP 错误——和上面所有面板画在同一条时间轴上,否则每次故障的头二十分钟都花在弄清这件事上。

本站与 Adaptive Financial Consulting Limited 或 Aeron 项目无任何关联,未获其背书或赞助。 Aeron 是 Adaptive Financial Consulting Limited 的注册商标。

Aeron 是 Adaptive Financial Consulting Limited 在英国及其他国家/地区的商标。