node.js, mongodb, redis, w sprawie obniżenia wydajności Ubuntu w produkcji, pamięć RAM jest bezpłatna, procesor 100%

11

Jak sugeruje tytuł pytania, mam trudności z ustaleniem, co można ulepszyć w mojej aplikacji (lub dostroić w OS, Ubuntu), aby osiągnąć akceptowalną wydajność. Ale najpierw wyjaśnię architekturę:

Serwer front-end to 8-rdzeniowa maszyna z 8 gigabajtami pamięci RAM i systemem Ubuntu 12.04. Aplikacja jest napisana w całości w javascript i uruchomiona w node.js v 0.8.22 (ponieważ niektóre moduły wydają się narzekać na nowsze wersje węzła) używam nginx 1.4 do proxy ruchu HTTP z portu 80 i 443 do 8 pracowników węzłów, które są zarządzane i zaczął używać interfejsu API klastra węzłów. Używam najnowszej wersji socket.io 0.9.14 do obsługi połączeń websocket, na których włączyłem tylko websockets i xhr-polling jako dostępne transporty. Na tym komputerze uruchamiam również instancję Redis (2.2)

Przechowuję trwałe dane (takie jak użytkownicy i wyniki) na drugim serwerze na mongodb (3.6) z 4 GB pamięci RAM i 2 rdzeniami.

Aplikacja jest produkowana od kilku miesięcy (kilka tygodni temu działała na jednym pudełku) i jest używana przez około 18 000 użytkowników dziennie. Zawsze działało bardzo dobrze, pomijając jeden główny problem: spadek wydajności. Wraz z użyciem ilość procesora wykorzystywana przez każdy proces rośnie, dopóki nie osiągnie dojrzałości pracownika (który nie będzie już obsługiwał żądań). Tymczasowo rozwiązałem to sprawdzanie procesora używanego przez każdego pracownika co minutę i uruchamianie go ponownie, jeśli osiągnie 98%. Problemem jest więc głównie procesor, a nie pamięć RAM. Pamięć RAM nie jest już problemem, ponieważ zaktualizowałem do socket.io 0.9.14 (wcześniejsza wersja przeciekała pamięć), więc wątpię, aby była to kwestia nieszczelności pamięci, zwłaszcza że teraz jest to procesor, który rośnie dość szybko ( Każdego pracownika muszę ponownie uruchamiać około 10-12 razy dziennie!). Używana pamięć RAM również rośnie, mówiąc szczerze, ale bardzo powoli, 1 koncert co 2-3 dni użytkowania, a dziwne jest to, że nie jest on uwalniany nawet po całkowitym zrestartowaniu całej aplikacji. Zostanie wydany tylko po ponownym uruchomieniu serwera! tego tak naprawdę nie rozumiem ...

Teraz odkryłem, że Nodefly jest niesamowity, więc w końcu mogę zobaczyć, co się dzieje na moim serwerze produkcyjnym, i zbieram dane od kilku dni. Jeśli ktoś chce zobaczyć wykresy, mogę dać ci dostęp, ale w zasadzie widzę, że mam od 80 do 200 jednoczesnych połączeń! Spodziewałem się, że node.js obsłuży tysiące, a nie setki żądań. Również średni czas odpowiedzi dla ruchu http waha się między 500 a 1500 milisekund, co moim zdaniem to naprawdę dużo. Również w tym momencie, gdy 1300 użytkowników jest online, jest to wynik działania „ss -s”:

Total: 5013 (kernel 5533)
TCP:   8047 (estab 4788, closed 3097, orphaned 139, synrecv 0, timewait 3097/0), ports 0

Transport Total     IP        IPv6
*         5533      -         -
RAW       0         0         0
UDP       0         0         0
TCP       4950      4948      2
INET      4950      4948      2
FRAG      0         0         0

co pokazuje, że mam dużo zamkniętych połączeń w oczekiwaniu na czas. Zwiększyłem maksymalną liczbę otwartych plików do 999999, oto wynik działania ulimit -a:

core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 63724
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 999999
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 63724
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

