Jak skalować php5 + MySQL powyżej 200 żądań na sekundę?

16

Poprawiam swoją stronę główną pod kątem wydajności, obecnie obsługuje ona około 200 żądań na sekundę w 3.14.by, która zjada 6 zapytań SQL, i 20 req / sekundę w 3.14.by/forum, które jest forum phpBB.

O dziwo, liczby są prawie takie same na niektórych serwerach VPS i dedykowanym serwerze Atom 330.

Oprogramowanie serwera to: Apache2 + mod_php prefork 4 childs (tutaj wypróbowano różne numery), php5, APC, nginx, memcached do przechowywania sesji PHP.

MySQL jest skonfigurowany do zużywania około 30% dostępnej pamięci RAM (~ 150 Mb na VPS, 700 Mb na dedykowanym serwerze)

Wygląda na to, że gdzieś jest wąskie gardło, które nie pozwala mi iść wyżej, jakieś sugestie? (tj. wiem, że wykonanie mniej niż 6 SQLów przyspieszyłoby to, ale nie wygląda to na czynnik ograniczający, ponieważ sqld zjada nie więcej niż kilka% w górę z powodu zapytań buforowanych)

Czy ktoś przetestował, że kopanie gotowego apache2 i pozostawienie samego nginx + php jest znacznie szybsze?

Więcej testów porównawczych

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.

Aktualizacja: Wydaje się, że wąskim gardłem jest wydajność MySQL na buforowanych danych. Strona z pojedynczym kodem SQL pokazuje 354 req / s, z 6 kodami SQL - 180 req / s. Jak myślisz, co mogę tu ulepszyć? (Mogę rozwidlić 100-200 Mb dla MySQL)

[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
źródło
Dlaczego używasz zarówno Apache, jak i nginx?
jamieb
To jest powszechna konfiguracja, Apache2 na PHP i różne aplikacje wymagające infrastruktury Apache, nginx w celu zmniejszenia obciążenia pamięci apache2 przy ładowaniu.
BarsMonster,
Właściwie nie rozumiem twojego problemu. Czy Twoja witryna jest obecnie wolna? Jeśli tak, to jak wolno? A jak bardzo chcesz to przyspieszyć? Czy próbowałeś profilować jakieś fragmenty swojej witryny, aby ustalić, gdzie jest wąskie gardło?
jamieb
Jest w opisie: teraz jest na 180-200 żądań / sekundę. Chociaż jest to o wiele więcej niż strona główna, chcę dostosować tę konfigurację, aby inne witryny zbudowane na tej samej bazie kodu działały szybciej. Idealnie chcę nasycić połączenie 100Mbit dynamicznymi stronami :-)
BarsMonster
2
W tym kontekście „liczba żądań na sekundę” nie jest tak naprawdę znaczącą miarą. Mój netbook może obsłużyć „200 żądań na sekundę”. Musisz nam powiedzieć, jaki czas reakcji chcesz osiągnąć przy takim współczynniku połączenia.
jamieb

Odpowiedzi:

29

Oczywiście możesz wiele spróbować. Najlepszym rozwiązaniem jest gonienie dzienników w poszukiwaniu zapytań, które nie korzystają z indeksów (włącz dzienniki dla nich) i innych niezoptymalizowanych zapytań. Przez lata opracowałem ogromną listę opcji związanych z wydajnością, więc zamieściłem tutaj mały podzbiór dla twojej informacji - mam nadzieję, że to pomaga. Oto kilka ogólnych uwag na temat rzeczy, które możesz wypróbować (jeśli jeszcze tego nie zrobiłeś):

