记一次SAE Web服务器的调优过程
事情的原因,是发生在某个晚上的9点30左右,SAE的报警系统突然报出了异常,所有的Web服务器的负载突然变得很高,流量也变得异常的大。
这个是很有问题的,在SAE最前面的反向代理上,是部署了SAE自己开发的‘CC防火墙’的,如果出现了异常的被攻击的情况,这些异常的流量是不会到达
Web服务器的,现在这些流量都到达了Web服务器,说明要么是攻击没有被正常判断,要没就是这不是一次攻击。
事实上确实这也不是一次攻击。
随后的分析,我们发现了一堆比较‘奇怪’的应用,为什么说‘奇怪’呢,因为这些应用的访问趋势是这样子的:
这是其中一个应用的数据,然而在这个应用的帐号下面,大约有十个类似的应用,都是这样的趋势,所以对于我们的Web服务器来说,在特定的时刻要接受
接近10倍左右的流量,对于当前的规模就有点‘顶不住’了。
所以讨论过后,大家的想法还是要对Web服务器进行扩容,来满足这样突发的流量增长。但与此同时,Web服务器也有些不一样的异常,在报警的时候,系统负载
比较高,CPU也没有什么空闲了,但是有个比较特殊的现象,就是system占用的CPU比user占用的CPU要高,这是明显不合理的,一般情况下,system占用CPU较高
意味着系统可能存在瓶颈,比如大量的锁争用等情况。
所以,在着手准备扩容Web服务器的同时,也尝试对现有系统进行一些性能分析,看是否能找到瓶颈,便于做一些优化,提升单Web服务器的容量。
分析的第一步,就是要收集系统运行时的信息,因此我们使用了 perf
这个工具,在系统中对httpd进程进行了抓取,并使用
FlameGraph生成相应的火焰图。
$ perf record -g -a # 等待收集一段时间后Ctrl-c 退出
$ perf script | stackcollapse-perf.pl | flamegraph.pl > flame.svg
最终生成的火焰图如下:
看到这个图,一切都明白了,系统在此阶段调用了太多的connect,尝试建立TCP连接,造成了大量的系统开销,从而影响了整体的性能。
那么为什么会导致这个问题?
用户的代码逻辑非常简单,只是从memcache中取出一个值,然后做一个简单的转换,然后输出。那么,问题就出在了memcache上。
这其实是一个历史遗留问题,SAE的Web服务器是一个纯共享的环境,用户与用户之间,可以做到请求与请求之间的隔离,因此,服务器上的
每个httpd进程都可以为所有的应用服务,实现动态调度,这样的设计,保证了资源的高利用性,能够实现资源利用的最大化。
在当初实现资源隔离的时候,对于memcache,用户初始化memcache时总会帮他建立一个独立的连接,当使用完成,再把连接断掉。
问题就出在了这里,当应用访问量很大,每个请求占用的时间又很短,就出现了频繁和memcache建立连接-断开,又建立连接-断开的死循环。
造成的系统开销非常巨大。
找到了原因,那对应的解决办法就简单了,只需要修改memcache的扩展,把短链接修改为长连接就好,除此之外,如果遇到连接断掉的情况,则
尝试重新连接,保证一个用户的使用不会影响到下个用户。
就这样,最突出的问题就基本解决了。
当然,对于用户来说,其实完全不用关心用的是长连接或者是短连接,不需要调整代码,初始化的Memcache自动地就变成了长连接,完全没有感知。
再看一看调整之后的火焰图:
好了不少,没有像之前的火焰图那样非常夸张,相对的消耗都比较平均。
最后在一台即将上线的机器上使用ab测试了一下。
修改前:
Concurrency Level: 100
Time taken for tests: 77.282 seconds
Complete requests: 100000
Failed requests: 3863
(Connect: 0, Receive: 0, Length: 3863, Exceptions: 0)
Write errors: 0
Non-2xx responses: 3863
Total transferred: 29684301 bytes
HTML transferred: 9357111 bytes
Requests per second: 1293.96 [#/sec] (mean)
Time per request: 77.282 [ms] (mean)
Time per request: 0.773 [ms] (mean, across all concurrent requests)
Transfer rate: 375.10 [Kbytes/sec] received
修改后:
Concurrency Level: 100
Time taken for tests: 16.554 seconds
Complete requests: 100000
Failed requests: 6080
(Connect: 0, Receive: 0, Length: 6080, Exceptions: 0)
Write errors: 0
Non-2xx responses: 6086
Total transferred: 29450237 bytes
HTML transferred: 9218982 bytes
Requests per second: 6040.76 [#/sec] (mean)
Time per request: 16.554 [ms] (mean)
Time per request: 0.166 [ms] (mean, across all concurrent requests)
Transfer rate: 1737.32 [Kbytes/sec] received
QPS 从1293.96 [#/sec] 提升到了6040.76 [#/sec],效果还是比较明显的。
修改了memcache的逻辑,又加上扩容了几台服务器,之后,就没有再出现报警的情况,问题算是得到比较好的解决。
当然,使用了长连接之后,服务器维护的连接数会有很大的提升,基本上从原来了200个连接上升到接近2000个连接,不过对于我们的服务器来说,
这些连接其实不算什么,所以其实也不会有太大的影响,和长连接带来的性能提升相比,还是相当值得的。