Wie skaliere ich PHP5 + MySQL über 200 Anfragen / Sekunde?

16

Ich optimiere meine Homepage, um die Leistung zu verbessern. Derzeit werden ungefähr 200 Anfragen / Sekunde auf 3.14.by verarbeitet, bei denen 6 SQL-Abfragen verarbeitet werden, und 20 Anfragen / Sekunde auf 3.14.by/forum, bei dem es sich um das phpBB-Forum handelt.

Seltsamerweise sind die Zahlen auf einigen VPS- und dedizierten Atom 330-Servern ungefähr gleich.

Server-Software ist die folgende: Apache2 + mod_php prefork 4 Kinder (versuchte verschiedene Zahlen hier), PHP5, APC, Nginx, für PHP-Sitzungen Speicherung zwischengespeichert.

MySQL ist so konfiguriert, dass es etwa 30% des verfügbaren Arbeitsspeichers aufnimmt (~ 150 MB bei VPS, 700 MB bei dedizierten Servern).

Das sieht so aus, als ob es irgendwo einen Engpass gibt, der es mir nicht erlaubt, höher zu gehen. Irgendwelche Vorschläge? (dh ich weiß, dass weniger als 6 SQL-Anweisungen schneller sind, aber dies scheint kein einschränkender Faktor zu sein, da sqld aufgrund von zwischengespeicherten Abfragen nicht mehr als ein paar Prozent oben isst.)

Hat jemand getestet, dass es viel schneller ist, vorgegabelten Apache2 zu treten und nur Nginx + PHP zu lassen?

Noch ein paar Benchmarks

Small 40-byte static file: 1484 r/s via nginx+apache2, 2452 if we talk to apache2 directly. 
Small "Hello world" php script: 458 r/s via ngin+apache2.

Update: Es scheint, dass der Engpass die MySQL-Leistung bei zwischengespeicherten Daten ist. Seite mit einzelnem SQL zeigt 354 Req / Sek., Mit 6 SQL - 180 Req / Sek. Was denkst du, kann ich hier optimieren? (Ich kann 100-200Mb für MySQL herausfiltern)

[client]
port        = 3306
socket      = /var/run/mysqld/mysqld.sock

[mysqld_safe]
socket      = /var/run/mysqld/mysqld.sock
nice        = 0

[mysqld]
default-character-set=cp1251
collation-server=cp1251_general_cs

skip-character-set-client-handshake

user        = mysql
pid-file    = /var/run/mysqld/mysqld.pid
socket      = /var/run/mysqld/mysqld.sock
port        = 3306
basedir     = /usr
datadir     = /var/lib/mysql
tmpdir      = /tmp
skip-external-locking

bind-address        = 127.0.0.1

key_buffer      = 16M
max_allowed_packet  = 8M
thread_stack        = 64K
thread_cache_size   = 16
sort_buffer_size    = 8M
read_buffer_size    = 1M

myisam-recover      = BACKUP
max_connections        = 650
table_cache            = 256
thread_concurrency     = 10

query_cache_limit       = 1M
query_cache_size        = 16M

expire_logs_days    = 10
max_binlog_size         = 100M

[mysqldump]
quick
quote-names
max_allowed_packet  = 8M

[mysql]
[isamchk]
key_buffer      = 8M

!includedir /etc/mysql/conf.d/
BarsMonster
quelle
Warum verwenden Sie sowohl Apache als auch Nginx?
Jamieb
Dies ist die übliche Konfiguration von Apache2 zu PHP und verschiedenen Apps, für die eine Apache-Infrastruktur erforderlich ist. Nginx reduziert den Speicherbedarf von Apache2 beim Laden.
BarsMonster
Eigentlich verstehe ich Ihr Problem nicht. Ist Ihre Website derzeit langsam? Wenn ja, wie langsam ist es? Und wie viel möchten Sie beschleunigen? Haben Sie versucht, Teile Ihrer Website zu profilieren, um festzustellen, wo sich der Engpass befindet?
Jamieb
Es steht in der Beschreibung: Jetzt sind es 180-200 Anfragen / Sekunde. Dies ist zwar weit mehr als genug für eine Homepage, aber ich möchte dieses Setup optimieren, damit andere Sites, die auf derselben Codebasis basieren, schneller funktionieren. Idealerweise möchte ich die 100Mbit-Verbindung mit dynamischen Seiten sättigen :-)
BarsMonster
2
"Anfragen pro Sekunde" ist in diesem Zusammenhang keine aussagekräftige Metrik. Mein Netbook kann "200 Anfragen pro Sekunde" verarbeiten. Sie müssen uns mitteilen, welche Antwortzeit Sie bei einer solchen Verbindungsrate erreichen möchten.
Jamieb

