那个夜晚,我用 lumen 框架写了一个抢答系统,没想到,300个用户同时进行抢答的时候,服务器就假死了。

吓得我赶紧连上服务器看了一眼,原来是 CPU的使用率已经达到了 100%

心想,怎么办,活动还在进行着。。。

# 初步解决方案

我第一个想到的解决方案是升级我云服务器的配置,幸好服务器的提供商有提供临时升配的功能,所以把我 2核4GB 的云服务器临时提升至 8核16GB

CPU走势

从云服务器的 CPU 使用率记录中我们可以清晰的看到本次活动发生了些什么。

当用户同时往我的服务器发送请求的时候,几乎使用率达到了 100%。后面我花了近 20 分钟的时间粗暴地”解决“了问题,让活动得以继续进行。但是,尽管是升级配置之后,服务器的 CPU 使用率还是很高啊!所以很疑惑,决定调查清楚

# 补充

弄这么一个抢答系统已经不是我的第一次了,前面的几次也是一样 300 个用户可以同时在线进行抢答,也是用的 php。而且配置都是在 2核4GB。之前都是为了锻炼自己的能力,完全没有利用框架进行开发,无论是前端一块还是后端的那一块。

那么问题就来了,php的框架真的有这么差吗?我是很不相信的,所以一定要找出我哪里做的不好。

# TCP TIME_WAIT

在云服务器的监控中我发现了另外一张图表: TCP连接数 这张图表中有一个数据高的惊人,那就是 TIME_WAIT,但是由于之前对这一块实在很不熟悉,所以决定研究一下。

每一个前端发送给后端的请求都会建立一个 TCP 连接,当这个请求完成之后,服务器会主动关闭 这个连接,但是呢,在真正完全关闭(CLOSED)之前,这些连接会处于一个叫做 TIME_WAIT 的状态。

(本来这里打算稍微解释一下这个状态的,但是感觉可以独立成一篇单独的文章)

一旦TCP连接没有被完全关闭,那就意味着有一个端口被占用着,有一个应用程序可能还没有被释放。你再看看我上面那个图表,那个时刻,竟然有5000+个连接正处于这个状态!怪不得会有用户反馈说连不进我们的系统。

根据 TCP协议 的相关规定,这个状态下的连接要等 4分钟(2MSL) 才会变成完全关闭。

4分钟,这样也太久了吧,300个用户占用了300个连接4分钟,在这4分钟内第二次抢答又占用多了300个连接,这样就一共占用了600个连接,以此类推的话好像很夸张,所以我们要想办法让这个时间变得更短一点,由于我用的是 Windows Server 2019,我们可以修改通过注册表来缓解这个问题。

微软官方的建议是 最低设置为 30 秒

改短的方式是修改注册表项:[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\Tcpip\Parameters] 。找到其中的 TcpTimedWaitDelay,这是一个 DWORD 值,将它设置成十进制的 30 。如果没找到,那就建立一个吧!

TcpTimedWaitDelay

改完之后需要重启服务器(啊,就是关机重启,不是重启iis)

通过这个设置之后,TIME_WAIT 的数量就减少了很多。虽然我感觉可能还有更好的解决方案,但目前基本可以满足需求~之后有时间再深入研究。

# Lumen 的 CPU 占用率

我尝试着用 ab -c 300 -n 300 指令模拟300个用户同时发送请求给我的服务器,一看,其中 php-cgi这个程序的 cpu 使用率最高,而且整体的使用率还是飙到了 100。

你问我的代码到底做了什么使得使用率这么高?oh,很遗憾的是,我只是打印了一句 hello world

这也太夸张了。我在想,是不是我的 php 有什么问题,所以我就单纯的新建了一个文件,直接

<?php
echo "hello world";

然后进行还是一样使用 ab -c 300 -n 300 进行测试。现在我的 CPU 使用率 3% 都不到。

所以我在怀疑是不是框架加载了太多东西的缘故,我尝试在 bootstap/app.php 中初始化完项目之后 die() 掉,再次使用同样的命令进行压测,这次 CPU 的使用率大概在 70% 左右。

看看我在什么地方die掉的

emmmmmmm....不禁产生了深厚的怀疑 laravel 和 lumen 作为 php 各种框架里面的翘楚,难道性能这么差,这里才 300 个用户啊!

我直接在谷歌用 laravel cpu 100 作为关键字搜索了很多遍,都没找到合适的答案,难道大家都不会遇到像我这样的问题吗?

后来,在一些调优的文章里面发现了一个设置,就是开启 opcache,我就怀着忐忑的心情试了一下。

具体的做法是打开 php.ini。在里面找到

[opcache]
; Determines if Zend OPCache is enabled
; 取消注释下面这个配置
opcache.enable=1

; 并且添加这个配置:
zend_extension = php_opcache.dll

保存后重启一下服务器程序。

当启用了 opcache 之后,php程序会提前编译好代码以供运行,每一次代码做了修改,上线的时候需要重启一下服务器程序。如果不想这么麻烦,可以调整 php.ini 中的 opcache.validate_timestamps=1 并且设置 opcache.revalidate_freq=60 让它每 60 秒就扫描一次代码是否发生了变化。

然后还是一样的指令进行压测,这一次,发现服务器的 CPU 使用率,最高也不过 13% 了。

看到这样的结果,十分感人,先记录一下。

这其中一定还有其他可以优化的细节被我忽略了,所以还要继续学习呀~