在我们的 WordPress 服务器中找到高 CPU 使用率的罪魁祸首

已发表: 2020-05-07

几天前,我们收到了来自我们的托管服务提供商 (SiteGround) 的一封电子邮件,告诉我们我们的网站已经“允许每月使用 CPU 的 90%”,一旦我们超过 100%,“网络服务将受到限制”我们可能“无法访问它”。 相当可怕,对吧? 这绝对是一个完全不受欢迎的情况,我们必须尽快解决。 但是……从哪里开始?

高CPU使用期间我们网站的统计数据
在 CPU 使用率高的情况下,我们网站的统计数据。

今天,我们想与您分享我们在网站中遇到一个相当普遍的问题时的经验,解释我们为确定罪魁祸首所做的工作以及我们如何解决问题。 这样,如果您遇到类似的问题,您将对如何开始有一些想法……

CPU 使用率高的原因

WordPress 是一个用 PHP 编写的内容管理系统。 这意味着它所提供的内容是由一组 PHP 脚本动态生成的:每次访问者到达您的网站时,WordPress 都会处理请求(类似于“请给我您的主页”)并生成响应(在在这种情况下,它发送主页)。 显然,响应请求意味着对服务器资源的某种使用:必须查看请求本身,确定访问者想要访问的内容,从数据库中获取它,生成 HTML 响应,等等。

缓存系统加快网站加载时间的原因之一现在应该很明显了:它基本上节省了这个处理时间。 当特定请求第一次到达时(“将您的主页发送给我”),WordPress 启动并生成响应。 如果缓存到位,它会在实际发送给访问者之前存储所述响应。 这样,未来对同一资源(在我们的示例中为主页)的请求不再需要 WordPress 处理任何内容; 缓存可以将之前保存的副本发回,从而节省时间和资源。

考虑到这种性能,不难想象我们在服务器上看到高 CPU 使用率的原因是什么:

  • 你收到了太多的请求。 如果很多用户同时访问您的网站,或者您收到许多非法请求(可能有人在攻击您的服务器),WordPress 将不得不处理所有这些请求,因此服务器资源的使用会增加。
  • 请求解决起来很慢。 如果您安装了很多插件,或者您的某些插件由于某种原因效率低下,那么您收到的所有请求将花费比需要更长的时间,因为 WordPress 将运行大量低效代码。

所以看起来缓存可以很好地防范这些问题,对吧? 确实如此。 但是,请记住缓存并不能“修复”问题; 它只是“隐藏”它。 记住这一点很重要,因为有些 WordPress 功能无法缓存,因此总是需要运行 WordPress:

  • 使用 WP-Cron 安排的任务。 WP-Cron 是一种 WordPress 机制,用于安排将来运行的任务。 例如,WordPress 使用它来发布预定的帖子。
  • WordPress 的 REST API。 REST API 是一种接口,可以从第三方应用程序中使用,通过发送和接收 JSON 对象与 WordPress 站点进行交互。 一些 REST API 请求可能会被缓存(即 GET 请求),但其他的则不会(POST 和 PUT)。 因此,这是您通常无法缓存的东西……
  • WordPress 中的 AJAX 请求。 在我们在 WordPress 中使用 REST API 之前,我们必须使用它的 AJAX API 来创建动态网站。 这个 API 与 REST API 非常相似,因为我们也可以使用它来发送和接收来自服务器的信息。 这是一个不同的系统,但它受到与 REST API 相同的限制。

分析问题

首先,我们必须确定我们网站上 CPU 使用率增加的原因。 对我们网站的请求数量是否增加了? 现在处理个人请求的速度变慢了吗? 为了回答这些问题,我们的服务器上有一个非常有用的工具:访问日志

