省流
中医四诊"望闻问切"与程序诊断有异曲同工之妙。在Golang问题排查中,我们需要建立系统化的诊断思维:通过观察表象(望)、收集信息(闻)、追溯根源(问)、精准施治(切)四个维度,形成完整的诊断闭环。
引入prometheus上报
Prometheus能有效暴露线上服务实时数据,包括程序指标和运营指标,也能对指标设定阈值告警,是服务可观测的标配组件。
官方地址:https://prometheus.io/docs/prometheus/latest/getting_started/
引入jaeger链路追踪系统
链路追踪系统可以看作加强版的日志,能解决日志关联性差、信息易缺失的问题,更是分布式服务排查问题的好帮手。能对开发和线上问题调试提供重要助力。
其中数据存储方式有很多种,建议使用ES部署,并在框架提供上报频率配置。
https://www.jaegertracing.io/
探活机制
实现一些实时对服务进行检测的接口,用于异常时验证服务状态,以及使用类k8s的方式对服务进行巡检。
- 返回工作队列任务数。
- 检测DB和缓存健康。
- 返回服务发现状态。
望+闻:察其表象,观其脉络
第一道防线:Prometheus
通过prometheus,我们能制作进程内存、CPU等系统指标,也能对在线、QPS等业务指标进行上报监控。指标监控和日志往往是上线项目发现问题的第一现场。
当某项数值过高、过低,或变动较大时,我们可以设置告警。版本更新发布后也常通过prometheus观察发布后在线、接口报错等时候符合预期。
prometheus常结合grafana使用,prometheus负责收集服务数据,grafana负责显示数据,通过编写hql语句生成线形图。
sum rate大法,常用于查看平均值。这里查询对2min取平均值,计算每秒每个服务、接口发生了多少次调用
sum(rate(requests_total[2m])) by (server,cmd)
日志系统
日志系统也是日常开发用的最多的,那么一个好的日志系统应具备哪些能力?
- 应具备中间件,支持框架、项目手动给日志打tag,记录关键信息。默认追加调用接口名、UID等关键信息。
- 针对error、fatal级别日志,打印其错误堆栈、traceID等关键信息;Error级别日志做每日汇总,FATAL级别日志需立刻告警。
- 支持线上实时调整日志级别。
日志和监控往往是发现问题的第一现场,但打日志对于业务是冗杂工作,会在主线开发之外增大工作量,在框架层开发中间件等机制能明显让业务开发放空大脑。
系统性能观测(vmstat/top)
vmstat能够实时输出系统的各种资源使用情况,比如进程信息、内存使用、CPU使用以及IO情况。监控系统级 资源瓶颈(CPU、内存、I/O、进程队列)。
$ vmstat 1 # 每秒刷新
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
2 0 0 250000 10240 180000 0 0 50 20 100 200 30 10 55 5 0
- procs:进程队列。r表示可运行进程数,反应CPU饱和度。b表示阻塞的进程数(例如等IO)。
- memory:内存使用。swpd表示已用交换分区(若持续增长警惕内存不足)。free为空闲内存。
- cpu:us:用户态,sy:内核态,id:空闲,wa:I/O 等待
top显示当前系统正在执行的进程的相关信息,包括进程ID、内存占用率、CPU占用率等。实时监控单个进程 的资源占用(CPU、内存等)。
- load average:分别是1分钟、5分钟、15分钟的负载情况。超过18需要重点关注。
- cpu:当sy过高时,需要考虑是否过度使用系统函数。
- 进程信息:重点关注每个进程使用的内存、CPU。
有一次服务接口调用耗时非常高,我们排查到机器发现机器load值超过了20,进一步排查发现业务侧开启了过多time.Timer导致CPU调用忙。
资源限制诊断(ulimit/lsof)
另一类常见报错是系统资源限制,例如:too many open files,或文件描述符泄露。
显示指定pid进程打开的fd
lsof -p
通过ulimit -n命令查看系统文件描述符上限;若确诊,通过prlimit --pid --nofile=65535:65535临时调整fd限制,再通过ulimit -n 65535修改当前会话fd限制,后续再修改系统文件保证机器重启也生效。
问:追本溯源,审证求因 ****
这里的问可能并不是问go哪里病了,而是定位问题原因,针对具体问题获取更明确的信息。
实时火焰图:pprof
CPU火焰图生成
go tool pprof -http=:8080 http://localhost:6060/debug/pprof/profile?seconds=30
内存分配分析
go tool pprof -http=:8080 http://localhost:6060/debug/pprof/heap
通过web查看pprof火焰图
go tool pprof -http :18000 pprof.comet.alloc_objects.alloc_space.inuse_objects.inuse_space.002.pb
在第一步中我们可以通过prometheus和top发现进程病了,接下来我们可以通过go tool火焰图观察病灶在哪里,往往就能确定病灶。
- 对于CPU问题,火焰图会显示占用CPU最高的调用在哪里。
- 对于内存问题,火焰图会显示目前使用堆内存最高的函数调用堆栈。
实时堆栈:pprof
pprof还有一个功能是抓取当前所有go routine实时堆栈,查看有无问题。
排查协程泄露:
go tool pprof http://localhost:6060/debug/pprof/goroutine?debug=2
经典输出结果如下:
goroutine 1 [running]:
main.main()
/path/to/file.go:10 +0x45
goroutine 2 [runnable]:
runtime.gopark(0x123456, 0x0, 0x0, 0x0)
/usr/local/go/src/runtime/proc.go:307 +0x89
...
- 阻塞分析:特别关注状态为 [semacquire]、[IO wait] 或 [select] 的 Goroutine
- IO Wait的go routine可以认为是在等待网络回包。可能是DB或RPC连接。
- semacquire状态的协程一般是在等待锁,可以根据堆栈判断是在竞争哪里的锁。重点注意阻塞时间。
- select就是我们写的select,在等待channel,重点注意阻塞时间。
所有 Goroutine 都处于等待状态时可能发生死锁。
- 泄漏检测:长期处于 [runnable] 但无实际工作的 Goroutine 可能是泄漏
- 占用大量CPU资源的罪魁祸首
更详细的日志:Jaeger
链路追踪系统用于将请求中发生的离散事件记录并连接。
比如一个简单的请求会从:gatesrv->gamesrv->db,我们可以给UID、RedisKey等关键指标打上标签,文本中记录请求、响应内容,记录从DB中读出了什么,并对出现error的接口进行定向上报。将记录和上报放置于框架中,业务开发时无需操作即可完成自定义集成。
这将极大提高测试环境开发以及线上定位问题的效率:当请求和响应,中间发生的任何rpc和db调用都一目了然,又能对函数调用进行自定义补充时,我们能掌控接口的所有。
jaeger在公司也是战功赫赫:
- 项目出现诡异报错,通过对指定UID进行灰度抓取,发现其DB数据有问题。
- 业务时常反馈第三方登录、支付偶现报错,通过jaeger快速发现第三方报错。
- 开发时减少bug定位修复时间,调用中发生了什么都心中有数。
系统工具(tcpdump/netstat)
针对网络问题,我们往往通过抓包直接获取原始数据。
tcpdump -i any tcp -nn 'tcp port 9096' -w game_20240110.pcap
抓到包后,直接扔到Wireshark进行分析。
- 连接问题,检查是否有断连、重连,检查握手是否正常,检查断开连接。
- 客户端SYN无响应 → 服务未监听/防火墙拦截
- SYN后立即收到RST → 服务崩溃或拒绝连接
- 检查消息收发。出现异常也可能会导致C/S主动断开连接。
- 请求后无响应 → 服务端未处理
- 连续包间隔 >200ms → 网络延迟或应用处理慢
- 检查是否回空包。
有一次遇到线上问题,现象为部分接口回空包并直接关闭连接。
通过tcpdump定位到是proxy,proxy设置http超时时间为3s,但上传文件消耗时间过长,导致该请求在代理被认为是超时,连接被关闭。
切:精准把脉,对症下药
综合案例:计时器滥用诊治
望:接口耗时一路飚高,但观察到DB耗时没有异常。且部分队列出现 "queue busy"触发熔断,观察到CPU也在一路增高,通过top发现服务"us"调用明显高。重启后过短暂恢复,但会继续呈现一路增高。
问:通过查pprof,我们发现CPU耗时指向计时器------但正常情况下大部分CPU会集中在proto的序列化,我们还发现go routine在不断增多。通过查阅业务代码,定位为计时器滥用。
综合案例:网络问题诊治
望:公司有个客户端日志收集服务,接口之一需要上传日志文件,耗时大部分为500ms。测试阶段一切正常,上线后发现有时需要的日志没传上来。通过检查服务日志,发现日志服务正常响应了,
问:我们模拟了巨大文件的上传,并用tcpdump抓取了回包,发现网关层回了空包。检查代码发现我们使用proxy时,给http.transport设置了1s的超时。线上网络环境非常复杂,时常就会上传耗时超过1s。
综合案例:业务BUG
望:这个BUG不会看到接口异常或耗时增加,但会损伤玩家利益。线上玩家反馈,进行游玩有时会丢棋盘上的道具。该功能项目的实现是,客户端进行一系列合成操作并生成结果,并周期性地将合成操作和棋盘同步到服务器,若出现检查异常,服务器将对客户端操作进行回滚。
棋盘同步会有大量的请求,通过日志难以完整复现玩家操作。
问:项目打开了链路追踪的UID尾号采样机制,对出现棋盘回滚+被尾号命中的玩家一系列请求进行分析,发现客户端和服务器在某步出现逻辑不一致。