创建仓库
This commit is contained in:
152
系统基座文件/2/2.3/2.3.2 全链路追踪上下文传递 (Trace Context Propagation).md
Normal file
152
系统基座文件/2/2.3/2.3.2 全链路追踪上下文传递 (Trace Context Propagation).md
Normal file
@@ -0,0 +1,152 @@
|
||||
---
|
||||
tags: []
|
||||
date created: 星期五, 十一月 21日 2025, 12:00:13 凌晨
|
||||
date modified: 星期一, 十一月 24日 2025, 4:31:24 下午
|
||||
---
|
||||
|
||||
# 2.3.2 全链路追踪上下文传递 (Trace Context Propagation)
|
||||
|
||||
## 一、 约束输入与对齐 (Constraints & Alignment)
|
||||
|
||||
基于设计文档和 C++14 环境,我们需要对齐以下硬性约束:
|
||||
|
||||
1. **无侵入性 (Non-Intrusive)**:业务逻辑代码(如算法计算)不应到处传递 `trace_id` 参数。追踪上下文的获取应当是“隐式”的。
|
||||
2. **跨线程连续性 (Cross-Thread Continuity)**:系统大量使用异步队列(`EventBus::publishAsync`)和工作线程池。TraceID 必须能跨越线程边界,不能断链。
|
||||
3. **性能极其敏感**:追踪机制是**热路径 (Hot Path)**。获取当前 TraceID 的开销必须是纳秒级,严禁涉及锁竞争或复杂的哈希查找。
|
||||
4. **来源明确**:
|
||||
|
||||
- **数据面**:由 `DataReceiver` 在收到 UDP 包时生成。
|
||||
- **控制面**:由 `TaskScheduler` 在定时任务或外部 API 调用时生成。
|
||||
|
||||
---
|
||||
|
||||
## 二、 权衡分析与选项呈现 (Trade-off Matrix)
|
||||
|
||||
### 议题 1:上下文存储方式 (Storage Mechanism)
|
||||
|
||||
|**选项**|**A. 显式参数传递 (Explicit Parameter)**|**B. 全局 Map 映射 (Global Map)**|**C. 线程本地存储 (Thread Local Storage - TLS) (推荐)**|
|
||||
|---|---|---|---|
|
||||
|**机制**|每个函数增加 `const TraceId& tid` 参数。|维护 `Map<ThreadID, TraceID>`。|使用 C++ `thread_local` 关键字。|
|
||||
|**侵入性**|**极高**。所有接口签名都要改,污染业务代码。|**低**。但在读写时需要加锁(或无锁 Map),有性能开销。|**零**。业务代码无感。|
|
||||
|**性能**|最佳。|差(锁竞争)。|**极佳**。直接的内存地址访问,无锁。|
|
||||
|**缺陷**|代码丑陋。|性能瓶颈。|**跨线程时会丢失**(需额外机制弥补)。|
|
||||
|
||||
### 议题 2:跨线程传递策略 (Propagation Strategy)
|
||||
|
||||
针对 TLS 跨线程丢失的问题:
|
||||
|
||||
|**选项**|**A. 手动拷贝 (Manual Copy)**|**B. 智能闭包捕获 (Smart Closure Capture) (推荐)**|
|
||||
|---|---|---|
|
||||
|**机制**|在 `publishAsync` 前手动取出 ID,在回调里手动设置。|封装 `EventBus` 的任务包装器,**在入队瞬间自动捕获 TLS,在执行瞬间自动恢复 TLS**。|
|
||||
|**可靠性**|**低**。开发者容易忘,导致断链。|**高**。由基础设施层保证,业务无感。|
|
||||
|**复杂度**|低。|中。需要编写通用的任务包装模板。|
|
||||
|
||||
---
|
||||
|
||||
## 三、 基线确立与实施规范
|
||||
|
||||
为了实现“高性能”与“全链路无感”,我们确立 **C. 线程本地存储 (TLS) + B. 智能闭包捕获** 为技术基线。
|
||||
|
||||
### 1. 核心数据结构基线
|
||||
|
||||
- **`TraceId` 类型**:使用 `uint64_t` 或 `uuid`(推荐 64 位整数配合 SnowFlake 算法,追求极致性能)。
|
||||
- **`BaseEvent` 接口**:所有控制面事件必须继承此基类。
|
||||
|
||||
```cpp
|
||||
struct BaseEvent {
|
||||
uint64_t trace_id; // 事件携带的“信封”
|
||||
uint64_t timestamp;
|
||||
|
||||
BaseEvent() {
|
||||
// 构造时自动从当前线程 TLS 捕获 TraceID
|
||||
// 如果当前是根源(无 ID),则保持 0 或生成新 ID(视策略而定)
|
||||
trace_id = TraceContext::getCurrentId();
|
||||
timestamp = CurrentTimeMillis();
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 2. 上下文管理基线 (RAII + TLS)
|
||||
|
||||
我们定义一个静态辅助类 `TraceContext` 和一个 RAII 守卫 `TraceContextGuard`。
|
||||
|
||||
- **`TraceContext` (TLS 管理)**
|
||||
|
||||
```cpp
|
||||
class TraceContext {
|
||||
public:
|
||||
static void set(uint64_t id) { current_trace_id_ = id; }
|
||||
static uint64_t get() { return current_trace_id_; }
|
||||
static void clear() { current_trace_id_ = 0; }
|
||||
|
||||
// 生成一个新的全局唯一 ID
|
||||
static uint64_t generateNew();
|
||||
|
||||
private:
|
||||
// 核心:每个线程独立一份,无锁,极速
|
||||
static thread_local uint64_t current_trace_id_;
|
||||
};
|
||||
```
|
||||
|
||||
- **`TraceContextGuard` (RAII 自动恢复)**
|
||||
- **作用**:在作用域结束时自动还原之前的 ID,支持嵌套调用。
|
||||
- **场景**:用于事件处理函数入口,确保处理完事件后,线程状态复原,不污染后续逻辑。
|
||||
|
||||
### 3. EventBus 集成规范 (跨线程核心)
|
||||
|
||||
这是本节最关键的设计:**如何在 `publishAsync` 时“偷渡”上下文?**
|
||||
|
||||
- **基线实现逻辑**:
|
||||
|
||||
1. **Publish 时 (线程 A)**:`publishAsync` 函数内部,获取当前线程 A 的 `TraceContext::get()`。
|
||||
2. **入队时**:将取出的 `trace_id` 和用户的 `handler` 打包成一个 `WrappedTask`。
|
||||
3. **Execute 时 (线程 B)**:`WrappedTask` 被执行。它首先使用 `TraceContextGuard` 将线程 B 的 TLS 设置为保存的 `trace_id`,然后执行用户 `handler`,最后 RAII 自动清理。
|
||||
|
||||
- **伪代码范式**:
|
||||
|
||||
```cpp
|
||||
template <typename EventType>
|
||||
void IEventBus::publishAsync(const EventType& event) {
|
||||
// 1. 捕获上下文 (此时还在发送者线程)
|
||||
uint64_t context_id = event.trace_id;
|
||||
|
||||
// 2. 包装任务 (Lambda Capture)
|
||||
auto wrapped_task = [handler, event, context_id]() {
|
||||
// 3. 恢复上下文 (此时已在接收者线程)
|
||||
TraceContextGuard guard(context_id);
|
||||
|
||||
// 4. 执行业务逻辑 (此时日志库能读到正确的 TLS TraceID)
|
||||
handler(event);
|
||||
};
|
||||
|
||||
// 5. 推入队列
|
||||
work_queue_.push(wrapped_task);
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 日志集成规范
|
||||
|
||||
- **基线要求**:所有日志宏(如 `RADAR_INFO`)必须自动读取 `TraceContext::get()` 并打印。
|
||||
- **格式**:`[Time][Level][ThreadID][TraceID] Message`。
|
||||
- **效果**:业务代码只需写 `RADAR_INFO("Processing data")`,日志文件里会自动出现 `… [TraceID: 12345] Processing data`。
|
||||
|
||||
---
|
||||
|
||||
## 总结:2.3.2 基线图谱
|
||||
|
||||
|**维度**|**基线决策**|**关键技术点**|
|
||||
|---|---|---|
|
||||
|**存储**|**Thread Local Storage (TLS)**|`thread_local uint64_t`,无锁,纳秒级访问。|
|
||||
|**载体**|**`BaseEvent` 继承**|所有事件自动携带 `trace_id` 字段。|
|
||||
|**跨线程**|**智能闭包捕获 (Smart Capture)**|`EventBus` 在入队/出队时自动 Switch Context。|
|
||||
|**作用域**|**RAII Guard**|`TraceContextGuard` 保证作用域内的上下文一致性和退出后的还原。|
|
||||
|
||||
---
|
||||
|
||||
**下一步行动**:
|
||||
|
||||
我们已经搞定了“大喇叭(总线)”和“条形码(TraceID)”。接下来要定的是“握手暗号” —— **2.3.3 生命周期编排与状态同步协议**。
|
||||
|
||||
在这个系统中,模块启动不是简单的 `start()`,它涉及依赖检查、顺序控制和状态回执。比如,`SignalProcessor` 启动前必须确认 GPU 资源就绪,启动后必须告诉调度器“我好了”。
|
||||
|
||||
**提问**:您是否确认 **“TLS + 智能闭包捕获”** 的上下文传递基线?确认后我们将进入 2.3.3。
|
||||
Reference in New Issue
Block a user