本文是武汉 gopher meetup 的分享内容整理而成,分享内容在“无人值守”的两篇和其它社区分享中亦有提及。(也就是说你看过那两篇,这个可以不用看了)
先来看看苦逼的开发人员
老板说:
![](https://i-blog.csdnimg.cn/blog_migrate/b94587b7a53c7030d2c411be9504d205.jpeg)
队友说:
![](https://i-blog.csdnimg.cn/blog_migrate/9fba08272cd9e44d231f6f062a64f652.jpeg)
外组同事说:
![](https://i-blog.csdnimg.cn/blog_migrate/0913b692f192087ba4a2eb802b214cc5.jpeg)
底层团队说:
![](https://i-blog.csdnimg.cn/blog_migrate/8d4ca41b63fd6432fd72a9289340d489.jpeg)
你:
业界的思路?
混口饭吃也是不容易,既然有问题了,我们还是要解决的。要先看看有没有现成的思路可以借鉴?
Google 在这篇论文[1]里提到过其内部的线上 profile 流程:
![](https://i-blog.csdnimg.cn/blog_migrate/8edb12738d62af0992741ddf3970a6f9.jpeg)
架构图已经比较简单了,线上应用暴露 profile 接口,collector 定时将 profile 信息采集到二进制存储,由统一的在线平台展示。
这篇论文催生了一些开源项目和创业公司,例如在 这篇文章[2] 中,对 continuous profiling 有很不错的解释。
![](https://i-blog.csdnimg.cn/blog_migrate/f938b05c2f5d53fe816acdb05b6de6b2.jpeg)
我们日常的 CI 和 CD 流水线可以高频次发布线上系统,当线上有 continuous profiling 系统在运行时,每次发布我们都能够得到实时的性能快照,并与发布前的性能做快速对照。
系统的性能问题和其它技术问题一样,同样是发现得越早,解决起来就越快,损失越小。
Google Cloud 上也有 profiler 相关的产品[3],是由 Go 原先的 pprof 开发者开发的,她在文章中声称每分钟对应用采集 10s 的 profile[4],大约有 5% 的性能损失,还是可以接受的。
简单来说,continuous profiling 带给我们的优势主要就是三点:
缩短性能问题反馈周期
节省服务器成本
改进开发者的工作流程
有了思路,再看看有没有具体的开源产品可以参考。
相关开源产品
![](https://i-blog.csdnimg.cn/blog_migrate/f31d71839aaf3dac94af9b140c2aab39.jpeg)
stackimpact-go 是社区比较早的开源项目了,不过只开源了 client 部分,所以 dashboard 和后端部分一直是一个谜,不过我们大概也能看出来,有这种曲线图形式的 profile 数据,有性能衰减时能及时发现。
![](https://i-blog.csdnimg.cn/blog_migrate/af143c432af8ab7c9456bc7b9d02c25a.jpeg)
conprof 其实也差不多。
![](https://i-blog.csdnimg.cn/blog_migrate/897dc7859132c632705759bc9236a6a7.jpeg)
profefe 也差不多。
看起来思路都一样,这个需求很简单,我们只要找监控团队帮忙做一套皮肤就可以了!
但监控团队也很无奈。
![](https://i-blog.csdnimg.cn/blog_migrate/06eb1196dd9b15594ceec503a06f8b27.jpeg)
我们的方案
求人不如求己,我们需要定位的是抖动问题,那我们以 5s 为单位,把进程的 CPU 使用(gopsutil),RSS(gopsutil),goroutine 数(runtime.NumGoroutine),用 10 大小的环形数组保存下来,每次采集到新值时,与之前多个周期的平均值进行 diff 就可以了:
![](https://i-blog.csdnimg.cn/blog_migrate/c790d6b7d887ea93c9ffaf7ddf8c2e1c.jpeg)
当波动率超过可以接受的范围,则认为当前进程发生了资源使用抖动,那么:
若 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://i-blog.csdnimg.cn/blog_migrate/50a08b915b3fd04f07933e3e9fec3ef5.jpeg)
类似这样的 goroutine 泄露问题,也是可以很容易发现的。
deadlock(扩展)
因为我们可以采集到所有 goroutine 的栈信息,所以理论上通过遍历我们可以发现哪些 goroutine 是持锁的,例如下列代码,当发生死锁时,我们可以直接把持锁的 goroutine dump 下来,就很容易发现死锁了:
![](https://i-blog.csdnimg.cn/blog_migrate/a2d6f39f00e7c198b9f023778a7d679e.jpeg)
thread block(扩展)
当线程因为调用 cgo 等原因发生了阻塞,会造成线程数暴涨,这时候我们可以将 goroutine 和 thread profile dump 下来,进行诊断。
总结
有了自动化的工具,日子更好过了。
[1]
这篇论文: 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