Den Schuldigen der hohen CPU-Auslastung in unserem WordPress-Server finden
Veröffentlicht: 2020-05-07Vor ein paar Tagen haben wir eine E-Mail von unserem Hosting-Provider (SiteGround) erhalten, in der uns mitgeteilt wurde, dass unsere Website bereits „90 % der zulässigen monatlichen CPU-Nutzung“ hat und dass der „Webservice eingeschränkt“ wird, sobald wir 100 % überschritten haben. und wir haben möglicherweise „Probleme beim Zugriff darauf“. Ziemlich beängstigend, oder? Es war definitiv eine völlig unerwünschte Situation, die wir so schnell wie möglich beheben mussten. Aber… wo anfangen?

Heute wollten wir Ihnen unsere Erfahrungen mit einem ziemlich häufigen Problem auf Websites mitteilen und erklären, was wir getan haben, um den Übeltäter zu identifizieren und wie wir das Problem gelöst haben. Wenn Sie auf ein ähnliches Problem stoßen, haben Sie auf diese Weise einige Ideen, wie Sie anfangen können …
Gründe, warum Sie eine hohe CPU-Auslastung haben können
WordPress ist ein in PHP geschriebenes Content-Management-System. Das bedeutet, dass der bereitgestellte Inhalt dynamisch von einer Reihe von PHP-Skripten generiert wird: Jedes Mal, wenn ein Besucher auf Ihrer Website ankommt, verarbeitet WordPress die Anfrage (was so etwas wie „Bitte senden Sie mir Ihre Homepage“) und generiert eine Antwort (in in diesem Fall sendet es die Homepage). Die Beantwortung einer Anfrage impliziert natürlich eine gewisse Nutzung von Serverressourcen: Man muss sich die Anfrage selbst ansehen, bestimmen, worauf der Besucher zugreifen möchte, sie aus der Datenbank abrufen, die HTML-Antwort generieren und so weiter.
Einer der Gründe, warum ein Cache-System die Ladezeit Ihrer Website beschleunigt, sollte jetzt ganz klar sein: Es spart im Grunde genommen diese Verarbeitungszeit ein. Wenn zum ersten Mal eine bestimmte Anfrage eintrifft („senden Sie mir Ihre Homepage“), startet WordPress und generiert die Antwort. Wenn ein Cache vorhanden ist, speichert er diese Antwort, bevor sie tatsächlich an den Besucher gesendet wird. Auf diese Weise erfordern zukünftige Anfragen an dieselbe Ressource (in unserem Beispiel eine Homepage) nicht länger, dass WordPress irgendetwas verarbeitet; Der Cache kann die zuvor gespeicherte Kopie zurücksenden und spart so Zeit und Ressourcen.
Angesichts dieser Leistung ist es nicht schwer, sich vorzustellen, warum wir eine hohe CPU-Auslastung auf unserem Server feststellen können:
- Sie erhalten zu viele Anfragen. Wenn viele Benutzer gleichzeitig auf Ihre Website kommen oder Sie viele unzulässige Anfragen erhalten (wahrscheinlich greift jemand Ihren Server an), muss WordPress all diese Anfragen verarbeiten, und daher wird die Verwendung von Serverressourcen zunehmen.
- Anfragen werden nur langsam bearbeitet. Wenn Sie viele Plugins installiert haben oder einige Ihrer Plugins aus irgendeinem Grund ineffizient sind, werden alle Anfragen, die Sie erhalten, länger dauern als nötig, da WordPress eine Menge ineffizienten Code ausführt.
Es sieht also so aus, als wäre ein Cache ein guter Schutz vor diesen Problemen, oder? Und das ist es tatsächlich. Denken Sie jedoch bitte daran, dass der Cache das Problem nicht „behebt“; es „versteckt“ es einfach. Und dies ist wichtig zu beachten, da es WordPress-Funktionalitäten gibt, die nicht zwischengespeichert werden können und daher immer die Ausführung von WordPress erfordern:
- Mit WP-Cron geplante Aufgaben. WP-Cron ist ein WordPress-Mechanismus zum Planen von Aufgaben, die in der Zukunft ausgeführt werden. Beispielsweise verwendet WordPress es, um geplante Posts zu veröffentlichen.
- Die REST-API von WordPress. Die REST-API ist eine Schnittstelle, die man von Anwendungen von Drittanbietern verwenden kann, um mit einer WordPress-Site zu interagieren, indem JSON-Objekte gesendet und empfangen werden. Einige REST-API-Anforderungen werden möglicherweise zwischengespeichert (insbesondere GET-Anforderungen), andere jedoch nicht (POST und PUT). Daher ist es etwas, das Sie im Allgemeinen nicht zwischenspeichern können ...
- AJAX-Anfragen in WordPress. Bevor wir die REST-API in WordPress hatten, mussten wir ihre AJAX-API verwenden, um dynamische Websites zu erstellen. Diese API ist der REST-API ziemlich ähnlich, da wir sie auch verwenden können, um Informationen an den Server zu senden und von ihm zu empfangen. Es ist ein anderes System, aber es unterliegt denselben Einschränkungen wie die REST-API.
Analyse des Problems
Zuerst müssen wir herausfinden, warum die CPU-Auslastung auf unserer Website zugenommen hat. Hat sich die Anzahl der Anfragen auf unsere Website erhöht? Ist es jetzt langsamer, einzelne Anfragen zu bedienen? Um diese Fragen zu beantworten, haben wir ein sehr nützliches Tool auf unserem Server: das Zugriffsprotokoll .
Das Zugriffsprotokoll ist eine Textdatei, in der der Server jede empfangene Anfrage zusammen mit nützlichen Informationen darüber protokolliert. Insbesondere sagt uns das Zugriffsprotokoll, wann eine Anfrage eingegangen ist (Datum und Uhrzeit), wer sie gestellt hat (eine IP), welche Ressource sie angefordert hat (eine URL), ob die Anfrage erfolgreich war usw. Hier ist ein Beispiel von unserem Server :
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" ...Schauen wir es uns genauer an:
-
66.249.83.82. Dies ist die IP des Geräts, das die Anfrage gestellt hat. -
22/Apr/2020:14:04:59 +0200. Dies ist das genaue Datum und die Uhrzeit der Anfrage. -
GET /es/blog/imagenes-gratuitas-para-tu-blog/ HTTP/1.0. Dann sehen wir, dass der Besucher einen Beitrag aus unserem Blog (auf Spanisch) angefordert (GET) hat. -
Mozilla/5.0 (Linux; Android ...Dies ist der User-Agent des Browsers und gibt uns einige Informationen über das Gerät und das Betriebssystem, das die Anfrage gestellt hat.
Beachten Sie, wie unser Server nach der ersten mehrere Anfragen von derselben IP ( 66.249.83.82 ) erhalten hat. Dies mag wie ein Angriff aussehen, ist es aber eigentlich nicht: Webseiten enthalten normalerweise mehrere Elemente (Bilder, Skripte, Stile) und es ist völlig normal, dass ein Besucher, der auf eine bestimmte Webseite Ihrer Website zugreift, mehrere Anfragen ausführt, um sie alle abzurufen .

Nun, in unserem Fall konnten wir feststellen, dass wir tatsächlich eine ungewöhnlich hohe Anzahl von Anfragen hatten. Eigentlich extrem hoch. Und wir wussten das, weil die Protokolldatei viel größer als gewöhnlich war.
Eine mögliche Erklärung wäre, dass es aus irgendeinem Grund einen Höhepunkt der Besuche gab… aber laut Google Analytics war das nicht der Fall. Es geschah also etwas anderes.
Eine genauere Analyse des Zugriffsprotokolls ermöglichte es uns, die folgende Tatsache zu identifizieren: Mehr als 15 % aller Anfragen, die wir erhielten, kamen von derselben IP. Und (Trommelwirbel) diese IP war unser eigener Webserver!

Täter identifizieren
An diesem Punkt wussten wir endlich, dass es unser eigener Server war, der so viele Anfragen stellte, dass er einen Spitzenwert in seiner CPU-Auslastung erzeugte. Aber warum? Warum geschah das? Wer hat diese Anfragen generiert? Das sind schwieriger zu beantwortende Fragen.
Zuerst haben wir uns unsere Protokolle erneut angesehen, nach IP gefiltert und versucht, ein Muster zu identifizieren, das etwas Licht auf das vorliegende Problem werfen könnte:
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" ... und wir haben einen gefunden: Der User-Agent all dieser abnormalen Anfragen war php-requests/1.7-3470169 . Interessant!
WordPress hat mehrere Funktionen, um Anfragen auszulösen: wp_remote_request . Wenn Sie sich den Quellcode dieser Funktionen ansehen, werden Sie feststellen, dass sie im Grunde einige Methoden einer Klasse namens WP_Http . Diese Klasse ist interessant, da standardmäßig alle Anfragen, die sie stellt, den User-Agent auf „WordPress/version“ setzen, sodass es möglich ist, dass dies etwas mit unserem Problem zu tun hat. Aber das tat es nicht… noch nicht.
Wenn wir den Quellcode von WordPress weiter untersuchen, sehen wir, dass WP_Http eine andere WordPress-Klasse verwendet, um die Anfragen tatsächlich zu stellen: Requests . Und Junge, ist diese Klasse interessant: Ganz am Anfang ihrer Definition sehen wir, dass sie eine Konstante namens VERSION definiert, deren Wert 1.7-3470169 ist. Und etwas später verwendet es diese Konstante, um den User-Agent zu erstellen, den wir in unseren Protokollen gefunden haben: php-requests/1.7-3470169 .
Brillant! Wir haben jetzt bestätigt, dass all diese seltsamen Anfragen, die wir bekommen, von unserer WordPress-Seite kommen. Das bedeutet wahrscheinlich, dass der Übeltäter ein Plugin ist … aber welches?
Die Idee, die wir hatten, um das herauszufinden, war ganz einfach: Wenn wir den User-Agent so ändern, dass er den Namen des Plugins enthält, das die Requests -Klasse verwendet, sehen wir den Namen des Plugins im Zugriffsprotokoll unseres Servers. Und das ist eigentlich ganz einfach zu erreichen. Wir haben lediglich die get_default_options -Funktion von Requests mit dem folgenden Snippet bearbeitet:
$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})";Mal sehen, was es Schritt für Schritt tut:
- Zuerst holen wir uns den Ausführungsstack mit
debug_backtrace. Dies ist eine PHP-Funktion, die einen Backtrace generiert und alle Funktionen aufdeckt, die aufgerufen wurden, um den aktuellen zu erreichen. - Für jedes Element im Ausführungsstapel haben wir Informationen wie die aufgerufene Funktion, die Datei und Zeile, in der sie definiert ist, die Argumente, mit denen sie aufgerufen wurde usw. Worauf wir uns jedoch konzentrieren möchten, ist die Datei, in der die Funktion definiert wurde: Wenn sie sich in
wp-content/pluginsbefindet, wissen wir mit Sicherheit, dass es sich um eine in einem Plugin definierte Funktion handelt. - Sobald wir alle Elemente im Stack verarbeitet haben, müssen wir einfach die Namen (falls vorhanden) aller Plugins, die wir gefunden haben, abrufen und sie in die
useragentVariable aufnehmen.
Nachdem wir WordPress wie beschrieben erweitert hatten, begannen wir schnell zu sehen, wer der Übeltäter war:
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)" ...Lösung des Problems
Sobald wir den Namen des betreffenden Plugins kannten, kontaktierten wir einfach seinen Entwickler und baten um Hilfe. Sie antworteten und sagten uns, wie wir das Problem lösen können, während sie an einer geeigneten Lösung arbeiteten. Zum Glück für uns wurde es schnell besser:

Wie Sie sehen können, ist eines der großartigen Dinge an freier Software, dass wir den Quellcode der von uns verwendeten Anwendungen untersuchen und an unsere Bedürfnisse anpassen können. In diesem Fall konnten wir WordPress selbst so modifizieren, dass es einige von uns benötigte Informationen preisgeben konnte.
Ich hoffe, Ihnen hat der Trick gefallen, mit debug_backtrace herauszufinden, wer eine bestimmte Funktion ausführt. Sicher, es ist keine orthodoxe Methode, aber sie ist schnell umzusetzen und hat sich bisher immer als äußerst hilfreich erwiesen.
Beitragsbild von Michal Mancewicz auf Unsplash.
