记一次.NET性能故障排查

最近一次公司服务出了一些性能的问题,主要是内存不释放。

领到任务后就开始展开工作。项目是用.net core 6写的,在框上应该不会有什么问题,这是大背景。另外服务是部署在k8s上的,于是就和性能测试人员,开发人员搭测试环境,展开问题重现的测试。

问题很简单,集中在排查几个大查询,并发10个大查询,每次请求数据在数十万到上百万条记录,pod的内存就很快飙上去了,并且之后不释放。重现了,就看看怎么解决吧,内存不释放,第一个想到的就是手动GC一下看看什么现象,结果是手动GC是管理用的,那就说明服务内部没有有效的调用GC,或调用的GC的释放内存的速度,没有请求增长内存的速度快。.

这个问题可以通过修改项目配置,开启项目工作站后台并发回收模式,就能有效地改善。这是个配置,很容易做到,于是修改完后推到镜像仓库,并部署在性能pod中,再次进行测试。同样的并发,同样的请求,这时,内存会边用边回收,不会飙了。在观察各项性能指标时,发现一个问题,当并发变更时,tps始终是30来个,更奇怪的是,pod的资源有有空闲,数据库的资源也有空闲(大数据查库的方案在路中)。这不仅仅是一个问题,还是一个棘手的问题。

先看看代码逻辑,就是在service层先查一下缓存,拿上信息再查数据库,回来的数据进行重组封装,缓存用的官方分布式Redis缓存组件,数据库访问用的dapper,按理这些组件都应该是靠得住的,自己写的逻辑中到是有少许复杂。总感觉是那里有个锁,卡住了,所有资源都还有剩余,但tps起不来。

在这时,生产环境报出了504错误,网关超时,说明上游请求有超时现象。

这时又增加了排查任务,504超时,结合上面的分析,觉得tps上不来,更多的请求等待,必然会有超时的,隐隐觉得是一个问题,只要疏通那个锁,就应该能解决504超时。同时经过测试发现,只要一个大查询,数据量足够大,需要数据库等待,其他查询就会卡住,这肯定会有504出现。

继续排查吧,现在只能把这个复杂的查询分解开来测试,只进行缓存操作,没问题;只进行大数据查询,资源占用也能上来。那就是只要缓存组合慢的大查询,就有问题。

为了验证问题,把Redis分布缓存组件改成内存式分布式缓存(因为测试是在单pod上进行),再次测试时,发现问题解决了,数据库的cpu就会升起来,pod的cpu也会升,最终以数据库cpu打满结束,这个时候,tps也升到是150多个,这就足以说明官方Redis分布缓存有坑(原因是缓存包是从旧的版本升上来的,官方已发布新版本)。

到此,我的分析工作就告一段落,内存和tps上升不了(504也一样的原因)都找到问题了。接下来那就整改代码,再度进行性能测试,验证问题。

想在这里说的是,排查性能问题是个慢活,细活,通过现象推断问题,有很多时间,现象会把人带偏,没关系,再通过更多的现象进行再推断,再排查。另外,不要相信所有组件都是可靠的,有可能单个可靠,组合就有问题,一定要分段排查,完整验证,再去信任。

这次排查后,可能还有其他性能问题,没关系,那就再排查,一个好的服务性能,肯定是经过千锤百炼的,不可能是天生的。