背景
一个月前,我们仓库的操作人员感觉系统的响应太慢,点击一个按钮需要等待十几秒才能出结果,于是给我们加了个前端监控的需求,也就是监控 API 的响应时间。我跟同事借用了漫威宇宙中的观察者 Uatu 的名字,给项目的前后端分别命名为 uatu-lib
和 uatu-service
。后来项目经过几次修改,已经可以在前端监控 DNS、TCP、响应时间,还有前端的一些未被捕获的报错(包括 Vue 的报错,这里借用了 sentry
的部分代码)。
我们公司其实已经用上了一套监控平台,是美团开源的 CAT,于是我们也打算把数据接入到这儿,大概的思路是:前端收集监控信息,发送到我们自己写的后端,再通过后端转发到公司内网的 CAT。前后端交互没啥好说的,重点在后端转发到 CAT。CAT 的文档并没有特别的易懂,而且网上的 Go Client 并不支持 Transaction 的嵌套,因此我们决定对其加以修改。
Go CAT Client 的结构
最主要的文件如下:
agent.go
:与 CAT 服务器连接、发送消息,属于底层api.go
:负责生成、启用、禁用 Agentclient.go
:获取连接信息、生成 CAT 的基本数据类型message.go
:构造一条消息、对其编码、调用 Agent 发送消息
需要对其优化的点是:
- 之前的 Message 自身有
encode
和Send
方法,也就是说,需要调用msg.Send(msgBytes)
来发送数据,这显然是很不合理的; - 不支持消息树,也就是不支持 Transaction 嵌套,我们需要加上这个功能。
第一次优化
我先实现了消息树功能,为 message.go
添加了 CommitMultiple
函数,用 map[int64][]*Message
来存储每次的消息树内容(由于 Go 是一个实例在跑,因此我需要一个 ID 来区分是哪个 Request)。至于树结构,我通过 Parent ID 构造了一个邻接表,然后做一次深度优先遍历,就可以生成 CAT 的数据格式并发送了。
第一次 Bug 的现象
线上多了好多 5xx
的错误,看 Log 发现是 concurrent map read and map write
,由于我对 Go 了解不深,于是查资料,发现 Go 的 map
不是线程安全的……询问了某大佬后,大佬给我推荐了新版 Go 自带的 concurrent_map
。但我对照着它的原理,以及网上的分析,发现我们这种需要大量写操作的业务并不适用,于是还是手动加锁吧,读的时候调用 RLock
,写的时候调用 Lock
。
加了锁之后,5xx
的错误全部消失。
第二次优化
为了把发送相关的函数抽出来,我写了个 message_stage.go
用来存储和批量发送消息。我先把深度优先遍历啥的也都挪了进去,又把 Send
方法也挪到了里面。
第二次 Bug 的现象
在 Jenkins 上面 Build 了之后,发现并不能跑起来。Mesos 的记录表示:Docker 服务启动一段时间后会报 255 错误,然后重启。同事眼疾手快,趁 Docker 没崩掉的时候进去看了 Log,错误是 OOM(内存超限),我完全理解不了。
第二天,同事告诉我,我们好像有个叫 Grafana 的线上监控系统诶!我登录进去一看,几个国家的 CPU 都占到了 100%,内存使用也超过了 8 GB 的限制(之前基本只用 50 MB),这更疑惑了。我用 Go 的 pprof
调试了好久,除了发现 Go 在垃圾回收之后会不断占着内存、不还给操作系统以外(正常现象,因为我系统内存很足,Go 这样可以避免大量内存分配),并没发现有什么可能造成内存泄漏的地方。
后来,我在同事发过来的 Log 里面发现了:某一个 goroutine 中的调用堆栈有一大堆的 traverse
函数,但目前所有业务传过来的数据不可能多于两层,这说明我的深度优先遍历无限递归了!以一个当年 OI 选手的名义起誓,我写这种东西还是很轻松的,不可能出现这个问题,于是我暂时限制了递归层数,并把可能造成无限递归的数据打到了 Log 里面,发现 ID 是 0
,Parent ID 也是 0
……这特么成环了……
所以 CPU 疯转和 OOM 的原因是:由于成环了,程序无限递归,traverse
里面的变量又不多,所以可以很轻松的递归上千层,这耗尽了 CPU 的资源,也耗尽了有限的内存。
仔细一想,好像是因为线上环境的前端库还没更新,于是没有传这两个 Key 过来,我在后端 Struct 里面写的结构也没有声明 required
,就被解析成 0
了。
在后端对旧数据做了一些兼容之后,可以跑起来了,CPU 和内存的占用也恢复了正常。
经验教训
- 要对语言足够熟悉,才不会踩语言的坑;
- 线上的 API 如果做了不兼容的升级,请务必要区分版本。