W jakich warunkach (jeśli w ogóle) dobrą praktyką jest odpytywanie dwóch serwerów i korzystanie z najszybszej odpowiedzi?


12

Zapytałem, jakie jest teraz pytanie usunięte przez społeczność na temat SO, dlaczego ktoś miałby używać skryptu javascript Promise.race, a użytkownik z wysokim przedstawicielem skomentował to:

Jeśli masz dwie usługi, które obliczają pewną wartość, możesz przesłać do nich zapytanie równolegle i użyć tej, która zawsze zwróci najpierw wartość, zamiast zapytania o jedną, czekając na awarię, a następnie sprawdzając drugą.

Znalazłem się na temat redundancji i ogólnie tego przypadku użycia, ale nic nie mogłem znaleźć, a z mojego POV nigdy nie jest dobrym pomysłem, aby po prostu dodać obciążenie do serwera / usługi, jeśli nie zamierzasz użyć odpowiedzi.


Przykład zabawki: zamiast zawsze używać szybkiego sortowania, kopiujesz dane, wysyłasz je do szybkiego sortowania, scalania i heapsortu ... itd. Nie musisz sprawdzać danych wejściowych, aby sprawdzić, czy jest to przypadek patologiczny na każdy z nich, ponieważ nie będzie pathalogical sprawa dla wszystkich z nich
Caleth

Artykuł Deana i Barroso Ogon w skali nazywa odmianę tego podejścia „żądaniami zabezpieczonymi”. Omówiono także zalety i wady kilku powiązanych podejść do kontrolowania długofalowej zmienności poziomów błędów i opóźnień.
Daniel Pryden

Drugie „żądanie serwera” może być fałszywe. Może po prostu chcieć przez 5 sekund, a następnie zwrócić odpowiedź zastępczą. To daje ci limit czasu na prawdziwą prośbę.
user253751,

Zamów Lyft, a następnie zamów Ubera. Weź to, co nastąpi pierwsze.
user2023861,

@ user2023861 w tej analogii, podczas gdy jeden kierowca bezcelowo jechał w kierunku Twojej lokalizacji, mógł zamiast tego przyjąć inną prośbę
Adelin

Odpowiedzi:


11

Twierdziłbym, że jest to bardziej kwestia ekonomiczna. Jest to jednak wezwanie do osądu, które inżynierowie powinni być w stanie wykonać. Dlatego odpowiadam.

Podzielę swoją odpowiedź na cztery części:

  • Zarządzanie ryzykiem
  • Strategie
  • Koszty
  • Intuicja

Zarządzanie ryzykiem

Czasami więc twój klient nie otrzymuje odpowiedzi z serwera. Zakładam, że nie dzieje się tak z powodu błędu programowego (w przeciwnym razie rozwiązaniem jest naprawa, więc zrób to). Zamiast tego musi to wynikać z sytuacji losowej, na którą nie masz wpływu ...

Ale nie poza twoją wiedzą. Musisz wiedzieć:

  • Jak często to się zdarza.
  • Jaki to ma wpływ.

Na przykład, jeśli awaria i ponowna próba zdarzają się tylko w około 2% przypadków, prawdopodobnie nie warto tego robić. Jeśli zdarzy się to w 80% przypadków, cóż ... zależy ...

Ile czasu musi czekać klient? A jak to przekłada się na koszty ... Widzisz, masz niewielkie opóźnienie w regularnej aplikacji, to chyba nie jest wielka sprawa. Jeśli jest to znaczące, a masz aplikację czasu rzeczywistego lub grę online, odstraszy to użytkowników i prawdopodobnie lepiej inwestować w więcej lub lepsze serwery. W przeciwnym razie prawdopodobnie możesz umieścić komunikat „ładowanie” lub „czekanie na serwer”. O ile opóźnienie nie jest naprawdę duże (rzędu dziesiątek sekund), może być zbyt duże nawet w przypadku zwykłej aplikacji.


