记一次线上服务内存优化历程
背景
近期看到企业微信告警,发现线上服务进程隔一段时间就会重启一次,遂开始进行排查。
线上服务描述
- Python服务(使用
aiohttp
) - K8S部署
- 容器中使用
gunicorn
,每个容器启动4个worker进程
排查过程
- 观察报警信息,只有一部分线上环境收到了影响。
- 观察报警时间,观察到只在整点时有一次服务重启告警(告警是进程自己发送的,也就是说只有一个进程重启了),并且不是必现。
- 观察资源信息,在服务重启前,重启信息的容器的内存有一次跳跃式增长。
最终,总结出几个信息: 某一个环境(该环境数据量大),整点时分,单进程重启,内存暴涨造成的。
而服务在整点时只有一个定时任务,这个定时任务是做多数据库的数据同步。
并且,任务的调起,是由外部的一个服务向本服务发送的一个HTTP请求(只会有一个进程来处理这个HTTP请求),所以,能够说通为什么只有一个进程会重启。
- 观察该HTTP请求的信息,查看该请求日志中的进程ID和重启的进程ID,最后发现是一致的,并且重启时间的定时任务日志中,没有定时任务完成得日志数据,所以最终推断出进程得重启是由该定时任务导致内存暴涨,从而导致进程重启。
- 但,还有一个问题没有被印证,进程重启的问题不是必现的,进而,我们需要拉长时间观察,最终发现,进程会因为定时任务导致内存有一次跳跃式的增长,但不一定会导致
OOM
,但有一个问题,内存虽然增加了,但是并没有看到内存回收,这个需要了解一下Python的内存管理机制,是因为进程执行过程中没有一个Arena中的内存被释放完,所以就不会被回收。
解决问题
通过多进程的方式
从上文的排查过程来看,我们可以从内存回收的角度来,在每次定时任务后保证申请的内存能够还给操作系统,避免造成Python一直持有内存不归还。但直接优化GC和内存管理对我一个普通的小开发来说不太现实,所以换个角度看,进程死亡,他的内存一定会归还,那这样就可以绕开Python内存管理造成的内存不归还的问题了。所以,我尝试了以下办法:
-
直接在http服务进程中做fork操作,但在
aiohttp
中执行fork视乎会出现各种各样的小问题,例如,fork出去的进程会copy一份父进程的内存,相当于和父进程使用了同一份async loop
对象,在子进程中,会报错loop
是异常的报错。所以我猜测,Python异步和同步fork操作应该不同。 -
在GitHub中查到了一个叫aiomultiprocess的三方库解决了办法1的问题,不会报出
loop
异常的问题。但,我开发时是未使用gunicorn
的,所以在测试环境测试时,发现fork操作其实会卡死,很是困惑,遂Google到一篇文章Gunicorn+GeventWorker环境下fork进程意外结束的问题,这篇文章的解决办法有些trick,不太想用,放弃。 -
既然fork两条路都走不通,再想出了一个新的方式,脚本执行。我将定时任务中的逻辑,不再放到HTTP请求里面了,而是重新修改为脚本,在HTTP调用中,调起这个脚本。最终发现,行得通,上线。
上线后,发现内存确实是能够正常回收了,内存跳跃式增长不回收,变成了一个又一个的内存尖刺。但过了几周后,又发现了服务重启,查看原因是脚本占用内存太大,导致某个服务进程在申请内存时OOM,虽然告警的频率极低,但也算是一个隐患,需要优化它。
优化代码逻辑,降低内存消耗
从上文中看到,虽然解决了内存回收的问题,但随着业务的日益增长,数据量也会增长,数据同步逻辑会随着数据量的增长导致内存增长,而影响了服务进程。但是由于某些原因,不能新开一个服务来做这个事情,所以,查看了数据同步逻辑的代码,大概逻辑是:对比一个时间段内源数据库与目标数据的数据数量,如果相等,不迁移数据,如果不相等,同步数据。但同步数据并不是按照差异同步,而是使用了源数据库单时间段内的数据全量覆盖。有些暴力了,并且单看数据数量就判断是否需要同步数据也有失偏颇。
所以,最终修改为,根据最后更新时间来对比数据,同步有差异数据。
总结
解决本次的问题,从技术上来说,学习到了:
- Python的内存管理
- 了解了Gunicorn fork进程的一些机制
- Python异步直接fork子进程的一些坑
从经验上看:
- 排查该类问题的一些思路以及逻辑。
- 对解决问题的思考,或者说对无法解决的问题,我如何的避开它,也就是如果规避一个问题的发生。
- 对数据同步,或者数据对比一些业务逻辑的思考。
- 原文作者:Daryl
- 原文链接:https://siskinc.github.io/post/%E8%AE%B0%E4%B8%80%E6%AC%A1%E7%BA%BF%E4%B8%8A%E6%9C%8D%E5%8A%A1%E5%86%85%E5%AD%98%E4%BC%98%E5%8C%96%E5%8E%86%E7%A8%8B/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。