Skalowanie aplikacji internetowej: podstawowe kroki

Opublikowany: 2022-09-13

Nie wystarczy tworzyć aplikacje dla swojego biznesu, trzeba je zoptymalizować. Skutecznym sposobem jest skalowanie. W tym artykule dowiesz się o optymalizacji kodu, optymalizacji architektury i ogólnie o tworzeniu skalowalnych aplikacji internetowych.

Optymalizacja

Gearheart sugeruje zadawanie sobie następujących pytań:

  • czy zapytania do bazy danych są optymalne (analiza EXPLAIN, wykorzystanie indeksów)?
  • czy dane są prawidłowo przechowywane (SQL vs NoSQL)?
  • jest używane buforowanie?
  • żadnych zbędnych zapytań do FS lub bazy danych?
  • czy algorytmy przetwarzania danych są optymalne?
  • czy ustawienia środowiska są optymalne: Apache/Nginx, MySQL/PostgreSQL, PHP/Python?

Każde z tych pytań mogłoby zostać omówione w osobnym artykule, więc szczegółowe omówienie ich w ramach tego artykułu jest wyraźnie nadmierne. Ważne jest, aby zrozumieć, że przed rozpoczęciem skalowania aplikacji bardzo pożądane jest maksymalne zoptymalizowanie jej pracy – w rzeczywistości możliwe jest, że skalowanie nie będzie w ogóle wymagane.

skalowanie

Załóżmy, że już zoptymalizowałeś swoją aplikację, ale nadal nie jest ona w stanie obsłużyć obciążenia. W tym przypadku oczywistym rozwiązaniem jest rozproszenie aplikacji na wielu hostach w celu zwiększenia ogólnej wydajności aplikacji poprzez zwiększenie dostępnych zasobów. Takie podejście jest oficjalnie nazywane „skalowaniem” aplikacji. Dokładniej, skalowalność to zdolność systemu do zwiększania wydajności poprzez zwiększanie ilości dostępnych mu zasobów.

Istnieją dwa rodzaje skalowalności: pionowa i pozioma. Skalowalność pionowa oznacza zwiększenie wydajności aplikacji poprzez dodanie zasobów (procesora, pamięci, dysku) w ramach jednego węzła (hosta). Skalowanie poziome jest typowe dla aplikacji rozproszonych i oznacza zwiększenie wydajności aplikacji przez dodanie kolejnego węzła.

Oczywiste jest, że najprostszym sposobem jest prosta modernizacja sprzętu (procesora, pamięci, dysku) – czyli skalowanie w pionie. Ponadto takie podejście nie wymaga żadnych modyfikacji aplikacji. Jednak skalowanie w pionie szybko osiąga swoje granice, po czym deweloper i administrator nie mają innego wyjścia, jak przejść na skalowanie poziome aplikacji.

Architektura aplikacji

Większość aplikacji webowych jest rozproszona a priori, ponieważ ich architekturę można podzielić na co najmniej trzy warstwy: web-serwer, logika biznesowa (aplikacja), dane (baza danych, statyczna).

Każdą z tych warstw można skalować. Jeśli więc Twój system ma aplikację i bazę danych znajdujące się na tym samym hoście, pierwszym krokiem powinno być zdecydowanie rozdzielenie ich na różnych hostach.

Wąskie gardło

Przechodząc do skalowania systemu, pierwszą rzeczą do zrobienia jest określenie, która z warstw jest „wąskim gardłem”, tj. wolniejsza niż reszta systemu. Na początek możesz użyć trywialnych narzędzi, takich jak top (htop) do oceny zużycia procesora/pamięci i df, iostat do oceny zużycia dysku. Jednak pożądane jest zapewnienie oddzielnego hosta z emulacją obciążenia bitewnego (przy użyciu AB lub JMeter), na którym można profilować aplikację za pomocą narzędzi takich jak xdebug, oprofile i tak dalej. Możesz użyć narzędzi takich jak pgFouine do identyfikacji wąskich zapytań do bazy danych (oczywiście lepiej zrobić to na podstawie logów z serwera bitewnego).

Zwykle zależy to od architektury aplikacji, ale generalnie najbardziej prawdopodobnymi kandydatami na wąskie gardło są baza danych i kod. Jeśli aplikacja obsługuje wiele danych użytkownika, wąskim gardłem może być magazyn statyczny.