MySQL

  • query_cache_type = 1 - buforowane zapytania SQL są włączone. Jeśli ustawione na 2, zapytania są buforowane tylko wtedy, gdy przekazana jest do nich wskazówka SQL_CACHE. Podobnie z typem 1, można wyłączyć pamięć podręczną dla określonego zapytania za pomocą podpowiedzi SQL_NO_CACHE
  • key_buffer_size = 128M (domyślnie: 8M) - bufor pamięci dla indeksów tabel MyISAM. Na serwerach dedykowanych staraj się ustawić rozmiar klucza_bufor na co najmniej jedną czwartą, ale nie więcej niż połowę całkowitej ilości pamięci na serwerze
  • query_cache_size = 64M (domyślnie: 0) - rozmiar pamięci podręcznej zapytania
  • back_log = 100 (domyślnie: 50, max: 65535) - Kolejka oczekujących żądań połączenia. Ma to znaczenie tylko wtedy, gdy jest dużo połączeń w krótkim czasie
  • join_buffer_size = 1M (domyślnie: 131072) - bufor używany podczas pełnego skanowania tabeli (bez indeksów)
  • table_cache = 2048 (domyślnie: 256) - powinno być max_user_connections pomnożone przez maksymalną liczbę JOINów, które zawiera najcięższe zapytanie SQL. Użyj zmiennej „open_tables” w godzinach szczytu jako przewodnika. Spójrz także na zmienną „open_tables” - powinna być zbliżona do „open_tables”
  • query_prealloc_size = 32K (domyślnie: 8K) - trwała pamięć do analizowania i wykonywania instrukcji. Zwiększ, jeśli masz złożone zapytania
  • sort_buffer_size = 16M (domyślnie: 2M) - pomaga w sortowaniu (operacje ORDER BY i GROUP BY)
  • read_buffer_size = 2M (domyślnie: 128K) - Pomaga przy skanowaniu sekwencyjnym. Zwiększ, jeśli istnieje wiele kolejnych skanów.
  • read_rnd_buffer_size = 4M - pomaga tabeli MyISAM przyspieszyć czytanie po sortowaniu
  • max_length_for_sort_data - rozmiar wiersza do przechowywania zamiast wskaźnika wiersza w pliku sortowania. Można uniknąć przypadkowych odczytów z tabeli
  • key_cache_age_threshold = 3000 (domyślnie: 300) - czas przechowywania bufora kluczy w strefie aktywnej (zanim zostanie zdegradowany do ciepłego)
  • key_cache_division_limit = 50 (domyślnie: 100) - włącza bardziej wyrafinowany mechanizm eksmisji pamięci podręcznej (dwa poziomy). Oznacza procent do utrzymania na najniższym poziomie. delay_key_write = ALL - bufor klucza nie jest opróżniany dla tabeli przy każdej aktualizacji indeksu, ale tylko wtedy, gdy tabela jest zamknięta. Przyspiesza to zapisywanie kluczy, ale jeśli korzystasz z tej funkcji, powinieneś dodać automatyczne sprawdzanie wszystkich tabel MyISAM, uruchamiając serwer z opcją --myisam-recovery = BACKUP, FORCE
  • memlock = 1 - proces blokady w pamięci (w celu zmniejszenia zamiany wejścia / wyjścia)

Apacz

  • zmień metodę spawnowania (na przykład na mpm)
  • wyłącz dzienniki, jeśli to możliwe
  • AllowOverride None - w miarę możliwości wyłącz .htaccess. Zatrzymuje apache do wyszukiwania plików .htaccess, jeśli nie są one używane, więc zapisuje żądanie wyszukiwania plików
  • SendBufferSize - Ustaw na domyślny system operacyjny. W przeciążonych sieciach należy ustawić ten parametr zbliżony do rozmiaru największego pliku, który jest normalnie pobierany
  • KeepAlive Off (domyślnie On) - i zainstaluj lingerd, aby poprawnie zamykać połączenia sieciowe i działa szybciej
  • DirectoryIndex index.php - Lista plików powinna być jak najkrótsza i absolutna.
  • Opcje FollowSymLinks - w celu uproszczenia procesu dostępu do plików w Apache
  • Unikaj używania mod_rewrite lub przynajmniej złożonych wyrażeń regularnych
  • ServerToken = prod

