Czy powinienem pozostać przy Python, czy porzucić go, aby zająć się współbieżnością?

31

Mam projekt 10K LOC napisany w Django z całkiem sporą ilością selera ( RabbitMQ ) do zadań asynchronicznych i zadań w tle tam, gdzie to konieczne, i doszedłem do wniosku, że niektóre części systemu skorzystałyby na przepisaniu na coś innego niż Django dla lepszej współbieżności . Powody obejmują:

  • Obsługa sygnałów i obiekty zmienne. Zwłaszcza, gdy jeden sygnał wyzwala inny, obsługa ich w Django za pomocą ORM może być zaskakująca, gdy instancje zmieniają się lub znikają. Chcę zastosować metodę przesyłania wiadomości, w której przekazywane dane nie zmieniają się w module obsługi ( podejście kopiowania przy zapisie Clojure wydaje się być dobre, jeśli mam rację).
  • Części systemu nie są oparte na sieci Web i wymagają lepszego wsparcia w zakresie wykonywania zadań jednocześnie. Na przykład, system odczytuje tagi NFC , a gdy zostanie odczytany, dioda LED zaświeci się na kilka sekund (zadanie Celery), odtwarzany jest dźwięk (inne zadanie Celery), a baza danych jest odpytywana (inne zadanie). Jest to zaimplementowane jako polecenie zarządzania Django, ale Django i jego ORM są z natury synchroniczne i współużytkują pamięć ograniczają (myślimy o dodaniu większej liczby czytników NFC i nie sądzę, aby podejście Django + Celery działało dłużej, Chciałbym zobaczyć lepsze możliwości przekazywania wiadomości).

Jakie są zalety i wady korzystania z czegoś takiego jak Twisted lub Tornado w porównaniu z wyborem języka takiego jak Erlang lub Clojure ? Interesują mnie praktyczne korzyści i szkody.

Jak doszedłeś do wniosku, że niektóre części systemu wypadłyby lepiej w innym języku? Czy masz problemy z wydajnością? Jak poważne są te problemy? Jeśli może być szybszy, czy konieczne jest, aby był szybszy?

Przykład 1: Django w pracy poza żądaniem HTTP:

  1. Tag NFC jest czytany.
  2. Baza danych (i ewentualnie LDAP) jest odpytywana, a my chcemy coś zrobić, gdy dane staną się dostępne (czerwone lub zielone światło, odtwórz dźwięk). To blokuje za pomocą Django ORM, ale dopóki są dostępni pracownicy Selera, nie ma to znaczenia. Może być problem z większą liczbą stacji.

Przykład 2: „przekazywanie wiadomości” przy użyciu sygnałów Django:

  1. post_deleteZdarzenie jest przetwarzane inne przedmioty mogą być zmieniane lub usuwane z tego powodu.
  2. Na koniec powiadomienia powinny być wysyłane do użytkowników. Byłoby dobrze, gdyby argumenty przekazane do modułu obsługi powiadomień były kopiami usuniętych lub przeznaczonych do usunięcia obiektów i gwarantowały, że nie zostaną zmienione w module obsługi. (Można to zrobić ręcznie, po prostu nie przekazując obiektom zarządzanym przez ORM do programów obsługi.)
Simon Pantzare
źródło
Myślę, że lepsze odpowiedzi pojawią się, jeśli wyjaśnisz więcej o tym, dlaczego doszedłeś do wniosku
Winston Ewert,
5
Zanim ktokolwiek powie, że pytania dotyczące wyboru języka są nie na temat, zacznę mówić, że uważam, że to jest w porządku, ponieważ jest to praktyczny problem z określonymi wymaganiami. Mam nadzieję, że zawiera kilka szczegółowych porównań.
Adam Lear
Twisted jest przeciwieństwem współbieżnego! Jest to serwer jednowątkowy, który jest sterowany zdarzeniami, nigdzie cię nie doprowadzi, jeśli potrzebujesz prawdziwej współbieżności.

Odpowiedzi:

35

Myśli otwierające

Jak doszedłeś do wniosku, że niektóre części systemu wypadłyby lepiej w innym języku? Czy masz problemy z wydajnością? Jak poważne są te problemy? Jeśli może być szybszy, czy konieczne jest, aby był szybszy?

Asynchronia jednowątkowa

Istnieje kilka pytań i innych zasobów internetowych, które już zajmują się różnicami, zaletami i wadami asynchronii jednowątkowej vs. współbieżności wielu wątków. Interesujące jest przeczytanie o tym, jak działa jednowątkowy model asynchroniczny Node.js, gdy we / wy jest głównym wąskim gardłem, a jednocześnie obsługiwanych jest wiele żądań.

