Wenn der Server langsam ist, Threads erhöhen?
Als ich zum ersten Mal in das Unternehmen eintrat, wurde der Spring Boot API-Server, für den ich verantwortlich war, immer langsamer, wenn viel Datenverkehr herrschte. Da ich die Ursache nicht kannte, habe ich zunächst gegoogelt.
„Langsame Reaktion des Spring Boot-Servers“ „Tomcat-Leistungsoptimierung“
Als Ergebnis der Suche war der in Blogs und Communities am häufigsten gefundene Ratschlag einfach. ‚Erhöhen Sie die Thread-Pool-Größe von Tomcat. Ihre Anfrage wartet, da nicht genügend Mitarbeiter vorhanden sind.‘
Ich dachte: „Aha, uns mangelt es an Arbeitskräften!“ Ich dachte einfach. Ich habe sofort die application.yml-Einstellungen geöffnet und die Anzahl der Threads vom Standardwert 200 auf 2.000 erhöht. Nach meinen Berechnungen hat sich die Anzahl der Arbeiter verzehnfacht, sodass die Verarbeitungsgeschwindigkeit schneller sein musste.
Aber als ich nach der Bereitstellung den Überwachungsbildschirm sah, erstarrte ich. Der Server bewegte sich tatsächlich langsamer, die CPU-Auslastung stieg stark an, aber die Anzahl der verarbeiteten Anfragen nahm tatsächlich ab. Es schien, als würden die Arbeiter nur in der Luft schaufeln, ohne etwas zu tun.
Warum um alles in der Welt ist die Zahl der Arbeiter gestiegen, aber die Fabrik wurde langsamer? Als ich mich mit dem Grund beschäftigte, stieß ich auf den teuersten Kostenfaktor des Betriebssystems: „Context Switching“.

Rezension: Threads, erinnerst du dich?
Für diejenigen, die diesen Artikel zum ersten Mal lesen oder mit dem Inhalt des vorherigen Artikels (Teil 4: Prozesse und Threads) nicht vertraut sind, spulen wir kurz zurück.
In unserem Weltbild „Digitales Logistikzentrum“:
Spring Boot ist grundsätzlich multithreaded. Jedes Mal, wenn eine Anfrage eingeht, wird ein Worker (Thread) mit der Ausführung der Arbeit beauftragt. Also dachte ich einfach: „Wenn es mehr Mitarbeiter gibt, werden mehr Anfragen gleichzeitig bearbeitet, oder?“
Aber da war etwas, das ich übersehen habe. Es handelt sich um die Anzahl der CPU-Kerne, den Schlüsselarbeitern in unserer Fabrik.
Schichtarbeit in einem digitalen Distributionszentrum
Tatsächlich kann der CPU-Kern, das Herzstück eines Computers, jeweils nur eine Aufgabe erledigen. (Basierend auf Single Core) Wir hören jedoch gleichzeitig Songs, programmieren und verwenden KakaoTalk. Wie ist das möglich?
Das liegt daran, dass der Fabrikleiter (OS) den Arbeitern (CPUs) mit unglaublich hoher Geschwindigkeit befiehlt, die Arbeit zu verschieben. „Spielen Sie das Lied 0,001 Sekunden lang und stoppen Sie! Senden Sie KakaoTalk für die nächsten 0,001 Sekunden und stoppen Sie!“
Das ist „Time Sharing“, und der Prozess, bei dem der Arbeiter das Werkzeug ablegt und ein neues Werkzeug in die Hand nimmt, ist „Context Switching“.
Der Preis für den Aufgabenwechsel: Zeit zum Umziehen
Das ist passiert, als ich die Anzahl der Threads auf 2.000 erhöht habe.
Die CPU ist ein einziger Körper, in dem 2.000 Thread-Worker rufen: „Schafft sie weg!“ Die CPU trifft sich ständig mit 2.000 Personen, um die Arbeit fair zu verarbeiten.
Das Problem besteht darin, dass „Vorbereitungszeit“ benötigt wird, wenn man von der Arbeit von Arbeitnehmer A zur Arbeit von Arbeitnehmer B übergeht.
Diese „Zeit zum Aufzeichnen, Einräumen und Lesen“ wird als „Kontextwechselkosten (Overhead)“ bezeichnet. Wenn genügend Arbeitskräfte vorhanden sind, sind diese Kosten vernachlässigbar. Was aber, wenn es zu viele Arbeitskräfte gibt? CPU gerät in eine Situation, in der es den ganzen Tag Arbeitsbücher organisiert, aber keine „eigentliche Arbeit (Berechnung)“ ausführen kann. Das war der wahre Grund, warum mein Server langsam war.