Strategie

Jak powiedziałem powyżej, istnieje więcej niż jeden sposób rozwiązania tego problemu. Zakładam, że masz już implementację pętli try-fail-retry. Zobaczmy więc ...

  • Umieść komunikat ładowania. Jest tani, pomaga utrzymać użytkownika.
  • Zapytanie równoległe. Może być szybszy, nadal może zawieść. Będzie wymagał nadmiarowego serwera (może być kosztowny), zmarnuje czas serwera i ruch sieciowy.
  • Zapytanie równoległe w celu ustalenia szybszego serwera i korzystania z niego odtąd. Może być szybszy, nadal może zawieść. Będzie wymagał nadmiarowego serwera (może być kosztowny), nie zmarnuje tyle czasu serwera i ruchu sieciowego.

Zauważ, że mówię, że nadal mogą się nie powieść. Jeśli założymy, że zapytanie do serwera ma 80% szansy na awarię, to równoległe zapytanie do dwóch serwerów ma 64% szansy na niepowodzenie. W związku z tym nadal możesz spróbować ponownie.

Dodatkową zaletą wyboru szybszego serwera i korzystania z niego jest to, że szybszy serwer jest mniej podatny na awarie z powodu problemów z siecią.

Co mi przypomina, jeśli możesz dowiedzieć się, dlaczego żądanie się nie powiedzie, zrób to. Pomoże ci to lepiej zarządzać sytuacją, nawet jeśli nie możesz zapobiec awariom. Na przykład, czy potrzebujesz większej prędkości transferu po stronie serwera?

Trochę więcej:

  • Wdróż wiele serwerów na całym świecie i wybierz serwer według geolokalizacji.
  • Wykonuj równoważenie obciążenia po stronie serwera (dedykowana maszyna przyjmie wszystkie żądania i przekaże je do serwerów, możesz mieć tam równoległość lub lepszą strategię równoważenia).

A kto powiedział, że musisz zrobić tylko jedną z nich? Możesz umieścić komunikat ładowania, wysłać zapytanie do wielu serwerów rozmieszczonych w folderze Wrold, aby wybrać szybciej i używać go tylko odtąd, po ponownym niepowodzeniu w pętli, a każdy z tych serwerów może być klastrem maszyn z równoważeniem obciążenia . Dlaczego nie? Cóż, kosztuje ...


Koszty

Istnieją cztery koszty:

  • Koszt opracowania (zwykle bardzo tani)
  • Koszt wdrożenia (zwykle wysoki)
  • Koszt czasu wykonania (zależy od rodzaju aplikacji i modelu biznesowego)
  • Koszt awarii (prawdopodobnie niski, ale niekoniecznie)

Musisz je zrównoważyć.

Powiedzmy na przykład, że zarabiasz około dolara na zadowolonego użytkownika. Że masz 3000 użytkowników dziennie. Że żądania kończą się niepowodzeniem przez około 50% czasu. I że 2% użytkowników wychodzi bez płacenia, gdy żądanie nie powiedzie się. Oznacza to, że tracisz (3000 * 50% * 2%) 30 dolarów dziennie. Teraz powiedzmy, że opracowanie nowej funkcji będzie kosztować 100 dolarów, a wdrożenie serwerów będzie kosztować 800 dolarów - i zignorowanie kosztów środowiska wykonawczego - oznacza to, że uzyskasz zwrot z inwestycji w ((100 + 800) / 30 ) 30 dni. Teraz możesz sprawdzić swój budżet i zdecydować.

Nie uważaj tych wartości za reprezentatywne dla rzeczywistości, wybrałem je ze względu na matematykę.

Dodatki:

  • Pamiętaj, że ignoruję również szczegóły. Na przykład możesz mieć niewielkie koszty wdrożenia, ale płacisz za czas procesora i musisz to wziąć pod uwagę.
  • Niektórzy klienci mogą to docenić, jeśli nie zmarnujesz ich pakietu danych na zbędne żądania.
  • Ulepszenie produktu może pomóc w uzyskaniu naturalnej reklamy.
  • Nie zapomnij o kosztach alternatywnych. Czy powinieneś rozwijać coś innego?

