有人问我Go怎么上手,干脆写一篇,省得每次都重复回答。

问题背景

用 Go 重写了一个 Python 爬虫服务,内存从 2GB 降到 80MB,QPS 从 500 涨到 8000。关键是协程比线程轻太多了,10万并发也就几百MB内存。

先确认环境没问题

# 安装 Go
wget https://go.dev/dl/go1.22.5.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.22.5.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin
go version

排查过程

按这个顺序排查,大部分问题都能定位:

  1. 第1步:go mod tidy 经常跑一下,清理没用的依赖
  2. 第2步:err != nil 一定要处理,别用 _ 忽略,出了 bug 找不到
  3. 第3步:sync.Pool 复用对象,GC 压力能小不少
  4. 第4步:pprof 是性能调优神器:import _ "net/http/pprof"

修复后的代码

// HTTP 服务 + 优雅关闭
package main

import (
    "context"
    "fmt"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/api/health", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        fmt.Fprintf(w, `{"status":"ok","time":"%s"}`, time.Now().Format(time.RFC3339))
    })

    srv := &http.Server{
        Addr:         ":8080",
        Handler:      mux,
        ReadTimeout:  5 * time.Second,
        WriteTimeout: 10 * time.Second,
        IdleTimeout:  120 * time.Second,
    }

    // 优雅关闭
    go func() {
        if err := srv.ListenAndServe(); err != http.ErrServerClosed {
            log.Fatalf("listen: %s\n", err)
        }
    }()

    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit
    log.Println("正在关闭服务器...")

    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()
    if err := srv.Shutdown(ctx); err != nil {
        log.Fatalf("服务器强制关闭: %v", err)
    }
    log.Println("服务器已优雅关闭")
}

常见问题

goroutine 泄漏怎么查?

runtime.NumGoroutine() 打印数量。如果一直在涨就是泄漏了。用 go tool pprof http://localhost:8080/debug/pprof/goroutine 看goroutine栈。常见原因:channel 没关、context 没 cancel。

Go 的包管理怎么搞?

go mod init 初始化,go get 加依赖,go mod tidy 清理。别再用 GOPATH 模式了,都 2026 了。私有仓库配 GOPRIVATE 环境变量。

并发写 map panic 怎么办?

sync.Map 适合读多写少。通用场景用 sync.RWMutex 保护,或者按 key 分片用多个 map + 各自的锁。

这篇会持续更新,有新的踩坑经验就加上来。