Ubuntu 14.04系统上使用Apache2.4.10+PHP5+FastCGI的WordPress网站大量php5-cgi defunct引起网站访问异常缓慢

最近网站访问异常缓慢,网站响应时间明显延迟很多,观察系统的处理器占用,一点都不高,带宽也在合理范围之内,因此分析 Apache-2.4.10的错误日志

发现大量的出现

如下图:

感觉很奇怪,于是看了一下 php相关的进程,执行命令

执行结果如下图:

发现大量的 php5-cgi进程处于 defunct状态(僵尸状态),导致达到了进程数量的限制上限,无法继续提供服务。

但是上面的日志,是已经出现问题的时候的日志,既然进程不足了,自然无法提供服务了,继续向上追踪,发现大量的进程崩溃日志

感觉很奇怪,最初以为是由于调整 Apache-2.4.10服务器,多次在正常运行中重启服务器,导致正在服务的 php5-cgi进程变成了僵尸进程。但是重启服务器之后,过了几天后,问题依旧发生了。

于是网上搜索了一下,感觉应该更像是 PHP-CGI进程本身存在内存泄漏问题,导致进程长时间执行之后,由于资源开销问题导致进程崩溃。

目前我的服务器上启用的是 Apache2Event MPM模块,查看其中的配置文件:

可以看到里面的内容如下:

发现,设置 MaxConnectionsPerChild参数为0,根据 Apache MPM Common Directives 里面的介绍,这个参数的作用就是一个进程处理多少个请求后就退出。而如果设置了 0 则代表,除非出现了异常,否则进程会一直存在。这样的话,一旦资源泄漏,进程就会各种异常了。因此需要设置一下这个参数,一般这个参数设置为 1000- 5000左右,根据实际情况自己调整一下参数。

注意一下这个介绍的最后一句话,讲的就是这个参数可以修正资源泄漏问题,如下:

修改参数后,需要重启服务生效。

按照上面的方法实现后,过了一个礼拜后,这个现象再次发生了。

观察了一下 Apache2当前已经启用的模块

发现,由于是从 Apache2.2升级到的 Apache2.4,或许是某次的配置,导致 fastcgifcgi两个模块同时被启用了。

是不是两个相同功能的模块同时启用导致功能冲突了呢?

先尝试禁用 fastcgi(这个模块已经被 fcgi替换了).

目前看来,问题依旧发生了!!!

查看 Apache2中的 FastCGI的配置信息

可以看到如下内容

其中的 AddHandler中的 .php引起了注意,因为在日志中出现问题之前一直会出现如下的错误日志:

而这个配置项是从 Ubuntu 12.04中手工引入的,我们尝试去掉这个。另外,可以看到

也就是 FastCGI的最大进程数是被设置成了 10,但是,我这边限制 PHP-FPM的最大进程数为 5,是不是 FastCGI的进程数如果大于 PHP-FPM能够提供服务的数量的时候,会导致 FastCGI进程出现僵尸状态呢? 是不是这个原因导致的呢? 于是修改后的结果如下:

经过这几天的观察,貌似现象不再出现了,这个应该是 Apache 2.4版本引入的 BUG,以前的配置的 Apache 2.2版本上是没有任何问题的,究竟原因是 mod_fcgid无法处理 .php格式的脚本导致,还是由于 mod_fcgid进程数高于 php-fpm的进程数导致 mod_fcgid进程长时间无法获取到可用的 php-fpm连接,导致进程被饿死,这个暂时不详细追究了。

今天在修改配置的时候,由于配置错误,导致 PHP-FPM服务无法正常启动,但是整个服务器竟然一直可以正常访问,那么结论就是 Apache 2.4在当前的配置下,根本就没有使用 PHP-FPM模式进行通信,而是使用标准的 FastCGI的方式调度的。
于是网上搜索半天,结合自己的尝试,找到了根本原因:

上面的配置在 Apache 2.2版本上是不影响后面启用的 PHP-FPM配置的,对于 PHP的请求,最终都是定向到 PHP-FPM的。但是相同的配置在 Apache 2.4上面,则被定向到了 FastCGI上面。因此修改方式如下(至少要高于2.4.10,否则配置无效):

至此,应该才是从根本上解决了服务器上的问题。

开始的时候忽略了一个基础的问题,那就是 php5-cgiphp5-fpm进程本身就不应该同时存在,如果服务器使用 php5-fpm,那么 php5-cgi的进程就不应该被调用,这个问题没有重视,导致了整个问题分析时间冗长,反复。

本质上是服务器的配置错误导致问题。

如何在VS2010 IE11 下调试BHO控件

VS2010 IE11 下调试BHO控件,在新版本的调试器中已经简化调试流程,简单的设置一下,就可以直接调试BHO的DLL文件了,方便很多。

如下图设置,点击工程属性,然后选择 “调试”在“要启动的调试器”中选择 “Web 浏览器调试器”,在“HTTP URL”中输入具体的网站地址,然后直接启动调试即可。

