Mginx/php-fpm关键参数:max_children和backlog与502bad gateway导致原因

Song1260 次浏览0个评论2022年08月02日

背景分析

在高并发或者压测的情况下,网站请求容易出现一种错误 ——502 Bad Gateway。
出现这个错误的原因有很多,但在高并发的情况下,主要原因就在于 php-cgi 的进程数不够用。
对于一般的站点,简化版的请求处理流程图如下:



假如流程 1 的请求数在一瞬间达到一个很高的数值(比如 2000),由于 nginx 的异步非阻塞架构,能从容应对这么多请求同时到达,并将这些请求都转发到了 fpm 主进程。


但 fpm 并不一定能同时处理这么多请求,假如 fpm 只配置了能并发处理 1000 个请求,那么就会导致接近 1000 个请求会被异常返回 502 bad gateway。


此时,就需要调整 fpm 的两个配置参数: max_children 和 listen.backlog 的值

max_children说明

由于我们开启的是 static 模式,所以这个参数表示的就是 fpm 子进程的数量。这个数量不是越多越好,空闲的子进程太多会增加进程管理的开销以及上下文切换的开销。网上推荐的子进程数量计算公式为:
n = M / (m * 1.2)

其中,M 是 PHP 能利用的内存数量。m 是每个 PHP 子进程平均使用的内存数量。
一般来讲,我们的 m 在 20Mb 到 30Mb 之间,假设取最大值 30Mb,M 取 8G,那么可以计算得到子进程数量 n 约等于 228。由于我们是按 m 取较大值算的,所以一般我们可以设置 n 为 256 左右。也就是说,此时,fpm 可以同时处理 256 个请求(这里的 “同时” 其实并不是真实的并发,受机器 CPU 核数的限制,其实真正的并发处理请求个数也只有最高也只能等于 CPU 核数,但忽略这个影响的话,可以认为机器的 fpm 可以并发处理 256 个请求)。

listen.backlog说明

假如我们的 max_children 配置了 256,那如果此时并发数达到 300,会不会出现 40 多个请求返回 502 错误呢?实际上不会。


首先 TCP 建立连接时要经过 3 次握手,在客户端向服务器发起连接时,对于服务器而言,一个完整的连接建立过程,服务器会经历 2 种 TCP 状态:SYN_ RECEIVED,ESTABELLISHED。对应也会维护两个队列:

1. 一个存放 SYN 的队列(半连接队列);

2. 一个存放已经完成连接的队列(全连接队列)。


当一个连接的状态是 SYN RECEIVED 时,它会被放在 SYN 队列中。当它的状态变为 ESTABLISHED 时,它会被转移到另一个队列。所以后端的应用程序只从已完成的连接的队列中获取请求。

如果一个服务器要处理大量网络连接,且并发性比较高,那么这两个队列长度就非常重要了。

因为,即使服务器的硬件配置非常高,服务器端程序性能很好,但是这两个队列非常小,那么经常会出现客户端连接不上的现象,因为这两个队列一旦满了后,很容易丢包,或者连接被复位。

所以,如果服务器并发访问量非常高,那么这两个队列的设置就非常重要了。

对于以上说到的例子,实际上这 300 个请求会先依次到达全连接队列,然后 fpm 的子进程再依次去消费这个队列中的请求。

由于这个过程耗时很短,所以几乎可以认为是此时有 256 个请求被 fpm 子进程进行处理,然后剩余的 40 多个请求就还是在这个全连接队列中等待被取出消费。

所以此时不会出现 502 的错误返回。

但是如果此时并发请求量不止 300,已经超过了(全连接队列的长度 + max_children)的总和,就会有一部分已连接的请求没办法被及时处理,此时就会出现 502 bad gateway 的错误。

所以高并发情况下 fpm 是否会返回 502 错误,除了 max_children 的大小限制,另外一个限制就是全连接队列的长度,而这个长度配置就是在 fpm.conf 里的 listen.backlog 这个值。

这个值一般默认是 - 1,也就是没限制,但其实由于 linux 系统本身也有一个对应的全连接队列长度的限制,而且 fpm 配置的 backlog 大小不能大于系统配置的大小。

所以假设系统配置的全连接队列大小为 128 时,当 fpm 配置里的值为 - 1 或者是一个大于 128 的值时,此时 backlog 的最大值还是只有 128。也就是说,如果我们要修改 fpm 全连接队列长度,首先要保证系统的全连接队列值足够大。

目前还没发现这个值设置为多少是最合适的,但最好设置为大于 1024 的值。同时这个值也不是越大越好,因为设置大了之后,虽然可以一定程度上避免出现 502 错误,但由于 nginx 有响应超时时间的设置,如果 fpm 处理不过来,nginx 那边等待超时,断开连接,就会报 504 gateway timeout 的错。

优化操作

一、max_children 与 backlog 的合适大小
  • 1、对于 8G 的机器,max_children 取 256 应该是比较合适的,如果内存较大,这个值可以对应的增大一些。
  • 2、至于 backlog 的值,一般来讲,大于 1024 即可。后续如果要改的话,可以把当前值加倍增大,保持数值是 2 的指数最好,因为系统最终也会将这个值转为 2 的指数倍。


二、如何改 max_children 大小

  • 1、打开fpm配置文件
  • 2、找到pm.max_children这个配置,将等于号后面的值改为需要的值之后,保存配置。
  • 3、重启fpm,执行 ps -ef|grep fpm|grep -v master|grep -v grep |wc -l 可以查看当前fpm子进程的数量,确认是不是已经改过来了

三、如何改 backlog 大小

  • 1、cat /proc/sys/net/core/somaxconn (先查看系统的连接队列长度限制,如果太小,需要先通过下面的步骤2、3修改系统的这个值,否则就跳过这两步)
  •  2、vim /etc/sysctl.conf(在文件中新增一行net.core.somaxconn=x,x为希望改到的队列长度值,如果里面已经有这一行配置,则直接改这行配置的值)
  • 3、sysctl -p(重新加载系统配置)
  • 4、打开fpm配置文件
  • 5、找到listen.backlog这个配置,将等于号后面的值改为需要的值。
  • 6、重启fpm。


四、避免被同一个 ip 频繁请求攻击导致无法正常访问的优化建议


当大量非法请求通过 nginx 涌入到 fpm 时,正常用户的访问请求就会因为无法被 fpm 消费导致返回 502 或者接口一直处于加载中。所以,为了避免这种情况,最好的方式就是在 nginx 层就直接拒绝掉这个非法 ip 的请求,不让其到达 fpm,浪费正常的 fpm 子进程资源。
Nginx 有两个配置,可以用于限制每一个 IP 地址在一段时间内的访问次数:limit_req_zone 和 limit_req。具体的配置方法如下:

  • 1、在nginx.conf的http模块里,新增 limit_req_zone$binary_remote_addr zone=allips:10m rate=30r/s;
  • 2、在nginx.conf的server模块里,新增 limit_req zone=allips burst=5;
  • 3、nginx -s reload 平滑重启nginx。


这样,就实现了每个 ip 每秒最多能发起 35 个请求的限制,超过的请求,nginx 会直接返回 503Service Temporarily Unavailable(服务暂时不可用)错误。这样就既保证了 fpm 不会在短时间内接收到同一个 ip 的高并发请求,也可以保证正常 ip 的用户可以访问接口。

转自链接:https://learnku.com/articles/54341

提交评论

请登录后评论

用户评论

    当前暂无评价,快来发表您的观点吧...

更多相关好文