Go 教程:像高手一样 Debug Go 程序

Go

一旦您了解了基础知识,Golang就能使您的生产力比以往任何时候都高。 但是,如果出现问题,您该怎么办?

您可能不知道这一点,Go本身包含pprof ,用于记录和可视化运行时分析数据。 诸如delve之类的第三方工具增加了对逐行调试的支持。 泄漏检测器和种族检测器可以防御不确定性行为。

如果您以前从未看过或使用过这些工具,它们将迅速成为您Golang工具库的强大补充。


为什么我不打印所有内容呢?

我遇到了很多开发人员,他们在遇到代码问题时很少打开调试器。 我认为这没错。 如果您正在编写单元测试,编写代码并进行重构,那么在大多数情况下,快捷的方法都可以使用。

相反,我一直在排查故障问题,因此意识到在某些断点处打开一个交互式调试器比连续添加断点和打印语句更快

Go

图的示例显示了Kent Gruber修复的内存泄漏

例如,有一天,我正在查看自己帮助维护的Web应用程序的内存图。每天总内存使用量缓慢增加到需要重新启动服务器以保持稳定的程度。这是内存泄漏的经典示例。

快速而便捷的方法是建议我们通读代码以确保生成 goroutines exit, 分配的变量被垃圾回收,连接正确关闭等。相反,我们对应用程序进行了配置几分钟后发现内存泄漏。是一个复杂化的单个语句导致了此类错误的情况。

在此我将向您介绍我几乎每天都使用的一些工具来解决此类问题。


分析记录和可视化

首先,让我们以关闭正常的基本Golang Web服务器为例,发送一些人工流量。然后,我们将使用pprof工具收集尽可能多的信息。

// main.go
package main

import (
    "fmt"
    "log"
    "net/http"
    "time"
)
func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello World!\n")
}

func main() {
    srv := http.Server{
        Addr:         ":8080",
        ReadTimeout:  time.Minute,
        WriteTimeout: time.Minute,
    }
    http.HandleFunc("/", handler)
    done := make(chan os.Signal, 1)
    signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)

    go func() {
        srv.ListenAndServe()
    }()
    <-done
    err := srv.Shutdown(context.Background())
    if err != nil {
        log.Fatal(err)
    }
}

我们可以通过执行以下操作来确保此功能有效:

$ go run main.go &
$ curl localhost:8080
Hello World!

现在,我们通过以下代码段来分析CPU:

...
f, err := os.Create("cpu.prof")
if err != nil {
    log.Fatal(err)
}
err = pprof.StartCPUProfile(f)
if err != nil {
    log.Fatal(err)
}
defer pprof.StopCPUProfile()
...

我们将使用负载测试工具测试网络服务器,以模拟正常到繁忙的流量。我是拥测试工具vegeta来完成此任务:

$ echo "GET http://localhost:8080" | vegeta attack -duration=5s
Hello world!
...

关闭Go Web服务器时,我们会看到一个文件cpu.prof,其中包含CPU配置文件。然后可以使用pprof工具显示此配置文件:

