Go 应用的性能问题,往往不会只停留在某一个层面:有时是某个函数吃满 CPU,有时是请求延迟升高,有时又是微服务链路里某一跳突然变慢。在 CentOS 环境里,想把问题查清楚,通常要把代码剖析、指标监控、链路追踪和系统资源观测配合起来看。
这篇文章按“先看什么、再补什么”的思路,把几种常见工具串成一套完整方案。看完之后,你可以判断:单机热点该用什么查、在线指标该怎么接、基础运行时信息能否用标准库拿到,以及分布式场景下什么时候该上全链路追踪。
用 pprof 先定位代码热点
pprof 是 Go 内置的性能分析工具,适合先做“代码层”定位。它覆盖的维度很全,包括 CPU、内存、Goroutine、阻塞操作(Block)和互斥锁(Mutex),通常是 Go 性能调优的第一站。

如何在程序里开启 pprof
接入方式很轻,只需要导入 net/http/pprof,再单独启动一个 HTTP 服务即可,常见做法是监听 localhost:6060:
import (
"log"
"net/http"
_ "net/http/pprof" // 自动注册pprof处理器
)
func main() {
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil)) // 后台运行pprof服务
}()
// 你的应用逻辑
}
浏览器和命令行怎么用
服务启动后,浏览器直接访问 http://localhost:6060/debug/pprof/,就能看到可用端点,例如 profile(CPU)、heap(内存)、goroutine(协程)。如果是正式分析,命令行通常更直接。
例如,收集 30 秒 CPU 数据:
go tool pprof http://localhost:6060/debug/pprof/profileseconds=30
如果要生成内存分配的可视化页面,需要先安装 graphviz,然后执行:
go tool pprof -http=:8080 http://localhost:6060/debug/pprof/heap
pprof 的价值在于,它能把函数调用链和资源消耗热点直接摊开来看。CPU 高耗时函数、可疑的大对象分配、潜在内存泄漏位置,通常都能在这里先缩小范围。
用 Prometheus + Grafana 做持续监控
如果说 pprof 更适合“出问题时深入剖析”,那么 Prometheus + Grafana 更适合“平时一直盯着看”。这组组合主要解决实时指标采集和可视化问题,适合观察请求量、延迟、错误率以及资源使用趋势。