Twisted, Tornado i inne modele asynchroniczne doskonale wykorzystują jeden wątek. Ponieważ wiele programów internetowych ma wiele operacji we / wy (sieć, baza danych itp.), Czas oczekiwania na połączenia zdalne znacznie się zwiększa. To czas, który można poświęcić na wykonywanie innych czynności - takich jak uruchamianie innych wywołań bazy danych, renderowanie stron i generowanie danych. Wykorzystanie tego pojedynczego wątku jest niezwykle wysokie.

Jedną z największych zalet asynchronii jednowątkowej jest to, że zużywa ona znacznie mniej pamięci. W przypadku wykonywania wielu wątków każdy wątek wymaga pewnej ilości zarezerwowanej pamięci. Wraz ze wzrostem liczby wątków rośnie również ilość pamięci wymaganej tylko do istnienia wątków. Ponieważ pamięć jest skończona, oznacza to, że istnieją ograniczenia dotyczące liczby wątków, które można utworzyć w dowolnym momencie.


Przykład

W przypadku serwera WWW udawaj, że każde żądanie ma swój własny wątek. Powiedz, że 1 MB pamięci jest wymagany na każdy wątek, a serwer WWW ma 2 GB pamięci RAM. Ten serwer sieciowy byłby w stanie przetworzyć (w przybliżeniu) 2000 żądań w dowolnym momencie, zanim zabraknie pamięci do przetworzenia.

Jeśli obciążenie jest znacznie wyższe, żądania zajmą bardzo dużo czasu (czekając na zakończenie starszych wniosków) lub będziesz musiał wrzucić więcej serwerów do klastra, aby zwiększyć liczbę możliwych jednoczesnych żądań .


Współbieżność wielu wątków

Współbieżność wielowątkowa polega natomiast na wykonywaniu kilku zadań jednocześnie. Oznacza to, że jeśli wątek zostanie zablokowany w oczekiwaniu na wywołanie bazy danych w celu powrotu, inne żądania mogą być przetwarzane w tym samym czasie. Wykorzystanie wątku jest niższe, ale liczba wykonanych wątków jest znacznie większa.

Kod wielowątkowy jest również znacznie trudniejszy do uzasadnienia. Występują problemy z blokowaniem, synchronizacją i innymi problemami dotyczącymi współbieżności. Asynchronia jednowątkowa nie ma tych samych problemów.

Kod wielowątkowy jest jednak znacznie wydajniejszy w przypadku zadań intensywnie wykorzystujących procesor . Jeśli nie ma możliwości, aby wątek „ustąpił” - tak jak połączenie sieciowe, które normalnie by blokowało - model z jednym wątkiem po prostu nie będzie miał żadnej współbieżności.

Oba mogą współistnieć

Oczywiście oba te elementy nakładają się; nie wykluczają się wzajemnie. Na przykład kod wielowątkowy można zapisać w sposób nieblokujący, aby lepiej wykorzystać każdy wątek.


Dolna linia

Jest wiele innych kwestii do rozważenia, ale lubię myśleć o dwóch takich:

  • Jeśli twój program jest powiązany z We / Wy , to asynchronia jednowątkowa prawdopodobnie będzie działać całkiem dobrze.
  • Jeśli twój program jest powiązany z procesorem , prawdopodobnie najlepszy będzie system wielowątkowy.

W konkretnym przypadku musisz ustalić, jaki rodzaj pracy asynchronicznej jest wykonywany i jak często te zadania powstają.

  • Czy występują na każde żądanie? Jeśli tak, pamięć prawdopodobnie stanie się problemem wraz ze wzrostem liczby żądań.
  • Czy te zadania są zlecone? Jeśli tak, musisz rozważyć synchronizację, jeśli używasz wielu wątków.
  • Czy te zadania wymagają dużej mocy obliczeniowej procesora? Jeśli tak, to czy pojedynczy wątek jest w stanie nadążyć za obciążeniem?

Nie ma prostej odpowiedzi. Musisz rozważyć, jakie są twoje przypadki użycia i odpowiednio zaprojektować. Czasami lepszy jest asynchroniczny model jednowątkowy. Innym razem wymagane jest użycie wielu wątków w celu osiągnięcia masowego przetwarzania równoległego.

Inne uwagi

Należy również wziąć pod uwagę inne kwestie, a nie tylko wybrany model współbieżności. Czy znasz Erlang lub Clojure? Czy uważasz, że byłbyś w stanie napisać bezpieczny wielowątkowy kod w jednym z tych języków, aby poprawić wydajność swojej aplikacji? Czy przygotowanie jednego z tych języków zajmie dużo czasu i czy język, którego się uczysz, będzie korzystny w przyszłości?

Co powiesz na trudności związane z komunikacją między tymi dwoma systemami? Czy utrzymywanie równolegle dwóch oddzielnych systemów będzie zbyt skomplikowane? W jaki sposób system Erlang odbierze zadania od Django? W jaki sposób Erlang przekaże te wyniki z powrotem do Django? Czy wydajność jest na tyle istotnym problemem, że dodatkowa złożoność jest tego warta?


