在我們的 WordPress 服務器中找到高 CPU 使用率的罪魁禍首
已發表: 2020-05-07幾天前,我們收到了來自我們的託管服務提供商 (SiteGround) 的一封電子郵件,告訴我們我們的網站已經“允許每月使用 CPU 的 90%”,一旦我們超過 100%,“網絡服務將受到限制”我們可能“無法訪問它”。 相當可怕,對吧? 這絕對是一個完全不受歡迎的情況,我們必須盡快解決。 但是……從哪裡開始?

今天,我們想與您分享我們在網站中遇到一個相當普遍的問題時的經驗,解釋我們為確定罪魁禍首所做的工作以及我們如何解決問題。 這樣,如果您遇到類似的問題,您將對如何開始有一些想法……
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 是我們自己的網絡服務器!

識別罪魁禍首
在這一點上,我們終於知道是我們自己的服務器發出瞭如此多的請求,以至於它的 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-Agent是php-requests/1.7-3470169 。 有趣的!
WordPress 有幾個觸發請求的函數: wp_remote_request 。 如果您查看這些函數的源代碼,您會發現它們基本上包裝了一個名為WP_Http的類中的一些方法。 這個類很有趣,因為默認情況下,它發出的所有請求都將User-Agent設置為“WordPress/version”,所以這可能與我們的問題有關。 但它還沒有……還沒有。
如果我們繼續檢查 WordPress 的源代碼,我們會看到WP_Http在內部使用另一個 WordPress 類來實際發出請求: Requests 。 這個類很有趣:在它定義的最開始,我們看到它定義了一個名為VERSION的常量,其值為1.7-3470169 。 稍後,它使用這個常量來構建我們在日誌中找到的User-Agent : php-requests/1.7-3470169 。
傑出的! 我們現在已經確認我們收到的所有這些奇怪的請求都來自我們的 WordPress 網站。 這可能意味著罪魁禍首是一個插件......但是哪個?
我們必須弄清楚這一點的想法非常簡單:如果我們修改User-Agent使其包含使用Requests類的插件的名稱,我們將在服務器的訪問日誌中看到插件的名稱。 這實際上很容易實現。 我們所做的只是使用以下代碼段編輯Requests的get_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)" ...解決問題
一旦我們知道了有問題的插件的名稱,我們就聯繫了它的開發人員並尋求幫助。 他們回复並告訴我們如何在他們制定適當的解決方案時克服這個問題。 對我們來說幸運的是,情況很快好轉:

如您所見,自由軟件的一大優點是我們可以探索我們使用的應用程序的源代碼,並使其適應我們的需求。 在這種情況下,我們能夠修改 WordPress 本身,以便它可以顯示我們需要的一些信息。
我希望你喜歡使用debug_backtrace找出誰運行某個函數的技巧。 當然,這不是一種正統的方法,但它實施起來很快,而且到目前為止,它總是被證明是非常有用的。
Michal Mancewicz 在 Unsplash 上的特色圖片。