PHP

  • variable_order = "GPCS" (jeśli nie potrzebujesz zmiennych środowiskowych)
  • register_globals = Wyłączone - oprócz tego, że stanowi zagrożenie bezpieczeństwa, ma również wpływ na wydajność
  • Zachowaj minimalną liczbę ścieżek include_path (unika się dodatkowych wyszukiwań systemu plików)
  • display_errors = Wył. - Wyłącz wyświetlanie błędów. Zdecydowanie zalecane dla wszystkich serwerów produkcyjnych (nie wyświetla brzydkich komunikatów o błędach w przypadku problemu).
  • magic_quotes_gpc = Wyłącz
  • magic_quotes _ * = Wył
  • output_buffering = Wł
  • Wyłącz rejestrowanie, jeśli to możliwe
  • expose_php = Wył
  • register_argc_argv = Wył
  • always_populate_raw_post_data = Wył
  • umieść plik php.ini w miejscu, w którym php będzie go najpierw szukał.
  • session.gc_divisor = 1000 lub 10000
  • session.save_path = "N; / path" - W przypadku dużych witryn rozważ jego użycie. Dzieli pliki sesji na podkatalogi

Poprawki systemu operacyjnego

  • Montuj używane dyski twarde z opcją -o noatime (bez czasu dostępu). Dodaj także tę opcję do pliku / etc / fstab.
  • Dostosuj / proc / sys / vm / swappiness (od 0 do 100), aby zobaczyć, co ma najlepsze wyniki
  • Użyj dysków RAM - mount --bind -ttmpfs / tmp / tmp
Ivan Peevski
źródło
To ładna lista, miałem już większość z nich, a kiedy dodałem pozostałe rzeczy, wydajność się nie zwiększyła. Wygląda na to, że wąskie gardło między PHP a MySQL nie jest w stanie obsłużyć więcej niż 800 żądań na sekundę z pamięci podręcznej zapytań ...
BarsMonster
Ok, jak połączyć się z bazą danych (mysql_pconnect () zamiast mysql_connect ())? Czy korzystasz z trwałych połączeń? spróbuj na dwa sposoby ...
Ivan Peevski
Jestem już na pconnect i pula połączeń jest włączona w php.ini ...: -S
BarsMonster
Tylko dla kompletności, spróbowałbym się połączyć. Widziałem przypadki (szczególnie w testach obciążenia), w których działa to lepiej.
Ivan Peevski
1

Jeśli wąskim gardłem nie jest procesor, to jego we / wy - sieć lub dysk. Więc ... musisz zobaczyć, jak dużo się dzieje. Nie pomyślałbym, że to sieć (chyba, że ​​używasz łącza półdupleksowego 10 Mb / s, ale warto sprawdzić przełącznik na wypadek, gdyby automatyczne wykrywanie nie działało prawidłowo).

To pozostawia dyskowe operacje we / wy, co może być dużym czynnikiem, szczególnie w przypadku VPS. Użyj sar lub iostat, aby spojrzeć na dyski, a następnie google, jak znaleźć więcej szczegółów, jeśli dysk jest intensywnie używany.

gbjbaanb
źródło
Tak, sieć nie stanowi problemu - podczas uruchamiania ab z lokalnego serwera wydajność jest taka sama. Sprawdziłem czas oczekiwania - jest poniżej 0,01% - w zasadzie wszystko jest w pamięci podręcznej dysku, a zapisywanie na dysku nie jest zaangażowane w przetwarzanie żądania (wszystkie dzienniki są wyłączone).
BarsMonster
1

Zajrzałbym do buforowania za pomocą Nginx ( memcached ) lub Varnish .

Przynajmniej powinieneś serwerować pliki statyczne z Nginx, jak powiedział SaveTheRbtz.