Końcowe przemyślenia

Zawsze uważałem, że Django jest wystarczająco szybkie i jest używane przez niektóre witryny o dużym natężeniu ruchu. Istnieje kilka optymalizacji wydajności, które można wykonać w celu zwiększenia liczby jednoczesnych żądań i czasu odpowiedzi. Trzeba przyznać, że do tej pory nic nie robiłem z Selerem, więc zwykłe optymalizacje wydajności prawdopodobnie nie rozwiążą żadnych problemów związanych z tymi asynchronicznymi zadaniami.

Oczywiście zawsze pojawia się sugestia, aby rzucić więcej sprzętu na problem. Czy koszt zapewnienia nowego serwera jest tańszy niż koszt opracowania i utrzymania całkowicie nowego podsystemu?

W tym momencie zadałem zbyt wiele pytań, ale taka była moja intencja. Odpowiedź nie będzie łatwa bez analizy i dalszych szczegółów. Jednak umiejętność analizowania problemów sprowadza się do znajomości pytań, które należy zadać, ale… mam nadzieję, że pomogłem na tym froncie.

Moje przeczucie mówi, że przepisywanie w innym języku nie jest konieczne. Złożoność i koszt będą prawdopodobnie zbyt duże.


Edytować

Odpowiedź na działania następcze

Twoje działania następcze przedstawiają kilka bardzo interesujących przypadków użycia.


1. Django działa poza żądaniami HTTP

Twój pierwszy przykład obejmował odczyt tagów NFC, a następnie przeszukanie bazy danych. Nie sądzę, aby pisanie tej części w innym języku było dla ciebie przydatne, ponieważ zapytania do bazy danych lub serwera LDAP będą ograniczone przez sieciowe operacje we / wy (i potencjalnie wydajność bazy danych). Z drugiej strony liczba równoczesnych żądań będzie ograniczona przez sam serwer, ponieważ każde polecenie zarządzania będzie uruchamiane jako osobny proces. Nastąpi czas konfiguracji i porzucania, który wpływa na wydajność, ponieważ nie wysyłasz wiadomości do już uruchomionego procesu. Będziesz jednak mógł wysyłać wiele żądań jednocześnie, ponieważ każde z nich będzie procesem izolowanym.

W tym przypadku widzę dwie ścieżki, które możesz zbadać:

  1. Upewnij się, że baza danych jest w stanie obsłużyć wiele zapytań jednocześnie z pulą połączeń. (Na przykład Oracle wymaga odpowiedniego skonfigurowania Django 'OPTIONS': {'threaded':True}.) Mogą istnieć podobne opcje konfiguracji na poziomie bazy danych lub na poziomie Django, które można dostosować do własnej bazy danych. Bez względu na język, w którym piszesz zapytania do bazy danych, będziesz musiał poczekać na powrót tych danych, zanim zaświecisz diody LED. Wydajność kodu zapytania może jednak mieć znaczenie, a ORM Django nie jest błyskawiczny ( ale zazwyczaj wystarczająco szybki).
  2. Zminimalizuj czas instalacji / porzucania. Miej ciągły proces i wysyłaj do niego wiadomości. (Popraw mnie, jeśli się mylę, ale na tym właśnie koncentruje się twoje pierwotne pytanie.) Czy proces ten jest napisany w Pythonie / Django, czy w innym języku / frameworku został omówiony powyżej. Nie podoba mi się pomysł częstego używania poleceń zarządzania. Czy możliwe jest ciągłe działanie małego fragmentu kodu, który wypycha wiadomości z czytników NFC do kolejki wiadomości, którą Celery następnie czyta i przekazuje do Django? Konfiguracja i porzucenie małego programu, nawet jeśli jest napisany w Pythonie (ale nie w Django!), Powinno być lepsze niż uruchamianie i zatrzymywanie programu Django (ze wszystkimi jego podsystemami).

Nie jestem pewien, jakiego serwera używasz dla Django. mod_wsgidla Apache pozwala skonfigurować liczbę procesów i wątków w procesach, które obsługują żądania. Koniecznie dostosuj odpowiednią konfigurację serwera WWW, aby zoptymalizować liczbę żądań serwisowych.


2. „Przekazywanie wiadomości” za pomocą sygnałów Django

Drugi przypadek użycia jest również dość interesujący; Nie jestem pewien, czy mam na to odpowiedzi. Jeśli usuwasz instancje modelu i chcesz operować na nich później, może być możliwe ich serializowanie, JSON.dumpsa następnie deserializacja JSON.loads. Ponowne odtworzenie grafu obiektowego później (odpytywanie modeli pokrewnych) będzie niemożliwe, ponieważ powiązane pola są wczytywane z bazy danych leniwie, a to łącze już nie istnieje.