CentOS 上安装 Prometheus 和 Grafana
先安装并启动 Prometheus。文中的版本是 v2.36.1,默认监听 9090 端口:
wget https://github.com/prometheus/prometheus/releases/download/v2.36.1/prometheus-2.36.1.linux-amd64.tar.gz
tar xvfz prometheus-2.36.1.linux-amd64.tar.gz
cd prometheus-2.36.1.linux-amd64
./prometheus --config.file=prometheus.yml # 默认监听9090端口
Grafana 则可以直接通过 YUM 安装:
sudo yum install -y grafana
sudo systemctl start grafana-server
sudo systemctl enable grafana-server
配置 Prometheus 抓取 Go 应用指标
Prometheus 需要知道去哪里抓取数据。编辑 prometheus.yml,把 Go 应用暴露出来的 /metrics 接口加入抓取目标。下面这个示例假设应用监听在 8080:
scrape_configs:
- job_name: 'go_app'
static_configs:
- targets: ['localhost:8080']
Go 应用如何暴露自定义指标
在应用侧,常见做法是接入 prometheus/client_golang。这样不仅能输出基础指标,还能把业务相关信息做成计数器和延迟直方图,便于后续在 Grafana 里画图和告警。
import (
"net/http"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
httpRequestsTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests",
},
[]string{"method", "path"}, // 标签:HTTP方法、路径
)
requestDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "Duration of HTTP requests in seconds",
Buckets: prometheus.DefBuckets, // 默认桶(0.005s、0.01s、0.025s等)
},
[]string{"method", "path"},
)
)
func init() {
prometheus.MustRegister(httpRequestsTotal)
prometheus.MustRegister(requestDuration)
}
func middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
duration := time.Since(start).Seconds()
// 记录指标
httpRequestsTotal.WithLabelValues(r.Method, r.URL.Path).Inc()
requestDuration.WithLabelValues(r.Method, r.URL.Path).Observe(duration)
})
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, World"))
})
// 使用中间件包装路由
wrappedMux := middleware(mux)
// 暴露Prometheus指标接口
http.Handle("/metrics", promhttp.Handler())
// 启动服务
go func() {
log.Println(http.ListenAndServe("localhost:8080", wrappedMux))
}()
// 你的应用逻辑
}
完成之后,登录 Grafana:http://localhost:3000,默认账号密码是 admin/admin。把 Prometheus 添加为数据源,再导入一个 Go 监控仪表板,例如 ID 为 2583 的面板,就可以直接看到请求量、延迟、错误率等指标变化。
不想引入第三方库时,可以用 expvar
如果场景比较轻量,或者你只是想快速暴露一些基础运行时信息,那么标准库里的 expvar 是一条更省事的路。它不依赖额外组件,适合快速查看进程当前状态。
expvar 能暴露什么,怎么接入
expvar 可以输出内存使用、GC 次数、协程数量等运行时指标,也允许你补充简单的自定义计数器。下面是文中的接入示例:
import (
"expvar"
"net/http"
)
var (
numRequests = expvar.NewInt("num_requests") // 自定义计数器
)
func main() {
http.Handle("/metrics", expvar.Handler()) // 暴露指标接口
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
numRequests.Add(1) // 记录请求数
w.Write([]byte("Hello, expvar"))
})
log.Println(http.ListenAndServe("localhost:8080", nil))
}
适合哪些场景
运行后,访问 http://localhost:8080/debug/vars,就能直接看到 JSON 格式的指标结果。对于排查轻量服务、临时观察运行时状态,或者对接 Zabbix、Munin 这类传统监控系统,expvar 都很实用。
需要注意的是,它更偏“快速暴露”和“基础可见性”,不等同于 Prometheus 那种完整的指标体系。如果你需要长期趋势、灵活查询和告警能力,还是应该把它当作补充,而不是替代。
微服务场景下补上 OpenTelemetry 链路追踪
当系统从单体应用发展到多服务调用时,只看单机指标往往不够。请求到底卡在入口、数据库、RPC 调用,还是某个下游服务上,必须靠链路追踪来回答。这里更适合接入 OpenTelemetry。
先安装基础依赖
go get go.opentelemetry.io/otel
go get go.opentelemetry.io/otel/trace
go get go.opentelemetry.io/otel/sdk
在代码里初始化 Tracer
import (
"context"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
"net/http"
)
func main() {
tracer := otel.Tracer("go-app") // 创建Tracer
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
ctx, span := tracer.Start(r.Context(), "handle_request") // 开始Span
defer span.End() // 结束Span
// 业务逻辑
w.Write([]byte("Hello, OpenTelemetry"))
})
log.Println(http.ListenAndServe("localhost:8080", nil))
}
接入后,后续可以继续配置 OpenTelemetry Collector,把 Span 数据转发到 Jaeger、Zipkin 等追踪系统。这样一来,请求经过哪些微服务节点、每一跳花了多少时间,都会以链路形式展示出来,更适合排查分布式系统中的性能瓶颈。
别忽略 CentOS 系统级工具
应用层数据再完整,也无法完全代替系统层观测。很多看起来像“Go 程序变慢”的问题,最后可能是 CPU 打满、磁盘 IO 抖动,或者网络连接异常。这个时候,CentOS 自带或常用的系统命令就很关键。
几条常用命令够你先判断问题在哪一层
top/htop:实时查看进程 CPU、内存占用。htop需要先安装:sudo yum install -y htop。vmstat:查看系统整体 CPU、内存、IO 状态。
vmstat 1 5 # 每1秒刷新一次,共5次
iostat:查看磁盘 IO 性能,通常需要先安装sysstat。
iostat -x 1 5
netstat/ss:查看端口占用和网络连接状态。
netstat -tulnp | grep go_app
这些命令的作用很明确:先判断问题是在代码里,还是在资源层。如果系统本身已经接近瓶颈,那么只盯着 Go 代码优化,往往事倍功半。
一套更实用的 Go 监控组合怎么选
如果你的目标是尽快建立一套可落地的监控体系,可以按使用场景来组合:

- 定位函数级热点,用
pprof。 - 做持续指标监控和可视化,用 Prometheus + Grafana。
- 只想快速暴露基础运行时信息,用
expvar。 - 进入微服务和跨服务排障阶段,用 OpenTelemetry。
- 确认是否是资源瓶颈,再配合
top、vmstat、iostat、netstat/ss。
真正有效的做法,通常不是在这些工具里只选一个,而是把它们按层次拼起来:先看系统,再看指标,再钻到代码热点,最后在复杂调用链里补足上下文。这样排查 Go 应用性能问题时,路径会清晰得多。







