随着互联网电商的持续发展,很多品牌也在搭建自己的自营垂直电商网站,并且把放它到公有云上面去运行。这样不仅省去了自己维护硬件和现场团队的成本和烦恼,而且在基础架构出现问题的时候,还可以直接找公有云厂商做维护和排查,省去了自己维护这个能力的成本,岂不是一举两得。
当时我们云平台上,就有这样一个客户。该品牌小而美,面向年轻消费群体,同时不忘拓展更多销售渠道,于是在我们平台上建立了他们自己的电商平台。大家都知道,一个稍大一点的系统就需要负载均衡来合理分发外部请求了。该电商的架构并不大,简单来说是这样的:
Cloud LB运行在第四层(TCP层),设置的负载均衡策略是round robin(轮询),也就是新到达Cloud LB的请求,依次派发给后端3个nginx服务器。
nginx运行在第七层(应用层),它作为web服务器接收http请求,然后通过fastCGI接口传递给本机的php-fpm进程,进行应用层面的处理。
应该说就是一个非常典型的负载均衡架构。理应正常运行才对。事实上,上线后很长一段时间,这个应用还是跑的很顺利的,也赢得了客户对我们云平台的认可。
不过有一天,忽然客户报告系统出问题了。网站访问越来越慢,甚至经常会抛出http 504错误。比如下图:
借助浏览器的开发者工具可以看到,这个响应耗时达60秒,最终还是抛出了504错误。事实上,一般人浏览一个网站的时候,等个十来秒就没耐心了,要么点击刷新,要么就关闭窗口了。就算你是该品牌的真爱,就算你等满1分钟,结果等到的也只是一个错误页面,也是悲剧了。
从服务端的监控来看,三台nginx服务器的系统负载(CPU load)出现了严重的不均衡。它们的配置都是4核CPU。其中一台的负载值在10以下,一台在20以下,最后一台居然高达38到45,远远超过其CPU核的数量。大家知道,uptime或者top命令输出里的CPU load值,表示的是待运行的任务队列的长度。比如4核的机器,load到4,那就是4个CPU核处理4个任务,全部用满了。当然,到用满的程度,已经对应用的性能产生影响了,所以一般建议load不超过核数*0.7。也就是4核的机器,建议在load达到2.8的时候,就需要重点关注了。
回到这三台nginx机器,我们看看具体的load:
联系到前面我们提到的负载均衡,你大概也明白为什么客户找过来了:因为3台机器的负载不均衡啊,难道不是你们cloud LB工作不正常导致的吗?
首先,我们需要证明cloud LB工作正常(或者不正常)。从直观上看,处于LB后端的3台机器load不均,似乎也印证了LB的异常。不过,这个load不均(更严重的问题是load过高),可能只是表象,因为太多因素会影响到load了。我们回到最初,既然要证明LB工作是否正常,最直接的方式,还是看LB是否做到了round robin?
于是我们去查LB上的日志,看看是否真的LB分发请求做的不均衡:
以上就是LB上的统计,第三列是请求数,行是机器(3台nginx对应这3行),可见数量都是1364,分发很正常!可是为什么从nginx机器上看load就是不一样呢,有没有可能是最近才不均衡的,这个统计值只是混合了历史数据,并不反映当前情况?于是重启了LB,过一会再次拿这个统计数据:
也还是均衡的,完全符合round robin。LB的嫌疑可以排除。。。。。。了吗?
没做过公有云,可能还不明白客户的需求和这类问题的处理方式。人家都用你云平台了,整个系统都跑在云上,你一句”我检查下来证明我们没问题“,客户很可能是不会答应的。就算LB没问题,你怎么知道不是你云平台上其他问题导致的?
我们建议客户增加机器,因为来不及处理请求,就增加机器来分担,这也是很直观的道理。不过客户不干了,他们表示:“我们一直用这套系统,最近业务量也没增加,为什么忽然就不行了呢?是不是你们云主机不行?”
我们云主机当然也没做过任何更改。可是显然,在这个争论点上,双方的矛盾是不可调和的。差不多是这么个情况:
这个时候,我们忽然发现漏掉了很简单的一个线索:直接访问nginx机器会正常吗?结果测试发现,直接访问nginx的情况跟通过LB差不多,一样抛出504。就是说是否通过LB,一样有问题。再一次排除了LB的嫌疑。
于是排查方向转到了系统层面:搞清楚nginx机器上究竟是什么原因导致了load异常。这次,我们就没办法用tcpdump结合wireshark去排查了,因为主机层面的排查,不是网络分析,而是要确定:
1. 分析所有进程,找到具体是哪个进程引起了load升高?
2. 分析进程细节,找到是什么bug使该进程变成问题进程?
通过top命令,很容易的找到了消耗CPU资源最多的进程,我们发现是php-fpm进程。因为客户的电商程序框架本身是基于php开发的,所以需要php解释器来运行程序。又因为nginx本身不能处理php,所以需要结合php-fpm才能正常工作。
到这里,其实下一步就是客户自己去查php程序代码了对吗?答案当然是too young too simple。结合前面说过的原因,作为一家以服务取胜的云商,我们必定有业务把问题解决到底。因为即使你不这样做,客户也不会放过你,哈哈。开个玩笑,其实这个时候我们自己也很好奇了,虽然问题已经证明不是LB导致的,我们其实也非常想弄清楚究竟是什么原因。
因为是进程问题导致了load高,这种问题解决起来大致有两个思路:
2. 黑盒检查:不管代码怎么回事,我们从程序的外部表现来分析,寻求根因
因为核心代码部分是客户的国外合作方提供的,客户自己并不十分清楚这部分逻辑,所以第一条路是走不通了。那么自然的,我们要走第二条路。跟网络排查用tcpdump类似,进程排查常用手段之一是strace。通过strace,我们可以查到syscall(系统调用)级别,无论是调用文件不存在,还是系统调用失败等各种原因,都可以在strace的帮助下显形。
用strace命令,对php-fpm进程号进行监测,果然发现一个非常奇怪的现象:整屏刷的都是gettimeofday()这一个系统调用:
然后再次用strace命令,结合-cp参数来统计每个系统调用消耗的时间和次数,看看这个奇怪的系统调用占用了多少份额。结果发现,gettimeofday()占用了这个进程的高达97.91%的运行时间!看下图:
5秒之内,gettimeofday()居然调用了2万多次,而其他正常的系统调用(例如poll()、read()等)只有几十上百次。也相当于50倍的资源都浪费在非业务操作上!难怪进程这么卡,全都花在收集系统时间数据上了。
为什么会有这么多gettimeofday()的调用呢?我们去网上用"php-fpm gettimeofday”作为关键字搜索了一下,居然真的能找到类似的不少案例,也都是导致了CPU异常,原因多半是启用了某些性能侦测软件,比如newrelic。当然在此之前,我们压根不知道什么是newrelic。然后在客户服务器上搜了一下,果然有运行newrelic。看下图:
我们立刻把这个情况告诉客户,对方表情微妙,犹豫了1000毫秒后回答说,确实他们的国外团队最近好像在做程序性能调优的事情,有可能是他们把newrelic部署到服务器上去了。
好吧,一起典型的乌龙事件。不过对见怪不怪的技术支持团队来说,心里早已云淡风轻了。接下来就是客户卸载newrelic,应用立刻恢复正常。用strace再次检查,显示系统调用也恢复正常:
read()和poll()系统调用占用了一半以上的进程CPU时间,而gettimeofday()降低到不足1%,所以已经彻底解决问题了。
这个快乐的电商网站又可以快乐的运行下去了,我们的脸上也忍不住露出了会心的笑容。
用了负载均衡,反而更加不均衡?这样的问题,吸引我们刨根问底,最终找到根因。其实做技术的人,心里都应该有这样的劲头。因为如果真的每次问题我们找到根因并解决,技术水平的提升会是很快的。虽然甲方和乙方之间貌似有“模糊地带”,但做技术不应该有“模糊地带”。如果有,就把它搞清楚,不含糊。