Pomyślałem więc, że problem może dotyczyć ruchu HTTP, który z jakichś powodów nasyca dostępne porty / gniazda (?), Ale jedna rzecz nie ma dla mnie sensu: dlaczego po ponownym uruchomieniu pracowników i wszyscy klienci łączą się ponownie w ciągu kilku sekund, obciążenie procesora spada do 1% i jest w stanie poprawnie obsługiwać żądania, dopóki nie nasyci się po około 1 godzinie (w szczycie)?

Jestem głównie programistą javascript, a nie administratorem systemu, więc nie wiem, ile obciążenia powinienem oczekiwać na moich serwerach, ale na pewno nie działa tak, jak powinien. W przeciwnym razie aplikacja jest stabilna, a ten ostatni problem uniemożliwia mi wysyłanie gotowych wersji aplikacji mobilnych, ponieważ oczywiście przyniosą więcej obciążenia i ostatecznie spowodują awarię!

Mam nadzieję, że jest coś oczywistego, że robię źle, i ktoś pomoże to zauważyć ... nie wahaj się poprosić mnie o więcej informacji i przepraszam za długość pytania, ale było konieczne Wierzę ... z góry dziękuję!

Franjanko
źródło
Czy jest jakiś sposób, aby uzyskać coś takiego jak zrzut wątku z node.js? Prawdopodobnie jest kilka wątków w nieskończonej pętli. Ponadto, co tak naprawdę używa procesora? Co widzisz, topgdy użycie procesora jest bliskie 100%?
rvs
procesor jest używany w całości przez nodejs, kiedy uruchamiam top, widzę, że procesy węzła zajmują wszystkie procesory. Nie jestem pewien, jak mogę
rzucić
Inną rzeczą, na którą należy zwrócić uwagę, jest to, że większość czasu procesora wydaje się iść do systemu, a nie użytkownika
Franjanko
Czy ktoś przynajmniej wie, ile równoczesnych połączeń powinienem obsłużyć na moich serwerach? w tej chwili obsługuję 200 jednoczesnych połączeń max. Pomoże mi to oszacować, jak daleko jestem od optymalnej konfiguracji ... dzięki.
Franjanko

Odpowiedzi:

10

Po kilku dniach intensywnych prób i błędów cieszę się, że mogę powiedzieć, że zrozumiałem, gdzie jest wąskie gardło, i opublikuję go tutaj, aby inni mogli skorzystać z moich ustaleń.

Problem leży w połączeniach pub / sub, których używałem z socket.io, aw szczególności w RedisStore używanym przez socket.io do obsługi komunikacji między instancjami między procesami.

Po uświadomieniu sobie, że mogę łatwo wdrożyć własną wersję pub / sub za pomocą redis, postanowiłem spróbować i usunąłem redisStore z socket.io, pozostawiając domyślną pamięć (nie muszę transmitować do wszyscy połączeni klienci, ale tylko między 2 różnymi użytkownikami połączonymi ewentualnie na różnych procesach)

Początkowo zadeklarowałem tylko 2 globalne połączenia redis x proces do obsługi pub / sub na każdym podłączonym kliencie, a aplikacja zużywała mniej zasobów, ale ciągle odczuwałem ciągły wzrost wykorzystania procesora, więc niewiele się zmieniło. Ale potem postanowiłem spróbować utworzyć 2 nowe połączenia w celu ponownego wysłania dla każdego klienta, aby obsługiwał ich pub / sub tylko w swoich sesjach, a następnie zamknij połączenia, gdy użytkownik się rozłączy. Potem, po jednym dniu użytkowania w produkcji, procesory były na poziomie 0-5% ... bingo! bez ponownego uruchamiania procesu, bez błędów, z wydajnością, której się spodziewałem. Teraz mogę powiedzieć, że node.js się kołysze i cieszę się, że wybrałem go do budowy tej aplikacji.

Na szczęście redis został zaprojektowany do obsługi wielu równoczesnych połączeń (inaczej przez mongo) i domyślnie jest ustawiony na 10k, co pozostawia miejsce dla około 5k równoczesnych użytkowników, w jednym wystąpieniu redis, co jest wystarczające dla mnie w tej chwili, ale ja ' Przeczytałem, że można go zwiększyć do 64k równoczesnych połączeń, więc ta architektura powinna być wystarczająco solidna.

