ZimmerWen

Php 进程异常退出导致 502 的问题排查

502 是什么?

The server, while acting as a gateway or proxy, received an invaild response from the upstream server it accessed in attempting to fulfill the request.

从概念上来看:上游服务无响应导致 502,那什么是上游服务?以 LNMP 架构为例,MySQL 是 Php 的上游服务,Php 是 Nginx 的上游服务。一般情况下,发生 502 都不是 Nginx 的问题,问题主要集中在上游服务 Php 中,进程数的设置、内存设置、超时设置等都可能会导致 502。

我们可以先看看整个请求流程中,哪里出问题可能会出现 502?

image.png

从路径上来看,发生 502 有两大类场景:

  • Nginx 服务不可用;负载均衡通过健康检查发现上游服务不可用,会直接返回 502,不会转发请求到后端服务
  • fpm 异常;Nginx 接收不到上游服务 Php 的返回,会返回 502

回到线上业务现象,只有某个特定的接口出现了 502,只要一访问就会 502,但是其他接口是好的。这样的现象就比较诡异,通常我们碰到的 502 都是整体服务不可用导致或者偶发的不同业务 502,与我们出现的现象都不一样。那么我们如何定位呢?

  • 从主要路径排查,排查路径

    • 根据现象,我们可以首先排除 Nginx 的问题
    • 其次是 fpm 的问题,因为之前线上没有出现配置导致 502 的问题,并且其他的接口访问都是正常的,与我们的场景不一致,也可以排除是 fpm 配置的问题
    • Nginx 与 fpm 配置均无更新,无异常, ECS实例也没有任何异常,确认了运行环境没有任何变动与问题存在,这个时候我们通常可以猜测是代码的问题

    我们猜测 代码的问题导致了 fpm 进程异常退出,从而导致特定接口出现了 502,但其他接口都是好的。那如何验证我们的这个猜测呢?

  • 看日志

    fpm 进程异常退出,一般情况下都是有日志输出的,首先我们可以查看 fpm 日志:

    WARNING: [pool www] child 30296 exited on signal 11 (SIGSEGV) after 136.678208 seconds from start

    从 fpm 日志可以看到进程是执行了一个无效的内存引用而退出的。我们可以继续查看 /var/log/message 日志,确认一下原因:

    image.png

    发生 502 的时候都有对应的日志,从日志信息中可以看到是内存访问越界导致的。

    /var/log/message 日志中 error 后面的数字是比较有用的,上面的 4,转换成二进制就是 100,可以根据以下解释来查看具体含义。

    bit2: 值为 1 表示是用户态程序内存访问越界,值为 0 表示内核态程序内存访问越界

    bit1: 值为 1 表示是写操作导致内存访问越界,值为 0 表示读操作导致内存访问越界

    bit0: 值为 1 表示没有足够的权限访问非法地址的内容,值为 0 表示访问的非法地址根本没有对应的页面,也就是无效地址

    所以,出现问题的根本原因就是用户态度读取的内存地址无效

    结合 fpm 的日志,我们可以通过以下两种方式来定位具体问题所在:

    • 直接看代码;因为是特定的接口,我们只需要查看该接口的代码。因为是新代码上线后导致的问题,这样看变更代码可以更快速的定位问题。我们遇到的问题是yaf框架的问题,复现代码及环境如下:

      CentOS Linux release 7.6.1810(core)

      Php 7.1.12

      Yaf 3.0.7

      <?php
      
      class Test502Controller extends Controller {
      
          public function testRunAction() {
              $productId = $_REQUEST['product_id'];
      
              $params = [
                  'timestamp' => time(),
              ];
      
              $this->mergeArr($params, $_REQUEST);
      
              // 此处使用$this->post()或$_REQUEST是正常的,不会发生502
              $regionId = $this->request('region_id', 0);
              var_dump($productId, $regionId);
          }
      
          private function mergeArr(array $params = [], array &$merger = []):void  {
              $data = &$merger;
      
              $data = array_merge($data, $params);
          }
      }
      
    • core dump;适用于偶发性的不确定的进程异常退出,我们可以从core文件中看到整个调用,进而定位到问题所在