警告,服务器负载过高

这周二开始十一点半,突然zabbix上看到网站集群上每台机器有好几千的并发请求,然后就是噼里啪啦的cpu过载报警。因为我们服务器高峰时的请求不会过一千,所以看到这个报警第一反应就是是不是又被恶意刷了(呃,为什么要说又)。

首先筛了一遍日志,定位几个访问频率比较高的IP,详细看了他们的访问记录,似乎并没有恶意请求的样子,只不过日志里面有一种请求非常之多。服务器的流量也没有过高的起伏,后端数据库的压力也不大。再看一下php-fpm的status,所有进程都被叫起来干活了。

仔细用top看看服务器负载,发现load average非常高,但是usr态和sys态只是维持在25%左右,内存占用不高,swap也没用多少,IO也没有异常。懵逼三秒钟,再用vmstat看看先

system的interrupts和context switch都比较高,看起来cpu不仅在不停处理请求,内核还在不停切换进程。联想起超多的nginx连接数,以及那条频率很高的URL。应该是客户端发起了太多的请求,让服务器端开启了所有php-fpm的进程应付,但是处理不过来这么多请求,大量未处理的请求积压。所以决定先去网站上看看这个请求为何会如此之多。

chrome打开开发者工具,看到那条请求是网页上一个价格实时更新的模块,频率为每5秒钟请求一次。

既然找到了消耗资源的地方,那就想想办法解决这个问题吧。

一时半会儿,似乎找不到资源可以扩充,从服务器优化的角度也没有思路去优化,让PHP执行的更快。于是想了想,是不是可以把这样的请求让一台资源利用率低的服务器去执行。之后同事说要不对这样的请求做一个反向代理吧。听起来合乎逻辑,那么赶紧干起来。

既然做反向代理,我第一时间想起来的是nginx自带的proxy_pass或者upstream。可是对比实际的请求,似乎upstream不适合这种场景,那么就用一下fastcgi_pass吧。照着这个思路,试试用location匹配那个URI,再丢给别的机器执行看看。可是并不顺利,写的location规则一直匹配不上,于是用if来匹配request_uri。但是这里并不能放fastcgi_pass。折腾了一番之后,想起来可以用set赋值变量。然后根据这个变量,在处理php脚步的location里面指定不同的fastcgi server。配置完毕nginx -t,终于看到久违的successful。

1
2
3
4
5
6
7
8
9
10
#定义变量
set $nickelprice 0;
if ( $request_uri ~* /metals/nickel\?mid ) {
set $nickelprice 1;
}

#指定不同的fastcgi
location ~ .*\.php {
if ( $nickelprice = 1 ) {
fastcgi_pass 172.16.xx.xx:9000;}

服务器端全部启用之后,终于主站不再受这波请求的影响了。至于那台被牺牲的服务器,就随它去吧~~