访问日志是一个文本文件,服务器在其中记录它收到的每个请求以及有关它们的有用信息。 具体来说,访问日志会告诉我们何时收到请求(日期和时间)、谁发出请求(IP)、请求的资源(URL)、请求是否成功等。这是来自我们服务器的示例:

 66.249.83.82 - - [22/Apr/2020:14:04:59 +0200] "GET /es/blog/imagenes-gratuitas-para-tu-blog/ HTTP/1.0" 200 22325 "-" "Mozilla/5.0 (Linux; Android 4.2.1; en-us; Nexus 5 Build/JOP40D) AppleWebKit/535.19 (KHTML, like Gecko; googleweblight) Chrome/38.0.1025.166 Mobile Safari/535.19" 66.249.83.84 - - [22/Apr/2020:14:05:02 +0200] "GET /es/wp-content/uploads/sites/3/2018/07/aziz-acharki-549137-unsplash-540x350.jpg HTTP/1.0" 200 10566 "https://neliosoftware.com/es/blog/imagenes-gratuitas-para-tu-blog/" "Mozilla/5.0 (Linux; Android 4.2.1; en-us; Nexus 5 Build/JOP40D) AppleWebKit/535.19 (KHTML, like Gecko; googleweblight) Chrome/38.0.1025.166 Mobile Safari/535.19" 66.249.83.82 - - [22/Apr/2020:14:05:02 +0200] "GET /es/wp-content/uploads/sites/3/2018/07/Screenshot-of-Unsplah-website-768x520.png HTTP/1.0" 200 399577 "https://neliosoftware.com/es/blog/imagenes-gratuitas-para-tu-blog/" "Mozilla/5.0 (Linux; Android 4.2.1; en-us; Nexus 5 Build/JOP40D) AppleWebKit/535.19 (KHTML, like Gecko; googleweblight) Chrome/38.0.1025.166 Mobile Safari/535.19" ... 188.79.17.218 - - [22/Apr/2020:14:06:14 +0200] "GET /es/blog/problemas-mas-comunes-de-wordpress/ HTTP/1.0" 200 110741 "https://www.google.com/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.2 Safari/605.1.15" 188.79.17.218 - - [22/Apr/2020:14:06:16 +0200] "GET /es/wp-content/plugins/nelio-ab-testing/assets/dist/js/alternative-loader.js?version=52b0ff65c68ab39896d47d6ff673fd59 HTTP/1.0" 200 2763 "https://neliosoftware.com/es/blog/problemas-mas-comunes-de-wordpress/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.2 Safari/605.1.15" 188.79.17.218 - - [22/Apr/2020:14:06:16 +0200] "GET /es/wp-includes/css/dist/block-library/style.min.css?ver=5.4 HTTP/1.0" 200 7627 "https://neliosoftware.com/es/blog/problemas-mas-comunes-de-wordpress/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.2 Safari/605.1.15" ...

