高并发下指标为何不失真:apm_sdk Aggregator 原子聚合与异步上报实现原理
2026/9/24 13:45:51 网站建设 项目流程

高并发下指标为何不失真:apm_sdk Aggregator 原子聚合与异步上报实现原理

【免费下载链接】apm_sdk按照opentelemetry标准,用仓颉语言实现的APM SDK.项目地址: https://gitcode.com/Cangjie-TPC/apm_sdk

apm_sdk是遵循 OpenTelemetry 标准、用仓颉语言原生实现的应用性能监测(APM)SDK。在高并发场景下,多个线程同时累加指标却不会丢失一次计数,秘诀就在于它的Aggregator 原子聚合设计 +MetricReader 异步上报线程模型。本文带你快速看懂这套机制的原理。

一、问题背景:高并发为什么会让指标"失真"

想象一个电商服务,每秒上万次请求,每个请求都要给"活动请求数"这类 Counter 指标加 1。如果多个线程同时对同一个变量执行"读取 → 加一 → 写回",就可能出现:

  • 线程 A 读到 100,线程 B 也读到 100;
  • 两个线程都写回 101 —— 一次请求被"吞掉",指标偏小。

这就是典型的lost update(丢失更新)问题。指标一旦失真,基于它做的容量规划、报警阈值全部不可信。

📌 apm_sdk 的解法分两步:

  1. 写入侧:把聚合状态放进原子对象,用 CAS 保证每次累加不丢;
  2. 读取侧:用一个独立后台线程按固定周期采集并导出,业务线程完全不阻塞。

二、核心设计:Aggregator 分层架构

指标采集的核心逻辑由三层组成,对应源码目录结构如下:

src/sdk/metric/aggregator/ 核心指标数据计算(Aggregator) src/exporter/metric/ 指标数据处理(MetricReader)+ 数据上报(Exporter)

整体数据流是:

业务线程调用counter.add()→ 按 Attributes 维度路由到AggregatorHandle→ 原子累加 →MetricReader后台线程定时collect()MetricExporter写入文件

每个仪器(Counter / Histogram / Gauge)在创建时都会绑定一个聚合器,聚合器再为每一组标签(Attributes)维护独立的聚合句柄,实现见 abstract_instrument_builder.cj。它还做了两个高并发下很实用的保护:

  • 句柄池复用:已释放的 Handle 先进入NonBlockingQueue池,优先复用,避免高频创建对象;
  • 基数上限(Cardinality Limit):单个仪器的标签分组数达到上限后,多余的序列统一归入otel.metric.overflow,防止维度爆炸撑爆内存。

三、原子聚合:不同指标类型用不同的"原子武器"

所有聚合句柄都继承自 aggregator.cj 中的抽象基类AggregatorHandle,对外暴露两个方法:recordXxx()(写入)和aggregateThenMaybeReset()(读取+可选重置)。不同指标类型按语义选择不同的并发策略:

指标类型实现类并发策略适用场景
Long CounterLongSumHandlerAtomicInt64+ CAS 自旋累加请求数累加,最频繁
Double Counter/UpDownDoubleSumHandlerAtomicReference<Bigdecimal>+ CAS浮点累加
Gauge(瞬时值)LongLastValueHandler/DoubleLastValueHandler原子load/swap内存、线程数等"取最新值"
Max/Min/AVGDoubleMaxHandler原子读改写 + 互斥锁响应时间统计
HistogramDoubleExplicitBucketHistogramHandlerReentrantMutex保护多字段分位数统计

3.1 CAS 自旋:计数累加的"零丢失"写法

Long 类型的累加器 AtomicLongLongAdder 是最典型的一个:

public func add(x: Int64): Unit { while (true) { let current = atomicLong.load(); let next = current + x; if (atomicLong.compareAndSwap(current, next)) { return; } } }

这就是经典的Compare-And-Swap 自旋:先读当前值,算出期望值,尝试原子替换;被别人抢先了就重读重试。循环直到成功为止,保证每一次 add 最终都会被计入——高并发下指标不偏小的根本原因。