Antworten:

29

Natürlich gibt es eine Menge, die Sie ausprobieren können. Am besten jagen Sie Ihre Protokolle nach Abfragen, die keine Indizes verwenden (aktivieren Sie Protokolle für diese), und nach anderen nicht optimierten Abfragen. Ich habe im Laufe der Jahre eine große Liste von leistungsbezogenen Optionen zusammengestellt, daher habe ich hier eine kleine Teilmenge zu Ihrer Information aufgenommen - hoffentlich hilft es. Hier sind einige allgemeine Hinweise für Dinge, die Sie ausprobieren können (falls Sie dies noch nicht getan haben):

MySQL

  • query_cache_type = 1 - SQL-Cache-Abfragen sind aktiviert. Bei 2 werden Abfragen nur zwischengespeichert, wenn der SQL_CACHE-Hinweis an sie übergeben wird. Ähnlich können Sie mit Typ 1 den Cache für eine bestimmte Abfrage mit dem SQL_NO_CACHE-Hinweis deaktivieren
  • key_buffer_size = 128M (Standard: 8M) - Speicherpuffer für MyISAM-Tabellenindizes. Legen Sie auf dedizierten Servern für key_buffer_size mindestens ein Viertel, jedoch nicht mehr als die Hälfte des gesamten Arbeitsspeichers auf dem Server fest
  • query_cache_size = 64M (Standard: 0) - Größe des Abfragecaches
  • back_log = 100 (Standard: 50, max: 65535) - Die Warteschlange ausstehender Verbindungsanforderungen. Nur wichtig, wenn in kurzer Zeit viele Verbindungen bestehen
  • join_buffer_size = 1M (Standard: 131072) - ein Puffer, der verwendet wird, wenn vollständige Tabellenscans durchgeführt werden (keine Indizes)
  • table_cache = 2048 (Standard: 256) - sollte max_user_connections multipliziert mit der maximalen Anzahl von JOINs sein, die Ihre schwerste SQL-Abfrage enthält. Verwenden Sie die Variable "open_tables" zu Spitzenzeiten als Richtlinie. Schauen Sie sich auch die Variable "opens_tables" an - sie sollte sich in der Nähe von "open_tables" befinden.
  • query_prealloc_size = 32K (Standard: 8K) - Permanenter Speicher für das Parsen und Ausführen von Anweisungen. Bei komplexen Abfragen erhöhen
  • sort_buffer_size = 16M (Standard: 2M) - hilft beim Sortieren (ORDER BY- und GROUP BY-Operationen)
  • read_buffer_size = 2M (Standard: 128K) - Hilft bei sequentiellen Scans. Erhöhen Sie, wenn es viele aufeinanderfolgende Scans gibt.
  • read_rnd_buffer_size = 4M - beschleunigt das Lesen von MyISAM-Tabellen nach dem Sortieren
  • max_length_for_sort_data - Zeilengröße, die anstelle des Zeilenzeigers in der Sortierdatei gespeichert werden soll. Kann zufällige Tabellenlesevorgänge vermeiden
  • key_cache_age_threshold = 3000 (Standard: 300) - Zeit, um den Schlüssel-Cache in der Hot-Zone zu belassen (bevor er auf warm herabgestuft wird)
  • key_cache_division_limit = 50 (Standard: 100) - Aktiviert einen ausgefeilteren Cache-Eviction-Mechanismus (zwei Ebenen). Gibt den Prozentsatz an, der für die unterste Ebene beibehalten werden soll. delay_key_write = ALL - Der Schlüsselpuffer wird nicht bei jeder Indexaktualisierung für die Tabelle geleert, sondern nur, wenn die Tabelle geschlossen wird. Dies beschleunigt das Schreiben auf Schlüsseln erheblich, aber wenn Sie diese Funktion verwenden, sollten Sie die automatische Überprüfung aller MyISAM-Tabellen hinzufügen, indem Sie den Server mit der Option --myisam-recover = BACKUP, FORCE starten
  • memlock = 1 - sperren Sie den Prozess im Speicher (um das Ein- und Auslagern zu reduzieren)

