土法炼钢兴趣小组的算法知识备份

异步调试与追踪

目录

异步编程最大的痛点在于:堆栈不连续。当程序 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

返回 Libevent 专题索引


By .