几十万实例线上系统的抖动问题定位
共 2530字,需浏览 6分钟
· 2021-04-17
本文是武汉 gopher meetup 的分享内容整理而成,分享内容在“无人值守”的两篇和其它社区分享中亦有提及。(也就是说你看过那两篇,这个可以不用看了)
先来看看苦逼的开发人员
老板说:
![](https://filescdn.proginn.com/fba6067559a1865df02b3a331563d5f4/284c37639fe62f9baa83c0b4dc37adf1.webp)
队友说:
![](https://filescdn.proginn.com/d7a0ca6f57b7a08cef60b618b3696470/51eb4fb2fd8cdf359d95d1fab347ec83.webp)
外组同事说:
![](https://filescdn.proginn.com/493683b9d52410b95c7687b1d5c5f52e/7ec7e93f5e2e625031d9d6f6d4d47796.webp)
底层团队说:
![](https://filescdn.proginn.com/6c8b510ebf98aef3429fecdbec06fc51/ac4d5ea0e99a0a6ae004e69f97fc51ed.webp)
你:
业界的思路?
混口饭吃也是不容易,既然有问题了,我们还是要解决的。要先看看有没有现成的思路可以借鉴?
Google 在这篇论文[1]里提到过其内部的线上 profile 流程:
![](https://filescdn.proginn.com/ca8ff84c5b7067309113d680206ffab5/1a40249cdab41f705e0803a5ac52abd9.webp)
架构图已经比较简单了,线上应用暴露 profile 接口,collector 定时将 profile 信息采集到二进制存储,由统一的在线平台展示。
这篇论文催生了一些开源项目和创业公司,例如在 这篇文章[2] 中,对 continuous profiling 有很不错的解释。
![](https://filescdn.proginn.com/b9e992d795745de3ffd772464018ad64/68252c96dab6c07a5b8f47d7b9973e70.webp)
我们日常的 CI 和 CD 流水线可以高频次发布线上系统,当线上有 continuous profiling 系统在运行时,每次发布我们都能够得到实时的性能快照,并与发布前的性能做快速对照。
系统的性能问题和其它技术问题一样,同样是发现得越早,解决起来就越快,损失越小。
Google Cloud 上也有 profiler 相关的产品[3],是由 Go 原先的 pprof 开发者开发的,她在文章中声称每分钟对应用采集 10s 的 profile[4],大约有 5% 的性能损失,还是可以接受的。
简单来说,continuous profiling 带给我们的优势主要就是三点:
缩短性能问题反馈周期 节省服务器成本 改进开发者的工作流程
有了思路,再看看有没有具体的开源产品可以参考。
相关开源产品
![](https://filescdn.proginn.com/182c4a5487320c71745442a96a5864d0/cf6e67df3c183a09b8bfc1efc50b8ed8.webp)
stackimpact-go 是社区比较早的开源项目了,不过只开源了 client 部分,所以 dashboard 和后端部分一直是一个谜,不过我们大概也能看出来,有这种曲线图形式的 profile 数据,有性能衰减时能及时发现。
![](https://filescdn.proginn.com/5ee078b09fd15f3f72c55250895c20cf/549ec40bbc5235fcfdf6518b51db3561.webp)
conprof 其实也差不多。
![](https://filescdn.proginn.com/66e986c624cbb29be1e4ef679f6b3716/41f8c49c6b0154fcd4afa0866bbf4506.webp)
profefe 也差不多。
看起来思路都一样,这个需求很简单,我们只要找监控团队帮忙做一套皮肤就可以了!
但监控团队也很无奈。
![](https://filescdn.proginn.com/835a95c0f343a4fcec053f806b312cb6/525bb40490e94517aa3fdcc678ac7e99.webp)
我们的方案
求人不如求己,我们需要定位的是抖动问题,那我们以 5s 为单位,把进程的 CPU 使用(gopsutil),RSS(gopsutil),goroutine 数(runtime.NumGoroutine),用 10 大小的环形数组保存下来,每次采集到新值时,与之前多个周期的平均值进行 diff 就可以了:
![](https://filescdn.proginn.com/b01a53be768efb26ded6ec03c774c193/8378d6b3ea6e976a8197edc230f56c90.webp)
当波动率超过可以接受的范围,则认为当前进程发生了资源使用抖动,那么:
若 CPU 抖动,从当前开始采集 CPU profile 5秒 若 RSS 抖动,将当前进程的 heap profile 记录下来 若 goroutine 抖动,将当前进程的 goroutine profile 记录下来
一套方案做下来,还挺简单的。当我们收到模块的 CPU、内存或 goroutine 数报警时,上线到相应的实例来查看即可。
实际的案例
解码 bug
某模块突然出现了 RSS 使用飚升,上线看之后发现自动 dump 文本的 profile 中,单个对象的 inuse_space 超过了 1GB:
// inuse_objects: inuse_space [alloc_objects : alloc_space]
1 : 1024000000 [1 : 1024000000] git.xxx.xxx
这是很反常的,阅读代码后发现在 decode 中没有对这些情况进行一定的防御操作,有形如下面的代码:
var l = readLenFromPacket()
var list = make([]byte, l)
虽然理论上 body 是可以传 1GB 的,不过内部的 RPC 框架,还是应该对这种情况进行一些限制。
CPU 尖刺
有些在线系统是定时任务调用的,所以其访问峰值非常不平均,在定时任务触发后,会有一段时间 CPU 使用非常高,有了自动 CPU profile dump,就非常容易找到具体哪里使用的 CPU 很高了。
goroutine 泄露
![](https://filescdn.proginn.com/db72249000b1aae7d6fd77d4c2d8a2a8/0781b48fb661f885e1a8bf31e9c4678d.webp)
类似这样的 goroutine 泄露问题,也是可以很容易发现的。
deadlock(扩展)
因为我们可以采集到所有 goroutine 的栈信息,所以理论上通过遍历我们可以发现哪些 goroutine 是持锁的,例如下列代码,当发生死锁时,我们可以直接把持锁的 goroutine dump 下来,就很容易发现死锁了:
![](https://filescdn.proginn.com/317e7f5d61743697912998129f0c8b1a/01d122495f69d66c4f3893c05e0bcc38.webp)
thread block(扩展)
当线程因为调用 cgo 等原因发生了阻塞,会造成线程数暴涨,这时候我们可以将 goroutine 和 thread profile dump 下来,进行诊断。
总结
有了自动化的工具,日子更好过了。
这篇论文: https://research.google/pubs/pub36575/
[2]这篇文章: https://www.opsian.com/blog/what-is-continuous-profiling/
[3]产品: https://cloud.google.com/profiler/
[4]profile: https://medium.com/google-cloud/continuous-profiling-of-go-programs-96d4416af77b