← 中文资源

简体中文版本

持续性能剖析

持续剖析在生产代码上常年低开销地采样 CPU、内存与锁行为——把剖析与部署、流量关联——抓住任何基准都复现不了的回归。

阅读英文原文

概念定义

事故时才挂的剖析器只看得见事故。持续剖析保有对一切的低分辨率记录,“这个分配点何时出现、当时改了什么”从此有答案。

工作机制

采样剖析器按固定间隔中断线程记录栈快照;eBPF 变体无需应用插桩即可抓内核与用户栈。样本聚合成按服务按版本带时间窗的剖析;跨部署 diff 剖析浮出新热点,分配剖析在 OOM 之前给出泄漏候选。

使用场景

反复出现性能谜团的生产服务、部署频繁到“哪个变更干的”成为日常之问的舰队、以及任何被期望在生产直接排障而非本地复现的平台。

常见误区

剖析不只在事故响应用武:连续剖析的价值恰恰在事故之前——以部署间剖析 diff 捕回归。统计采样在百赫兹级的开销只有个位数百分比 CPU,会失真的是插桩式剖析器。剖析也不能替代 trace:trace 把延迟归因到跨服务,剖析把消耗归因到进程内——生产排障两者都要。剖析基数与指标同构:按实例打标签会爆存储,聚到有用维度并限制原始样本;语言运行时不同开销预算也不同,先在最慢路径验证再全网铺开。

代码示例

package main

import (
	"fmt"
	"log"
	"net/http"
	"net/http/pprof"
	"runtime"
	"time"
)

func busyWork() {
	total := 0
	for i := 0; i < 2_000_000; i++ {
		total += i % 7
	}
	_ = total
}

func setupProfiler(addr string) *http.Server {
	mux := http.NewServeMux()
	mux.HandleFunc("/debug/pprof/", pprof.Index)
	mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
	mux.HandleFunc("/debug/pprof/heap", pprof.Handler("heap").ServeHTTP)
	mux.HandleFunc("/debug/pprof/goroutine", pprof.Handler("goroutine").ServeHTTP)

	server := &http.Server{Addr: addr, Handler: mux}
	go func() {
		log.Printf("pprof listening on %s", addr)
		if err := server.ListenAndServe(); err != http.ErrServerClosed {
			log.Printf("pprof server error: %v", err)
		}
	}()
	return server
}

func main() {
	prof := setupProfiler("127.0.0.1:6060") // 仅内网可达,不暴露公网
	defer prof.Close()

	runtime.SetMutexProfileFraction(5)
	runtime.SetBlockProfileRate(200)

	for i := 0; i < 3; i++ {
		busyWork()
		time.Sleep(100 * time.Millisecond)
	}

	// 使用方式:
	// go tool pprof http://127.0.0.1:6060/debug/pprof/profile?seconds=10
	// go tool pprof http://127.0.0.1:6060/debug/pprof/heap
	fmt.Println("profile samples collected")
}