让我们仔细看看它:

  • 66.249.83.82 。 这是发出请求的设备的 IP。
  • 22/Apr/2020:14:04:59 +0200 。 这是请求的确切日期和时间。
  • GET /es/blog/imagenes-gratuitas-para-tu-blog/ HTTP/1.0 。 然后我们看到访问者从我们的博客(西班牙语)请求( GET )一个帖子。
  • Mozilla/5.0 (Linux; Android ...这是浏览器的用户代理,向我们提供有关发出请求的设备和操作系统的一些信息。

请注意我们的服务器在第一个请求之后如何从同一个 IP ( 66.249.83.82 ) 收到多个请求。 这可能看起来像一种攻击,但实际上并非如此:网页通常包含多个资产(图像、脚本、样式),访问您网站中某个网页的访问者会执行多个请求以全部检索它们是完全正常的.

好吧,在我们的案例中,我们能够验证确实,我们有异常多的请求。 实际上,极高。 我们知道这是因为日志文件比平时大得多。

一种可能的解释是由于某种原因出现了访问高峰……但根据谷歌分析,情况并非如此。 所以发生了一些不同的事情。

对访问日志的更详细分析使我们能够确定以下事实:我们收到的所有请求中有超过 15% 来自同一个 IP。 并且(鼓声)IP 是我们自己的网络服务器!

Gif 显示一个人有一个好主意

识别罪魁祸首

在这一点上,我们终于知道是我们自己的服务器发出了如此多的请求,以至于它的 CPU 使用率达到了峰值。 但为什么? 为什么会这样? 谁产生了这些请求? 这些是更难回答的问题。

首先,我们再次查看我们的日志,按 IP 进行过滤,并尝试确定一种模式,可以阐明手头的问题:

 35.214.244.124 - - [22/Apr/2020:14:06:08 +0200] "GET /es HTTP/1.0" 301 - "https://neliosoftware.com/es" "php-requests/1.7-3470169" 35.214.244.124 - - [22/Apr/2020:14:06:08 +0200] "GET /es?..." "php-requests/1.7-3470169" 35.214.244.124 - - [22/Apr/2020:14:06:18 +0200] "GET /es?..." "php-requests/1.7-3470169" 35.214.244.124 - - [22/Apr/2020:14:07:21 +0200] "GET /es?..." "php-requests/1.7-3470169" 35.214.244.124 - - [22/Apr/2020:14:07:24 +0200] "GET /es?..." "php-requests/1.7-3470169" ...

我们发现了一个:所有这些异常请求的User-Agentphp-requests/1.7-3470169 。 有趣的!

WordPress 有几个触发请求的函数: wp_remote_request 。 如果您查看这些函数的源代码,您会发现它们基本上包装了一个名为WP_Http的类中的一些方法。 这个类很有趣,因为默认情况下,它发出的所有请求都将User-Agent设置为“WordPress/version”,所以这可能与我们的问题有关。 但它还没有……还没有。

如果我们继续检查 WordPress 的源代码,我们会看到WP_Http在内部使用另一个 WordPress 类来实际发出请求: Requests 。 这个类很有趣:在它定义的最开始,我们看到它定义了一个名为VERSION的常量,其值为1.7-3470169 。 稍后,它使用这个常量来构建我们在日志中找到的User-Agentphp-requests/1.7-3470169

杰出的! 我们现在已经确认我们收到的所有这些奇怪的请求都来自我们的 WordPress 网站。 这可能意味着罪魁祸首是一个插件......但是哪个?

我们必须弄清楚这一点的想法非常简单:如果我们修改User-Agent使其包含使用Requests类的插件的名称,我们将在服务器的访问日志中看到插件的名称。 这实际上很容易实现。 我们所做的只是使用以下代码段编辑Requestsget_default_options函数:

 $trace = debug_backtrace(); $files = []; foreach ( $trace as $log ) { if ( false !== strpos( $log['file'], '/wp-content/plugins/' ) ) { $files[] = $log['file']; } } if ( empty( $files ) ) { $debug = 'no-plugin'; } else { $plugins = array_map( function( $x ) { return preg_replace( '/.*\/wp-content\/plugins\/([^\/]+)\/.*/', '$1', $x ); }, $files ); $plugins = array_unique( $plugins ); $debug = implode( ' ', $plugins ); } $defaults['useragent'] .= " (NelioDebug {$debug})";

让我们一步一步看看它做了什么:

  • 首先,我们使用debug_backtrace获取执行堆栈。 这是一个 PHP 函数,它生成一个回溯,显示为到达当前函数而调用的所有函数。
  • 对于执行堆栈中的每个元素,我们都有信息,例如已调用的函数、定义它的文件和行、调用它的参数等。然而,我们想要关注的是定义函数的文件:如果它在wp-content/plugins中,我们肯定知道它是在插件中定义的函数。
  • 一旦我们处理了堆栈中的所有元素,我们只需要获取我们找到的所有插件的名称(如果有的话)并将它们包含在useragent变量中。

在按照描述扩展 WordPress 之后,我们很快就开始了解罪魁祸首是谁:

 35.214.244.124 - - [23/Apr/2020:10:59:08 +0200] "GET /es HTTP/1.0" 301 - "https://neliosoftware.com/es" "php-requests/1.7-3470169 (NelioDebug culprit)" 35.214.244.124 - - [23/Apr/2020:10:59:20 +0200] "GET /?..." "php-requests/1.7-3470169 (NelioDebug culprit)" 35.214.244.124 - - [23/Apr/2020:10:59:36 +0200] "GET /?..." "php-requests/1.7-3470169 (NelioDebug culprit)" 35.214.244.124 - - [23/Apr/2020:11:00:01 +0200] "GET /es?..." "php-requests/1.7-3470169 (NelioDebug culprit)" 35.214.244.124 - - [23/Apr/2020:11:05:21 +0200] "GET /es?..." "php-requests/1.7-3470169 (NelioDebug culprit)" ...

解决问题

一旦我们知道了有问题的插件的名称,我们就联系了它的开发人员并寻求帮助。 他们回复并告诉我们如何在他们制定适当的解决方案时克服这个问题。 对我们来说幸运的是,情况很快好转:

纠正 CPU 使用率问题后来自我们网站的统计数据
寻求帮助后来自我们网站的统计数据。

如您所见,自由软件的一大优点是我们可以探索我们使用的应用程序的源代码,并使其适应我们的需求。 在这种情况下,我们能够修改 WordPress 本身,以便它可以显示我们需要的一些信息。

我希望你喜欢使用debug_backtrace找出谁运行某个函数的技巧。 当然,这不是一种正统的方法,但它实施起来很快,而且到目前为止,它总是被证明是非常有用的。

Michal Mancewicz 在 Unsplash 上的特色图片。