Espennilsen
źródło
Są to strony dynamiczne, więc wolałbym ich nie buforować.
BarsMonster,
1
memcached nie jest tradycyjną aplikacją buforującą i może zdziałać cuda dla dynamicznych stron. Znajduje się między bazą danych a twoją aplikacją. Najpierw aplikacja zapisuje w pamięci dla obiektu, jeśli go nie ma, to jest ładowany z bazy danych. Efektem netto jest to, że używasz pamięci RAM do obsługi żądań DB, a nie znacznie wolniejszej pamięci trwałej na DB.
jamieb
Memcache może być używany z nginx, znaną funkcją. Wolne przechowywanie trwałe nie jest używane, wszystko znajduje się w pamięci podręcznej zapytań w MySQL.
BarsMonster,
Pamięć podręczna Memcached i MySQL nie są tak naprawdę porównywalne; nawet nie robią tego samego. Szybko zestrzeliłeś prawie wszystkie zamieszczone tutaj sugestie, nie zawracając sobie głowy ich zrozumieniem. Polecam, abyś był bardziej otwarty.
jamieb
Doskonale rozumiem różnicę między pamięcią podręczną zapytań memcached a MySQL. Ale ze względu na fakt, że wszystko znajduje się w buforze zapytań ze współczynnikiem trafień 100%, nie nazwałbym tego „długim trwałym przechowywaniem”. Oryginalna wczorajsza odpowiedź dotyczyła używania NginX + Memcached, co jest dość powszechnym scenariuszem do buforowania całych stron. Buforowanie pojedynczych obiektów to kolejny, zupełnie inny scenariusz. Podczas gdy używanie memcached przed MySQL jest na stole, myślę o tym, aby uzyskać więcej soku bez niego na razie (ponieważ wymagałoby to sporo zmian kodu).
BarsMonster,
1

Ponieważ serwer nie wydaje się stanowić problemu, być może generator obciążenia jest. Spróbuj uruchomić go na kilku komputerach.

OliverS
źródło
Wydajność jest taka sama, nawet jeśli uruchomię ją z samego serwera. Bez względu na to, jak wiele współbieżnych połączeń - 10 lub 50. Testowanie obciążenia odbywa się za pomocą ab-c 10-t 10
BarsMonster
1

Wydaje mi się, że osiągasz maksymalną liczbę połączeń, które pozwala Apache. Spójrz na konfigurację Apache. Zwiększenie limitu serwera i maksymalnej liczby klientów powinno pomóc, jeśli nie jesteś już związany jakimś innym limitem, takim jak We / Wy lub pamięć. Spójrz na obecne wartości dla modułu mpm_prefork_module lub mpm_worker_module i dostosuj odpowiednio do swoich potrzeb.

ServerLimit 512
MaxClients 512
Erik Giberti
źródło
Czy naprawdę potrzebuję tego, pod warunkiem, że mam nginx przed Apache2, więc wierzę, że nie ma większego sensu posiadania więcej niż fizyczne rdzenie * 2 procesy Apache2 ....
BarsMonster
Właśnie to zweryfikowałem. Zwiększenie liczby procesów Apache2 z 4 do 16 wcale nie poprawiło wydajności (nawet spadło o 0,5%). Zwiększenie liczby pracowników nginx do 2 lub 4 niczego nie poprawiło.
BarsMonster
1
Jeśli twoje dane są dość statyczne, tj. Nie aktualizują się przy każdym ładowaniu strony, możesz zwiększyć ilość twoich zapytań. MySQL w ten sposób zatrzyma zestaw wyników i pobierze z pamięci. Jeśli jednak buforowana tabela odbiera w tym czasie jakiekolwiek zapisy, unieważnia pamięć podręczną (nawet jeśli nie ma to wpływu na dane), co powoduje marnowanie pamięci.
Erik Giberti
W tej chwili widzę współczynnik trafień w pamięci podręcznej 100%, a MySQL wciąż jest powolny ...
BarsMonster
1
Dodaj pominięcie nazwy-rozwiązania do pliku konfiguracyjnego MySQL. Pozwoli to zaoszczędzić wyszukiwanie DNS dla każdego połączenia z serwerem. Wadą jest to, że wszystkie połączenia będą musiały być zablokowane przez IP (zakładając, że nie używasz „%”). Jeśli SQL znajduje się na tym samym serwerze i nie ma potrzeby uzyskiwania do niego dostępu w innym miejscu niż localhost, możesz również dodać pominięcie sieci, aby zabić cały stos TCP / IP. Myślę jednak, że wąskim gardłem jest Apache.
Erik Giberti
0

