1. SAPI全称
Server Application Programming Interface: the API used by PHP to interface with Web Servers
https://github.com/laruence/phpbook/blob/master/1.1.md
2. PHP-FPM 工作细节
PHP FastCGI进程管理器PHP-FPM的架构 - 山上小和尚 - 博客园 一个master进程, 支持多个pool, 每个pool由master进程监听不同的端口, pool中有多个worker进程. 每个worker进程都内置PHP解释器, 并且进程常驻后台, 支持prefork动态增加. 每个worker进程支持在运行时编译脚本,并在内存中缓存生成的opcode来提升性能. 每个worker进程支持配置响应指定请求数后自动重启, master进程会重启挂掉的worker进程. 每个worker进程能保持一个到MySQL/Memcached/Redis的持久连接, 实现"连接池", 避免重复建立连接, 对程序透明. 「解读: 只是一个持久连接吗?是的(其实可以保持多个, 但是对于一个php进程保持多个没有意义, 只要确保持一个即可, 因为针对一个php进程, 请求是串行执行的, 同一时间只要保证有一个连接可用即可), 参见 连接池 - 数据库短连接&长连接&连接池 - 学习/实践_穿素白衫的中少年的博客-CSDN博客」 使用数据库持久连接时应该设置固定数量的worker进程数, 不要使用动态的prefork模式. 「应是为了避免动态的prefork模式,创建太多的worker进程,导致内存紧张,以及CPU上下文切换频繁,导致性能下降」
经 @syaokun219 和 @IM鑫爷 纠正,以下两句有误: master进程采用epoll模型异步接收和分发请求, listen监听端口, epoll_wait等待连接. 然后分发给对应pool里的worker进程, worker进程accept请求后poll处理连接. 应该是: master进程并不接收和分发请求, 而是worker进程直接accept请求后poll处理. master进程不断调用epoll_wait和getsockopt是用来异步处理信号事件和定时器事件. 补充
在PHP-FPM中,Master进程的主要作用是管理和监控Worker进程,而不直接接收和处理请求。
Worker进程负责实际的请求处理。
以下是Master进程和Worker进程的主要功能:
Master进程:
- 负责启动和管理多个Worker进程。
- 监控Worker进程的运行状态,如果Worker进程异常退出,Master进程会重新启动新的Worker进程来替代。
- 处理信号事件,例如接收到重启、关闭等信号时进行相应的处理。
- 管理共享内存、进程池、连接池等资源,以供Worker进程使用。
- 定时器事件,例如定时清理资源、定时检查Worker进程状态等。
Worker进程:
- 处理实际的请求,接收客户端请求、解析请求、执行PHP脚本、返回响应等。
- 使用epoll来进行事件驱动的异步处理。Worker进程通过epoll_wait监听客户端的连接事件,一旦有新的连接到来,Worker进程就会接收请求并进行处理。这可以提高并发处理能力,避免阻塞。
- 处理具体的业务逻辑,包括数据库操作、文件读写、缓存访问等。
- 与其他Worker进程之间共享资源,例如连接池等。
Master进程主要负责管理和监控Worker进程,而Worker进程负责接收和处理实际的请求。
Worker进程会使用epoll_wait和getsockopt等函数来处理信号事件和定时器事件,这是为了实现异步的信号处理和定时任务。
在PHP-FPM中,Master进程不负责接收请求或将请求分配给Worker进程。
实际上,Master进程与Worker进程之间存在一个Socket通信,当一个请求到达PHP-FPM服务器时,操作系统会将请求直接发送给Worker进程,而不经过Master进程。Master进程的主要职责是管理和监控Worker进程,而不直接参与请求的处理。
Master进程会启动一组Worker进程,并监听一个特定的Socket。当有请求到达时,操作系统会选择一个可用的Worker进程来接收和处理该请求。这个过程是由操作系统的网络层来完成的,Master进程并不直接涉及请求的分配。
在PHP-FPM中,请求的负载均衡和分配是由操作系统的网络层来完成的,而不是由PHP-FPM的Master进程或Worker进程直接控制。
当一个请求到达PHP-FPM服务器时,请求会通过操作系统的网络层进行处理。
操作系统会根据某种负载均衡算法(如轮询、最少连接等)选择一个可用的Worker进程来接收和处理该请求。
具体的负载均衡和请求分配是由操作系统的网络层实现的,使用的机制可能是操作系统提供的套接字(Socket)或其他网络技术。操作系统会维护一个连接池或请求队列,根据负载均衡算法将请求分配给可用的Worker进程。
PHP-FPM的Master进程的主要职责是管理和监控Worker进程,而不直接参与请求的负载均衡和分配。Master进程确保有足够的Worker进程来处理请求,并处理Worker进程的异常情况。Worker进程则负责实际的请求处理。
因此,PHP-FPM依赖于操作系统的网络层来实现请求的负载均衡和分配,而不是由PHP-FPM自身的进程控制来完成。
这里提一下, Nginx也类似, master进程并不处理请求, 而是worker进程直接处理, 不过区别在于Nginx的worker进程是epoll异步处理请求, 而PHP-FPM仍然是poll. --- 被标注部分
如果worker进程不够用, master进程会prefork更多进程, 如果prefork达到了pm.max_children上限, worker进程又全都繁忙, 这时master进程(应该是操作系统?tbd)会把请求,挂起到连接队列backlog里(默认值是511). 1个PHP-FPM工作进程在同一时刻里只能处理1个请求. MySQL的最大连接数max_connections默认是151. 只要PHP-FPM工作进程数不超过151, 就不会出现连接不上MySQL的情况.
[root@ip-172-31-5-63 ~]# ps -ef |grep php
root 890 1 0 7月03 ? 00:09:34 php-fpm: master process (/etc/ph-fpm.conf)
apache 12284 890 0 11月28 ? 01:32:05 php-fpm: pool www
apache 12304 890 0 11月28 ? 01:31:40 php-fpm: pool www
apache 12317 890 0 11月28 ? 01:32:05 php-fpm: pool www
apache 12318 890 0 11月28 ? 01:31:35 php-fpm: pool www
apache 12328 890 0 11月28 ? 01:31:46 php-fpm: pool www
apache 12329 890 0 11月28 ? 01:31:57 php-fpm: pool www
apache 12330 890 0 11月28 ? 01:31:29 php-fpm: pool www
apache 12331 890 0 11月28 ? 01:32:02 php-fpm: pool www
root 17073 17055 0 14:19 ? 00:00:00 php /var/www/web/artisan match schedule_update 0 1
root 17074 17044 1 14:19 ? 00:00:00 php /var/www/web/artisan general_backend backend_list2
root 17086 17054 0 14:19 ? 00:00:00 php /var/www/web/artisan match schedule_update 1 1
root 17089 17049 0 14:19 ? 00:00:00 php /var/www/web/artisan match schedule_update 6 1
root 17090 17046 0 14:19 ? 00:00:00 php /var/www/web/artisan match schedule_update 9 1
root 17091 17047 0 14:19 ? 00:00:00 php /var/www/web/artisan match schedule_update 8 1
root 17092 17059 0 14:19 ? 00:00:00 php /var/www/web/artisan match schedule_update 6 0
root 17093 17045 1 14:19 ? 00:00:00 php /var/www/web/artisan general_backend backend_list1
root 17094 17060 0 14:19 ? 00:00:00 php /var/www/web/artisan match schedule_update 5 0
root 17095 17052 0 14:19 ? 00:00:00 php /var/www/web/artisan match schedule_update 3 1
root 17096 17071 0 14:19 ? 00:00:00 php /var/www/web/artisan email send
root 17097 17053 0 14:19 ? 00:00:00 php /var/www/web/artisan match schedule_update 2 1
root 17098 17056 0 14:19 ? 00:00:00 php /var/www/web/artisan match schedule_update 9 0
root 17099 17065 2 14:19 ? 00:00:00 php /var/www/web/artisan match schedule_update 0 0
root 17100 17063 0 14:19 ? 00:00:00 php /var/www/web/artisan match schedule_update 2 0
root 17101 17048 0 14:19 ? 00:00:00 php /var/www/web/artisan match schedule_update 7 1
root 17102 17057 0 14:19 ? 00:00:00 php /var/www/web/artisan match schedule_update 8 0
root 17103 17051 0 14:19 ? 00:00:00 php /var/www/web/artisan match schedule_update 4 1
root 17104 17050 0 14:19 ? 00:00:00 php /var/www/web/artisan match schedule_update 5 1
root 17106 17067 0 14:19 ? 00:00:00 php /var/www/web/artisan think_data think_list2
root 17108 17068 0 14:19 ? 00:00:00 php /var/www/web/artisan think_data think_list1
root 17109 17058 0 14:19 ? 00:00:00 php /var/www/web/artisan match schedule_update 7 0
root 17110 17064 0 14:19 ? 00:00:00 php /var/www/web/artisan match schedule_update 1 0
root 17112 17062 0 14:19 ? 00:00:00 php /var/www/web/artisan match schedule_update 3 0
root 17113 17061 0 14:19 ? 00:00:00 php /var/www/web/artisan match schedule_update 4 0
root 17114 17070 0 14:19 ? 00:00:00 php /var/www/web/artisan notify_server sync
root 17372 16628 0 14:19 pts/0 00:00:00 grep --color=auto php
apache 18847 890 0 11月27 ? 01:52:12 php-fpm: pool www
apache 18848 890 0 11月27 ? 01:52:08 php-fpm: pool www
apache 18849 890 0 11月27 ? 01:52:10 php-fpm: pool www
apache 18858 890 0 11月27 ? 01:52:28 php-fpm: pool www
apache 18859 890 0 11月27 ? 01:52:37 php-fpm: pool www
apache 18860 890 0 11月27 ? 01:52:49 php-fpm: pool www
apache 18861 890 0 11月27 ? 01:52:45 php-fpm: pool www
apache 18862 890 0 11月27 ? 01:51:59 php-fpm: pool www
apache 18872 890 0 11月27 ? 01:52:24 php-fpm: pool www
apache 18873 890 0 11月27 ? 01:52:35 php-fpm: pool www
apache 18874 890 0 11月27 ? 01:52:13 php-fpm: pool www
apache 22734 890 0 12月07 ? 00:12:48 php-fpm: pool www
apache 22764 890 0 12月07 ? 00:12:46 php-fpm: pool www
apache 22799 890 0 12月07 ? 00:12:43 php-fpm: pool www
apache 22800 890 0 12月07 ? 00:12:44 php-fpm: pool www
apache 22877 890 0 12月07 ? 00:12:49 php-fpm: pool www
apache 22901 890 0 12月07 ? 00:12:47 php-fpm: pool www
apache 22902 890 0 12月07 ? 00:12:47 php-fpm: pool www
apache 22917 890 0 12月07 ? 00:12:49 php-fpm: pool www
apache 22918 890 0 12月07 ? 00:12:50 php-fpm: pool www
apache 22919 890 0 12月07 ? 00:12:43 php-fpm: pool www
apache 22920 890 0 12月07 ? 00:12:41 php-fpm: pool www
apache 22947 890 0 12月07 ? 00:12:44 php-fpm: pool www
apache 22948 890 0 12月07 ? 00:12:45 php-fpm: pool www
apache 22949 890 0 12月07 ? 00:12:42 php-fpm: pool www
apache 22950 890 0 12月07 ? 00:12:49 php-fpm: pool www
apache 32646 890 0 12月05 ? 00:24:24 php-fpm: pool www
[root@ip-172-31-5-63 ~]#
而且正常情况下, 也不需要开启那么多的PHP-FPM工作进程, 比如,4个PHP-FPM进程就能跑满4个核心的CPU, 那么你开40个PHP-FPM进程也没有任何意义, 只会占用更多的内存, 造成更多的CPU上下文切换, 性能反而更差. 为了减少每个请求都重复建立和释放连接的开销, 可以开启持久连接, 一个PHP-FPM进程保持一个到MySQL的长连接, 实现透明的"连接池".
Nginx跟PHP-FPM分开, 其实是很好的解耦, PHP-FPM专门负责处理PHP请求, 一个页面对应一个PHP请求, 页面中所有静态资源的请求都由Nginx来处理, 这样就实现了动静分离, 而Nginx最擅长的就是处理高并发. PHP-FPM是一个多进程的FastCGI服务, 类似Apache的prefork的进程模型, 对于只处理PHP请求来说, 这种模型是很高效很稳定的. 不像Apache(libphp.so), 一个页面, 要处理多个请求, 包括图片, 样式表, JS脚本, PHP脚本等.
php-fpm从5.3开始才进入PHP源代码主干, 之前版本没有php-fpm. 那时的spawn-fcgi是一个需要调用php-cgi的FastCGI进程管理器, 另外像Apache的mod_fcgid和IIS的PHP Manager也需要调用php-cgi进程, 但php-fpm则根本不依赖php-cgi, 完全独立运行, 也不依赖php(cli)命令行解释器. 因为php-fpm是一个内置了php解释器的FastCGI服务, 启动时能够自行读取php.ini配置和php-fpm.conf配置. 「所以,我们通常只要下载安装php-fpm即可,不需要再单独安装PHP」
个人认为, PHP-FPM工作进程数, 设置为2倍CPU核心数就足够了.「这是经过实践得出的一个适合的数字,针对具体的业务场景,未必是最佳,但是不差」 毕竟, Nginx和MySQL以及系统同样要消耗CPU.「这句话的前提是Nginx和MYSQL,与PHP-FPM都部署在同一台机器上,现实中,Nginx和MySQL通常是分开的,至于PHP-FPM要不要单独部署,应视情况而定」 根据服务器内存,来设置PHP-FPM进程数非常不合理, 把内存分配给MySQL, Memcached, Redis, Linux磁盘缓存(buffers/cache)这些服务显然更合适. 过多的PHP-FPM进程反而会增加CPU上下文切换的开销. PHP代码中应该尽量避免curl或者file_get_contents这些可能会产生较长网络I/O耗时的代码.
「但是实际是,很多服务涉及到网络请求和接收请求的参数,通常会用到curl和file_get_contents,但是应知道,这些网络I/O操作,确实可能会产生较长耗时」
注意设置CURLOPT_CONNECTTIMEOUT_MS超时时间, 避免进程被长时间阻塞. 如果要异步执行耗时较长的任务, 可以 pclose(popen('/path/to/task.php &', 'r')); 打开一个进程来处理, 或者借助消息队列, 总之就是要尽量避免阻塞到PHP-FPM工作进程.
「异步执行耗时较长的任务,通过方式一,以往的工作中没见过,通常采用的是方式二
原因推测:方式一依然要消耗服务器的各种资源,而且存在消息/数据丢失的可能,而且代码耦合,也不利于服务扩展。
方式二,则可以使用独立的服务部署,解耦,适合扩展,降低或保证消息/数据不会丢失,方案很成熟」
在php-fpm.conf中把request_slowlog_timeout设为1秒, 在slowlog中查看是否有耗时超过1秒的代码. 优化代码, 能够为所有PHP-FPM工作进程减负,这个才是提高性能的根本方法.
能让CPU满负荷运行的操作可以视为CPU密集型操作. curl和下载则是典型的I/O密集型操作, 因为耗时主要发生在网络I/O和磁盘I/O. 需要PHP认证的下载操作可以委托为Nginx的AIO线程池: header("X-Accel-Redirect: $file_path"); 至于curl操作, 比如可以建立一个监听9001端口的名为upload的PHP-FPM进程池(pool), 专门负责处理curl操作(通过Nginx分发), 避免curl操作阻塞到监听9000端口的计算密集的www进程池. 这时upload进程池多开点进程也无所谓.
这里想达到的效果:
就是将计算密集型业务和I/O密集型业务分开,达到互不影响,从而达到资源的最大有效利用率。
另外,下面这篇文章,也可以佐证上面红色标注部分 21 | 为什么用了负载均衡更加不均衡?-极客时间 |