W tym momencie zastanawiałem się nad zaimplementowaniem jakiegoś rodzaju pul połączeń do redis, aby zoptymalizować je nieco dalej, ale nie jestem pewien, czy nie spowoduje to ponownego wystąpienia zdarzeń pub / sub na połączeniach, chyba że każde z nich jest niszczony i odtwarzany za każdym razem, aby je wyczyścić.

W każdym razie dziękuję za odpowiedzi, a ja będę ciekawy, co myślisz i czy masz jakieś inne sugestie.

Twoje zdrowie.

Franjanko
źródło
2
Mam coś, co wydaje się być tym samym problemem w mojej aplikacji produkcyjnej, również nowością w roli administratora serwera. Podążam za tym, co zrobiłeś w koncepcji, ale mam kilka pytań, jak to zrobić - być może mógłbyś podać link do jakiegoś zasobu w zaakceptowanej odpowiedzi? Lub po prostu podać więcej informacji? W szczególności o „Ale potem postanowiłem spróbować utworzyć 2 nowe połączenia w celu ponownego wysłania dla każdego klienta, który będzie obsługiwał ich pub / sub tylko w swoich sesjach, a następnie zamknij połączenia, gdy użytkownik się rozłączy”.
toblerpwn
2

Czy masz jakiś kod źródłowy do zrzucenia? Być może połączenia z bazą danych nie są zamknięte? Procesy oczekujące na połączenia HTTP, które nigdy się nie zamykają.

Czy możesz opublikować jakieś dzienniki?

Zrób ps -ef i upewnij się, że nic nie działa. Widziałem, jak procesy sieciowe opuszczają zombie, które nie umrą, dopóki nie zabijesz -9. Czasami wyłączenie nie działa lub nie działa w pełni, a te wątki lub procesy będą utrzymywać pamięć RAM, a czasem procesor.

Może to być nieskończona pętla gdzieś w kodzie lub zawieszony proces utrzymujący połączenie db na szczycie.

Jakie moduły NPM używają? Czy wszystkie są najnowsze?

Czy łapiesz wyjątki? Zobacz: http://geoff.greer.fm/2012/06/10/nodejs-dealing-with-errors/ Patrz: /programming/10122245/capture-node-js-crash-reason

Ogólne wskazówki:

http://clock.co.uk/tech-blogs/preventing-http-raise-hangup-error-on-destroyed-socket-write-from-crashing-your-nodejs-server

http://blog.nodejitsu.com/keep-a-nodejs-server-up-with-forever

http://hectorcorrea.com/blog/running-a-node-js-web-site-in-production-a-beginners-guide

/programming/1911015/how-to-debug-node-js-applications

https://github.com/dannycoates/node-inspector

http://elegantcode.com/2011/01/14/taking-baby-steps-with-node-js-debugging-with-node-inspector/

Tim Spann
źródło
1

Nie jest to odpowiedź sama w sobie, ponieważ twoje pytanie jest bardziej opowieścią niż jednoznaczne pytanie.

Po prostu powiem, że udało mi się zbudować serwer node.js z socket.io obsługujący ponad 1 milion trwałych połączeń ze średnią wartością wiadomości wynoszącą 700 bajtów.

Karta interfejsu sieciowego o przepustowości 1 Gb / s była nasycona na początku, i widziałem, jak DUŻO operacji we / wy czekało na publikowanie zdarzeń wszystkim klientom.

Usunięcie nginx z roli proxy również zwróciło cenną pamięć, ponieważ osiągnięcie miliona trwałych połączeń tylko z JEDNYM serwerem jest trudnym zadaniem dostrajania konfiguracji, aplikacji i dostrajania parametrów systemu operacyjnego. Należy pamiętać, że jest to wykonalne tylko przy dużej ilości pamięci RAM (około 1M połączeń websockets zjada około 16 GB pamięci RAM, z node.js, myślę, że użycie sock.js byłoby idealne do niskiego zużycia pamięci, ale na razie socket.io zużywa tyle).

To łącze było moim punktem wyjścia do osiągnięcia takiej liczby połączeń z węzłem. Poza tym, że jest to aplikacja Erlang, całe dostrajanie systemu operacyjnego jest dość niezależne od aplikacji i powinno być przydatne dla każdego, kto dąży do wielu trwałych połączeń (gniazda sieciowe lub długie odpytywanie).

HTH,

Marcel
źródło