BHODebug

用Visual Studio 2008开发IE BHO (浏览器帮助对象)之一

BHO 通常并不提供其自身的任何用户界面 (UI)。它们而是通过在后台响应浏览器事件和用户输入数据来发挥作用。例如,BHO 可以拦截弹出窗口、自动填充窗体或为鼠标手势添加支持。

有一种常见误解认为工具栏扩展项需要 BHO.但如果将 BHO 与工具栏配合使用,则可以实现更丰富的用户体验。(关于IE工具栏的编程,在另一篇文章中说明).

BHO 的生命周期与它所交互的浏览器实例的生命周期相等。在 IE 6 和早期版本中,这意味着要为每个新的顶层窗口创建(和销毁)一个新 BHO。在IE 7中则是为每个选项卡都创建和销毁一个新 BHO。

BHO 必须实现 IObjectWithSite 接口, 该接口提供了两个方法GetSite和SetSite。根据MSDN的说明:

我们主要是对后者进行调用,此方法方便了与 Internet Explorer 的初始通信,并会在其将要释放时通知 BHO。我们实现此接口,然后将 BHO 的 CLSID 添加到注册表中,就可以创建一个简单的浏览器扩展。过程如下:

1. 在Visual Studio中,选择VC++中的ATL项目, 创建一个新的项目MySolutionPlugin, 在随后的向导中,确认Server Type是Dll, Visual Studio会为我们创建程序的模板.

2. 为该项目添加我们的程序主体, (不熟悉visual studio的同学在资源浏览器里的右键菜单里选 add-->class, 可别选到New Item), 类型选ATL Simple Object ,  short name命名为RayBHO,各项属性如下:
a) “线程模型” ---“Apartment”
b) “聚合”---“否”
c) “接口”---“双重”
d) “支持”---勾上“IobjectWithSite”。

具体的含义请参考MSDN.

一般来说,Internet Explorer 至少调用SetSite方法两次: 一次用于建立连接,另一次则是在浏览器退出时。我们 BHO 中的 SetSite 实现将执行以下操作:

•存储对站点的引用。在初始化期间,浏览器将 IUnknown 指针传递给顶层 WebBrowser 控件,然后 BHO 将对它的引用存储在一个专用成员变量中。

•释放目前被占用的站点指针。Internet Explorer 传递 NULL 时,BHO 必须释放所有接口引用并且断开与浏览器的连接。

要实现SetSite,我们需手工在添加一个public的方法:

STDMETHOD 宏是将方法标记为虚方法并且确保其具有适用于公共 COM 接口的调用约定的一个ATL 约定, 它有助于区分 COM 接口和该类中可能存在的其他公共方法。其实现成员方法时应相应使用 STDMETHODIMP 宏。同时我们需要声明一个私有变量来保存Browser的指针"

私有变量
然后是SetSite的实现

从上面的介绍我们知道, 初始化期间,浏览器将传递一个对其顶层 IWebBrowser2 接口(我们对其进行缓存处理)的引用。浏览器关闭时将传递 NULL,为避免内存泄漏和循环引用计数,此时释放所有指针和资源非常重要。最后,我们调用基类实现以便继续执行接口合约的其余部分。

加载DLL 后,系统将通过 DLL_PROCESS_ATTACH 通知调用 DllMain 函数。由于 Internet Explorer 大量使用多线程,因此,对 DllMain 的频繁的 DLL_THREAD_ATTACH 和 DLL_THREAD_DETACH 通知会降低扩展和浏览器进程的整体性能。

如果BHO 不需要线程级的跟踪,我们可以在 DLL_PROCESS_ATTACH 通知期间调用 DisableThreadLibraryCalls 以避免新线程通知的额外开销。修改DllMain.cpp 中的DllMain函数:

要使BHO工作,我们还需要把BHO 的 CLSID 添加到注册表中。此条目会将 此DLL 标记为浏览器帮助程序对象,并使 Internet Explorer 在启动时加载 BHO。我们可以在MySolutionPlugin.idl中找到该BHO的CLSID.幸运的,Visual Studio会帮助我们实现这些, 你看到:

您的机器的CLSID可能有所不同, 接着打开RayBHO.rgs文件,添加入:

这一段是为了在注册表里添加一个双字节的NoExplorer=1的键,不让Windows Explorer加载该BHO,因此该BHO只能在ie中运行.

注意,这里的UUID字段跟MySolutionPlugin.idl 中的是一致的。

你可以编译这个BHO. 如果一切正常, 你可以在IE的管理加载项里看到这个BHO.

如果不幸报错: 该BHO无法被注册,根据我的经验,原因大概有2类,可以依次检查

1. 你是否有管理员权限以修改注册表,如不是管理员身份,可以在菜单上右击Microsoft Visual studio 2008,从右键菜单中选择"运行方式"...
2. 你的注册表条目语法是否正确,或者含有非法字符.

引用 http://blog.csdn.net/dupei/article/details/6092692