记一次性能调优经历(非代码层级)

项目:服务代理平台,顾名思义,做服务代理,本身不包含复杂的业务逻辑,主要做请求分发和调度,调用后台核心服务,然后对外暴露统一的服务入口;部署方式为分布式部署,使用Nginx做负载均衡,日常QPS峰值2000出头;
简单的网络架构图:


image.png

从代理平台到后台服务都是走公网专线,能够保证较为稳定的网络速度,中间为防火墙(交互机没有标识出来),一次完整的交互会有两次请求经过公网出入口,并打到防火墙上。
问题:其中一个大客户发现上量后,我方服务代理平台耗时开始增加,无法达到客户要求的标准;客户的调用的峰值大概是1000QPS出头,加上其他中小客户的调用,在高峰期超过了2000QPS,大客户给出的耗时情况:


image.png
image.png

客户要求99.9的请求耗时都在400毫秒以下,而且客户的超时时间设置的500毫秒,并且不能有太大波动(波峰波谷少),因此我们针对性地进行优化。
正题:要进行优化,首先需要搞明白问题出在哪里。我们是服务代理平台,主要的业务接口并不在我们这一层,而是在各个后台服务,我们对每个业务请求(从我方到后台服务)的耗时都做了记录,并且有相对健全的监控(基于Prometheus),有核心服务请求超时的告警。在高峰期,大客户的请求从到代理平台开始一直到调用后台服务结束这一阶段的耗时超过500毫秒的已经达到100-500个/每分钟,按客户1000QPS算,在1分钟里,平台内部最多会有0.8%超时,而如果再把超时时间缩小到200毫秒,1分钟内就会超过1%的请求超时,这仅仅是平台到后台服务的耗时时间,再加上客户到平台和平台返回的时间,整体的请求耗时很容易就超过400毫秒了。
从研发的角度,我们优先真对应用做耗时分析,代理平台的很多接口都是透传调用后台服务返回结果,同时也有一半的接口是暴露给服务后台进行回调然后将回调数据直接透传给客户的,所以我们基于HttpClient维护着两个Http连接池,防止连接池不够用导致请求排队。针对客户的直接请求,接口逻辑中需要与DB和中间件存在交互的操作都已尽可能做异步,请求后台服务前的耗时基本维持在5毫秒以下,所以问题最有可能是出在后台服务本身或者到后台服务的网络上。让人沮丧的是,我们的后台服务没有做好耗时数据的统计和监控,而且后台服务内部的调用链条复杂,想要得到后台服务的应用耗时数据十分困难,只能看到后台服务节点的负载水平在业务高峰期的时候没有达到很高,所以我们只能优先从代理平台本身和到后台服务的网络去分析了。
我们观察了应用节点的一些参数,如CPU负载,内存使用率等,发现一些节点的CPU负载在高峰期也远没有达到危险值(四核心时负载3以下):

image.png

那问题也很有可能在入口上,也就是Nginx上,我们首先查看了其中一个高峰期Nginx的负载:
TODO
对一下Nginx的配置:
worker_connections最大值为1024,根据Nginx官方文档上的描述:每一个worker进程能并发处理(发起)的最大连接数(包含所有连接数)),高峰期时我们Nginx的连接数维持在1000,而我们的work线程是2,理论上来说应该最多可以并行使用2048个连接才对,后台查看了一下Nginx的官方文档,才发现我们并没有配置worker_rlimit_nofile(可被一个工作进程打开的最大文件描述符数量),而这个参数又会首先于Linux的内核参数ulimit -n,ulimit -n查看Nginx节点发现数值是1024,原来是系统内核参数限制Nginx的最大连接数(Socket在Linux中也是一个文件,理论上配置完Nginx的worker_connections,还需要配置worker_rlimit_nofile与Linux的ulimit -n数一致)。然后我们把Nginx的upstream_time 和 后台记录的应用耗时(从接到请求到调用后台服务返回)对一个对比,于是有了下图:
TODO时间太久远,补不了图了
upstream_time(从Nginx到被代理服务返回的耗时时间)比应用内部(从平台到后台服务)耗时在同一时刻最多多出100毫秒。我们推测,是由于Nginx达到了极限,导致请求排队。于是我们直接让运维对Nginx进行扩容,从双核扩到八核,并且修改ulimit -n为65535,同时修改Nginx配置,使用epoll,worker_connections设为4096,worker_rlimit_nofile设为65535与ulimit -n一致。
次日的高峰期我们满怀希望地盯着监控系统,觉得应该这下应该没什么问题了。遗憾的是,大客户服务请求的超时报警(>500ms)又一次次的出现了...客户给了他们那边的耗时监控图,发现和昨天差别并不大。入口的性能优化后,能承载的吞吐量是大了,那代理平台服务集群的负载又如何呢?我们一个一个查看服务节点的参数,发现高峰时期,有几个节点的CPU负载已经超过了4(四核的虚拟机),我们认为是服务节点的负载太高导致多个请求排队从而造成响应耗时增加,于是说服运维同学继续针对服务节点进行扩容,这下直接扩容了一倍的节点数量。
我们胆战心惊地等到了第二天的又一波业务高峰,这次报警的确减小了,可是还是没有达到客户要求的标准(99.9%的请求都在500毫秒以下,要求太高了 囧),
image.png

这下我们基本上对代理平台应用层做了扩容,剩下优化的点就是网络了。
针对网络的问题,有一个很明显的点,就是公网出入口的防火墙,整个调用链条其实会经过两次出入口防火墙(客户请求代理平台,代理平台调用后台服务),试想一下,如果客户的QPS2000,每个客户请求的平均大小为1KB,在极端情况下,通过公网出入口的流量就会达到 20001KB2=4000KB=4MB/s,带宽占32Mbps。而公网出入口的带宽为100Mbps,同时还承接着其他项目的流量,很可能在这个入口造成网络拥塞。于是每一次高峰期出现超时报警(>500ms)的时候,运维人员都会紧盯着公网出入口的流量情况。期间出现过一次极端情况,在某个高峰时段,有个项目在5分钟内传输了几个G的文件,因为没有限速,所以在短时间占满了出入口100M的带宽,直接导致代理平台出现1秒左右的业务中断。因此我们觉得,问题极有可能就在公网出入口,业务高峰期的流量很大,如果无法保证足够带宽给代理平台使用,网络响应速度必然受到影响,因此,我们让运维针对代理平台的出入口IP做了流量优先级处理,保证高峰时段有32Mbps的带宽供业务代理平台使用,后续对带宽进行扩容。
网络优化的次日高峰期,只出现了零星的超时报警(>500ms),和这个要求极高的大客户要了延时响应图:
image.png

image.png

耗时波动比之前降低了不少,客户那边也基本接受了这样的服务响应水平。

最后,总结一下,提高系统性能,首先需要定位到影响性能的点在哪里,要定位到这个点,不仅需要熟悉应用层级的架构,还要了解基础设备层级的架构。这几年一直从事的是应用开发,发现性能问题总是习惯从应用层级去查问题,甚至过早地去优化代码层级的性能,这样很容易钻牛角尖。个人觉得,性能分析前需要先把整个调用链条理清,结合整个应用集群的架构,对调用链条的每个阶段进行分析。这个时候整个服务集群调用链条的“地图”就显得尤为重要,后续我们需要花时间去构造出这个“地图”。

你可能感兴趣的:(记一次性能调优经历(非代码层级))