第一章:Go期末最后一搏:用go tool trace反向推导3道调度器相关大题的标准答案
go tool trace 是 Go 运行时调度行为的“X光机”——它不依赖源码注释或文档推测,而是直接捕获 Goroutine 创建、阻塞、唤醒、P/M/G 状态切换等真实事件。期末考前最后冲刺,可利用 trace 文件反向还原调度器核心机制,精准命中高频考点。
准备可追踪的调度场景
先编写一个典型多 Goroutine 争抢 I/O 和 CPU 的测试程序:
package main
import (
"fmt"
"runtime/trace"
"time"
)
func main() {
f, _ := trace.Start("trace.out") // 启动追踪
defer f.Close()
for i := 0; i < 5; i++ {
go func(id int) {
time.Sleep(time.Millisecond * 10) // 模拟阻塞型 I/O
fmt.Printf("G%d done\n", id)
}(i)
}
// 主 Goroutine 占用 CPU 防止提前退出
time.Sleep(time.Millisecond * 50)
}
执行 go run main.go && go tool trace trace.out,浏览器中打开交互式界面,重点查看 “Goroutines” 和 “Scheduler” 视图。
识别三类经典调度现象
- Goroutine 唤醒延迟:在 “Goroutines” 视图中筛选 G1,观察其从
runnable→running的时间差,若 >100μs,说明存在 P 竞争或全局队列窃取延迟; - M 频繁创建/销毁:切换至 “Threads” 视图,若 M 数量在短时间内剧烈波动(如从 1→4→1),对应题目中“为何系统调用后 M 会脱离 P”的标准答案;
- Work-Stealing 发生时刻:在 “Scheduler” 视图中定位
steal事件(蓝色小方块),其左侧必有某 P 的本地队列为空,右侧为另一 P 的本地队列非空——这是调度器负载均衡的铁证。
反向推导标准答案的关键路径
| 考题类型 | trace 中定位点 | 对应原理 |
|---|---|---|
| Goroutine 为何不立即执行? | 查看 G 的 runnable 时间戳与首次 running 时间戳差值 | 本地队列满 → 入全局队列 → 等待窃取或 schedule 循环 |
| 系统调用后 G 是否丢失? | 搜索 GoSysExit 事件 → 观察后续 G 是否绑定新 M |
runtime 将 G 放入全局队列,由其他 M 获取 |
| 为什么 2 个 G 并发 sleep 却几乎同时唤醒? | 对比多个 G 的 blocking → runnable 时间戳精度 |
netpoller 批量唤醒 + 事件循环统一调度 |
运行 go tool trace -http=localhost:8080 trace.out 后,所有时间轴、事件标记、协程状态流转均以毫秒级精度可视化,无需记忆理论,直接“看懂”调度器正在做什么。
第二章:Goroutine调度机制核心考点精析
2.1 G、M、P三元组状态转换与trace事件映射
Go 运行时通过 G(goroutine)、M(OS thread)、P(processor)协同实现并发调度,其状态跃迁直接受 trace 事件驱动。
状态映射核心逻辑
runtime.traceGoStart() 触发 G 从 _Grunnable → _Grunning,同时绑定 P;traceGoPark() 记录 _Grunning → _Gwaiting,解绑 P 并可能唤醒 M。
关键 trace 事件对照表
| Trace Event | G 状态变迁 | P/M 影响 |
|---|---|---|
GoStart |
runnable → running | P 被占用,M 绑定 G |
GoPark |
running → waiting | P 释放,M 可能进入自旋或休眠 |
GoUnpark |
waiting → runnable | G 入 P 的本地队列 |
// runtime/trace.go 片段:GoPark 事件记录
func traceGoPark(traceEv byte, skip int) {
if !trace.enabled { return }
// 参数说明:
// traceEv: 事件类型码(如 traceEvGoPark)
// skip: 跳过调用栈帧数,用于准确定位 park 位置
traceEvent(traceEv, 0, skip+1)
}
该函数在 gopark 调用链中插入,确保调度器状态变更与 trace 时间线严格对齐。
graph TD
A[G: _Grunnable] -->|GoStart| B[G: _Grunning]
B -->|GoPark| C[G: _Gwaiting]
C -->|GoUnpark| D[G: _Grunnable]
B -->|GoStop| E[G: _Gdead]
2.2 Goroutine创建与阻塞唤醒在trace中的可视化识别
Go trace 工具(go tool trace)将 goroutine 生命周期映射为时间轴上的状态跃迁,核心状态包括 Grunnable、Grunning、Gsyscall、Gwaiting。
goroutine 状态跃迁关键信号
- 创建:
proc.start事件触发Grunnable → Grunning - 阻塞:
block事件标记Grunning → Gwaiting(如semacquire、netpollwait) - 唤醒:
unblock事件触发Gwaiting → Grunnable
典型阻塞唤醒代码示例
func blockingIO() {
conn, _ := net.Dial("tcp", "example.com:80")
conn.Write([]byte("GET / HTTP/1.1\r\n\r\n"))
// 此处 read 可能触发 netpollwait → Gwaiting
buf := make([]byte, 1024)
conn.Read(buf) // trace 中显示为 block → unblock → runnable
}
该调用链在 trace UI 中表现为:绿色(running)→ 灰色(waiting)→ 黄色(runnable),对应 runtime.gopark 和 runtime.ready 的调用栈。
trace 中的 Goroutine 状态对照表
| trace 状态名 | runtime 状态 | 触发时机 |
|---|---|---|
Goroutine created |
Grunnable |
newproc1 分配 G 结构体 |
Scheduling |
Grunning |
execute 抢占调度 |
Block Sync |
Gwaiting |
gopark 显式阻塞(如 mutex) |
Netpoll |
Gwaiting |
netpollblock 网络 I/O 阻塞 |
graph TD
A[Goroutine created] --> B[Scheduling]
B --> C{I/O or sync?}
C -->|Yes| D[Block Sync / Netpoll]
C -->|No| E[Running]
D --> F[unblock via netpoll or semawakeup]
F --> G[Goroutine runnable]
2.3 系统调用(Syscall)与网络轮询(netpoll)在trace中的双路径判别
在 Go 运行时 trace 中,read/write 等 I/O 操作会分叉为两条可观测路径:
- Syscall 路径:阻塞式系统调用,直接陷入内核,
runtime.syscall记录完整上下文; - netpoll 路径:非阻塞 I/O +
epoll_wait(Linux)或kqueue(macOS),由runtime.netpoll驱动,goroutine 挂起于Gwaiting状态。
核心判别依据
proc.status == "syscall"→ Syscall 路径proc.status == "gwaiting"+netpoll在栈帧中 → netpoll 路径
// trace 示例片段(简化)
// goroutine 17: syscall path
runtime.syscall -> sys_read -> ... // trace event: "SyscallEnter"
// goroutine 19: netpoll path
runtime.netpoll -> epollwait -> ... // trace event: "NetPollBlock"
逻辑分析:
SyscallEnter事件携带fd和op参数,标记内核态入口;NetPollBlock则附带mode=0x1(读就绪)等位掩码,反映用户态轮询调度意图。
| 路径类型 | 触发条件 | trace 关键事件 | goroutine 状态 |
|---|---|---|---|
| Syscall | O_NONBLOCK 未设 |
SyscallEnter/Exit |
Gsyscall |
| netpoll | 文件描述符注册至 netpoll |
NetPollBlock/Unblock |
Gwaiting |
graph TD
A[I/O 操作发起] --> B{fd 是否注册 netpoll?}
B -->|是| C[进入 netpoll 循环<br>goroutine 挂起]
B -->|否| D[执行 raw syscall<br>线程阻塞]
C --> E[epoll_wait 返回就绪]
D --> F[内核返回 syscall 结果]
2.4 抢占式调度触发点(如preemptible point)在trace timeline中的定位方法
抢占式调度触发点是内核可被中断并切换任务的关键位置,常见于 cond_resched()、might_resched() 或系统调用返回路径。
关键识别模式
- 在 ftrace 或 perf script 输出中,搜索
sched_preempt_enable、preempt_schedule或__schedule入口; preempt_count变化前后伴随preempt_disable/preempt_enable调用对。
典型 trace 片段分析
// kernel/sched/core.c: __schedule() 调用前的 preempt check
if (should_resched(prev)) { // 判定是否到达抢占点
preempt_schedule(); // 显式触发调度(trace 中可见)
}
should_resched() 检查 TIF_NEED_RESCHED 标志与 preempt_count == 0;仅当两者同时满足时,该位置才构成有效抢占点。
常见抢占点类型对照表
| 触发场景 | trace 事件名 | 是否用户态可见 |
|---|---|---|
| 系统调用返回 | sys_exit → preempt_schedule |
否 |
| 中断返回 | irq_handler_exit → preempt_schedule_irq |
是(需开启 irq trace) |
| 显式让出(cond_resched) | cond_resched → __cond_resched |
是 |
定位流程图
graph TD
A[采集 sched:sched_switch + preempt:* 事件] --> B[过滤 preempt_count == 0 的上下文]
B --> C[匹配 __schedule 入口前的 TIF_NEED_RESCHED 置位点]
C --> D[标记为有效抢占点]
2.5 GC STW与Mark Assist对P状态和G队列的trace痕迹分析
Go 运行时在 GC 标记阶段通过 Mark Assist 机制分摊标记工作,避免 STW 时间过长。该过程深度影响 P(Processor)的调度状态与 G(Goroutine)队列的可见性。
Mark Assist 触发时的 P 状态迁移
当 Goroutine 分配内存触发辅助标记时,当前 P 会从 _Prunning 进入 _Pgcassist 状态,暂停常规调度,专注执行标记任务。
G 队列在 trace 中的关键痕迹
启用 GODEBUG=gctrace=1 后,可观察到:
gc assist start→ P 状态切换日志gc assist done→ 恢复调度前清空本地 G 队列缓存
// runtime/proc.go 中关键逻辑片段
if gp.m.p.ptr().status == _Pgcassist {
gcMarkDone() // 协助标记完成后,重置 P 状态
mp := gp.m
mp.p.ptr().status = _Prunning // 恢复运行态
}
此代码确保 P 在完成协助后立即回归调度循环;
status字段变更被runtime.traceProcStatusChange()捕获并写入 trace event,形成 P 状态跃迁链。
| Event | P 状态变化 | G 队列影响 |
|---|---|---|
| gc assist start | _Prunning → _Pgcassist | 本地运行队列冻结 |
| gc assist done | _Pgcassist → _Prunning | 全局队列扫描+本地队列重载 |
graph TD
A[goroutine 分配触发 assist] --> B{P.status == _Prunning?}
B -->|是| C[切换为 _Pgcassist]
C --> D[执行 markroot & scan]
D --> E[恢复 _Prunning]
E --> F[resume scheduler loop]
第三章:MOS调度关键场景建模与反推验证
3.1 长时间阻塞导致M脱离P:trace中P.idle与M.blocked事件链分析
当 Goroutine 因系统调用(如 read、netpoll)陷入不可中断等待时,运行时会将当前 M 与 P 解绑,触发 M.blocked 事件;随后该 P 进入空闲状态,记录 P.idle 事件。
事件链时序特征
M.blocked先于P.idle发生(延迟通常- 同一 M 的
M.blocked与后续M.unblocked构成闭合周期 - 若
M.blocked持续 > 10ms,P 将被窃取给其他 M 复用
trace 事件链示例
M.blocked: m=3, when=124567890123, reason="syscalls"
P.idle: p=2, when=124567890215
此处
when为纳秒级单调时间戳;reason字段标识阻塞根源(如"syscalls"、"gc assist"),是定位阻塞类型的关键依据。
典型阻塞场景对比
| 场景 | 是否触发 M 脱离 P | P.idle 持续时间 | 可恢复性 |
|---|---|---|---|
| 网络 I/O 阻塞 | 是 | ~ms–s 级 | 异步唤醒 |
| 锁竞争(mutex) | 否 | 无 | 自旋/休眠 |
| 垃圾回收辅助 | 否 | 无 | 主动让出 |
graph TD
A[M 执行 syscall] --> B{是否可异步?}
B -->|否| C[M.blocked → P.idle]
B -->|是| D[注册 netpoller → 继续绑定]
C --> E[P 被 steal 或 GC 抢占]
3.2 工作窃取(Work-Stealing)失败场景:runqueue为空但trace显示G持续等待的归因推演
当 runtime.trace 显示 Goroutine(G)长期处于 Gwaiting 状态,而其所属 P 的本地运行队列(_p_.runq)与全局队列(sched.runq)均为空时,需排查窃取窗口关闭与自旋竞争失效的耦合故障。
数据同步机制
P 在尝试窃取前会检查 atomic.Load(&sched.nmspinning) —— 若为 0,则跳过窃取直接挂起;此时即使其他 P 有任务,本 P 也因未置位 nmspinning 而无法进入窃取循环。
// src/runtime/proc.go:4921
if atomic.Load(&sched.nmspinning) == 0 &&
atomic.Cas(&sched.nmspinning, 0, 1) {
// 进入自旋窃取模式
}
逻辑分析:
Cas失败即表示已有其他 M 抢占了自旋权,本 M 将放弃窃取直接stopm()。参数nmspinning是全局计数器,非原子累加而是 CAS 控制唯一性,易在高并发下形成“饥饿漏斗”。
关键状态表
| 状态变量 | 合法值 | 含义 |
|---|---|---|
_p_.runqhead |
== runqtail |
本地队列空 |
sched.runqsize |
== 0 | 全局队列空 |
sched.nmspinning |
== 0 | 无 M 正在自旋窃取 → 根本原因 |
故障传播路径
graph TD
A[G 阻塞于 channel recv] --> B{P.runq 为空?}
B -->|是| C[尝试 work-stealing]
C --> D{sched.nmspinning == 0?}
D -->|是| E[跳过窃取 → parkm]
D -->|否| F[成功窃取并唤醒]
E --> G[trace 显示 Gwaiting 持续]
3.3 全局队列溢出与批量迁移:trace中gqueue.growth与schedule.trace事件关联解读
当全局任务队列(gqueue)容量触达阈值,运行时触发 gqueue.growth 事件并启动批量迁移——将待调度的 goroutine 批量转移至 P 的本地队列,以缓解中心化调度压力。
数据同步机制
schedule.trace 事件在迁移完成后发出,携带关键字段:
p_id: 目标 P 编号batch_size: 迁移 goroutine 数量overflow: 溢出前队列长度
// runtime/trace.go 片段(简化)
traceGQueueGrowth(uint32(len(gqueue)), uint32(cap(gqueue)))
// → 触发 gqueue.growth 事件,记录扩容前 size/cap
该调用在 gqueue.push() 检测到 len >= cap*0.9 时执行,参数为当前长度与容量,用于定位高水位点。
关联性验证
| 事件类型 | 触发条件 | 关联字段 |
|---|---|---|
gqueue.growth |
队列使用率 ≥ 90% | old_cap, new_cap |
schedule.trace |
批量迁移完成 | p_id, batch_size |
graph TD
A[gqueue.push] -->|len ≥ 0.9*cap| B[gqueue.growth]
B --> C[select N goroutines]
C --> D[schedule.trace]
第四章:基于trace的典型考题还原与标准答案生成
4.1 题目一:「高并发HTTP服务中goroutine泄漏」的trace特征提取与调度器归因
关键trace信号识别
在 go tool trace 中,goroutine泄漏表现为持续增长的 Goroutines 曲线,且大量 goroutine 停留在 GC sweep wait 或 chan receive 状态超时(>5s)。
典型泄漏模式代码
func handleRequest(w http.ResponseWriter, r *http.Request) {
ch := make(chan string, 1)
go func() { defer close(ch); ch <- fetchFromDB(r.Context()) }() // ❌ 无超时控制
select {
case data := <-ch: w.Write([]byte(data))
case <-time.After(3 * time.Second): w.WriteHeader(http.StatusGatewayTimeout)
}
// ch 未被消费完,goroutine 永久阻塞于 close(ch) 后的 runtime.gopark
}
逻辑分析:
defer close(ch)在匿名 goroutine 执行完毕后触发,但若主协程已退出且ch无接收者,该 goroutine 将卡在runtime.closechan的锁等待;time.After不影响子 goroutine 生命周期,导致泄漏。参数ch容量为1,但fetchFromDB若 panic 或阻塞,close(ch)永不执行。
调度器归因路径
| 状态码 | 占比 | 关联调度事件 |
|---|---|---|
Gwaiting |
68% | block on chan recv |
Grunnable |
22% | ready but never scheduled |
graph TD
A[HTTP handler] --> B[spawn goroutine]
B --> C{fetchFromDB returns?}
C -- yes --> D[close chan]
C -- no --> E[block at closechan → Gwaiting]
E --> F[Scheduler skips G due to no runnable M]
4.2 题目二:「定时器大量触发导致CPU飙升」在trace中识别timerproc争抢与netpoll延迟
trace关键观测点
runtime.timerproc在 pprof CPU profile 中高频出现,且常与runtime.netpoll堆栈交织go tool trace中TimerGoroutine持续运行、NetPoll事件延迟 >100μs 是典型信号
timerproc 争抢的典型堆栈
// go tool trace -http=:8080 trace.out → View Trace → Goroutines → Filter "timer"
runtime.timerproc
runtime.adjusttimers
runtime.doaddtimer
runtime.addtimerLocked // 竞争热点:全局 timers heap 锁
addtimerLocked需持timersLock全局互斥锁;高并发time.AfterFunc/time.NewTicker导致 goroutine 阻塞排队,引发 timerproc 持续唤醒与自旋。
netpoll 延迟关联表
| 指标 | 正常值 | 异常表现 | 根因线索 |
|---|---|---|---|
netpoll delay avg |
> 200μs | timerproc 占用 P,抢占 netpoll 执行时机 | |
timerproc run count |
~100/s | > 5000/s | 定时器泄漏或误用短周期 ticker |
关键诊断流程
graph TD
A[trace.out] --> B{TimerGoroutine 活跃度}
B -->|过高| C[检查 addtimerLocked 锁等待]
B -->|伴随 NetPoll 延迟| D[确认 P 被 timerproc 长期占用]
C & D --> E[定位高频创建定时器的业务代码]
4.3 题目三:「channel操作卡死」对应trace中block、unblock与select.go调度点交叉验证
当 goroutine 在 chan receive 或 chan send 上阻塞时,运行时会调用 gopark 并标记状态为 Gwaiting,同时在 trace 中记录 block 事件;待另一端就绪后触发 goready,生成 unblock 事件。关键交叉点位于 runtime.selectgo——它统一处理多路 channel 操作,其内部 block/unblock 调度逻辑与 select.go 中的 selpc(选中的 case PC)强绑定。
数据同步机制
以下为典型阻塞场景的 trace 关键字段映射:
| trace event | 对应 runtime 函数 | 调度点位置 |
|---|---|---|
GoBlock |
park_m → gopark |
chanrecv/chansend |
GoUnblock |
ready → goready |
chansend/chanrecv |
Select |
selectgo 循环末尾 |
select.go:421 |
核心调度路径
// runtime/select.go#L421(简化)
func selectgo(cas0 *scase, order0 *uint16, ncase int) (int, bool) {
// ... 尝试非阻塞 case
for i := 0; i < ncase; i++ {
cas := &cas0[order0[i]]
if cas.kind == caseRecv && chantryrecv(cas.chan, cas.elem) {
return int(order0[i]), true // 快速路径,无 block
}
}
// 进入 park:此处触发 trace.GoBlock
gopark(nil, nil, waitReasonSelect, traceEvGoBlockSelect, 1)
}
该代码块表明:仅当所有 case 均不可就绪时,selectgo 才调用 gopark,此时 trace 中 GoBlock 与 GoUnblock 必须成对出现,且时间戳严格嵌套于同一 Select 事件区间内。
graph TD A[selectgo entry] –> B{any case ready?} B –>|Yes| C[return immediately] B –>|No| D[gopark → GoBlock trace] D –> E[wait on sudog queue] E –> F[sender/receiver wakes up] F –> G[goready → GoUnblock trace]
4.4 综合题:融合GC、sysmon、netpoll的多线程竞争trace图谱解构与标准答案反向组装
数据同步机制
当 runtime 启动时,sysmon 线程周期性采集 netpoll 就绪事件与 GC 栈快照,通过 traceEvent 注入统一 trace buffer。关键在于时间戳对齐与 goroutine ID 关联。
核心 trace 事件流
// runtime/trace.go 中的典型注入点(简化)
traceGCStart() // GC mark start → 触发 STW 标记
traceNetpollWait(0x1a) // netpoller 等待 fd=26,ts=12489321 ns
traceGoSched() // G127 主动让出,因 netpoll 被抢占
逻辑分析:traceNetpollWait 的 fd 参数标识就绪文件描述符;ts 为单调递增纳秒时间戳,用于后续与 GC mark termination 阶段对齐;traceGoSched 的隐含上下文表明该 goroutine 正在等待 netpoll 唤醒,但被 GC stop-the-world 中断。
事件关联维度表
| 维度 | GC Phase | sysmon Tick | netpoll State |
|---|---|---|---|
| 时间精度 | µs 级标记点 | ms 级轮询 | ns 级就绪事件 |
| 关键 ID | gcCycleID | mID | goroutine ID |
反向组装流程
graph TD
A[原始 trace buffer] --> B{按 ts 排序}
B --> C[提取 GC-start / GC-end]
C --> D[定位其间所有 netpoll-wait + go-sched]
D --> E[构建 goroutine 生命周期图谱]
第五章:附录:go tool trace实战速查表与期末冲刺建议
常用 trace 启动命令速查
启动带 trace 的 HTTP 服务(生产环境慎用,仅限调试):
go run -gcflags="-l" -trace=trace.out main.go
# 或对已编译二进制注入 trace(推荐)
GOTRACEBACK=all ./myserver -trace=trace.out 2>/dev/null &
采集 10 秒运行时 trace(配合 pprof 采样更精准):
go tool trace -http=localhost:8080 trace.out
关键 trace 视图功能对照表
| 视图名称 | 快捷键 | 核心诊断场景 | 注意事项 |
|---|---|---|---|
| Goroutine view | g |
定位阻塞 goroutine、长生命周期协程 | 需结合 Start/Stop 时间轴交叉验证 |
| Network blocking | n |
发现 net/http.ServeHTTP 中的 Accept 阻塞 | 通常伴随 runtime.netpollblock 调用栈 |
| Scheduler latency | s |
识别 P 抢占延迟、G 等待 M 超过 10ms | 延迟 >20ms 表明调度器压力显著上升 |
| GC events | v |
查看 STW 时间、GC 周期频率与堆增长曲线 | 若 GC pause 占比 >5%,需检查内存泄漏 |
典型性能问题 trace 模式识别
- 高频 GC 导致吞吐骤降:在
View trace中观察到密集的紫色 GC 标记条(GC sweep+GC mark),同时Heap profile显示runtime.mallocgc占比超 35%;此时应立即导出go tool pprof -alloc_space分析分配热点。 - 锁竞争瓶颈:
Goroutine视图中多个 G 在同一sync.Mutex.Lock处长时间等待(>50ms),且Sync blocking子视图显示runtime.semacquire调用栈集中于某结构体方法——需改用sync.RWMutex或分片锁。
期末冲刺实操清单
- ✅ 每日固定时段执行
go tool trace -http=:8081 trace_$(date +%s).out采集线上灰度节点 trace(启用-trace编译标志并配置 SIGUSR2 动态触发) - ✅ 使用
go tool trace -pprof=goroutine trace.out > goroutines.pb.gz生成可导入 pprof 的 goroutine 快照 - ✅ 对比两次 trace:用
go tool trace -diff trace_v1.out trace_v2.out输出差异报告,聚焦Scheduler latency和Goroutine creation变化量 - ✅ 将
trace.out文件压缩为trace_20240615_prod.tar.gz并上传至内部 trace 归档系统(路径/traces/prod/20240615/)
Mermaid 性能排查流程图
flowchart TD
A[收到 CPU 使用率突增告警] --> B{是否已采集 trace?}
B -->|否| C[立即执行 go run -trace=trace_alert.out main.go]
B -->|是| D[go tool trace -http=:8080 trace_alert.out]
C --> D
D --> E[打开 Goroutine view 查看高亮阻塞项]
E --> F{是否存在 >100ms 的 runtime.chansend/recv?}
F -->|是| G[检查 channel 缓冲区大小与消费者速率]
F -->|否| H[切换至 Scheduler view 查看 P idle 时间]
G --> I[调整 buffer size 或引入 worker pool]
H --> J[确认 GOMAXPROCS 设置与 CPU 核数匹配]
trace 文件体积控制技巧
默认 trace 会记录所有事件,导致文件爆炸性增长。生产环境必须添加过滤:
# 仅记录关键事件,体积减少 70%
go run -gcflags="-l" -trace=trace.out -trace-alloc=1M -trace-gc=on main.go
# 或通过环境变量控制
GOTRACE=1,GOTRACEALLOCSAMPLE=1048576,GOTRACEGC=1 ./myserver
线上 trace 安全规范
- trace 文件含完整调用栈与部分内存地址,严禁上传至公网 GitHub/GitLab;
- 所有 trace 文件须经
sha256sum trace.out | cut -d' ' -f1生成校验码,并与归档元数据绑定; - 自动化脚本需在 trace 采集后 5 分钟内执行
rm -f trace.out && gzip trace_*.out,防止磁盘写满。
