Jak odtworzyć warunki błędu i zobaczyć, co dzieje się podczas działania aplikacji?
Jak wizualizujesz interakcje między różnymi współbieżnymi częściami aplikacji?
Z mojego doświadczenia wynika, że odpowiedzi na te dwa aspekty są następujące:
Rozproszone śledzenie
Rozproszone śledzenie to technologia, która przechwytuje dane czasowe dla poszczególnych współbieżnych elementów systemu i przedstawia je w formacie graficznym. Reprezentacje jednoczesnych wykonań są zawsze przeplatane, co pozwala zobaczyć, co działa równolegle, a co nie.
Rozproszone śledzenie ma swoje początki w (oczywiście) systemach rozproszonych, które z definicji są asynchroniczne i wysoce współbieżne. Rozproszony system z rozproszonym śledzeniem umożliwia ludziom:
a) zidentyfikuj ważne wąskie gardła, b) uzyskaj wizualną reprezentację idealnych „uruchomień” twojej aplikacji, oraz c) zapewnij wgląd w to, jakie współbieżne zachowanie jest wykonywane, d) uzyskaj dane czasowe, które można wykorzystać do oceny różnic między zmianami w twojej system (bardzo ważne, jeśli masz silne umowy SLA).
Konsekwencje rozproszonego śledzenia są jednak:
Dodaje to narzut do wszystkich współbieżnych procesów, ponieważ przekłada się na więcej kodu do wykonania i przesłania potencjalnie przez sieć. W niektórych przypadkach obciążenie to jest bardzo znaczące - nawet Google używa systemu śledzenia Dapper tylko w niewielkim podzbiorze wszystkich żądań, aby nie zniszczyć doświadczenia użytkownika.
Istnieje wiele różnych narzędzi, z których nie wszystkie są interoperacyjne. Jest to nieco poprawione przez standardy takie jak OpenTracing, ale nie w pełni rozwiązane.
Nie mówi nic o zasobach udostępnionych i ich bieżącym statusie. Możesz zgadnąć na podstawie kodu aplikacji i tego, co pokazuje wykres, ale nie jest to przydatne narzędzie w tym zakresie.
Obecne narzędzia zakładają, że masz do dyspozycji pamięć i pamięć. Hostowanie serwera timeseries może nie być tanie, w zależności od twoich ograniczeń.
Oprogramowanie do śledzenia błędów
Łączę do Sentry powyżej przede wszystkim dlatego, że jest to najczęściej używane narzędzie, i nie bez powodu - oprogramowanie do śledzenia błędów, takie jak wykonywanie środowiska wykonawczego Sentry, do jednoczesnego przesyłania śladu błędów napotkanych błędów na centralny serwer.
Korzyści netto z takiego dedykowanego oprogramowania w współbieżnym kodzie:
- Zduplikowane błędy nie są duplikowane . Innymi słowy, jeśli jeden lub więcej współbieżnych systemów napotka ten sam wyjątek, Sentry zwiększy raport o incydencie, ale nie prześle dwóch kopii incydentu.
Oznacza to, że możesz dowiedzieć się, który system współbieży napotyka dany rodzaj błędu, bez konieczności przechodzenia przez niezliczoną liczbę równoczesnych raportów o błędach. Jeśli kiedykolwiek spotkałeś się z spamem pochodzącym z rozproszonego systemu, wiesz, jak się czuje piekło.
Możesz nawet „otagować” różne aspekty systemu współbieżnego (chociaż zakłada to, że nie przeplatasz pracy dokładnie nad jednym wątkiem, co technicznie i tak nie jest zbieżne, ponieważ wątek po prostu skutecznie przeskakuje między zadaniami, ale nadal musi przetwarzać procedury obsługi zdarzeń do ukończenia) i zobacz rozkład błędów według tagu.
- Możesz zmodyfikować to oprogramowanie do obsługi błędów, aby podać dodatkowe szczegóły dotyczące wyjątków czasu wykonywania. Jakie otwarte zasoby miał proces? Czy istnieje współużytkowany zasób przechowywany przez ten proces? Który użytkownik doświadczył tego problemu?
To, oprócz drobiazgowych śladów stosu (i map źródłowych, jeśli musisz dostarczyć zminimalizowaną wersję swoich plików), ułatwia ustalenie, co dzieje się źle przez większą część czasu.
- (Specyficzne dla Sentry) Możesz mieć osobny pulpit raportowania Sentry dla testów uruchomieniowych systemu, co pozwala wychwytywać błędy podczas testowania.
Wady takiego oprogramowania obejmują:
Jak wszystko, dodają luzem. Na przykład możesz nie chcieć takiego systemu na sprzęcie wbudowanym. Zdecydowanie zalecam wykonanie próbnego uruchomienia takiego oprogramowania, porównując proste wykonanie z próbką i bez niej w kilkuset uruchomieniach na bezczynnej maszynie.
Nie wszystkie języki są jednakowo obsługiwane, ponieważ wiele z tych systemów polega na domniemanym wyłapywaniu wyjątku i nie wszystkie języki mają solidne wyjątki. To powiedziawszy, są klienci dla wielu systemów.
Mogą być podnoszone jako zagrożenie bezpieczeństwa, ponieważ wiele z tych systemów jest zasadniczo zamkniętych źródeł. W takich przypadkach dołóż należytej staranności, by je zbadać lub, jeśli wolisz, rzuć własną.
Nie zawsze dają ci potrzebne informacje. Jest to ryzyko przy wszystkich próbach zwiększenia widoczności.
Większość z tych usług została zaprojektowana dla wysoce współbieżnych aplikacji internetowych, więc nie każde narzędzie może być idealne dla twojego przypadku użycia.
Podsumowując : widoczność jest najważniejszą częścią każdego współbieżnego systemu. Dwie metody, które opisałem powyżej, w połączeniu z dedykowanymi pulpitami nawigacyjnymi na temat sprzętu i danych w celu uzyskania holidetycznego obrazu systemu w dowolnym punkcie czasowym, są szeroko stosowane w branży właśnie w celu rozwiązania tego aspektu.
Kilka dodatkowych sugestii
Spędziłem więcej czasu niż zależy mi na poprawianiu kodu przez ludzi, którzy próbowali rozwiązać współbieżne problemy w okropny sposób. Za każdym razem znajdowałem przypadki, w których następujące rzeczy mogą znacznie poprawić wrażenia programistów (co jest równie ważne jak wrażenia użytkownika):
Polegaj na typach . Wpisanie istnieje w celu sprawdzenia poprawności kodu i może być używane w czasie wykonywania jako dodatkowa ochrona. Tam, gdzie pisanie nie istnieje, polegaj na stwierdzeniach i odpowiedniej procedurze obsługi błędów, aby wychwycić błędy. Współbieżny kod wymaga kodu obronnego, a typy służą jako najlepszy dostępny sposób sprawdzania poprawności.
- Testuj połączenia między składnikami kodu , a nie tylko samym składnikiem. Nie należy tego mylić z pełnoprawnym testem integracyjnym - który testuje każde łącze między każdym komponentem, a nawet wtedy szuka tylko globalnej weryfikacji ostatecznego stanu. To okropny sposób na wyłapywanie błędów.
Dobry test linku sprawdza, czy kiedy jeden komponent rozmawia w izolacji z innym komponentem , otrzymana wiadomość i wysłana wiadomość są takie same, jak się spodziewasz. Jeśli masz dwa lub więcej składników polegających na wspólnej usłudze do komunikacji, rozłącz je wszystkie, poproś, aby wymieniły wiadomości za pośrednictwem usługi centralnej i sprawdź, czy wszystkie dostają to, czego oczekujesz w końcu.
Rozbijanie testów obejmujących wiele komponentów na test samych komponentów i test na to, jak również komunikują się poszczególne komponenty, daje większą pewność co do poprawności kodu. Mając tak rygorystyczny zestaw testów, możesz egzekwować umowy między usługami, a także wychwytywać nieoczekiwane błędy, które pojawiają się, gdy są uruchomione jednocześnie.
- Użyj odpowiednich algorytmów, aby sprawdzić poprawność stanu aplikacji. Mówię o prostych rzeczach, takich jak proces nadrzędny, który czeka na wszystkich pracowników, aby ukończyli zadanie, i chcę przejść do następnego kroku, jeśli wszyscy pracownicy są w pełni skończeni - jest to przykład wykrywania globalnego zakończenie, dla którego istnieją znane metodologie, takie jak algorytm Safry.
Niektóre z tych narzędzi są dostarczane w pakiecie z językami - na przykład Rust gwarantuje, że Twój kod nie będzie miał warunków wyścigu w czasie kompilacji, podczas gdy Go ma wbudowany wykrywacz zakleszczeń, który działa również w czasie kompilacji. Jeśli potrafisz wychwycić problemy, zanim trafią do produkcji, zawsze jest to wygrana.
Ogólna ogólna zasada: projektowanie awarii w systemach współbieżnych . Przewiduj, że wspólne usługi ulegną awarii lub pękną. Dotyczy to nawet kodu, który nie jest dystrybuowany między komputerami - współbieżny kod na jednym komputerze może polegać na zewnętrznych zależnościach (takich jak wspólny plik dziennika, serwer Redis, cholerny serwer MySQL), które mogą zniknąć lub zostać usunięte w dowolnym momencie .
Najlepszym sposobem na to jest sprawdzanie stanu aplikacji od czasu do czasu - sprawdzanie stanu każdej usługi i upewnianie się, że konsumenci tej usługi są powiadamiani o złym stanie zdrowia. Nowoczesne narzędzia kontenerowe, takie jak Docker, robią to całkiem dobrze i powinny być używane do piaskownicy.
Jak wymyślić, co można uczynić współbieżnym, a co sekwencyjnym?
Jedną z największych lekcji, których nauczyłem się podczas pracy nad wysoce współbieżnym systemem, jest to: nigdy nie możesz mieć wystarczającej liczby wskaźników . Metryki powinny napędzać absolutnie wszystko w twojej aplikacji - nie jesteś inżynierem, jeśli nie mierzysz wszystkiego.
Bez wskaźników nie można zrobić kilku bardzo ważnych rzeczy:
Oceń różnicę wprowadzoną przez zmiany w systemie. Jeśli nie wiesz, czy pokrętło strojenia A sprawiło, że metryka B wzrosła, a metryka C obniżyła się, nie wiesz, jak naprawić system, gdy ludzie pchają nieoczekiwanie złośliwy kod w twoim systemie (i będą pchać kod do twojego systemu) .
Dowiedz się, co musisz zrobić, aby poprawić. Dopóki nie dowiesz się, że w aplikacjach zaczyna brakować pamięci, nie możesz stwierdzić, czy powinieneś uzyskać więcej pamięci, czy kupić więcej dysku dla swoich serwerów.
Miary są tak ważne i niezbędne, że podjąłem świadomy wysiłek, aby zaplanować to, co chcę zmierzyć, zanim nawet zastanowię się, czego będzie wymagał system. W rzeczywistości metryki są tak ważne, że uważam, że są właściwą odpowiedzią na to pytanie: wiesz tylko, co można uczynić sekwencyjnym lub współbieżnym, gdy mierzysz, co robią bity twojego programu. Właściwa konstrukcja wykorzystuje liczby, a nie zgadywanie.
Biorąc to pod uwagę, z pewnością istnieje kilka podstawowych zasad:
Sekwencyjny oznacza zależność. Dwa procesy powinny być sekwencyjne, jeśli jeden jest w pewien sposób zależny od drugiego. Procesy bez zależności powinny być współbieżne. Zaplanuj jednak sposób radzenia sobie z awarią w górę strumienia, która nie uniemożliwia procesom w dalszym ciągu oczekiwania na czas nieokreślony.
Nigdy nie należy mieszać zadania związanego z operacjami we / wy z zadaniem związanym z procesorem na tym samym rdzeniu. Nie pisz (na przykład) przeszukiwacza sieci, który uruchamia dziesięć równoczesnych żądań w tym samym wątku, zgarnia je, gdy tylko się pojawią, i spodziewaj się skalowania do pięciuset - żądania we / wy idą do kolejki równolegle, ale CPU nadal będzie je przechodził szeregowo. (Ten jednowątkowy model sterowany zdarzeniami jest popularny, ale jest ograniczony ze względu na ten aspekt - zamiast tego zrozumieć, ludzie po prostu załamują ręce i mówią, że Node nie skaluje się, aby dać ci przykład).
Pojedynczy wątek może wykonać wiele operacji we / wy. Aby jednak w pełni wykorzystać współbieżność sprzętu, użyj pul wątków, które razem zajmują wszystkie rdzenie. W powyższym przykładzie uruchomienie pięciu procesów Pythona (z których każdy może korzystać z rdzenia na maszynie sześciordzeniowej) tylko do pracy z procesorem, a szósty wątek Pythona tylko do pracy we / wy będzie skalowany znacznie szybciej niż myślisz.
Jedynym sposobem na wykorzystanie współbieżności procesora jest dedykowana pula wątków. Pojedynczy wątek jest często wystarczający do wielu prac związanych z operacjami wejścia / wyjścia. Dlatego serwery sieciowe sterowane zdarzeniami, takie jak Nginx, lepiej skalują się (wykonują wyłącznie operacje związane z operacjami we / wy) niż Apache (które łączą pracę związaną z operacjami we / wy z czymś wymagającym procesora i uruchamiają proces na żądanie), ale po co używać węzła do działania dziesiątki tysięcy równoległych obliczeń GPU to okropny pomysł.