php-fpm详解
感谢作者,原文链接:http://www.stelin.me/2017/06/09/php-fpm%E8%AF%A6%E8%A7%A3#%E4%B8%89%E7%A7%8D%E5%B7%A5%E4%BD%9C%E6%A8%A1%E5%BC%8F
LNMP架构运行原理
nginx
Nginx 采用单线程,非阻塞,异步 IO 的工作模型。在接到 php的脚本 请求后,nginx通过,fastcgi_pass 指令将请求传递给后端 php-fpm 的 worker 进程处理,在此过程中, nginx 做了各种超时机制、缓存机制、 buffer 机制和长连接机制等来保障与后端的 php-fpm 能够良性高效的合作。在超时机制方面控制 nginx 对后端 php 的等待时间,通过各种 timeout 指令进行控制,例如:
// 后端链接时间
fastcgi_connect_timeout
// 数据发送时间,两次成功发送时间差,不是整个发送时间
fastcgi_send_timeout
// 数据接收时间,两次成功接收时间差,不是整个接收时间
fastcgi_read_timeout
502
502 Bad Gateway
第一种情况-[fpm超时]
php-fpm的worker进程 执行php程序脚本时,超过了配置的最长执行时间, master 进程将 worker 进程杀掉,直接返回 502 。返回 502 后 nginx 对应的 error 日志是 104: Connection reset by peer。对应的 php 执行时间的配置如下,一些版本中 php-fpm 的配置会覆盖 php.ini 的配置,使 php.ini 的配置不起作用。
php.ini 中默认 30s : max_execution_time = php-fpm 中: request_terminate_timeout =
// 以PHP-FPM的request_terminate_timeout=30秒时为例,报502 Bad Gateway错误 nginx
2013/09/19 01:09:00 [error] 27600#0: *78887 recv() failed (104: Connection reset by peer) while reading response header from upstream,
client: 192.168.1.101, server: test.com, request: "POST /index.php HTTP/1.1", upstream: "fastcgi://unix:/dev/shm/php-fcgi.sock:",
host: "test.com", referrer: "http://test.com/index.php"
// PHP-FPM报错日志:
WARNING: child 25708 exited on signal 15 (SIGTERM) after 21008.883410 seconds from start
第二种情况-[nginx连接已满]
连接请求数( accpet 之前)超出了端口所能监听的 tcp 连接的最大值( backlog 的值),进不了fpm 等待accept的链接队列,直接返回 502 ,这里可能会产生 tcp 重传;返回 502 后 nginx 对应的 error 日志是 111: Connection refused
backlog 的值是半连接和全连接的总和,他的存在也有短时间缓冲解耦 nginx 请求与fpm 处理的作用,半连接指收到了 syn 请求, 3 次握手尚未建立,全连接指的是 3 次握手已经成功,不过尚未被 accpet 的请求, fpm 里面有调节的参数,如果 fpm 的参数设置为 -1 ,则默认走的是系统内核参数 net.core.somaxconn 的设置值,如果不设置可以在 /proc/sys/net/core/somaxconn 里查看,默认值是 128 ,所以在连接请求较高的业务里要增大这个值。
优化建议
502主要从php-fpm的配置方考虑,根据服务器情况, 适量增大 php-fpm 的工作进程数,适当增加 php 的执行时间,适当增加 backlog 值 。 php 的工作进程数也不是越大越好,这种进程模型运行时间长了占的内存会增大,一般一个 php 进程是占到 30M 左右的内存,开多少合适自己算吧, nginx 的 worker 进程一般也能跑到 30M 的内存,综合计算一下; php 的执行时间可以根据你的服务标准来设定,超过服务时间浏览器返回的是 502 错误,这个按照实际的情况处理吧,反正我是觉得执行的慢有返回结果总比直接返回 502 错误的强;至于 backlog 值,当程序写的比较好时,建议设置其数量为 php 工作进程的 1 到 2 倍。
504
504 Gateway Time-out
第一种情况-[nginx连接超时]
php 的 worker 进程池处理慢,无法尽快处理等待 accept 的链接队列,导致 3 次握手后的链接队列长时间没有被 accept , nginx 链接等待超时;返回 504 后 nginx 对应的 error 日志是 110: Connection timed out
第二种情况-[nginx超时]
后端php-fpm执行脚本的时间太长,超过了nginx配置的超时机制,这个时候也是会报出504错误的。
// 以Nginx超时时间为90秒,PHP-FPM超时时间为300秒为例,报504 Gateway Timeout
2013/09/19 00:55:51 [error] 27600#0: *78877 upstream timed out (110: Connection timed out) while reading response header from upstream,
client: 192.168.1.101, server: test.com, request: "POST /index.php HTTP/1.1", upstream: "fastcgi://unix:/dev/shm/php-fcgi.sock:",
host: "test.com", referrer: "http://test.com/index.php"
优化建议
504主要从nginx的配置方考虑,根据业务情况配置好超时的各种机制。在配置过程中,比如遇到大并发或者是特殊业务的场景,不合理的fd、buffer等设置也会带来5XX错误,比如说大并发连接的业务要增大系统和单个程序的fd数量,如果是上传业务要增大头buffer等,这些要视情况而做优化
pm = static | dynamic | ondemand 静态池、服务优先、内存优先
pm.max_children = 256 开启的最大 php 进程数
pm.max_requests = 1024 在执行了 1024 个请求后重启 worker 进程
web 服务的机器是 12 核 cpu 、 16G 内存, nginx 开启 12 个 worker 进程, php 开启 256 个进程,
跑起来后每个进程大概占用 30M 内存,也就是( 256+12 ) *30=8G ,
这种静态池的配置大大减少了prefork 进程带来的开销,RT时间100ms以内的占到85%(这个与程序写的如何有关).
php-fpm
CGI
全称:common gateway interface,通用网关接口,用户将web server传来的数据格式化成标准格式,方便CGI程序的编写者。就是一种网络数据传输协议(json),web server(nginx)只是内容的分发者。 如果请求/index.html,那么web server会去文件系统中找到这个文件,发送给浏览器,这里分发的是静态数据。如果现在请求的是/index.php,根据配置文件,nginx知道这个不是静态文件,需要去找PHP解析器来处理,那么他会把这个请求简单处理后交给PHP解析器。Nginx会传哪些数据给PHP解析器呢?url要有吧,查询字符串也得有吧,POST数据也要有,HTTP header不能少吧,好的,CGI就是规定要传哪些数据、以什么样的格式传递给后方处理这个请求的协议。仔细想想,你在PHP代码中使用的用户从哪里来的。 当web server收到/index.php这个请求后,会启动对应的CGI程序格式化数据格式,将数据和请求信息传给PHP的解析器。接下来PHP解析器会解析php.ini文件,初始化执行环境,然后处理请求,再以规定CGI规定的格式返回处理后的结果,退出进程。web server再把结果返回给浏览器
FAST-CGI
Fastcgi是用来提高CGI程序性能的,fastcgi其实就是实现cgi的程序。标准的CGI对每个请求都会执行上面这些步骤,所以处理每个请求的时间会比较长。这明显不合理嘛!Fastcgi会先启一个master,解析配置文件,初始化执行环境,然后再启动多个worker。当请求过来时,master会传递给一个worker,然后立即可以接受下一个请求。这样就避免了重复的劳动,效率自然是高。而且当worker不够用时,master可以根据配置预先启动几个worker等着;当然空闲worker太多时,也会停掉一些,这样就提高了性能,也节约了资源。这就是fastcgi的对进程的管理。
工作原理
fpm全名是FastCGI进程管理器,fpm启动后会先读php.ini,然后再读相应的conf配置文件,conf配置可以覆盖php.ini的配置。 启动fpm之后,会创建一个master进程,监听9000端口(可配置),master进程又会根据fpm.conf/www.conf去创建若干子进程,子进程用于处理实际的业务。 当有客户端(比如nginx)来连接9000端口时,空闲子进程会自己去accept,如果子进程全部处于忙碌状态,新进的待accept的连接会被master放进队列里,等待fpm子进程空闲;这个存放待accept的半连接的队列有多长,由listen.backlog配置。
三种工作模式
php-fpm(FastCGI 进程管理器)用于替换 PHP FastCGI 的大部分附加功能,对于高负载网站非常有用。php-fpm进程管理一共有三种模式:ondemand、static、dynamic(静态池、服务优先、内存优先),我们可以在同一个fpm的master配置三种模式。
php-fpm的工作模式和nginx类似,都是一个master,多个worker模型。每个worker都在accept本机pool内的监听套接字。
ondemand
在php-fpm启动的时候,不会给这个pool启动任何一个worker,是按需启动,当有连接过来才会启动。php-fpm配置如下:
- 新建worker的触发条件是连接的到来,而不是实际的请求(例如,只进行连接比如telnet,不发请求数据也会新建worker)
- worker的数量受限于pm.max_children配置,同时受限全局配置process.max(准确的说,三种模式都受限于全局配置)
- 1秒定时器作用,找到空闲worker,如果空闲时间超过pm.process_idle_timeout大小,关闭。这个机制可能会关闭所有的worker。
优点 按流量需求创建,不浪费系统资源(在硬件如此便宜的时代,这个优点略显鸡肋)
缺点 由于php-fpm是短连接的,所以每次请求都会先建立连接,建立连接的过程必然会触发上图的执行步骤,所以,在大流量的系统上master进程会变得繁忙,占用系统cpu资源,不适合大流量环境的部署
dynamic
在php-fpm启动时,会初始启动一些worker,在运行过程中动态调整worker数量,worker的数量受限于pm.max_children配置,同时受限全局配置process.max,配置如下:
1秒定时器作用
检查空闲worker数量,按照一定策略动态调整worker数量,增加或减少。增加时,worker最大数量>=max_children· <=全局process.max;减少时,只有idle >pm.max_spare_servers时才会关闭一个空闲worker。 idle > pm.max_spare_servers,关闭启动时间最长的一个worker,结束本次处理 idle >= pm.max_children,打印WARNING日志,结束本次处理 idle <pm.max_children,计算一个num值,然后启动num个worker,结束本次处理
优点
动态扩容,不浪费系统资源,master进程设置的1秒定时器对系统的影响忽略不计;
缺点 如果所有worker都在工作,新的请求到来只能等待master在1秒定时器内再新建一个worker,这时可能最长等待1s
static
php-fpm启动采用固定大小数量的worker,在运行期间也不会扩容,虽然也有1秒的定时器,仅限于统计一些状态信息,例如空闲worker个数,活动worker个数,网络连接队列长度等信息。
pm.max_children> 0 必须配置,且只有这一个参数生效
优点缺点
如果配置成static,只需要考虑max_children的数量,数量取决于cpu的个数和应用的响应时间。一次性启动固定大小进程浪费系统资源。
配置优化
TCP切换为UNIX域套接字
UNIX域套接字相比TCP套接字在loopback接口上能提供更好的性能(更少的数据拷贝和上下文切换)。 但有一点需要牢记:仅运行在同一台服务器上的程序可以访问UNIX域套接字(显然没有网络支持)。
upstream backend
{
# UNIX domain sockets
server unix:/var/run/fastcgi.sock;
# TCP sockets
# server 127.0.0.1:8080;
}
调整工作进程数
现代计算机硬件是多处理器的,NGINX可以利用多物理或虚拟处理器。
多数情况下,你的Web服务器都不会配置为处理多种任务(比如作为Web服务器提供服务的同时也是一个打印服务器),你可以配置NGINX使用所有可用的处理器,NGINX工作进程并不是多线程的。
将nginx.conf文件中work_processes的值设置为机器的处理器核数。同时,增大worker_connections(每个处理器核心可以处理多少个连接)的值,以及将”multi_accept”设置为ON,如果你使用的是Linux,则也使用”epoll”:
// 机器处理器个数
cat /proc/cpuinfo | grep processor
# We have 16 cores
worker_processes 16;
# connections per worker
events
{
use epoll;
worker_connections 4096;
multi_accept on;
}
设置upstream负载均衡
同一台机器上多个upstream后端相比单个upstream后端能够带来更高的吞吐量。如果你想支持最大1000个PHP-fpm子进程(children),可以将该数字平均分配到两个upstream后端,各自处理500个PHP-fpm子进程:
upstream backend {
server unix:/var/run/php5-fpm.sock1 weight=100 max_fails=5 fail_timeout=5;
server unix:/var/run/php5-fpm.sock2 weight=100 max_fails=5 fail_timeout=5;
}
禁用访问日志文件
这一点影响较大,因为高流量站点上的日志文件涉及大量必须在所有线程之间同步的IO操作。
access_log off;
log_not_found off;
error_log /var/log/nginx-error.log warn;
// 如果不能关闭,新增日志缓冲区,减少频繁IO操作
access_log /var/log/nginx/access.log main buffer=16k;
启用GZip
gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_min_length 1100;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
缓存被频繁访问的文件相关的信息
open_file_cache max=200000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
调整客户端超时时间
client_max_body_size 500M;
client_body_buffer_size 1m;
client_body_timeout 15;
client_header_timeout 15;
keepalive_timeout 2 2;
send_timeout 15;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
调整输出缓冲区大小
fastcgi_buffers 256 16k;
fastcgi_buffer_size 128k;
fastcgi_connect_timeout 3s;
fastcgi_send_timeout 120s;
fastcgi_read_timeout 120s;
reset_timedout_connection on;
server_names_hash_bucket_size 100;
/etc/sysctl.conf调优
# Recycle Zombie connections
net.inet.tcp.fast_finwait2_recycle=1
net.inet.tcp.maxtcptw=200000
# Increase number of files
kern.maxfiles=65535
kern.maxfilesperproc=16384
# Increase page share factor per process
vm.pmap.pv_entry_max=54272521
vm.pmap.shpgperproc=20000
# Increase number of connections
vfs.vmiodirenable=1
kern.ipc.somaxconn=3240000
net.inet.tcp.rfc1323=1
net.inet.tcp.delayed_ack=0
net.inet.tcp.restrict_rst=1
kern.ipc.maxsockbuf=2097152
kern.ipc.shmmax=268435456
# Host cache
net.inet.tcp.hostcache.hashsize=4096
net.inet.tcp.hostcache.cachelimit=131072
net.inet.tcp.hostcache.bucketlimit=120
# Increase number of ports
net.inet.ip.portrange.first=2000
net.inet.ip.portrange.last=100000
net.inet.ip.portrange.hifirst=2000
net.inet.ip.portrange.hilast=100000
kern.ipc.semvmx=131068
# Disable Ping-flood attacks
net.inet.tcp.msl=2000
net.inet.icmp.bmcastecho=1
net.inet.icmp.icmplim=1
net.inet.tcp.blackhole=2
net.inet.udp.blackhole=1
监控
持续监控打开连接的数目,空闲内存以及等待状态线程的数目。设置警报在超出阈值时通知你。你可以自己构建这些警报,或者使用类似ServerDensity的东西。确认安装了NGINX的stub_status模块。该模块默认并不会编译进NGINX,所以可能你需要重新编译NGINX
./configure --with-http_ssl_module --with-http_stub_status_module --without-mail_pop3_module