$ go tool pprof cpu.prof
Type: cpu
Time: Jan 16, 2020 at 4:51pm (EST)
Duration: 9.43s, Total samples = 50ms ( 0.53%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top 10
Showing nodes accounting for 50ms, 100% of 50ms total
Showing top 10 nodes out of 24
      flat  flat%   sum%        cum   cum%
      20ms 40.00% 40.00%       20ms 40.00%  syscall.syscall
...

为此,当您遇到一个需要花费很长时间才能解决的问题时,它会非常有效。

使用该工具非常容易,只需按照安装步骤。添加runtime.Breakpoint()语句并使用dlv运行代码:

$ dlv debug main.go
Type 'help' for list of commands.
(dlv) continue

到达断点后,您将看到代码块,例如在上面的Web服务器中,我在处理程序中放置了it

> main.handler() ./main.go:20 (PC: 0x1495476)
    15:         _ "net/http/pprof"
    16: )
    17:
    18: func handler(w http.ResponseWriter, r *http.Request) {
    19:         runtime.Breakpoint()
=>  20:         fmt.Fprintf(w, "Hello World!\n")
    21: }
    22:
    23: func main() {
    24:         srv := http.Server{
    25:                 Addr:         ":8080",
(dlv)

现在您可以使用nextn命令逐行进行操作,或者使用steps命令更深入地研究函数。

Go

带有Golang扩展名的VS Code示例,显示调试测试按钮

如果您喜欢使用漂亮的UI,并单击按钮而不是使用键盘,则VS Code非常有用。使用本机测试库编写单元测试时,您会看到一个调试测试的按钮,它将初始化delve并允许您在交互式会话中通过VS Code逐步遍历代码。

有关使用VS Code调试Go代码的更多信息,请查看其上的Microsoft Wiki

Delve可添加断点,测试断点使深入研究软件包变得轻而易举。下次您遇到问题并想更多地了解正在发生的事情时,不要害怕使用它。


泄漏和竞争检测器

我要讲的最后一个主题是如何在测试中添加Golang泄漏检测和竞争检测器。如果您没有遇到比赛状况或Goroutine内存泄漏,那么你很幸运。

在2017年,Uber开源了goleak软件包,这是一个简单的工具,可以检查是否通过标记了给定的TestingTgoroutines 将给定标记为失败Find。

看起来像这样:

func TestA(t *testing.T) {
   defer goleak.VerifyNone(t)
   // 在这里测试逻辑。
}

在进行复杂的异步工作时,可以确保既避免回归,又遵循 The Go of Zen 的第五条原则:

启动goroutine之前,要知道它什么时候会停止。

最后,确保没有Goleaks后,您需要针对比赛条件进行保护。幸运的是,数据竞赛检测器是内置的。考虑竞争检测器文档中的示例:

func main() {
    c := make(chan bool)
    m := make(map[string]string)
    go func() {
        m["1"] = "a" // 首次冲突的访问。
        c <- true
    }()
    m["2"] = "b" //第二个冲突的访问。
    <-c
    for k, v := range m {
        fmt.Println(k, v)
    }
}

这是一种数据争用,可能导致崩溃和内存损坏。使用-race标志运行此代码片段会导致死机,并显示一条有用的错误消息:

go run -race main.go 
==================
警告:数据争用
Write at 0x00c0000e2210 by goroutine 8:
  runtime.mapassign_faststr()
      /usr/local/Cellar/go/1.13.6/libexec/src/runtime/map_faststr.go:202 +0x0
  main.main.func1()
      /PATH/main.go:19 +0x5dPrevious write at 0x00c0000e2210 by main goroutine:
  runtime.mapassign_faststr()
      /usr/local/Cellar/go/1.13.6/libexec/src/runtime/map_faststr.go:202 +0x0
  main.main()
      /PATH/main.go:22 +0xc6Goroutine 8 (running) created at:
  main.main()
      /PATH/main.go:18 +0x97
==================
2 b
1 a
Found 1 data race(s)

虽然您可以在代码执行期间使用该标志,但在编写测试时向go test命令中添加最有帮助的方法是在编写测试时检测竞赛。


结论

这些只是Golang生态系统中可用的一些出色工具,有助于观察,调试和防止代码库中的生产故障。如果您想走的更远,建议您看一下:

有关上面列出的任何工具的更多信息,请参见参考资料部分,以获取完整的文档和手册。

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://medium.com/better-programming/de...

译文地址:https://learnku.com/go/t/46717

本文为协同翻译文章,如您发现瑕疵请点击「改进」按钮提交优化建议
讨论数量: 1

这个文章有个翻译的地方是需要超链接,但是翻译过后网址不能完全包在标签内,这种情况一般如何处理呢

3年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!