问题背景
某个周末,线上实例开始偶发cpu利用率到达80%的报警,且当时业务几乎无流量,个别实例的cpu利用率仍居高不下
业务是处理websocket的长连接,在请求结束时会通过channel的关闭事件通知各协程退出
排查过程
- 首先通过pprof命令采集了一段时间的cpu时间的占用情况,发现大量时间被运行时的协程调度占用,但当时已无流量,所以这种占用情况是不符合预期的
- 通过以下命令拉取当时存活的协程
bash
curl http://localhost:6790/debug/pprof/goroutine?debug=2 > goroutines.txt
less goroutines.txt
grep yourFunc goroutines.txt
分析过程
其中存活的协程分为以下两部分
- hertz, mertrics这些长期存活的和服务监听或者是监控上报有关,本身监控或者对端口的监听会持续存在,这部分没问题。
- 另一部分是少量的业务处理过程中开辟的协程,这部分应该随着请求的结束而推出,但实际并没有,问题看来就出现在了这里,因为大量协程没有退出导致调度成本越来越大
解决方案
当前程序对于开辟的协程管理十分不严谨,是通过在各协程中感知channel的close行为然后自行退出,并且关闭channel的行为仅发生在处理请求的过程中。将其修改为go自带的context管理,感知context的cancel行为,将原来对channel的关闭替换为对cancel的调用,并且在websocket被对端关闭的情况下调用cancel。
上线观察几天后,cpu利用率下降,之后继续观察线上问题是否彻底缓解