问题排查和避坑文档整理
发布时需要监控指标
一、内存过高问题 -
Go值传递导致的问题
Go语言默认都是值传递,只有显式使用指针才是引用传递。当传递大结构体或大对象时,值传递会导致内存复制和占用增加。
问题代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| func ProcessUser(u User) error { u.Name = "Updated" return nil }
func ProcessUser(u *User) error { u.Name = "Updated" return nil }
type User struct { ID int Name string Email string Profile []byte Metadata map[string]interface{} Settings []Setting }
func badExample(users []User) { for _, user := range users { processUser(user) } }
func goodExample(users []*User) { for _, user := range users { processUser(user) } }
|
常见内存问题场景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| func processData(data []byte) []byte { result := make([]byte, len(data)) copy(result, data) return result }
var cache = make(map[string]User) func addToCache(key string, u User) { cache[key] = u }
var cache = make(map[string]*User) func addToCache(key string, u *User) { cache[key] = u }
func badClosure() { bigData := make([]byte, 10*1024*1024) for i := 0; i < 10; i++ { go func() { process(bigData) }() } }
|
二、CPU过高问题 - 过度打日志
问题代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| func processItems(items []Item) { for _, item := range items { log.Printf("Processing item: %+v", item) doSomething(item) } }
func processItems(items []Item) { for i, item := range items { if i%100 == 0 { log.Printf("Processing item %d/%d", i, len(items)) } doSomething(item) } }
import "go.uber.org/zap"
func processItems(items []Item, logger *zap.Logger) { for _, item := range items { logger.Debug("Processing item", zap.Any("item", item)) doSomething(item) } logger.Info("Processed items", zap.Int("total", len(items))) }
|
日志性能对比
1 2 3 4 5 6 7 8 9 10
| log.Printf("User %s performed action %s at time %v with details %+v", user.Name, action, time.Now(), details)
logger.Info("User action", zap.String("user", user.Name), zap.String("action", action), zap.Time("timestamp", time.Now()), )
|
三、监控阈值建议
CPU使用率阈值
| 正常 |
< 60% |
系统运行良好 |
保持监控 |
| 警告 |
60%-75% |
负载较高 |
关注峰值时段,准备扩容 |
| 严重 |
75%-85% |
高负载 |
检查是否有性能问题,考虑扩容 |
| 紧急 |
> 85% |
过载 |
立即排查,可能需要紧急扩容或降级 |
特殊情况: - 短时间峰值(< 5分钟)到 90% 可以容忍
- 持续 > 75% 超过 10分钟需要关注 -
CPU使用率同时伴随高延迟需要立即处理
内存使用率阈值
| 正常 |
< 70% |
内存使用合理 |
保持监控 |
| 警告 |
70%-80% |
内存较高 |
检查是否有内存泄漏 |
| 严重 |
80%-85% |
内存紧张 |
分析内存使用,考虑扩容 |
| 紧急 |
> 85% |
内存不足 |
立即排查,防止OOM |
Go程序特殊考虑: -
Go的GC在内存使用达到一定阈值时触发(默认是上次GC后内存的100%) -
建议预留 20-30% 内存给GC使用 - 内存持续上涨不释放可能是内存泄漏
四、关键监控指标
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 1. CPU使用率 - user time:用户态CPU时间 - system time:内核态CPU时间 - iowait:IO等待时间
2. 内存使用 - RSS(Resident Set Size):实际物理内存占用 - Heap Allocated:堆分配内存 - Heap In-use:正在使用的堆内存 - GC频率和GC耗时
3. Goroutine数量 - 正常:几百到几千 - 警告:> 10000 - 严重:> 50000(可能goroutine泄漏)
4. 关键业务指标 - QPS(Queries Per Second) - P99 延迟 - 错误率 - 请求队列长度
|
五、监控工具推荐
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" )
var ( cpuUsage = promauto.NewGauge(prometheus.GaugeOpts{ Name: "app_cpu_usage_percent", Help: "CPU使用率百分比", }) memoryUsage = promauto.NewGauge(prometheus.GaugeOpts{ Name: "app_memory_usage_bytes", Help: "内存使用字节数", }) goroutineCount = promauto.NewGauge(prometheus.GaugeOpts{ Name: "app_goroutine_count", Help: "Goroutine数量", }) )
|
六、快速排查命令
1 2 3 4 5 6 7 8 9 10 11 12
| ps aux | head -1; ps aux | sort -k3 -nr | head -10
ps aux | head -1; ps aux | sort -k4 -nr | head -10
curl http://localhost:6060/debug/pprof/heap > heap.prof go tool pprof heap.prof
curl http://localhost:6060/debug/pprof/goroutine?debug=2
|