Inną opcją byłoby jakoś oznaczyć obiekt do usunięcia i usunąć go tylko na końcu cyklu żądania / odpowiedzi (po obsłużeniu wszystkich sygnałów). Może to wymagać niestandardowego sygnału do wdrożenia tego, zamiast polegać na post_delete.

Josh Smeaton
źródło
1
mnóstwo FUD i wątpliwości co do blokowania i innych rzeczy, które nie są problemami z Erlangiem, żaden z tradycyjnych problemów ze stanami współdzielonymi, które wymieniasz, nie są rozważaniami dotyczącymi języka i środowiska wykonawczego specjalnie zaprojektowanego do nieudostępniania stanu. Erlang radzi sobie z dziesiątkami tysięcy dyskretnych procesów w bardzo małym pamięci RAM, presja pamięci również nie stanowi problemu.
@Jarrod, ja osobiście nie znam Erlanga, więc zaakceptuję to, co powiesz na ten temat. W przeciwnym razie prawie wszystko, co wspomniałem, jest istotne. Koszt, złożoność oraz to, czy obecne narzędzia są właściwie wykorzystywane, czy nie.
Josh Smeaton
To jest taka epicka odpowiedź, którą naprawdę lubię czytać ^^. +1, dobra robota!
Laurent Bourgault-Roy
Również jeśli masz szablony DJango, można ich używać w Erlangu z Erlydtl
Zachary K
8

Zrobiłem bardzo wyrafinowane wysoce skalowalne opracowanie dla dużego amerykańskiego dostawcy usług internetowych . Zrobiliśmy kilka poważnych transakcji przy użyciu serwera Twisted i koszmarem złożoności było sprawienie, aby Python / Twisted skalował się na wszystko, co było związane z procesorem . Ograniczenie wejścia / wyjścia nie stanowi problemu, ale ograniczenie procesora było niemożliwe. Mogliśmy szybko połączyć systemy, ale doprowadzenie ich do skalowania do milionów współbieżnych użytkowników było koszmarem konfiguracji i złożoności, gdyby były one związane z procesorem.

Napisałem o tym post na blogu, Python / Twisted VS Erlang / OTP .

TLDR; Erlang wygrał.


źródło
4

Praktyczne problemy z Twisted (które uwielbiam i używałem od około pięciu lat):

  1. Dokumentacja pozostawia wiele do życzenia, a model i tak jest dość złożony. Trudno mi zmusić innych programistów Pythona do pracy nad kodem Twisted.
  2. Skończyło się na korzystaniu z blokowania we / wy pliku i dostępu do bazy danych z powodu braku dobrych blokujących interfejsów API. To może naprawdę zaszkodzić wydajności.
  3. Wydaje się, że nie ma ogromnej społeczności i zdrowej społeczności używającej Twisted; na przykład Node.js ma o wiele bardziej aktywny rozwój, szczególnie w zakresie programowania zaplecza WWW.
  4. Nadal jest to Python i przynajmniej CPython nie jest najszybszą rzeczą w okolicy.

Zrobiłem trochę pracy przy użyciu Node.js z CoffeeScript i jeśli zależy Ci na równoczesnej wydajności, może to być warte skoku.

Czy zastanawiałeś się nad uruchomieniem wielu instancji Django z pewnymi ustaleniami, aby rozdzielić klientów między instancje?

Dickon Reed
źródło
1
Dokumentacja Pythona ogólnie pozostawia coś do życzenia: / (Nie mówię, że jest tak źle, ale dla języka, którego popularny język mógłby oczekiwać, że będzie o wiele lepszy).
Rook
3
Uważam, że dokumentacja Python, aw szczególności dokumentacja Django, to jedne z najlepszych dokumentów dla każdego języka. Wiele bibliotek stron trzecich pozostawia jednak coś do życzenia.
Josh Smeaton,
1

Zasugeruję następujące, zanim rozważysz przejście na inny język.

  1. Użyj LTTng do rejestrowania zdarzeń systemowych, takich jak błędy stron, przełączniki kontekstu i połączenia systemowe.
  2. Konwertuj tam, gdzie korzystanie z biblioteki C zajmuje zbyt dużo czasu, i używaj dowolnego wzorca projektowego (wielowątkowego, opartego na zdarzeniach sygnałowych, asynchronizacji wywołania zwrotnego lub tradycyjnego systemu uniksowego select), który jest dobry dla I / O.

Nie używałbym wątków w Pythonie, gdy aplikacja ma priorytet pod względem wydajności. Wybrałbym powyższą opcję, która może rozwiązać wiele problemów, takich jak ponowne użycie oprogramowania, łączność z Django , wydajność, łatwość programowania itp.

holmes
źródło