Skalowanie bazy danych

Jak wspomniano powyżej, wąskim gardłem nowoczesnych aplikacji jest często baza danych. Problemy z nim dzielą się zwykle na dwie klasy: wydajność oraz konieczność przechowywania dużej ilości danych.

Możesz zmniejszyć obciążenie bazy danych, dzieląc ją na kilka hostów. Występuje poważna trudność w synchronizacji między nimi, którą można rozwiązać, wdrażając schemat master/slave z replikacją synchroniczną lub asynchroniczną. W przypadku PostgreSQL możesz użyć Slony-I do replikacji synchronicznej i PgPool-II lub WAL (9.0) do replikacji asynchronicznej. Aby rozwiązać problem dzielenia żądań odczytu i zapisu, a także równoważenia obciążenia między urządzeniami podrzędnymi, można skonfigurować specjalną warstwę dostępu do bazy danych (PgPool-II).

Problem przechowywania dużych ilości danych w przypadku relacyjnych baz danych można rozwiązać poprzez partycjonowanie („partycjonowanie” w PostgreSQL) lub wdrożenie bazy danych w rozproszonej bazie danych, takiej jak Hadoop DFS.

Możesz przeczytać o obu rozwiązaniach w doskonałej książce o konfiguracji PostgreSQL.

1. Jednak do przechowywania dużych ilości danych najlepszym rozwiązaniem jest sharding, który jest nieodłączną zaletą większości baz danych NoSQL (np. MongoDB).

2. Co więcej, bazy danych NoSQL na ogół działają szybciej niż ich pobratymcy SQL ze względu na brak narzutu na parsowanie/optymalizację zapytania, sprawdzanie integralności struktury danych itp. Temat porównywania relacyjnych baz danych z bazami NoSQL jest również dość obszerny i zasługuje na to osobny artykuł.

3. Osobno warte odnotowania jest doświadczenie Facebooka, który używa MySQL bez opcji JOIN. Taka strategia pozwala im znacznie łatwiej skalować bazę danych, jednocześnie przenosząc obciążenie z bazy na kod, który, jak zostanie opisane poniżej, skaluje się łatwiej niż baza danych.

Skalowanie kodu

  • Złożoność kodu skalowania zależy od liczby udostępnionych zasobów potrzebnych hostom do uruchomienia aplikacji. Czy będą to tylko sesje, czy będziesz musiał udostępniać pamięci podręczne i pliki? Tak czy inaczej, pierwszą rzeczą do zrobienia jest uruchomienie kopii aplikacji na wielu hostach w tym samym środowisku.
  • Następnie musisz skonfigurować równoważenie obciążenia/żądania między tymi hostami. Możesz to zrobić zarówno przez TCP (HAProxy), HTTP (nginx) jak i DNS.
  • Następnym krokiem, jak wspomniał Gearheart, jest udostępnienie plików statycznych, pamięci podręcznej i sesji aplikacji internetowych na każdym hoście. W przypadku sesji możesz użyć serwera pracującego w sieci (na przykład Memcached). Jako serwer pamięci podręcznej sensowne jest używanie tego samego Memcached, ale oczywiście na innym hoście.
  • Pliki statyczne można montować z niektórych współdzielonych magazynów plików przez NFS/CIFS lub przy użyciu rozproszonego FS (HDFS, GlusterFS, Ceph).

Możliwe jest również przechowywanie plików w bazie danych (np. Mongo GridFS), rozwiązując tym samym problem dostępności i skalowalności (uwzględniając, że dla bazy danych NoSQL problem skalowalności rozwiązuje sharding).

Osobno warto zwrócić uwagę na kwestię wdrożenia na wielu hostach. Jak upewnić się, że użytkownik klikając „Aktualizuj” nie widzi różnych wersji aplikacji? Moim zdaniem najprostszym rozwiązaniem byłoby wyłączenie z konfiguracji load balancera (serwera WWW) hostów, które nie są aktualizowane, i włączanie ich sekwencyjnie w miarę dokonywania aktualizacji. Możesz także powiązać użytkowników z określonymi hostami za pomocą pliku cookie lub adresu IP. Jeżeli aktualizacja wymaga znaczących zmian w bazie danych, najprostszym sposobem jest tymczasowe zamknięcie projektu.