解决MySql布尔型新旧版本兼容问题,采用枚举来表示布尔型的数据表。由正向工程赋值
|
# APM 埋点性能测试报告
> 测试日期:2026-10-11 | å£å¾„ï¼šå•æœºå¾®åŸºå‡†ï¼ˆBenchmarkDotNet,Release 模å¼ï¼‰
> 基准代ç :`Benchmark/TracingBenchmarks/` | 原始输出:`BenchmarkDotNet.Artifacts/results/`
## æµ‹è¯•ç›®æ ‡
é‡åŒ–两件事,为"是å¦/如何接入 APM"æä¾›æ•°æ®ä¾æ®ï¼š
1. NewLife 原生埋点(`ITracer`,星尘侧)相对**完全ä¸ç”¨ APM** å¢žåŠ å¤šå°‘å¼€é”€ï¼›
2. 在æ¤åŸºç¡€ä¸Šå†å åŠ **OpenTelemetry 输出**å¢žåŠ å¤šå°‘å¼€é”€ã€‚
## 测试环境
| 项 | 值 |
|---|---|
| OS | Windows 10 (10.0.19045.6456 / 22H2) |
| CPU | Intel Core i9-10900K @ 3.70GHz,10 ç‰©ç†æ ¸ / 20 é€»è¾‘æ ¸ |
| è¿è¡Œæ—¶ | .NET 10.0.12(X64 RyuJIT,x86-64-v3) |
| GC | Concurrent Workstation |
| 工具 | BenchmarkDotNet 0.15.8 |
| Job | LaunchCount=1ã€WarmupCount=3ã€IterationCount=10 |
| 内å˜è¯Šæ– | `[MemoryDiagnoser]`(Allocated ä¸ºæ¯æ¬¡æ“作的托管分é…é‡ï¼‰ |
## 测试方法
**åŒä¸€ä»½è¢«æµ‹æ–¹æ³•体,åªåˆ‡æ¢è·Ÿè¸ªå™¨æ¡£ä½**,ä¿è¯å„æ¡£ä¸¥æ ¼å¯æ¯”:
| æ¡£ä½ | é…ç½® |
|---|---|
| None | `tracer = null`。`Tracer?.NewSpan()` 被 `?.` çŸè·¯ï¼Œä»£è¡¨"ä¸ç”¨ APM" |
| Star | `DefaultTracer`。NewLife 原生本地èšåˆï¼›`StarTracer` çš„æ¯ç‰‡æ®µè·¯å¾„与它相åŒï¼ˆä»…覆盖周期上报) |
| OtelNoListener | `OpenTelemetryTracer`,进程内没有任何 `ActivityListener` |
| OtelListener | `OpenTelemetryTracer` + 一个真实的活动监å¬è€…(`AllDataAndRecorded`) |
| Dual | `OpenTelemetryTracer { Inner = DefaultTracer }`,OTel 输出 + 星尘åŒå†™ |
å››ç§çœŸå®žåŸ‹ç‚¹å½¢æ€ï¼š**空埋点**(`using var span = tracer?.NewSpan("op")`)ã€**å¸¦æ ‡ç¾**(匿å对象)ã€**嵌套两层**ã€**异常埋点**(`SetError`)。
---
## 测试结果
### 1. å•线程:æ¯åŸ‹ç‚¹å¼€é”€
| å½¢æ€ | æ— APM | 星尘(Star) | OTel æ— ç›‘å¬è€… | OTel 有监å¬è€… | åŒå†™ï¼ˆDual) |
|---|---|---|---|---|---|
| 空埋点 | 0.02 ns / 0 B | **137 ns / 72 B** | **5.2 ns / 0 B** | **254 ns / 672 B** | **443 ns / 832 B** |
| å¸¦æ ‡ç¾ | 0.05 ns / 0 B | 144 ns / 72 B | 5.3 ns / 0 B | 1 110 ns / 2 208 B | 2 063 ns / 3 880 B |
| 嵌套两层 | 1.4 ns / 0 B | 401 ns / **360 B** | 11.5 ns / 0 B | 1 266 ns / 2 864 B | 2 574 ns / 4 744 B |
| 异常埋点 | 0.34 ns / 0 B | 2 815 ns / 2 528 B | 6.4 ns / 0 B | 3 172 ns / 4 536 B | 8 726 ns / 10 064 B |
> æ— APM æ¡£ä½çš„çº³ç§’æ•°å—æ˜¯ JIT 把空æ“作消解åŽçš„循环开销,跨è¿è¡Œä¼šéšæœºå™¨çŠ¶æ€æ³¢åŠ¨ï¼Œè¯¥åˆ—åªéœ€çœ‹åˆ†é…为 0 B——å³**䏿ޥ APM å°±æ˜¯é›¶æˆæœ¬**。
### 2. 8 线程并å‘ï¼ˆæ¯æ“作摊算的墙钟时间)
| æ¡£ä½ | å•线程 | 8 线程 |
|---|---|---|
| æ— APM | 0.02 ns | 0.84 ns |
| 星尘(Star) | 137 ns | **125 ns** |
| OTel æ— ç›‘å¬è€… | 5.2 ns | 16.8 ns |
| OTel 有监å¬è€… | 254 ns | **57.0 ns** |
| åŒå†™ï¼ˆDual) | 443 ns | **158 ns** |
### 3. OpenTelemetry 采集器(第三方活动转为星尘埋点)
| 场景 | Mean | åˆ†é… |
|---|---|---|
| æœªæŒ‚é‡‡é›†å™¨ï¼ˆè¿›ç¨‹å†…æ— ä»»ä½•ç›‘å¬è€…) | 4.61 ns | 0 B |
| 已挂采集器,但æºä¸åœ¨é‡‡é›†èŒƒå›´å†… | **4.57 ns** | 0 B |
| 已挂采集器,且æºå‘½ä¸è¢«é‡‡é›† | **431 ns** | 640 B |
### 4. æ˜Ÿå°˜ä¾§å‘¨æœŸæˆæœ¬ï¼ˆ`TakeAll`,æ¯é‡‡æ ·å‘¨æœŸä¸€æ¬¡ï¼‰
| å‘¨æœŸå†…ç´¯è®¡åŸ‹ç‚¹æ¡æ•° | Mean | åˆ†é… |
|---|---|---|
| 100 æ¡ | 6.72 µs | 1.41 KB |
| 1 000 æ¡ | 6.78 µs | 1.41 KB |
> 两者差异在测é‡å™ªå£°å†…(Error 分别为 0.46 µs / 0.60 µsï¼‰ï¼šæˆæœ¬åªä¸Ž**ä¸åŒåŸ‹ç‚¹å的数é‡**(本例 8 ä¸ªï¼‰æœ‰å…³ï¼Œä¸ŽåŸ‹ç‚¹æ¡æ•°æ— 关——这是èšåˆæ¨¡åž‹çš„æ ¸å¿ƒä¼˜åŠ¿ã€‚æŒ‰ 60s é‡‡æ ·å‘¨æœŸæŠ˜ç®—å®Œå…¨å¯å¿½ç•¥ã€‚
---
## 分æž
**1. 䏿ޥ APM 是真æ£çš„é›¶æˆæœ¬ã€‚** 埋点ä½ç½®å†™æ³• `Tracer?.NewSpan(...)` 在 `tracer` 为 null 时被 JIT 消解(实测 0.02 ns / 0 Bï¼‰ï¼Œå› æ¤"默认ä¸å¯ç”¨"ä¸ä¼šç»™çŽ°æœ‰ä»£ç 带æ¥ä»»ä½•负担。
**2. 一次普通星尘埋点约 0.14 µs / 72 B,而这 72 B 全部æ¥è‡ªä¸Šä¸‹æ–‡å†™å…¥ã€‚** 对照真实业务:HTTP æŽ¥å£ 1–10 msã€SQL 数百 µs,0.13 µs å 0.001%–0.01%;但对**纯内å˜é«˜é¢‘æ“作**(缓å˜å‘½ä¸ã€åè®®æ ˆæ¯åŒ…)就是数é‡çº§å·®å¼‚,真æ£éœ€è¦å…³æ³¨çš„æ˜¯åˆ†é…速率。用线程级探针(`GC.GetAllocatedBytesForCurrentThread()`,与 `[MemoryDiagnoser]` åŒæºï¼‰é€é¡¹å½’å› ï¼š
| 动作 | 实测 |
|---|---|
| å¯¹è±¡æ± `Get` + `Return` | 0 B |
| 构建器å—典命ä¸ï¼ˆ`BuildSpan`) | 0 B |
| `AsyncLocal` 写入但值未å˜åŒ– | 0 B |
| `AsyncLocal` 值å‘生å˜åŒ–(`DefaultSpan.Current = this`) | **72 B** |
| `[ThreadStatic]` 写入(对照) | 0 B |
| `new DefaultSpan()`ï¼ˆæœªèµ°æ± åŒ–ï¼‰ | 112 B |
å³**å•层埋点的全部分é…就是一次 `ExecutionContext` é‡å»º**(约 40 B çš„ EC + 32 B çš„å€¼æ˜ å°„èŠ‚ç‚¹ï¼‰ã€‚`ExecutionContext` ä¸å¯å˜ï¼Œ`AsyncLocal` 值一å˜å°±åªèƒ½æ–°å»ºâ€”—这是 .NET 模型下é™ï¼Œä¸æ˜¯å®žçŽ°ç¼ºé™·ã€‚æŽ¢é’ˆå®žæµ‹å•层 72 Bã€ä¸¤å±‚ 360 B,与上表 BDN çš„ 72 B / 360 B 完全一致。
两个必须知é“的边界:
- **è¿™ä¸æ˜¯å¸¸æ•°ï¼Œéšå®¿ä¸»å¢žé•¿ã€‚** 分é…é‡å–决于环境 `ExecutionContext` ä¸å·²æœ‰å¤šå°‘个 `AsyncLocal` æ§½ä½ï¼šå¹²å‡€è¿›ç¨‹ 72 Bã€å·²æœ‰ 1 个槽 160 Bã€å·²æœ‰ 3 个槽 **224 B**(模拟真实 Web 宿主:`HttpContext` + `Activity.Current` + 日志作用域å„å 一槽。为探针模拟值,éžçœŸå®ž ASP.NET Core 实测)。**上表的 72 B 是最好情况,真实宿主应预期 200 B é‡çº§ã€‚**
- **嵌套更贵,且一åŠä¸æ˜¯ä¸Šä¸‹æ–‡ã€‚** 实测 1/2/3/4 层 = 72 / 360 / 560 / 760 B,å¯ç²¾ç¡®æ‹Ÿåˆä¸ºï¼š**æ¯å±‚ +200 B = 2 次上下文写入(144 B)+ 父片段 `Id` å—符串(56 B)**,其ä¸ç¬¬ 2 层é¢å¤– +88 B(父片段 `TraceId` 首次生æˆï¼‰ã€‚用"纯 `Current` æ“作ã€ä¸å«å—符串"çš„åˆæˆåºåˆ—对照(2 层 216 B / 3 层 360 B)验è¯äº†è¯¥æ‹Ÿåˆã€‚也就是说,`Id`/`TraceId` 的惰性生æˆåœ¨é“¾è·¯ä¸Šå¹¶æœªçœæŽ‰ï¼Œåªæ˜¯**推迟到第一个å片段出现时由父片段补付**。
**为什么ä¸èƒ½åˆ° 0:** 唯一能到 0 的办法是把 `Current` æ¢æˆ `[ThreadStatic]`ï¼Œä½†é‚£æ ·åŸ‹ç‚¹åœ¨ `await` 之åŽä¼šä¸¢å¤±çˆ¶çº§ã€é“¾è·¯ç›´æŽ¥æ–裂;把 `AsyncLocal` æ¢æˆ"å¯å˜çš„ `StrongBox`"看似 0 B,å´ä¼šè®©å¹¶è¡Œåˆ†æ”¯å…±äº«åŒä¸€ä¸ªç›’å互相覆盖,属于é™é»˜ä¸²åŒ…。两者都ä¸å¯æŽ¥å—,故ä¿ç•™å½“å‰å®žçŽ°ï¼ˆè¯¦è§ã€Šé“¾è·¯è¿½è¸ªITracer》收å£è®°å½•)。10 万埋点/秒按真实宿主的 224 B 折算约 22 MB/s,属å¯è§‚ä½†å¯æŽ§çš„æˆæœ¬ã€‚
**3. å¸¦æ ‡ç¾å‡ 乎ä¸é¢å¤–收费(星尘档)。** 144 ns vs 137 nsï¼Œå› ä¸º `BuildTag` ä»…åœ¨é‡‡æ ·ï¼ˆ`TraceFlag > 0`)时åºåˆ—åŒ–å¤æ‚å¯¹è±¡ï¼Œæœªé‡‡æ ·æ—¶å‡ ä¹Žé›¶æˆæœ¬â€”—这是埋点体系的一个良好设计。
**4. 异常埋点约 20× 于普通埋点(2.8 µs / 2.5 KB)。** `DefaultSpan.SetError` 会为异常**é¢å¤–生æˆä¸€æ¡ `ex:Xxx` 埋点**,一次调用产生两份片段与多份异常å—ç¬¦ä¸²ã€‚å¼‚å¸¸é£Žæš´åœºæ™¯ä¸‹çš„åŸ‹ç‚¹æˆæœ¬ä¸å¯å¿½ç•¥ã€‚
**5. OpenTelemetry 输出的分é…高于星尘档,"å¸¦æ ‡ç¾"å·®è·å°¤å…¶æ˜Žæ˜¾ã€‚**
- 空埋点:OTel 254 ns / 672 B vs 星尘 137 ns / 72 B(OTel 的分é…为 9.3×)
- å¸¦æ ‡ç¾ï¼šOTel **1 110 ns** vs 星尘 144 ns —— **7.7×**ã€‚åŽŸå› æ˜¯ OTel è¯ä¹‰è¦æ±‚æ ‡ç¾å˜æˆæ´»åŠ¨å±žæ€§ï¼Œ**å¿…é¡»**å…ˆåºåˆ—åŒ–ï¼›æ˜Ÿå°˜æ¡£åœ¨æœªé‡‡æ ·æ—¶ä¸åºåˆ—化
- 异常:3 172 ns vs 2 815 ns(相差约 13%)
**6. åŒå†™æˆæœ¬ ≈ ä¸¤è€…ç›¸åŠ ï¼Œæ²¡æœ‰éšè—æŸè€—。** 443 ns vs 254 + 137 = 391 ns,说明装饰器实现未引入é¢å¤–åŒæ¥æˆ–é‡å¤åºåˆ—化。
**7. é‡‡é›†å™¨ï¼šè¿‡æ»¤å‡ ä¹Žå…费,命ä¸åŽæŒ‰æ´»åŠ¨æ”¶è´¹ã€‚** 挂上采集器åŽï¼Œæºä¸åœ¨é‡‡é›†èŒƒå›´å†…的活动仅 4.57 nsï¼ˆä¸Žå®Œå…¨æ— ç›‘å¬è€…çš„ 4.61 ns æŒå¹³ï¼‰â†’ 白åå•/排除清å•ç–ç•¥æœ¬èº«ä¸æž„æˆè´Ÿæ‹…。命ä¸åŽæ¯ä¸ªæ´»åЍ 431 ns / 640 Bï¼ˆå…¶ä¸æ´»åŠ¨è‡ªèº«ä»… 4.6 ns)——**640 B/活动**是主è¦é£Žé™©ï¼šè‹¥ç¬¬ä¸‰æ–¹åº“æ¯ç§’产生 10 万活动(如高频 Redis 调用),就是 64 MB/s 分é…。
**8. å¹¶å‘ä¸‹æ— äº‰ç”¨æ”¾å¤§ã€‚** 8 çº¿ç¨‹æ—¶æ¯æ“作摊算耗时**下é™**(星尘 137→125 ns,OTel 有监å¬è€… 254→57 nsï¼‰ï¼Œè¯´æ˜ŽåŸ‹ç‚¹è·¯å¾„ï¼ˆå¯¹è±¡æ± ã€`ConcurrentDictionary` èšåˆã€AsyncLocal)没有全局é”瓶颈,å¯éšå¤šæ ¸æ‰©å±•。唯一的åå‘å˜åŒ–æ˜¯æ— APM æ¡£ 0.02→0.84 ns,æ¥è‡ª `HasListeners` å¯¹å…±äº«é™æ€ç»“构的读å–——ç»å¯¹å€¼ä»å¯å¿½ç•¥ã€‚
---
## 优化建议
1. **高频低延迟路径按需埋点**ï¼šç½‘ç»œæ ˆæ¯åŒ…ã€ç¼“å˜å‘½ä¸ç‰åœºæ™¯å»ºè®®ç”¨é‡‡æ ·æˆ–æ¡ä»¶åŸ‹ç‚¹æŽ§åˆ¶æ¡æ•°ï¼Œé‡ç‚¹ç›¯åˆ†é…速率——å•层埋点在真实宿主约 200 B,嵌套æ¯å¤šä¸€å±‚å† +200 B。
2. **异常路径节制**ï¼šå¼‚å¸¸åŸ‹ç‚¹æˆæœ¬çº¦ä¸ºæ™®é€šåŸ‹ç‚¹çš„ 20 å€ï¼ˆé¢å¤–ç”Ÿæˆ `ex:` 埋点),é¿å…åœ¨å¼‚å¸¸é£Žæš´è·¯å¾„ä¸Šæ— å·®åˆ«åŸ‹ç‚¹ã€‚
3. **é‡‡é›†å™¨åŠ é™æµ**:为高频第三方æºè®¾ç½®æ¯å‘¨æœŸæ¡æ•°ä¸Šé™æˆ–按æºé‡‡æ ·ï¼Œé¿å…活动转埋点带æ¥çš„æŒç»åˆ†é…。
4. **OTel è¾“å‡ºæŽ§åˆ¶æ ‡ç¾å¤§å°**ï¼šæ ‡ç¾ä¼šæ•´ä¸²å†™å…¥æ´»åŠ¨å±žæ€§å¹¶éšä¹‹å¯¼å‡ºï¼Œå»ºè®®åªæ”¾å…³é”®å—æ®µï¼ˆæ˜Ÿå°˜æ¡£åœ¨æœªé‡‡æ ·æ—¶ä¸åºåˆ—åŒ–ï¼Œå¯æ”¾å¿ƒä¼ 对象)。
---
## 已知é™åˆ¶
- 微基准为**啿œºã€å•进程ã€Workstation GC**ï¼›æœåŠ¡ç«¯ Server GC 与高并å‘下的分é…/回收曲线ä¸åŒï¼Œç»å¯¹å€¼ä¸å¯ç›´æŽ¥å¤–推。
- æœ¬æœºä¸ºå…±äº«å¼€å‘æœºï¼Œ**åŒä¸€å¤©å†…é‡å¤è¿è¡ŒåŒæ ·åŸºå‡†ï¼Œè€—时列曾出现约 35% 的漂移**(分é…列ä¸å—å½±å“ï¼Œä¸ºç¡®å®šæ€§æ•°å€¼ï¼‰ã€‚å› æ¤**ä¸è¦è·¨è¿è¡Œæ¯”较耗时**,åªå¯åœ¨åŒä¸€æ¬¡è¿è¡Œå†…æ¨ªå‘æ¯”较;表内耗时å–自åŒä¸€æ¬¡å®Œæ•´è¿è¡Œã€‚
- 第 2 èŠ‚çš„å¹¶å‘æ•°æ®æ˜¯**æ¯æ“作摊算的墙钟时间(åžåå£å¾„)**ï¼Œä¸æ˜¯äº‰ç”¨ä¸‹çš„啿¬¡å»¶è¿Ÿï¼Œä¹Ÿæœªç»™åˆ†ä½ï¼ˆP99)。
- æ ‡ç¾ä½¿ç”¨å°åž‹åŒ¿åå¯¹è±¡ï¼›çœŸå®žåŸ‹ç‚¹è‹¥ä¼ `IPacket`/大 JSON,åºåˆ—åŒ–æˆæœ¬ä¼šæ˜Žæ˜¾ä¸Šå‡ã€‚
- 埋点åå›ºå®šä¸ºå°‘æ•°å‡ ä¸ªï¼Œ**埋点å基数**对èšåˆå—典与 `TakeAll` çš„å½±å“æœªå•独测é‡ã€‚
- 分æžç¬¬ 2 æ¡çš„åˆ†é…æž„æˆï¼Œæ¥è‡ª**线程级探针**(`GC.GetAllocatedBytesForCurrentThread()` 逿®µæµ‹é‡ï¼‰ä¸Žåˆæˆå¯¹ç…§ï¼Œéž BenchmarkDotNet å…¨é‡å¤è·‘ï¼›å„项ç»å¯¹å€¼ä¸Ž BDN 交å‰éªŒè¯ä¸€è‡´ï¼ˆå•层 72 Bã€ä¸¤å±‚ 360 B),但ä¸å« BDN 的统计平滑。
- 真实宿主(ASP.NET Core)的 `AsyncLocal` æ§½ä½æ•°é‡æœªå®žæµ‹ï¼Œç”¨ 3 个自定义 `AsyncLocal` 模拟得出 224 B,属上界估计的近似而éžç›´æŽ¥æµ‹é‡ã€‚
- 未覆盖 net8 与 net10 的差异(åŒå±žä¸€ä¸ªè¿è¡Œæ—¶å®¶æ—),也未覆盖 .NET Framework(已决定ä¸ä¸ºå®ƒæä¾› OpenTelemetry 输出)。
|