前几天看到一篇博客,提到php所在服务器在大并发状况下,频繁建立TCP短链接,而其所在服务器的2MSL时间过长,致使没有端口可用,系统没法建立TCP socket,而大量报错。博主在后面给的解决方案是减小2MSL的时间,尽快清除TIME_WAIT状态的TCP链接,回收端口。同时,文章结尾写了不用长链接的理由,但这真的是最好的解决办法吗?有其余办法能够更好的作法吗?php
相似经历
之因此多这篇文章兴趣这么高,是由于在前段时间,末学也经历了一件相似的优化历程,先简短的描述下咱们的服务器架构。如图
html

一款webgame 粗略架构图mysql
想必看到这幅架构图的同窗,都比较熟悉吧,也比较简单。“最前面”的nginx 反代负责与玩家的http请求通信,这里是长链接。在其与后端游戏大区通信时,使用了短链接,也就是意味着,每处理用户的一个http请求,都要从新与后端的nginx创建一次TCP(http)请求,后端nginx处理完以后就关闭。后端的nginx与php在同一台服务器上,通信配置以下:linux
03 |
server 10.10.10.1 max_fails=2 fail_timeout=30s; #app1 |
04 |
server 10.10.10.2 max_fails=2 fail_timeout=30s; #app2 |
08 |
// Nginx 默认配置 http://trac.nginx.org/nginx/browser/nginx/trunk/conf/nginx.conf |
11 |
fastcgi_pass 127.0.0.1:9000; |
12 |
fastcgi_index index.php; |
13 |
fastcgi_param SCRIPT_FILENAME /scripts $fastcgi_script_name ; |
14 |
include fastcgi_params; |
18 |
//PHP-FPM 默认配置 https://github.com/php/php-src/blob/master/sapi/fpm/php-fpm.conf.in |
19 |
; The address on which to accept FastCGI requests. |
21 |
; 'ip.add.re.ss:port' - to listen on a TCP socket to a specific address on a specific port; |
22 |
; 'port' - to listen on a TCP socket to all addresses on a specific port; |
23 |
; '/path/to/unix/socket' - to listen on a unix socket. |
24 |
; Note: This value is mandatory. |
25 |
listen = 127.0.0.1:9000 |
在这个架构图中,反代承担这承上启下的做用,前面是用户,后面是web app服务器,长链接用户,短链接后端。若用户数在1W人,1W人与反代的链接,在反代服务器上都用80端口。但这1W人的每个请求,反代都要从新使用服务器的端口与后端nginx的80建立tcp socket,来处理请求。若2MSL时间为1分钟以上,同时,玩家两次请求间隔很短,想咱们游戏的服务器,两次请求间隔大约5秒(甚至2-3秒)。那么反代的端口,5秒用1W个,10秒2W,15秒3W,20秒4W,25秒5W,30秒6W!!!,前面已经用掉的端口还没被回收掉。那么将会有不少的用户请求被nginx反代接收以后,因没有端口资源而没法与后端nginx建立tcp链接。。。以游戏的其中一个大区为例:
nginx
关于这个问题,末学在新浪微博上请教过其余大牛,其中tengine负责人淘叔度前辈以及@120斤的大青蛙前辈告诉我,nginx将在1.1.4版本开始支持ngx_http_upstream_keepalive,3月1号左右,是1.1.16 dev版本,咱们的运维团队稍微因为几天,以后在部分小区的服务器上适用了1.1.16,效果不错,Nginx反代上与目标服务器的80端口的TCP链接中处于ESTABLISHED状态的很是多,TIME_WAIT的几乎没有,很稳定。咱们几乎计划为了这个性能的提高,决定用dev版了。接着nginx1.2Stable version也出来了,那么也就用这个版本了。至于反代上nginx与后端app server创建TCP链接个数,也就是端口数(满载状况下,维持ESTABLISHED状态)的为nginx worker_processes * ((upstream1 server * keepalive参数数量) + (upstream2 server * keepalive参数数量) + …),并很少。git
如上,端口占用数少了,那么还有其余有点吗?先看一副 socket建立,监听、数据接收发送、关闭与客户端链接的socket流程图:

如上图,nginx反代跟后端nginx的形式跟php-fpm与mysqld socket的模型同样的。
nginx反代跟php-fpm 相对来讲,就是客户端,这种作法,他们的每一个新socket 请求的发起,都会跟着左边的流程走一遍,一遍又一遍。。。。每次的socket建立,端口等资源申请,与服务端的三次握手,关闭时的四次握手,端口的回收。。。。
改用长链接以后,以下图:

只有php-fpm(或者nginx反代)的子进程分别与mysqld建立1次TCP链接,以后都是send、recv数据的事情了,端口占用也是为数很少的几个(每一个fpm子进程将与mysqld维持一个TCP长链接)。程序员
一样,后端服务器的nginx与php-fpm的通信也是如此,只是请求两没有反代那么大。区别就是IP地址是回环地址127.0.0.1。
既然是回环地址,那么两个服务都是在同一台机器上跑的,既然是同一台机器,为什么不用进程间通信的socket–unix domain socket呢?
socket是神马?摘抄一段描述:github
Socket 能够被定义描述为两个应用通讯通道的端点。一个 Socket 端点能够用 Socket 地址来描述, Socket 地址结构由 IP 地址,端口和使用协议组成( TCP or UDP )。http协议能够经过socket实现,socket在传输层上实现。从这个角度来讲,socket介于应用层和传输层之间。可是socket做为一种进程通讯机制,操做系统分配惟一一个socket号,是依赖于通讯协议的,可是这个通讯协议不只仅是 tcp或udp,也能够是其它协议。web
在同一台服务器上,用tcp socket与unix domain socket有什么区别?
如图所示,对于进程间通信的两个程序,unix domain socket的流程不会走到TCP 那层,直接以文件形式,以stream socket通信。若是是TCP socket,则须要走到IP层。sql
对于非同一台服务器上,TCP socket走的就更多了。