Apache

  • ändere die Laichmethode (zum Beispiel auf mpm)
  • Deaktivieren Sie die Protokolle, wenn möglich
  • AllowOverride None - Deaktivieren Sie nach Möglichkeit .htaccess. Apache wird angehalten, nach .htaccess-Dateien zu suchen, wenn diese nicht verwendet werden, sodass eine Dateisuchanforderung gespeichert wird
  • SendBufferSize - Auf die Standardeinstellung des Betriebssystems festlegen. In überlasteten Netzwerken sollten Sie diesen Parameter in der Nähe der Größe der größten Datei einstellen, die normalerweise heruntergeladen wird
  • KeepAlive Off (Standardeinstellung On) - und lingerd installieren, um Netzwerkverbindungen ordnungsgemäß zu schließen, und ist schneller
  • DirectoryIndex index.php - Halten Sie die Dateiliste so kurz und absolut wie möglich.
  • Optionen FollowSymLinks - zur Vereinfachung des Dateizugriffs in Apache
  • Vermeiden Sie mod_rewrite oder zumindest komplexe reguläre Ausdrücke
  • ServerToken = Prod

PHP

  • variables_order = "GPCS" (Wenn Sie keine Umgebungsvariablen benötigen)
  • register_globals = Aus - Dies ist nicht nur ein Sicherheitsrisiko, sondern wirkt sich auch auf die Leistung aus
  • Halte include_path so gering wie möglich (vermeidet zusätzliche Dateisystem-Lookups)
  • display_errors = Off - Deaktiviert das Anzeigen von Fehlern. Dringend empfohlen für alle Produktionsserver (zeigt im Falle eines Problems keine hässlichen Fehlermeldungen an).
  • magic_quotes_gpc = Aus
  • magic_quotes _ * = Aus
  • output_buffering = On
  • Deaktivieren Sie die Protokollierung, wenn möglich
  • expose_php = Aus
  • register_argc_argv = Aus
  • always_populate_raw_post_data = Off
  • Platzieren Sie die Datei php.ini dort, wo PHP zuerst danach suchen würde.
  • session.gc_divisor = 1000 oder 10000
  • session.save_path = "N; / path" - Bei großen Websites sollte die Verwendung in Betracht gezogen werden. Teilt Sitzungsdateien in Unterverzeichnisse auf

OS Tweaks

  • Hängen Sie gebrauchte Festplatten mit der Option -o noatime ein (keine Zugriffszeit). Fügen Sie diese Option auch zur Datei / etc / fstab hinzu.
  • Passen Sie / proc / sys / vm / swappiness (von 0 auf 100) an, um die besten Ergebnisse zu erzielen
  • Verwenden Sie RAM Disks - mount --bind -ttmpfs / tmp / tmp
Ivan Peevski
quelle
Das ist eine schöne Liste, von denen ich bereits die meisten hatte, und wenn ich die restlichen Dinge hinzufügte, hat sich die Leistung nicht erhöht. Es sieht so aus, als ob ein Engpass zwischen PHP und MySQL besteht, der nicht in der Lage ist, mehr als 800 Anfragen pro Sekunde aus dem Abfrage-Cache zu bearbeiten ...
BarsMonster
Ok, wie verbinde ich mich mit der Datenbank (mysql_pconnect () statt mysql_connect ())? Verwenden Sie dauerhafte Verbindungen? versuchen Sie es in beide Richtungen ...
Ivan Peevski
Ich bin bereits auf pconnect und das Verbindungs-Pooling ist in php.ini aktiviert ...: -S
BarsMonster
Nur der Vollständigkeit halber würde ich versuchen, einfach zu verbinden. Ich habe Fälle gesehen (vor allem in Lasttests), in denen das besser funktioniert.
Ivan Peevski
1

Wenn der Engpass nicht die CPU ist, dann sein IO - entweder Netzwerk oder Datenträger. Also .. Sie müssen sehen, wie viel IO los ist. Ich hätte nicht gedacht, dass es das Netzwerk ist (es sei denn, Sie haben eine 10-Mbit / s-Halbduplexverbindung, aber es lohnt sich, den Switch zu überprüfen, falls die automatische Erkennung nicht richtig funktioniert).

Dadurch bleibt Festplatten-E / A übrig, was insbesondere bei VPS ein wichtiger Faktor sein kann. Verwenden Sie sar oder iostat, um einen Blick auf die Festplatten zu werfen, und googeln Sie dann, um weitere Details zu finden, wenn Ihre Festplatte stark ausgelastet ist.

gbjbaanb
quelle
Ja, Netzwerk ist nicht das Problem. Wenn Sie ab vom lokalen Server ausführen, ist die Leistung genau gleich. Ich habe die iowait-Zeit überprüft - sie liegt unter 0,01% - im Grunde ist alles im Festplatten-Cache und es sind keine Festplatten-Schreibvorgänge bei der Verarbeitung der Anforderung beteiligt (alle Protokolle sind deaktiviert).
BarsMonster
1

Ich würde das Cachen entweder mit Nginx ( memcached ) oder Varnish untersuchen .

