You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

生产环境下如何监控并打印卡顿Go函数的堆栈跟踪?

解决Go函数超时后打印目标goroutine堆栈的方案

针对生产环境中函数低概率卡顿、无法定位冻结位置的问题,你可以通过追踪目标goroutine ID的方式,在超时后精准打印该goroutine的堆栈,而非全量输出所有goroutine堆栈。具体实现步骤如下:

1. 实现获取当前goroutine ID的工具函数

Go标准库未直接提供获取goroutine ID的API,但可通过解析runtime.Stack的输出来提取:

import (
    "bytes"
    "runtime"
    "strconv"
    "strings"
)

func getGoroutineID() int {
    buf := make([]byte, 64)
    buf = buf[:runtime.Stack(buf, false)]
    // 堆栈第一行格式为:goroutine 123 [running]:
    field := strings.Fields(string(buf))
    id, _ := strconv.Atoi(field[1])
    return id
}

2. 修改监控逻辑,追踪目标goroutine

在目标函数中传递自身的goroutine ID给监控goroutine,并通过通道监控函数是否正常完成:

import (
    "strconv"
    "time"
    "runtime"
    "bytes"
)

func funcToWatch() {
    // 用通道标记函数是否执行完成
    done := make(chan struct{})
    defer close(done)

    // 获取当前goroutine的ID
    targetGID := getGoroutineID()

    // 启动监控goroutine
    go monitor(targetGID, done)

    // 执行业务逻辑
    doSomeOtherwork()
}

func monitor(targetGID int, done <-chan struct{}) {
    select {
    case <-done:
        // 函数正常结束,监控直接退出
        return
    case <-time.After(30 * time.Minute):
        // 超时触发,打印目标goroutine堆栈
        printTargetStack(targetGID)
    }
}

func printTargetStack(targetID int) {
    // 分配足够大的缓冲区存储全量goroutine堆栈
    buf := make([]byte, 1024*1024) // 1MB,可根据实际场景调整
    buf = buf[:runtime.Stack(buf, true)]

    // 按goroutine分割堆栈内容
    goroutineBlocks := bytes.Split(buf, []byte("\ngoroutine "))
    for _, block := range goroutineBlocks {
        if len(block) == 0 {
            continue
        }
        // 提取当前块的goroutine ID
        idEnd := bytes.IndexByte(block, ' ')
        if idEnd == -1 {
            continue
        }
        currentID, err := strconv.Atoi(string(block[:idEnd]))
        if err != nil {
            continue
        }
        // 匹配目标ID,打印对应堆栈
        if currentID == targetID {
            println("=== 目标Goroutine", targetID, "堆栈信息 ===")
            println("goroutine " + string(block))
            return
        }
    }
    println("未找到目标Goroutine", targetID, ",可能已退出")
}

关键说明

  • goroutine ID复用问题:监控goroutine会先监听done通道,函数正常完成后监控直接退出,只有超时触发时才会查找目标goroutine,此时目标goroutine仍在运行,ID不会被复用,避免误打印。
  • 缓冲区大小:如果生产环境goroutine数量多,可适当增大buf的大小,确保能容纳全量堆栈内容。
  • 日志输出:生产环境建议将堆栈信息写入日志文件,而非标准输出,方便后续排查。

内容的提问来源于stack exchange,提问作者Syed Momin Naqvi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 19:45:11