一些面试问题的深入与思考
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.MemStats
runtime.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.MemStats
runtime.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 secs
Slowest: 0.2890 secs
Fastest: 0.0123 secs
Average: 0.0456 secs
Requests/sec: 470.31
Total data: 113.8 MB
Response 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.com
12 threads and 400 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 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 调用和函数。
多学中间件,上面几种方法很多在常用中间件中有体现。具体学“如何用;底层优秀的设计思想;理解为什么要这样设计;这种设计有什么好处;”
性能瓶颈测试
原题:性能瓶颈是在磁盘,网络还是哪里,怎么设计测试用例验证
1、确定瓶颈的位置
(1)网络带宽测试:
用工具如 iperf 或 netperf 测 A 到 B 的网络带宽。
在传输过程中监控网络带宽和延迟:用 nload、iftop 或 netstat 来监控网络带宽的使用情况。
(2)磁盘性能测试:
用 fio 或 iostat 测试源服务器 A 和目标服务器 B 的磁盘读写性能。
测试读写吞吐量(MB/s);磁盘 IOPS(每秒输入输出操作数);测试磁盘延迟。
再有,就是测不同磁盘(SSD vs HDD)的性能差异。
(3)CPU 和内存性能:
用 top 或 htop 工具监控 CPU 和内存的利用率,检查是否有高 CPU 使用率或内存瓶颈。
使用 stress 或 sysbench 对系统进行压力测试,观察负载和性能变化。
2、测试用例设计
(1)网络带宽测试用例:
目标:测量网络带宽和延迟
在 A 和 B 上分别运行 iperf,测试不同的带宽条件。
比较局域网(LAN)与广域网(WAN)的带宽差异。
测量 A 和 B 之间的延迟,使用 ping 或 traceroute 进行网络延迟分析。
预期结果:确保网络带宽充足,延迟较低。
(2)磁盘性能测试用例:
目标:测量磁盘读写性能
在 A 上使用 fio 执行大文件的顺序读取(sequential read)和顺序写入(sequential write)测试。
在 B 上执行相同的磁盘性能测试。
预期结果:磁盘的读取和写入性能足够高,不会成为瓶颈。
(3)传输工具性能测试用例:
目标:测试不同工具和协议的传输效率
使用 scp、rsync、ftp、sftp 和 Rclone 进行同样文件的传输,记录传输时间。
测不同的传输模式(压缩、不压缩)对性能的影响。
预期结果:选择最佳传输工具和配置(如:启用压缩)。
(4)并发传输测试用例:
目标:测 多线程或多连接传输对性能的影响
使用工具支持并行传输(如 rsync并行、多线程工具 aria2 等)。
对比单线程与多线程的传输性能。
预期结果:并行传输显著提升文件传输速度。