php重现400错误,API返回延迟,FPM重启后恢复之后又重现 问题解决方案

背景

最近在提供后台API时,提供了一个简单逻辑的接口

部署在测试环境,自测没问题,提交测试

突然有一天,接口响应延迟严重,几乎每次都是3-4秒返回

这对于一个接口来说,肯定是有问题的

于是便有了以下的方法尝试

重启FPM

最开始以为不是什么大问题,猜想可能是fpm问题

毕竟测试环境,部署代码繁多

重启后,接口响应正常,可不一会就又复现

重新分析定位问题

经过几次的尝试后,发现问题其实没有那么简单

重启fpm后,过了不到几秒,问题依然复现

编写简单的php文件,无复杂逻辑,只输出字符串,问题复现

命令行执行文件,无此问题

经过以上尝试,确认是FPM的问题

或者PHP的初始化解析执行的问题,当时想是不是有特定的模块加载

经过php -m 查看,无特殊模块(其实应该仔细看看,所以才绕了弯路)

日志定位

有了上面的分析,最先想到的是利用日志查看问题

查看nginx日志

nginx 日志 格式,添加详细的 各个时间段的相应

log_format main '$remote_addr [$host] - $remote_user [$time_local] $request upstream_response_time: $upstream_response_time

request_time: $request_time upstream_connect_time: $upstream_connect_time upstream_header_time:$upstream_header_time

$status $body_bytes_sent $http_referer $http_user_agent ';

添加了

upstream_response_time

upstream_connect_time

upstream_header_time

含义如下:

request_time

nginx服务,从发起请求的客户端获取到第一个字节开始,到返回给客户端最后一个字节,日志写入文件后,所经过的时间。单位为秒。

upstream_connect_time

nginx服务,从与上游服务开始建立连接,到连接建立成功,所经过的时间。单位为秒。

upstream_header_time

nginx服务,从与上游服务开始建立连接,到接收到响应返回头的第一个字节,所经过的时间。单位为秒。

upstream_response_time

nginx服务,从与上游服务开始建立连接,到接收完响应返回的最后一个字节,所经过的时间。单位为秒