Chodzi o to, że jeśli weźmiesz pod uwagę problem związany z równoważeniem kosztów, możesz oszacować koszt rozważanych strategii i użyć tej analizy do podjęcia decyzji.


Intuicja

Intuicja, jeśli jest wspierana przez doświadczenie. Nie sugeruję przeprowadzania tego rodzaju analiz za każdym razem. Niektóre osoby tak robią i to jest w porządku. Sugeruję, abyście trochę to zrozumieli i wypracowali intuicję.

Ponadto w inżynierii, oprócz wiedzy, którą zdobywamy z faktycznej nauki, uczymy się również w praktyce i opracowujemy wytyczne dotyczące tego, co działa, a co nie. Dlatego często mądrze jest zobaczyć aktualny stan techniki ... chociaż czasami trzeba zobaczyć poza swoim obszarem.

W takim przypadku patrzyłbym na gry wideo online. Mają ekrany ładowania, mają wiele serwerów, wybiorą serwer na podstawie opóźnienia, a nawet mogą zezwolić użytkownikowi na zmianę serwerów. Wiemy, że to działa.

Sugerowałbym to zrobić zamiast marnować ruch sieciowy i czas serwera na każde żądanie, należy również pamiętać, że nawet w przypadku nadmiarowego serwera może wystąpić awaria.


2
Nie sądzę, żebym musiał to powiedzieć, ale to świetna odpowiedź :) Wiedziałem, że zaakceptuję to w pierwszych 10 liniach, ale dałem ci szansę, byś nadal poniósł klęskę i przeczytał ją do końca. Nie zrobiłeś
Adelin

9

Jest to do przyjęcia, jeśli czas klienta jest cenniejszy niż czas na serwerze.

Jeśli klient musi być szybki i dokładny. Możesz uzasadnić zapytanie do kilku serwerów. I dobrze jest anulować żądanie, jeśli otrzymana zostanie prawidłowa odpowiedź.

Oczywiście zawsze mądrze jest skonsultować się z właścicielami / menedżerami serwerów.


Dlaczego musisz anulować prośbę? Z pewnością jest to subiektywne.
JᴀʏMᴇᴇ

@ JᴀʏMᴇᴇ, To jest kompilacja w paranoi. Kiedyś pracowałem z systemem, który nie wyczyścił swojej kolejki i zawiesił się, gdy kolejka była pełna (Tak, to było profesjonalne oprogramowanie).
Toon Krijthe

4

Ta technika może zmniejszyć opóźnienia. Czas odpowiedzi serwera nie jest deterministyczny. W skali prawdopodobnie istnieje co najmniej jeden serwer wykazujący słabe czasy odpowiedzi. W związku z tym wszystko, co korzysta z tego serwera, również będzie miało słabe czasy odpowiedzi. Przesyłając do kilku serwerów, zmniejsza się ryzyko rozmowy ze słabo działającym serwerem.

Koszty obejmują dodatkowy ruch w sieci, zmarnowane przetwarzanie serwerów i złożoność aplikacji (sądzono, że można to ukryć w bibliotece). Koszty te można zmniejszyć, anulując nieużywane wnioski lub czekając krótko przed wysłaniem drugiego żądania.

Oto jeden papier , a inny . Pamiętam też, jak czytałem gazetę Google, ich implementację.


2

W większości zgadzam się z innymi odpowiedziami, ale uważam, że powinno to być niezwykle rzadkie w praktyce. Chciałem podać znacznie bardziej powszechny i ​​rozsądny przykład tego, kiedy chcesz go użyć Promise.race(), coś, z czego zdarzyło mi się korzystać kilka tygodni temu (cóż, odpowiednik Pythona).

