热路径日志与指标采集
本站其他地方的规则始终如一:撮合线程上不分配、不阻塞 I/O、不做外部调用 —— 三者中任意一个都会 直接体现在 p99 上(见 客户端 ↔ 集群通信 与 确定性处置手册)。这条规则引出一个 本页要回答的显而易见的问题:那么,我该如何从一段不被允许做这些事的代码里记录事件、测量延迟、 暴露 counter?
答案是一条原则的三种应用:在热路径上廉价采集,把昂贵的工作放到热路径之外。 在纳秒级把一个值 写入预分配内存;让另一个线程去格式化、聚合、发送。
唯一的原则:把采集与发射分开
Section titled “唯一的原则:把采集与发射分开”下面每一种技术都是同一个形状。
热路径永远只做一次有界、无分配的 存储。格式化字符串、获取锁、刷盘、走网络 —— 这些全部发生在 一个你并不关心其延迟的线程上。这正是 Aeron 自身为它的 counter 与错误日志所采用的架构 —— 也正因 如此,把指标发布进那套基础设施(见下文)是最自然的做法。
1 · 日志:事件日志才是你的审计线索,应用日志走异步
Section titled “1 · 日志:事件日志才是你的审计线索,应用日志走异步”在一个集群化、事件溯源的引擎上,首先要内化的一点是:你已经拥有一份完美、全序、持久的全量输入 日志 —— Aeron Cluster 日志本身。 对于审计、重建、以及”引擎在 14:32:07 到底做了什么”,答案是 回放日志,而不是 grep 应用日志。不要在热路径上把事件流复刻进一个日志框架。
对于你仍然需要的 运维型 日志(警告、状态迁移、罕见错误),把它挪出撮合线程:
- 通过 ring buffer 记日志,在后台线程排空。 从热路径把一条紧凑、预编码的事件写入 Agrona
ManyToOneRingBuffer(单生产者用OneToOneRingBuffer)—— 这是一次有界、无锁、无分配的 put。 由一个独立 agent 排空它并完成格式化与文件 I/O。这正是异步日志器背后的模式(例如 Log4j2 的异步 appender 就由一个 ring buffer 支撑);在延迟敏感的线程上,把”异步”这件事显式化并自己掌控这个 buffer。 - 绝不在热路径上格式化。 把 原始 字段(订单 id、以
longcents 表示的价格、一个枚举序数) 放进 buffer;让排空者把它们变成文本。字符串构建正是你要避免的那次分配。 - Aeron 自己的错误日志就是这么做的。 Media Driver 把异常记录进 CnC 文件里一个 distinct-error buffer,由外部工具按时点导出 —— 它的 duty cycle 上没有同步日志。你的引擎可以照搬这个模型。
2 · 延迟采集:进程内用 HdrHistogram 记录,在别的线程上报
Section titled “2 · 延迟采集:进程内用 HdrHistogram 记录,在别的线程上报”你想要的是来自 在线 引擎的各阶段延迟(ingress → match、match → egress),按 p50/p99/p99.9 统计,而不仅仅是压测框架里的数字。HdrHistogram 正是为此而生的工具:记录一个 值是一次 廉价、无分配的数组自增。
- 在热路径上记录,在热路径之外上报。 使用
Recorder(HdrHistogram 的并发前端)。撮合线程调用recorder.recordValue(latencyNs)—— 常见情况下无分配、无锁。一个后台线程周期性调用recorder.getIntervalHistogram()(分配发生在这里,在热路径之外),再记录/导出各分位数。 - 廉价地拿时间戳。 用单调的
System.nanoTime()做区间计算(不要用挂钟时间),并在确定性撮合 逻辑 之外 读取它 —— 在传输边界打时间戳、相减、记录。让时钟远离状态机(见 确定性)。 - 当心协调遗漏(coordinated omission)。 如果你只给那些 进入了 处理函数的请求计时,你就 隐藏了那些卡在某次停顿后面的请求的排队延迟 —— 而那恰恰是你最在意的延迟。请对照一个 预期 到达 节奏来测量,或在尽可能早的点(传输 ingress)记录。基准测试 一页讲了协调遗漏 为何会美化你的数据;同样的纪律适用于在线采集。
// 热路径 —— 无分配记录final long startNs = /* 在 ingress 边界捕获的 nanoTime */;// ... 确定性撮合 ...recorder.recordValue(System.nanoTime() - startNs); // 廉价的数组自增
// 后台上报线程 —— 昂贵的部分放在这里final Histogram interval = recorder.getIntervalHistogram(); // 分配,在热路径之外log.info("match p99={}us p999={}us", interval.getValueAtPercentile(99.0) / 1000, interval.getValueAtPercentile(99.9) / 1000);3 · 指标:把你自己的 counter 发布进 Aeron 的 counter 基础设施
Section titled “3 · 指标:把你自己的 counter 发布进 Aeron 的 counter 基础设施”这是最大的收益,也是多数人漏掉的一环。Aeron 的 counter 不只是 它 的内部指标 —— 同一套共享内存 基础设施是一个 你可以往里发布自己指标的公开 API。 一个 counter 就是 CnC 文件里的一个 64 位值, 以有序存储无锁更新,可被任何进程从进程外读取。这使它成为理想的热路径指标:撮合线程做一次内存 写;每个抓取器都免费读到它。
从你的 Aeron client 分配一个 counter,并在热路径上更新它:
// 初始化(一次,在热路径之外):在 CnC 文件里分配一个 counter。// typeId 是你的应用级路由键;选一个在 Aeron 之外的范围//(0–99 client/driver,100–199 archive,200–299 cluster)。final Counter ordersMatched = aeron.addCounter(1001, "matching: orders matched");final Counter bookDepth = aeron.addCounter(1002, "matching: bid depth");
// 热路径:有序存储,无 CAS、无分配、无锁。ordersMatched.incrementOrdered(); // 计一次事件bookDepth.setOrdered(currentBidDepth); // 发布一个 gaugeincrementOrdered()/setOrdered()才是热路径安全的方法。 它们做的是一次带 release 栅栏的 普通存储(单写者有序语义)—— 没有 compare-and-swap 争用。Counter继承自 Agrona 的AtomicCounter;单写者请用*Ordered(或*Release)变体,不要用基于 CAS 的那些。- 读取早已解决。 因为值落进了
aeron-stat与CountersReader本就在读的同一个cnc.dat,你的 自定义 counter 会与 Aeron 的 counter 并排出现,无需任何额外管道 —— 而 指标与告警 里的 exporter 模式会自动把它们抓取到 Prometheus/Datadog。按你的typeId路由它们。 - 是 counter,不是推送。 热路径永远不跟 Prometheus 说话。它在共享内存里给一个数字 +1;进程外 的抓取器去拉取。这正是 Aeron 自身遥测”对性能无显著影响”的原因,而你的指标继承了这一性质。
这落在成熟度矩阵的哪里
Section titled “这落在成熟度矩阵的哪里”这就是成熟度矩阵 监控支柱 背后的 如何做:
- L2 生产 要求”应用层指标 —— 延迟直方图、订单/成交计数”。§2(HdrHistogram
Recorder)与 §3(你自己的 counter)正是如何在不付出 p99 代价的前提下采集它们。 - L4 顶级 要求这些信号”在持续负载下实测”。因为采集是无分配的、发射在别的线程上,你的埋点本身 不会移动它所测量的那个数字 —— 这是能够信任该测量的前提。
- 发布 counter:
Aeron.addCounter(int typeId, String label)返回一个Counter, 它继承自 AgronaAtomicCounter(incrementOrdered()、setOrdered()、incrementRelease()、setRelease())。 - 读回它们:Aeron cookbook —— 以编程方式读取 counter
(
CountersReader、aeron.countersReader()、CncFileReader)与 Monitoring and Debugging wiki (“read by any external process with no significant impact on performance”)。 - type-id 范围与标签:
AeronCounters.java, 以及 Understanding Cluster Counters。 - 用于异步日志的无锁 buffer:Agrona
ManyToOneRingBuffer/OneToOneRingBuffer。 - 延迟采集:HdrHistogram 的
Recorder/getIntervalHistogram();协调遗漏见 Gil Tene(参见 基准测试)。
本站与 Adaptive Financial Consulting Limited 或 Aeron 项目无任何关联,未获其背书或赞助。 Aeron 是 Adaptive Financial Consulting Limited 的注册商标。
Aeron 是 Adaptive Financial Consulting Limited 在英国及其他国家/地区的商标。