![](https://img2020.cnblogs.com/blog/385704/202007/385704-20200728162028181-1944819290.jpg

[参考链接](https://juejin.im/post/5d26eb16f265da1bd522f8d6)

程序真正的运行时间 = $upstream_header_time - $upstream_connect_time

$request_time 中包含了数据返回时间

$request_time 中包含了日志打印的时间

nginx 日志显示

xxx [xxx] - - [11/Jul/2020:13:36:45 +0000] POST /xxx/xxx HTTP/1.1 upstream_response_time: 3.048 request_time: 3.048 upstream_connect_time: 0.000 upstream_header_time:3.048 200 98 - Dalvik/2.1.0 (Linux; U; Android 8.1.0; vivo 1808 Build/O11019)

从中可以看出确实是 上游服务FPM 返回延迟了

配置 FPM 慢日志

lowlog = /xxx/www-slow.log request_slowlog_timeout = 1s

发现 慢日志中,并没有记录

于是怀疑是否 日志文件权限有问题,修改后依然未写入

想到用sleep的方式,写入慢日志,成功写入

现象就是 配置了1秒的FPM慢日志,接口响应超过1s,但没有写入慢日志

那就说明 PHP 程序执行完成后,又做了什么处理,导致了延迟

或者 其他的配置问题

文件描述符

网上搜索, 可能是 文件的打开数量有限制, 并没有做优化

于是 查看对应的配置

nginx 配置文件 worker_rlimit_nofile

ulimit -n

是做了优化的的

也并不是网上所说的问题

思考

所有的PHP程序响应都有延迟,超过3s左右

所以并不是个例,和其他的如mysql redis 连接没关系

突然间出现的,原来是没问题的,应该是近期做了更改

但由于测试环境,东西太多,一时也找不到是什么改动

命令行模式下,没有延迟,所以和FPM有关

而重复FPM后,几秒内正常,后又复现

一直以为是FPM配置有什么问题,目前看并不是

所以肯定是 程序处理完成后,又做了什么,导致了延迟

那有什么办法可以追踪代码的执行吗

对于php而言,xhprof扩展 是可以很好的展示一个流程中的耗时

而且还有对应的图片展示(需要安装扩展),但还需要安装

于是就有了下面的方法

strace调试跟踪

简单来说,strace是用来跟踪进程内的系统调用和接受的信号。

对程序调试很有帮助

于是,来调试FPM的进程,看看都做了哪些调用

strace -t -f $(pidof php-fpm| sed 's/\([0-9]*\)/\-p \1/g') -epoll -o /tmp/strace.txt

打开文件

12406 06:55:13 poll([{fd=5, events=POLLIN}], 1, 5000) = 1 ([{fd=5, revents=POLLIN}])

12406 06:55:13 poll([{fd=6, events=POLLOUT|POLLWRNORM}], 1, 10000) = 1 ([{fd=6, revents=POLLOUT|POLLWRNORM|POLLERR|POLLHUP}])

12405 06:55:16 --- SIGQUIT {si_signo=SIGQUIT, si_code=SI_USER, si_pid=12401, si_uid=0} ---

12405 06:55:16 +++ exited with 0 +++

12401 06:55:16 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12405, si_status=0, si_utime=0, si_stime=0} ---`

通过以上信息,在 06:55:13后,确实有超过3秒的时间,是 fd=6 导致的, 那 fd=6 是什么

可通过 lsof -p pid 的方式查看

但 进程id 12405 退出了

就是说,由于FPM的动态启动进程的配置,一个进程退出后,会重新生成进程,已达到配置的进程数

所以 一边打印调试信息,一边输出 工作pid的 文件信息

lsof -p 12408

php-fpm 12408 apache 5u IPv4 1203792101 0t0 TCP localhost:cslistener->localhost:22384 (ESTABLISHED)

php-fpm 12408 apache 6u IPv4 1203794606 0t0 TCP xx.com:28866->ip-10-66-100-169.ec2.internal:9411 (SYN_SENT)

此时,原因终于找到了,在每次结束后,又请求了某个ip 9411端口的服务

可以看到tcp状态是 SYN_SENT,虽然已发出连接请求,但对应没回应,并不是ESTABLISHED状态

说明是对应请求机器的问题

于是去查看此ip对应机器,原来确实有此机器,但不知什么时候起,此机器已下架了

导致了这个问题,此刻想到了为什么突然之间接口就有延迟了

问题修复

既然确认了是连接了某个服务导致的问题,对应的php配置文件应该有配置

于是 找到 所有模块配置文件ini路径,搜索ip

发现是 其 molten 扩展配置

molten 扩展是一个全链路追踪的工具

molten追踪php核心调用库运行时信息并且按照zipkin/optracing格式输出信息。

可以追踪curl,pdo,mysqli,redis,mongodb,memcached 这么多的运行信息。

去掉此扩展加载,重启FPM后,接口响应正常

总结

问题出现的时候,最直观的方式应该是日志,通过查看日志,可以解决大部分问题

当日志解决不了的问题,如何调试,就成了问题解决的关键

strace ltrace 以及 gdb 调试

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JS JSP ASP .NET J2AM API接口返回的版本 目前所有版本的JS JSP ASP .NET J2AM 都是提供源代码的,对于一些脚本语言来说,直接解压缩之后就可以使用了,不需要什么安装步骤。另外一些需要编译的语言,则提供了编译用的 shell 文件(Linux/Unix 下使用)和 bat 文件(Windows 下使用),或者直接提供编译好的二进制库文件。 不过为了让读者能够更清楚如何安装,我们还是对每种语言的安装都做详细的讲解,你可以在安装列表里找到你感兴趣的语言的安装方法。 示例 如果你已经把 JS JSP ASP .NET J2AM 安装好了,那么接下来就让我们开始第一个小程序吧。按照惯例,第一个演示程序几乎总是 HelloWorld,我们也不想打破这个惯例,不过对于 PHPRPC 来说,有服务器端就要有客户端,否则我们就没有什么好演示的啦,所以我们的第一个演示程序实际上是两个,一个是服务器端,另一个是客户端。我们都先用 PHP 语言来写好了。 服务器端 view plaincopy to clipboardprint? <?php include ("php/phprpc_server.php"); function HelloWorld() { return 'Hello World!'; } $server = new PHPRPC_Server(); $server->add('HelloWorld'); $server->start(); ?> 客户端 view plaincopy to clipboardprint? <?php include ("php/phprpc_client.php"); $client = new PHPRPC_Client('http://127.0.0.1/server.php'); echo $client->HelloWorld(); ?> 对于服务器端程序,我们应该将它命名为 server.php(这是因为客户端用时用的是这个名字,而不是 PHPRPC 的什么规定),然后把它放在本地 Web 服务器的根目录下,并保证服务器可以正常运行 PHP 程序,之后在浏览器或命令行下运行客户端程序,你就可以看到结果了。 这两个程序几乎简单到无需解释的地步,所以如果你已经明白它们的意思,那么就可以直接跳过下面的解释,继续看后面的例子。 服务器端第 1 句是将 它的服务器端程序包含到你的程序里,之后的 2 - 4 句是定义一个远程用的函数,你会发现它与本地函数没有任何区别。第 5 句是创建服务器端对象,第 6 句是添加要发布的方法,这里添加的就是刚刚定义的 HelloWorld 函数,在 PHP 中,添加的发布方法是函数名的字符串表示,在其它语言中可能略有不同。第 7 句是启动服务。 客户端就更简单了,第 1 句是将 它的客户端程序包含到你的程序里。第 2 句是创建客户端对象,其中的参数就是服务器端的地址。第 3 句是对远程方法(函数)的用,之后通过 echo 将它显示出来。如果顺利的话,执行后你就会看到输出的 Hello World!。 上面的例子是发布的是函数,下面我们来看一下类中的静态方法如何发布: view plaincopy to clipboardprint? <?php include ("php/phprpc_server.php"); class Hello { static function HelloWorld() { return 'Hello World!'; } } $server = new PHPRPC_Server(); $server->add('HelloWorld', 'Hello'); $server->start(); ?> 这个服务器端只要它的名字与发布的地址与上面那个发布函数的例子一样的话,上面的那个客户端就可以得到同样的结果,也就是说,在客户端看来是没有任何区别的。 它并不是只可以在 PHP 中使用,它同样支持其它语言的服务器和客户端,而且还可以无差别的相互用。 现在我们来看一下如何在 Java 中用这个 PHP 的服务器方法: view plaincopy to clipboardprint? import org.phprpc.*; interface IHello { public String helloWorld(); } public class HelloWorld

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值