Załóżmy, że masz długą listę zadań, z których niektóre mogą być uruchamiane równolegle, a niektóre muszą być wykonywane przed innymi. Możesz rozpocząć wszystkie zadania bez żadnych zależności, a następnie poczekać na tej liście za pomocą Promise.race(). Po zakończeniu pierwszego zadania możesz rozpocząć dowolne zadania zależne od tego pierwszego zadania i Promise.race()ponownie na nowej liście w połączeniu z niedokończonymi zadaniami z oryginalnej listy. Powtarzaj, aż wszystkie zadania zostaną wykonane.

Uwaga Interfejs API JavaScript nie jest do tego idealnie zaprojektowany. To właściwie absolutne minimum, które działa i musisz dodać sporo kodu kleju. Chodzi mi jednak o to, że takie funkcje race()są rzadko używane do nadmiarowości. Są one dostępne przede wszystkim wtedy, gdy naprawdę chcesz uzyskać wyniki wszystkich obietnic, ale nie chcesz czekać na ich spełnienie przed podjęciem kolejnych działań.


Problem polega na tym, że przynajmniej w przypadku Javascript Promise.race faktycznie uruchamiasz zadanie za każdym razem, gdy wykonujesz metodę wyścigu. Nie będzie na niedokończonym zadaniu, będzie to nowy zestaw zadań, bez względu na to, co zostało uruchomione wcześniej (chyba że zaimplementujesz tę logikę na poziomie zadania). Oryginalna lista jest inaczej zapomniana i pozostaje tylko wartość zwrotu pierwszego zadania
Adelin

1
Obietnice w Javascript są uruchamiane z niecierpliwością, gdy new Promisesą wywoływane, i nie są ponownie uruchamiane, gdy Promise.race()są wywoływane. Niektóre implementacje obietnic są leniwe, ale chętny jest o wiele bardziej powszechny. Możesz przetestować, tworząc obietnicę w konsoli, która loguje się do konsoli. Zobaczysz to od razu. Następnie przekaż tę obietnicę Promise.race(). Zobaczysz, że nie loguje się ponownie.
Karl Bielefeldt,

Ach to prawda. Ale afaik, wartość zwrotna reszty obietnic, z wyjątkiem pierwszej, jest zapomniana, z obietnicą. Race
Adelin

Dlatego powiedziałem, że API nie jest idealnie zaprojektowane. Musisz gdzieś przechowywać oryginalny zestaw zadań w zmiennej.
Karl Bielefeldt,

1

Oprócz względów technicznych możesz zastosować to podejście, gdy jest ono częścią twojego rzeczywistego modelu biznesowego.

Odmiany tego podejścia są stosunkowo częste w licytowaniu reklam w czasie rzeczywistym . W tym modelu wydawca (dostawca przestrzeni reklamowej) poprosi reklamodawców (dostawców reklam) o licytację za wyświetlenie określonego użytkownika. Zatem dla każdego takiego wyświetlenia zapytaj każdego z subskrybowanych reklamodawców, wysyłając zapytanie ze szczegółami wyświetlenia do punktu końcowego dostarczonego przez każdego reklamodawcę (lub alternatywnie, skrypt dostarczony przez reklamodawcę działający jako punkt końcowy na twoich serwerach), ścigając się wszystkie te żądania do limitu czasu (np. 100 ms), a następnie przyjęcie najwyższej oferty, ignorując pozostałe.

Szczególną odmianą tego, która pomaga skrócić czas oczekiwania klienta, jest umożliwienie wydawcy dopuszczenia minimalnej wartości celu dla oferty, tak że pierwsza oferta reklamodawcy, która przekroczy tę wartość, zostanie natychmiast zaakceptowana (lub, jeśli żadna z ofert nie przekroczy wartość, zostanie pobrana maksymalna). W tym wariancie pierwsze zapytanie przychodzące może wygrać, a drugie odrzucone, nawet jeśli są tak dobre lub nawet lepsze.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.