Zumindest sollten Sie statische Dateien mit Nginx wie SaveTheRbtz servern.

Espennilsen
quelle
Da es sich um dynamische Seiten handelt, möchte ich sie lieber nicht zwischenspeichern.
BarsMonster
1
memcached ist keine herkömmliche Caching-App und kann bei dynamischen Seiten Wunder wirken. Es befindet sich zwischen der DB und Ihrer App. Ihre App fragt zunächst im Speicher gespeicherte Abfragen für ein Objekt ab. Wenn es nicht vorhanden ist, wird es aus der Datenbank geladen. Der Nettoeffekt ist, dass Sie RAM verwenden, um Ihre DB-Anforderungen zu bedienen, anstatt den viel langsameren dauerhaften Speicher auf der DB.
Jamieb
Memcache kann mit Nginx verwendet werden, einer bekannten Funktion. Langsamere persistente Speicher werden nicht verwendet, sondern befinden sich in MySQL im Abfrage-Cache.
BarsMonster
Memcached und der Abfrage-Cache von MySQL sind nicht wirklich vergleichbar. Sie machen nicht einmal das Gleiche. Sie sind ziemlich schnell dabei, so ziemlich jeden hier veröffentlichten Vorschlag abzuschießen, ohne die Mühe zu machen, ihn zu verstehen. Ich würde empfehlen, dass Sie ein bisschen aufgeschlossener sind.
Jamieb
Ich verstehe klar den Unterschied zwischen memcached und MySQL Query Cache. Aber aufgrund der Tatsache, dass sich alles im Abfrage-Cache mit einer Trefferquote von 100% befindet, würde ich es nicht als "langsamen dauerhaften Speicher" bezeichnen. In der gestrigen Antwort ging es ursprünglich um die Verwendung von NginX + Memcached, einem gängigen Szenario zum Zwischenspeichern ganzer Seiten. Das Zwischenspeichern einzelner Objekte ist ein anderes, völlig anderes Szenario. Während die Verwendung von memcached vor MySQL auf dem Tisch steht, überlege ich mir, ob ich im Moment mehr Saft ohne es bekommen könnte (da dies einige Codeänderungen erfordern würde).
BarsMonster
1

Da der Server kein Problem zu sein scheint, ist es vielleicht der Lastgenerator. Versuchen Sie, es auf mehreren Computern auszuführen.

OliverS
quelle
Die Leistung ist die gleiche, auch wenn ich sie vom Server selbst ausführe. Egal, wie viele gleichzeitige Verbindungen gleichzeitig hergestellt werden - 10 oder 50. Das Testen der Last erfolgt über ab -c 10 -t 10
BarsMonster
1

Es hört sich für mich so an, als ob Sie die maximale Anzahl an Verbindungen erreichen, die Apache zulässt. Sehen Sie sich Ihre Apache-Konfiguration an. Das Erhöhen des Serverlimits und der maximalen Anzahl von Clients sollte hilfreich sein, wenn Sie nicht bereits an ein anderes Limit wie E / A oder Speicher gebunden sind. Sehen Sie sich die Werte für mpm_prefork_module oder mpm_worker_module an und passen Sie sie entsprechend Ihren Anforderungen an.

ServerLimit 512
MaxClients 512
Erik Giberti
quelle
Naja, brauche ich das wirklich, vorausgesetzt, ich habe nginx vor apache2, also glaube ich, dass es nicht viel Sinn macht, mehr als physische Kerne zu haben * 2 Apache2-Prozesse ....
BarsMonster
Ich habe das gerade verifiziert. Die steigende Anzahl von Apache2-Prozessen von 4 auf 16 verbesserte die Leistung überhaupt nicht (sie ging sogar um 0,5% zurück). Die Erhöhung der Anzahl der Nginx-Arbeiter auf 2 oder 4 hat nichts verbessert.
BarsMonster
1
Wenn Ihre Daten ziemlich statisch sind, dh nicht bei jedem zweiten Seitenaufruf aktualisiert werden, können Sie Ihren query_cache erhöhen. MySQL wird die Ergebnismenge auf diese Weise festhalten und aus dem Speicher ziehen. Wenn die zwischengespeicherte Tabelle jedoch während dieser Zeit Schreibvorgänge empfängt, wird der Cache ungültig (auch wenn die Daten nicht betroffen sind), wodurch Speicherplatz verschwendet wird.
Erik Giberti
Im Moment sehe ich eine Trefferquote von 100% im Abfrage-Cache und MySQL fühlt sich immer noch langsam an ...
BarsMonster
1
Fügen Sie Ihrer MySQL-Konfigurationsdatei die Funktion zum Auflösen von Sprungnamen hinzu. Dadurch wird bei jeder Verbindung zum Server eine DNS-Suche gespeichert. Der Nachteil hierbei ist, dass alle Verbindungen durch IP gesperrt werden müssen (vorausgesetzt, Sie verwenden nicht '%'). Befindet sich der SQL-Code auf demselben Server und muss nur auf localhost zugegriffen werden, können Sie auch Skip-Networking hinzufügen, um den gesamten TCP / IP-Stack zu löschen. Ich denke jedoch, der Engpass ist Apache.
Erik Giberti
0