[Codeüberprüfung] Ist es unbedingt schneller, nur weil es viele Threads gibt?
Sehen ist hörenswert. Lassen Sie es uns mit Code beweisen. Vergleichen wir die Geschwindigkeit, mit der die gleiche Anzahl an Additionsoperationen mit einem Thread ausgeführt und in 1 Million Threads aufgeteilt wird. Der gesunde Menschenverstand legt nahe, dass 1 Million Einheiten schneller sein sollten, aber die Realität sieht anders aus.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ContextSwitchingTest {
private static final int TASK_COUNT = 1_000_000;
public static void main(String[] args) throws InterruptedException {
// 1. Single-Thread-Verarbeitung (kein Schichtwechsel)
long start = System.currentTimeMillis();
for (int i = 0; i < TASK_COUNT; i++) {
simpleTask();
}
System.out.println("Single-Thread-Zeit: " + (System.currentTimeMillis() - start) + "ms");
// 2. Verarbeitung mit sehr vielen Threads (loest Context Switching aus)
// Unbegrenzten Thread-Pool erstellen (Achtung: Rechner kann einfrieren)
ExecutorService executor = Executors.newCachedThreadPool();
start = System.currentTimeMillis();
for (int i = 0; i < TASK_COUNT; i++) {
executor.submit(() -> simpleTask());
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.HOURS);
System.out.println("Multi-Thread-Zeit: " + (System.currentTimeMillis() - start) + "ms");
}
private static void simpleTask() {
int a = 1 + 1; // Sehr leichte Aufgabe
}
}
Beispielergebnisse (variiert je nach Umgebung):
Analyse: Die Aufgabe selbst (1+1) ist so einfach, dass sie im Handumdrehen erledigt werden kann. Bei der Multithread-Methode sind die Kosten für die Erstellung einer Million Threads und das Hin- und Herwechseln des Betriebssystems zwischen ihnen jedoch tausendmal höher als die Betriebszeit. Der Bauchnabel ist größer als der Magen
Lektionen aus der Praxis: Den Sweet Spot finden
Wie viele Threads sind dann für einen Spring Boot-Server angemessen? Die Antwort hängt davon ab, „was der Server tut.“
Aber wie in der Situation, die ich erlebt habe, ist es zu viel, die Zahl blind auf 2.000 zu erhöhen. Dies liegt daran, dass mit zunehmender Anzahl von Threads mehr Speicher (Stack) verbraucht wird und die CPU aufgrund der Kosten für den Kontextwechsel überlastet wird.
In letzter Zeit erregen nicht blockierende Technologien wie Node.js und WebFlux von Spring Aufmerksamkeit, um dieses Problem zu lösen. Sie verwenden die Strategie „Erhöhen Sie nicht die Anzahl der Threads, sondern lassen Sie eine Person schnell und ohne Unterbrechung verarbeiten.“

Abschluss: Es gibt kein kostenloses Mittagessen
Wir glauben oft fälschlicherweise, dass „die gleichzeitige Verarbeitung von Dingen schneller ist“. Allerdings ist „simultan“ in der Welt der Computer eigentlich nur Hochgeschwindigkeits-Schichtarbeit, was fast schon ein Trick ist.
Sobald Sie den Kontextwechsel verstanden haben, werden Sie verstehen, warum es bei der Serveroptimierung nicht nur darum geht, „die Zahlen in die Höhe zu treiben“. Willkürlich erhöhte Threads können tatsächlich zu einem Gift werden, das den Server erstickt.
Jetzt scheinen die internen Fabriken des Computers (CPU, RAM, Prozess) recht gut zu laufen. Jetzt öffnen wir die Fabriktür und gehen nach draußen. Wie senden wir in unserer Fabrik erstellte Daten an eine weit entfernte andere Fabrik (Kunden)?
Nächstes Mal werden wir über Netzwerke, HTTP und dieses unsichtbare Straßennetz sprechen.