问题排查和避坑文档整理

发布时需要监控指标

一、内存过高问题 - 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
// 不推荐:使用值传递,会导致整个User结构体被复制
func ProcessUser(u User) error {
// 处理用户逻辑
u.Name = "Updated"
return nil
}

// 推荐:使用指针传递,只传递8字节指针
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 {
// 每次调用都会复制整个User结构体
processUser(user)
}
}

func goodExample(users []*User) {
// 使用指针切片
for _, user := range users {
// 只传递8字节指针
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
// 场景1:大数组切片复制
func processData(data []byte) []byte {
// 创建新的切片,底层数组被复制
result := make([]byte, len(data))
copy(result, data)
return result // 内存翻倍
}

// 场景2:map中存储大对象
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
}

// 场景3:goroutine闭包捕获大变量
func badClosure() {
bigData := make([]byte, 10*1024*1024) // 10MB
for i := 0; i < 10; i++ {
go func() {
// 每个goroutine都会持有bigData引用
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) // 每个item都打日志
// 如果有10000个item,会产生大量IO和CPU开销
doSomething(item)
}
}

// 推荐:使用采样日志或条件日志
func processItems(items []Item) {
for i, item := range items {
// 每100个item记录一次
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 {
// 只在Debug级别记录详细信息
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
// 使用 Prometheus + Grafana 监控
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
# CPU使用率 Top 10进程
ps aux | head -1; ps aux | sort -k3 -nr | head -10

# 内存使用率 Top 10进程
ps aux | head -1; ps aux | sort -k4 -nr | head -10

# Go程序pprof分析
curl http://localhost:6060/debug/pprof/heap > heap.prof
go tool pprof heap.prof

# 查看goroutine堆栈
curl http://localhost:6060/debug/pprof/goroutine?debug=2