浮点类型AtomicDoubleLongAdder因为仓颉没有原生原子浮点,把Float64包装进Bigdecimal对象、用AtomicReference承载后走同样的 CAS 思路。

3.2 sumThenReset:读取和清零一步完成

周期性采集需要"取出当前值并归零"。如果拆成"读 + 写零"两步,两步之间新进来的数据会被清零丢掉。sumThenReset()把整个过程压成一个 CAS 循环:

public func sumThenReset(): Int64 { var prev: Int64 do { prev = atomicLong.load() } while (!atomicLong.compareAndSwap(prev, 0)) return prev }

读到的就是这段时间内的精确增量,天然适合 Delta 型采集。

3.3 什么时候用锁而不是 CAS?

Histogram 一个句柄要同时更新 sum、min、max、count、15 个桶计数——多字段必须一致地更新,CAS 就不够了,于是用ReentrantMutex保护整个临界区(aggregator.cj)。Gauge 这类"只关心最新值"的指标则更简单:写入端store(),读取端load(),重置时swap()原子换回初值并返回旧值,全程无锁。

💡 小结:能无锁就无锁,必须一致性才加锁,且锁的范围压到最小,这是高并发下既不失真又低延迟的关键。

四、异步上报:MetricReader 的独立 Worker 线程

业务线程只负责"写","读 + 上报"全部被甩给后台。metric_reader.cj 中的Worker通过仓颉的spawn关键字启动独立协程线程(命名为metricReader_worker_thread),核心循环逻辑是:

  1. interval(默认 2000ms)醒来一次;
  2. AtomicBoolcompareAndSwap(true, false)抢"执行权"——如果上一轮还没跑完,本轮直接跳过,绝不重叠执行,避免慢上报拖垮系统;
  3. 遍历所有仪器执行collect(),把聚合结果交给 Exporter;
  4. 无论成功还是异常,都finally里恢复标志并休眠到下一周期。

导出端 metric_exporter.cj 则把指标序列化为 JSON 按天滚动写入文件(如apm/2024-04-16/metric-0.json),支持按大小轮转、保留 N 天,写入 IO 完全不影响业务线程。

🔍 采集时还有一个细节:Delta 时序的仪器会调用sumThenReset()取增量,Cumulative 时序只取sum()不动状态——时间语义由 abstract_instrument_builder.cj 中的collect()统一处理。

五、对应用的影响:为什么可以放心接入

  • 线程隔离:metric 上报线程独立运行,异常只记录日志不向外抛;
  • 资源有边界:仪器 Map 上限 10000、每组标签 handler 上限 2000,超限自动归入 overflow 序列;
  • 无阻塞队列池化:Handle 对象走非阻塞队列复用,减少 GC 压力。

这些设计说明见 README.md 的"SDK资源使用情况说明"章节。

六、快速上手:3 步接入 apm_sdk

  1. 引入静态库:在工程module.jsonpath_option中加入../build/release/apm_sdk
  2. 创建配置:参考 basic_example 的 telemetry_config.cj,组装MeterProvider+MetricReader(内含后台 Worker)+MetricExporter
  3. 打点:参考 metric_example.cj,像示例里那样在业务和spawn异步线程中混合调用counter.add(...),多协程并发写入依然精确。

更完整的用法(silo 拦截器自动采集请求指标、trace 宏等)可直接参考 samples/ 目录下的两个示例工程。

总结

apm_sdk 让高并发指标不失真的核心思路可以浓缩为一句话:

写入侧用 CAS 原子累加消灭 lost update,读取侧用独立 Worker 线程 + CAS 防重入完成异步导出。

它不依赖任何第三方库,纯仓颉语言实现,且完全对齐 OpenTelemetry 语义。如果你的仓颉服务正面临"指标对不上账"的困扰,这套 Aggregator 原子聚合 + 异步上报的实现值得直接借鉴。

【免费下载链接】apm_sdk按照opentelemetry标准,用仓颉语言实现的APM SDK.项目地址: https://gitcode.com/Cangjie-TPC/apm_sdk

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询