至于localhost\127.0.0.1以及网络IP他们之间的区别,无心中找到一篇博客写的是以mysql做为验证,来讲明localhost不走TCP/IP层,跟127.0.0.1不同。末学认为他理解错了。他的理由以下
(如下截图均在linux上,windows的没有unix domain socket)
mysql链接本机时,不加-h参数:

mysql链接本机时,加-h参数且值是localhost:

mysql链接本机时,加-h参数且值是127.0.0.1:

那位同窗从mysql工具的使用方法、与结果的区别,来理解推导localhost与127.0.0.1的区别,这从方向上就存在问题,我更相信,这是mysql这个程序本身的行为,遇到-h参数没加,或者-h参数的值不是IP形式,且my.cnf里指定mysql的socket路径时,则直接使用unix domain socket来链接服务器,固然,这也是个人猜想,没有去验证,你们听听就好,别相信。
鉴于末学对以上的理解,将服务器的架构配置变动以下
04 |
//参见nginx官方wiki,记得看E文版,中文版的还没更新 http://wiki.nginx.org/NginxHttpUpstreamModule |
05 |
server 10.10.8.97 max_fails=2 fail_timeout=30s; #app1 |
06 |
server 10.10.8.99 max_fails=2 fail_timeout=30s; #app2 |
07 |
server 10.10.8.85 max_fails=2 fail_timeout=30s; #app3 |
12 |
location ~ ^([^.]+\.php)($|/.*) { |
13 |
fastcgi_pass unix:/ var /run/php5-fpm.sock; |
14 |
fastcgi_index index.php; |
15 |
include fastcgi_params; |
19 |
; Note: This value is mandatory. |
20 |
listen = / var /run/php5-fpm.sock //与nginx 的fastcgi_pass的路径一致便可,目录要有相应读写权限 |
至此,优化还为完毕,若php-fpm与mysql使用mysql_pconnect的话,那么php-fpm的子进程生成模式最好用static模式,若为dynamic模式,可能会出现mysql链接数被占满的状况,这也跟mysql服务的链接超时时间有关,适当调整也容易避免。
不过,咱们目前还没用mysql_pconnect,主要缘由是咱们的代码中,有些事务处理开启以后,对于代码的失败处理,忘记写回滚语句,在短链接的状况下,这个链接的销毁,哪怕客户端没提交ROLLBACK或者COMMIT指令,mysql会自动回滚以前的事务。但使用长链接以后,不一样请求会使用同一个MYSQL链接句柄,每一个事务开启都会禁用MYSQL的自动提交,即SET AUTOCOMMIT=0语句,这语句会提交以前的事务。对于咱们代码忘记写回滚,而直接返回结果的状况下,这是会出大问题的,也是咱们目前惟一没有使用MYSQL_pconnect的缘由。(计划近期找到没有写回滚语句的代码,修复,继续使用mysql_pconnect)
其实还有,咱们php-fpm使用了APC来缓存php file,以及 变量数据等,这些也是有优化的地方(若是有时间的话,则待续)。
回过头来再理解下文章开头那位同窗给的解决办法,我仍不能从他给的理由中,理解长链接的缺点,哪怕是解决了TIME_WAIT的问题,但每次建立TCP socket ,链接到服务器时三次握手,关闭TCP socket时的四次握手 这些也是开销。固然,缩短2MSL的时间,也是更好利用服务器资源的一个好方法。
最后,咱们调整优化的服务器架构图以下:

好像有点偏离这篇文章的标题了,其实我更想说我不能理解为啥nginx跟php-fpm给的默认配置中,都是TCP socket通信的,为啥不默认给unix domain socket的默认配置呢?若是说为了方便非同一台服务器时的状况,但给的默认IP也是回环地址呀。
并且,nginx给默认配置中,对于uri请求中的php文件的处理,匹配规则仍是老的,以前发生由于NGINX与PHP的配置而致使的安全问题,虽然不是nginx的错,但nginx也可给出更严谨的范例,但仍没有。
值得欣慰的是,在UBUNTU 12.4中,nginx的默认配置有了很大的改进,不论是匹配uri的规则,仍是nginx与php-fpm的交互方式:
02 |
# fastcgi_split_path_info ^(.+\.php)(/.+)$; //赞1 |
03 |
# # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini |
05 |
# # With php5-cgi alone: |
06 |
# fastcgi_pass 127.0.0.1:9000; //赞3 |
08 |
# fastcgi_pass unix:/ var /run/php5-fpm.sock; //赞3 |
09 |
# fastcgi_index index.php; |
10 |
# include fastcgi_params; |
PS:末学只是个web程序员,这些只是末学学习研究如上知识的总结,理解上不免有错,各位请海涵。