异步编程最大的痛点在于:堆栈不连续。当程序
Crash 在回调函数中时,你只能看到
event_base_loop ->
callback,却不知道这个请求是谁发起的、之前经历了什么。
1. 逻辑堆栈 (Logical Stack)
1.1. Request ID
为每个请求生成一个唯一的 req_id (UUID 或自增
ID)。 在所有日志中带上这个 ID。
// 错误示范
log_error("Database query failed");
// 正确示范
log_error("[req_id=%s] Database query failed", ctx->req_id);1.2. 上下文传递
将 req_id 放入
ConnectionContext
结构体,并在所有回调中传递这个结构体指针。
2. 慢回调分析
Event Loop 必须是飞快的。如果某个回调阻塞了 100ms,整个服务的吞吐量就会暴跌。
2.1. 自动检测
我们可以封装一个 Wrapper 回调函数:
void debug_wrapper_cb(evutil_socket_t fd, short events, void *arg) {
long start = get_current_time_ms();
real_callback(fd, events, arg);
long cost = get_current_time_ms() - start;
if (cost > 10) { // 阈值 10ms
log_warn("Slow callback detected! cost=%ldms, fd=%d", cost, fd);
}
}3. Core Dump 分析
当程序 Core Dump 时,GDB 是你的好帮手。但 Libevent
的结构体是不透明的,直接 p *base
看不到东西。
3.1. 编译时保留符号
确保编译 Libevent 时带上了 -g。
3.2. 检查 Pending 事件
我们需要知道死掉的时候,有哪些事件正在等待触发。
(需要查看 Libevent 源码 event-internal.h
了解结构布局)
# GDB 脚本示例
define show_active_events
set $base = (struct event_base*)$arg0
set $i = 0
while $i < $base->nactivequeues
set $q = $base->activequeues[$i]
# 遍历链表...
set $i = $i + 1
end
end
4. 动态追踪 (eBPF)
线上服务不能随便重启加日志。使用 bpftrace
可以无侵入地观测。
4.1. 观测 epoll_wait 延迟
# trace_epoll.bt
tracepoint:syscalls:sys_enter_epoll_wait {
@start[tid] = nsecs;
}
tracepoint:syscalls:sys_exit_epoll_wait /@start[tid]/ {
$lat = nsecs - @start[tid];
if ($lat > 100000000) { // > 100ms
printf("Long epoll_wait: %d ms\n", $lat / 1000000);
}
delete(@start[tid]);
}5. 总结
调试异步程序需要从“堆栈思维”转变为“事件思维”。通过 Request ID 串联日志,配合 eBPF 等现代工具,我们依然可以对异步系统了如指掌。
上一篇: 06-production/observability.md - 可观测性 下一篇: 07-hardening/testing.md - 测试与 QA