Czy to obciążenie jest generowane przez narzędzie lub obciążenia w świecie rzeczywistym?

Możesz sprawdzić memcached. Widziałem problemy z wysoką prędkością połączenia powodujące opóźnienia w aplikacji.

Jeśli korzystasz z generatora obciążenia, co otrzymujesz po wejściu na małą stronę statyczną?

Podczas ładowania możesz sprawdzić stos sieciowy pod kątem warunków TIME_WAIT. Być może zapełniasz kolejkę połączeń.

Istnieje około 100 innych powodów i przedmiotów, na które możesz spojrzeć, ale bez dodatkowych informacji, w tej chwili rzucam domysły.

jeffatrackaid
źródło
Jest testowany przez ab-c 10-t 10 URL. Testuję z samego serwera, więc sieć nie powinna być problemem. Opublikowałem więcej testów porównawczych na Twoje zapytanie.
BarsMonster,
Nie poświęciłbym zbyt wiele wysiłku na strojenie z ab. Może się okazać, że nie przekłada się to dobrze na rzeczywiste wyniki. Co możesz zrobić, to przeanalizować aplikację i przetestować każdy składnik. Na przykład, bezpośrednio na serwer apache, wystarczy bardzo mała statyczna strona. To da ci wyobrażenie o maksymalnych wymaganiach / sek. Na backend. Umieść nginx na pierwszym miejscu, ponownie przetestuj wywołując ten sam plik zaplecza. Następnie przetestuj za pomocą prostej strony php typu „hello world”. Czasami wszystkie warstwy mogą maskować coś prostego. Ponadto obserwuj połączenia podczas testu. Upewnij się, że stos sieciowy się nie zapełnia.
jeffatrackaid
Zrobiłem te testy wczoraj i są one w zaktualizowanym oryginalnym opisie pytania. Ponadto testy są przeprowadzane na localhost, więc sieć nie stanowi problemu.
BarsMonster,
Sieć może stanowić problem, nawet jeśli jest wykonywana na lokalnym hoście. W twoim przypadku jest to mało prawdopodobne, ale może powodować problemy. Przynajmniej teraz masz górny limit ~ 450 req / s przy obecnej konfiguracji PHP. Następnym krokiem jest usunięcie połączenia z bazą danych i sprawdzenie, jak się to zmienia. Lubię to rozróżniać podczas strojenia na wysokim poziomie, ponieważ może to naprawdę pomóc w określeniu warstwy powodującej najwięcej problemów.
jeffatrackaid
-1

99% procent problemów związanych z czasem pojawi się w bazie danych. Najpierw upewnij się, że Twoje indeksy uderzeń. Jeśli to nie zadziała, zacznij buforować wszystko, co możesz.


źródło
Są to wszystkie indeksy i, jak powiedziałem, trafiają nawet do pamięci podręcznej zapytań MySQL w 100% przypadków
BarsMonster
-1

Zalecam użycie (jeśli to możliwe) puli połączeń, aby baza danych była połączona z aplikacjami internetowymi (nie trzeba ponownie łączyć się przy każdym żądaniu). To może zrobić ogromną różnicę prędkości.

Spróbuj także przeanalizować wszystkie zapytania za pomocą EXPLAIN (a dlaczego nie profilować swoich zapytań za pomocą SHOW PROFILE?).

Kedare
źródło
Wszystkie zapytania wykorzystują indeksy. Używana jest pula połączeń MySQL.
BarsMonster