Wird diese Last von einem Werkzeug oder von realen Lasten erzeugt?

Möglicherweise möchten Sie memcached überprüfen. Ich habe Probleme mit hohen Verbindungsraten gesehen, die eine Latenz in der Anwendung verursachen.

Was erhalten Sie, wenn Sie einen Lastgenerator verwenden, wenn Sie eine kleine statische Seite aufrufen?

Während des Ladens möchten Sie möglicherweise den Netzwerkstapel auf TIME_WAIT-Bedingungen überprüfen. Möglicherweise füllen Sie Ihre Verbindungswarteschlange.

Es gibt ungefähr 100 weitere Gründe und Gegenstände, die Sie sich ansehen können, aber ohne weitere Informationen werde ich an dieser Stelle nur Vermutungen anstellen.

Jeffatrackaid
quelle
Es wurde über eine ab-c 10 -t 10-URL getestet, die ich vom Server selbst aus teste, daher sollte das Netzwerk nicht das Problem sein. Ich habe pro Anfrage mehr Benchmarks gepostet.
BarsMonster
Ich würde nicht zu viel Mühe mit ab tunen. Sie werden vielleicht feststellen, dass es sich nicht gut auf die Leistung in der realen Welt übertragen lässt. Möglicherweise möchten Sie Ihre App zerlegen und jede Komponente testen. Schlagen Sie zum Beispiel den Apache-Server direkt mit nur einer sehr kleinen statischen Seite an. Dadurch erhalten Sie eine Vorstellung von Ihren maximalen Anforderungen / Sek. Im Backend. Setzen Sie nginx voran und testen Sie den Aufruf derselben Backend-Datei erneut. Dann testen Sie mit einer einfachen PHP-Seite vom Typ "Hallo Welt". Manchmal können alle Ebenen etwas Einfaches maskieren. Beobachten Sie auch die Verbindungen während des Tests. Stellen Sie sicher, dass Ihr Netzwerkstapel nicht voll ist.
Jeffatrackaid
Ich habe diese Benchmarks gestern durchgeführt und sie sind in der aktualisierten Beschreibung der ursprünglichen Frage enthalten. Außerdem werden Tests auf localhost durchgeführt, sodass das Netzwerk kein Problem darstellt.
BarsMonster
Das Netzwerk kann ein Problem sein, selbst wenn es auf einem lokalen Host ausgeführt wird. Nicht wahrscheinlich in Ihrem Fall, aber es kann Probleme verursachen. Derzeit haben Sie mit Ihrem aktuellen PHP-Setup eine Obergrenze von ~ 450 Req / Sek. Der nächste Schritt besteht darin, einen Datenbankaufruf abzubrechen und zu sehen, wie sich dies ändert. Ich mag es, dies auseinander zu brechen, wenn ich auf hoher Ebene abstimme, da es Ihnen wirklich helfen kann, die Ebene zu lokalisieren, die die meisten Probleme verursacht.
Jeffatrackaid
-1

In 99% der Fälle werden solche Probleme auf die Datenbank zurückgeführt. Stellen Sie sicher, dass Ihre Trefferindizes zuallererst. Wenn das nicht funktioniert, fangen Sie an, alles, was Sie können, zwischenzuspeichern.


quelle
Es sind alles Indizes und wie ich schon sagte, trifft es in 100% der Fälle sogar den MySQL-Abfrage-Cache
BarsMonster
-1

Ich empfehle Ihnen, (wenn möglich) einen Verbindungspooler zu verwenden, um die Datenbank mit Ihren Webanwendungen verbunden zu halten (es ist nicht erforderlich, bei jeder Anforderung eine erneute Verbindung herzustellen). Das kann einen großen Unterschied in der Geschwindigkeit machen.

Versuchen Sie auch, alle Ihre Abfragen mit EXPLAIN zu analysieren (und warum nicht mit SHOW PROFILE ein Profil für Ihre Abfragen erstellen?).

Kedare
quelle
Alle Abfragen verwenden Indizes. Der MySQL-Verbindungspool wird verwendet.
BarsMonster