一些面试问题的深入与思考
Bug排查
原问题:多个服务的bug你是怎么排查的。如果是内存泄漏这种情况看日志看不了怎么办。
题解:内存泄漏的问题往往不会直接从日志中体现,需要用更多手段来定位解决。如下:
1、使用 Go 自带的性能分析工具
(1) pprof 工具(性能剖析)
pprof 是 Go 内置的性能分析工具,能分析内存、CPU 和 goroutine 使用等。如:heap:内存分配情况;goroutine:当前 goroutine 使用情况;cpu:CPU 使用情况
启动服务后,访问 http://localhost:6060/debug/pprof/ 即可查看不同类型的剖析数据,
import (_ "net/http/pprof" // 引入 pprof 包"net/http""log"
)func main() {// 在 6060 端口暴露 pprof 信息go func() {log.Println(http.ListenAndServe("localhost:6060", nil))}()// 其他业务逻辑代码
}
获取内存信息:用以下命令收集内存堆信息:这将打开一个交互式界面,显示内存使用情况。可以用 top 命令查看哪些函数占用了最多内存,或者用 list <func_name> 查看某个函数的详细内存分配情况。
go tool pprof http://localhost:6060/debug/pprof/heap
分析 goroutine 堆栈:如果怀疑程序中存在 goroutine 泄漏,可以用以下命令查看 goroutine 的堆栈:查看是否有不必要的 goroutine 一直存在,未能及时退出,导致内存泄漏。
go tool pprof http://localhost:6060/debug/pprof/goroutine
(2) Go Trace(跟踪)
go tool trace 提供了更深入的跟踪功能,帮助观察程序的调度情况、goroutine 的执行轨迹等。识别是否有 goroutine 持续占用内存,或者是否存在调度瓶颈等问题。
启用 pprof 后,访问 http://localhost:6060/debug/pprof/trace 来获取追踪数据。
go tool trace http://localhost:6060/debug/pprof/trace?seconds=30 # 收集 30 秒的追踪数据
2. 监控和日志结合
对于内存泄漏问题,日志本身可能不提供明确的异常信息,但可结合内存监控和日志来看。
(1) 使用 Prometheus + Grafana 监控内存使用情况
集成 Prometheus 客户端,定期暴露内存使用情况,用 Grafana 可视化展示。实时监控服务的内存消耗变化,及时发现内存泄漏。
暴露内存指标:在 Go 程序中使用 runtime.ReadMemStats 获取内存统计数据,并通过 Prometheus 暴露这些指标:如果内存使用不断增长而没有释放,就可能意味着存在内存泄漏。
import ("github.com/prometheus/client_golang/prometheus""github.com/prometheus/client_golang/prometheus/promhttp""net/http""runtime""log"
)var (memoryStats = prometheus.NewGaugeVec(prometheus.GaugeOpts{Name: "go_memory_stats",Help: "Go memory statistics",},[]string{"stat"},)
)func init() {prometheus.MustRegister(memoryStats)
}func recordMemoryStats() {var m runtime.MemStatsruntime.ReadMemStats(&m)memoryStats.WithLabelValues("heap_alloc").Set(float64(m.HeapAlloc))memoryStats.WithLabelValues("heap_sys").Set(float64(m.HeapSys))memoryStats.WithLabelValues("heap_idle").Set(float64(m.HeapIdle))memoryStats.WithLabelValues("heap_inuse").Set(float64(m.HeapInuse))
}func main() {go func() {for {recordMemoryStats()time.Sleep(10 * time.Second)}}()http.Handle("/metrics", promhttp.Handler())log.Fatal(http.ListenAndServe(":8080", nil))
}
(2) 定期记录内存使用信息
如果不使用 Prometheus,也可以定期记录程序的内存使用信息,并通过日志查看内存变化。例如:通过日志定期记录内存使用情况,可检测内存是否存在异常增长,尤其是在特定的负载下。
import ("log""runtime"
)func logMemoryUsage() {var m runtime.MemStatsruntime.ReadMemStats(&m)log.Printf("Alloc = %v MiB", bToMb(m.Alloc))log.Printf("TotalAlloc = %v MiB", bToMb(m.TotalAlloc))log.Printf("Sys = %v MiB", bToMb(m.Sys))log.Printf("NumGC = %v", m.NumGC)
}func bToMb(b uint64) uint64 {return b / 1024 / 1024
}
3、代码审查和手动检查
检查不必要的引用:确保没有无意中持有不再需要的引用,尤其是大对象(如 map、slice 等)。
检查 goroutine 的生命周期:确保所有启动的 goroutine 在任务完成后能够正确退出,避免因 goroutine 泄漏导致内存问题。
用 defer 确保资源释放:在会发生内存泄漏的地方,确保通过 defer 释放资源(例如关闭文件、数据库连接等)。
4、压力测试和负载测试
通过模拟实际业务场景的高并发或长时间运行,来进行压力测试。可以使用如 wrk、hey 等工具进行负载测试,观察在高负载下服务的内存使用情况,看看是否存在内存泄漏。
压测
1、hey
一个轻量级、易用的 HTTP 性能测试工具,适合对中小规模应用进行负载测试。配置简单,适合快速进行简单的压力测试。不支持脚本。
举例:
hey -n 1000 -c 100 http://example.com
#-n 1000:发送 1000 个请求
#-c 100:使用 100 个并发连接
输出:
Summary:Total: 2.1234 secsSlowest: 0.2890 secsFastest: 0.0123 secsAverage: 0.0456 secsRequests/sec: 470.31Total data: 113.8 MBResponse time histogram:0.012 [1] |0.045 [934] |0.100 [65] |0.150 [0] |
2、wrk
适用于大规模、高并发的性能测试。通过多线程和 Lua 脚本提供更高的灵活性和定制能力,适合需要进行详细性能分析的复杂场景。
压测效果影响因素:1、硬件资源:服务器的CPU、内存和网络带宽;2、并发量;3、请求类型;4、代码质量,数据库性能,缓存机制; 5、网络延迟
举例与输出:
wrk -t12 -c400 -d30s http://example.com
#-t12:使用 12 个线程进行测试
#-c400:使用 400 个连接
#-d30s:进行 30 秒的测试
Running 30s test @ http://example.com12 threads and 400 connectionsThread Stats Avg Stdev Max +/- StdevLatency 31.77ms 22.45ms 98.56ms 84.55%Req/Sec 1405.72 65.85 1552.00 83.43%506837 requests in 30.08s, 63.25MB read
Requests/sec: 16807.51
Transfer/sec: 2.10MB
性能优化方向 思考
1、 缓存
l1、l2、l3 级 cache、浏览器缓存、Redia、
缓存相关的问题:雪崩、穿透、击穿、热点 key监控再分散、缓存淘汰、数据一致性三类方法、
2、 并行化处理
后来的Redis 多线程、主从数据库
3、 批量化处理
AOF 缓冲区、Redis的pipeline 、
4、 数据压缩处理
AOF 文件对同一key的多次写、pb、
5、 无锁化
sync.Map、GM到GMP、mvcc 、消息队列
6、 分片化
Redis集群、
7、 避免请求
bufferpool 、
8、 池化
对象池:sync.pool
协程池:gopool
数据库连接池
9、 异步处理
回调函数实现、bgsave、Redis用unlink 异步删除key、
10、顺序写
MySQL 自增主键、写undolog 和 binlog 日志、
总结:做服务性能优化,要从自身服务架构出发,分析服务调用链耗时分布跟 CPU 消耗,优化有问题的 RPC 调用和函数。
多学中间件,上面几种方法很多在常用中间件中有体现。具体学“如何用;底层优秀的设计思想;理解为什么要这样设计;这种设计有什么好处;”