2016-08-24 49 views
9

Używam PHP i mam 10 zadań, które trzeba uruchomić. Każdy z nich nie powinien tracić czasu, ale wszystkie 10 zadań razem może.PHP zapobiega przekroczeniu limitu czasu przy użyciu modułów, które używają żądań http

Czy jest dobrym rozwiązaniem zastosowanie podejścia modułowego z nowymi żądaniami http?

coś takiego:

http://example.com/some/module/fetch 
http://example.com/some/module/parse 
http://example.com/some/module/save 

Może te adresy zrobić jedno zadanie każdego. Jeśli się powiedzie, wykonaj następne zadanie z tego zadania. Rodzaj reakcji łańcuchowej. Jedna ścieżka wywołuje następną (z zawijaniem).

Plusy i minusy? Czy to dobre podejście? Jeśli nie, jaka jest lepsza alternatywa?

+1

Myślę, że to normalne i możesz to zrobić, ale dlaczego nie robisz tego na jednym php? Czy chcesz go używać z interfejsem API? Lub te adresy URL w swoim własnym projekcie? –

+0

@TeymurMardaliyerLennon Nie chcę uruchamiać wszystkiego w tym samym czasie w przypadku przekroczenia limitu czasu. –

+1

Do takich rzeczy jak uruchamianie pracowników w bogatych konfiguracjach używam RabbitMQ https://www.rabbitmq.com/tutorials/tutorial-two-php.html "Głównym założeniem kolejki pracy (vel: Queues zadań) jest unikaj natychmiastowego wykonania zadania wymagającego dużego nakładu pracy i poczekaj aż skończy się " –

Odpowiedz

2

Podejście modułowe to dobry pomysł (jeśli jedna "jednostka" zawiedzie, zadanie zostanie przerwane zgodnie z oczekiwaniami, a ponadto łatwiej będzie debugować/przetestować każdą jednostkę).

to będzie działać, ale Twoje podejście do połączeń łańcuchowych ma pewne problemy:

  • jeśli jest wąskim gardłem (czyli jedna „jednostka” trwa dłużej niż inni), to może skończyć się z 100 z wąskim gardłem przetwarza wszystkie działa i tracisz kontrolę nad zasobami serwera.
  • brak kontroli; załóżmy, że serwer musi zostać zrestartowany: aby ponownie uruchomić zadania, musisz je wszystkie uruchomić na początku.
  • Podobnie, jeśli istnieje powód, dla którego musisz zatrzymać/uruchomić/debugować pojedynczą jednostkę podczas pracy, musisz ponownie uruchomić zadanie w pierwszej jednostce, aby powtórzyć.
  • wykonując żądanie WWW, używasz zasobów Apache/NGIX, pamięci, połączeń z gniazdami itp., Aby uruchomić proces PHP. Możesz po prostu uruchomić proces PHP bezpośrednio, bez używania kosztów ogólnych.
  • i na koniec, jeśli na serwerze internetowym DMZ serwer może nie być w stanie samodzielnie wysyłać żądania.

Aby uzyskać większą kontrolę, do tego rodzaju operacji należy użyć systemu kolejkowania.

Korzystanie z PHP (lub dowolnego języka, naprawdę), to podstawowy proces jest:

  1. każdy "jednostka" jest ciągły pętli skrypt php, który nigdy się nie kończy *

  2. każdy proces "unit" słucha systemu kolejkowania; Gdy zadanie dociera do kolejki, którą może obsłużyć, po każdym zatrzymaniu zadania przez jednostkę kończy pracę z kolejką, zatwierdza obsługę i przechodzi do następnej kolejki.

  3. Jeśli urządzenie zdecyduje, że zadanie nie powinno być kontynuowane, potwierdź zadanie, ale nie przesuwaj do następnej kolejki.

Zalety:

  • czy "jednostka" zatrzymuje się, a następnie praca pozostaje w kolejce i mogą być zbierane po ponownym uruchomieniu "Unit". Ułatwia ponowne uruchamianie jednostek/serwerów lub awarię jednego urządzenia.
  • jeśli jedna "jednostka" jest bardzo ciężka, można po prostu rozpocząć drugi proces robiąc dokładnie to samo, jeśli masz pojemność serwera przestrzeni. Jeśli nie ma możliwości serwera, akceptujesz wąskie gardło; dlatego masz bardzo przejrzysty obraz tego, ile zasobów używasz.
  • Jeśli zdecydujesz, że inny język lepiej poradzi sobie z żądaniem, możesz mieszać NodeJS, Python, Ruby i ... mogą rozmawiać z tymi samymi kolejkami.

Notatka boczna o "ciągłym zapętlaniu PHP": odbywa się to poprzez ustawienie max_execution_time "0". Upewnij się, że nie powodujesz "wycieków pamięci" i czysisz. Możesz automatycznie uruchomić proces podczas uruchamiania (systemd lub scheduler zadań w zależności od systemu operacyjnego) lub uruchomić ręcznie do testowania. Jeśli nie chcesz mieć ciągłej pętli, czas po 5 minutach i restartuj cron/task scheduler.

Notatka boczna w kolejkach: można "przetaczać własne", korzystając z bazy danych pamięci podręcznej dla prostych aplikacji (np. Z łatwością można poradzić sobie z 100 000 pozycji na godzinę w kolejce przy użyciu systemu baz danych), unikając konfliktów/zarządzania stanem/Retoryki to trochę sztuka. Lepszą opcją jest RabbitMQ (https://www.rabbitmq.com/). Instalacja jest trochę uciążliwa, ale gdy już ją zainstalujesz, postępuj zgodnie z samouczkami PHP, a już nigdy się nie obejrzysz!

1

Ta odpowiedź zakłada, że ​​korzystasz z PHP i uruchamiasz zadania, wysyłając żądania HTTP do każdego z adresów URL w pytaniu.

Twoje rozwiązanie zależy od wymagań biznesowych. Jeśli nie zależy Ci na kolejności wypełniania żądań HTTP, sugeruję, aby przejrzeć curl_multi_init(), aby zapoznać się z funkcjami curl_multi_ * rozszerzenia cURL PHP.

Jeśli zależy Ci na kolejności realizacji (np. Konkretne zadanie musi zostać wykonane przed następnym), spójrz na numer curl_init().

Aby wyeliminować możliwość przekroczenia limitu czasu wywołania skryptu wywołującego, przeczytaj o funkcji t lub rozważ rozwód za pomocą pcntl_fork.

Alternatywnie, chciałbym zbadać message queue. W szczególności sprawdź numer Amazon's SQS i przeczytaj o tym, jak połączyć się z nim w PHP. Oto kilka linków dotyczących SQS i PHP:

1

Zakładając chcesz użyć żądania HTTP, masz kilka opcji, należy ustawić limit czasu, każdy czas mniej:

function doTaskWithEnd($uri, $end, $ctx = null) { 
    if (!$ctx) { $ctx = stream_context_create(); } 
    stream_context_set_option($ctx, "http", "timeout", $end - time()); 
    $ret = file_get_contents($uri, false, $ctx)); 
    if ($ret === false) { 
     throw new \Exception("Request failed or timed out!"); 
    } 
    return $ret; 
} 

$end = time() + 100; 
$fetched = doTaskWithEnd("http://example.com/some/module/fetch", $end); 
$ctx = stream_context_create(["http" => ["method" => "POST", "content" => $fetched]]); 
$parsed = doTaskWithEnd("http://example.com/some/module/parsed", $end, $ctx); 
$ctx = stream_context_create(["http" => ["method" => "PUT", "content" => $parsed]]); 
doTaskWithEnd("http://example.com/some/module/save", $end, $ctx); 

Lub alternatywnie z n na blokowaniu rozwiązanie (użyjmy amphp/AMP + amphp/ARTAX do tego):

function doTaskWithTimeout($requestPromise, $timeout) { 
    $ret = yield \Amp\first($requestPromise, $timeout); 
    if ($ret === null) { 
     throw new \Exception("Timed out!"); 
    } 
    return $ret; 
} 

\Amp\execute(function() { 
    $end = new \Amp\Pause(100000); /* timeout in ms */ 

    $client = new \Amp\Artax\Client; 
    $fetched = yield from doTaskWithTimeout($client->request("http://example.com/some/module/fetch")); 
    $req = (new \Amp\Artax\Request) 
     ->setUri("http://example.com/some/module/parsed") 
     ->setMethod("POST") 
     ->setBody($fetched) 
    ; 
    $parsed = yield from doTaskWithTimeout($client->request($req), $end); 
    $req = (new \Amp\Artax\Request) 
     ->setUri("http://example.com/some/module/save") 
     ->setMethod("PUT") 
     ->setBody($parsed) 
    ; 
    yield from doTaskWithTimeout($client->request($req), $end); 
}); 

Teraz pytam, czy naprawdę chcą odciążyć odrębnych wniosków? Czy nie możemy po prostu założyć, że teraz są teraz funkcje?

W tym przypadku jest to łatwe i po prostu może skonfigurować alarm:

declare(ticks=10); // this declare() line must happen before the first include/require 
pcntl_signal(\SIGALRM, function() { 
    throw new \Exception("Timed out!"); 
}); 
pcntl_alarm(100); 

$fetched = fetch(); 
$parsed = parse($fetched); 
save($parsed); 

pcntl_alarm(0); // we're done, reset the alarm 

Alternatywnie, roztwór non-blocking działa zbyt (zakładając fetch(), parse($fetched) i save($parsed) prawidłowo powrócić Promises i mają zakaz blockingly):

\Amp\execute(function() { 
    $end = new \Amp\Pause(100000); /* timeout in ms */ 
    $fetched = yield from doTaskWithTimeout(fetch(), $end); 
    $parsed = yield from doTaskWithTimeout(parse($fetched), $end); 
    yield from doTaskWithTimeout(save($parsed), $end); 
}); 

Jeśli tylko chcą mieć globalny limit czasu dla różnych kolejnych zadań, bym najlepiej iść z robi to po prostu wszystko w o ne skrypt z numerem pcntl_alarm(), alternatywnie można skorzystać z opcji limitu czasu dla strumienia.

Rozwiązania nieblokujące są stosowane głównie w przypadku konieczności wykonywania innych czynności w tym samym czasie. Na przykład. jeśli chcesz wykonać to pobranie + analizowanie + zapisywanie cyklu kilka razy, niezależnie od siebie.

0

tło pracy z pracownikami jest najlepszym sposobem, ponieważ:

Aplikacje często trzeba wykonywać operacje, które są razem (lub obliczeniowo) intensywny, ale zwykle nie jest pożądane, aby to zrobić podczas żądania, jak powstały powolność jest postrzegana bezpośrednio przez użytkowników aplikacji. Zamiast tego każde zadanie, które trwa dłużej niż kilkadziesiąt milisekund, takie jak przetwarzanie obrazu, wysyłanie wiadomości e-mail lub jakakolwiek synchronizacja w tle, powinno być wykonywane jako zadanie w tle. Co więcej, kolejka robocza ułatwia również wykonywanie zaplanowanych zadań, ponieważ ta sama infrastruktura kolejki może być wykorzystana w procesie zegarowym.

używane php Resque dla wdrożenia zadania w tle: php resque workers

1

myślę „reakcja łańcuchowa” jest wskazówką, że takie podejście może być zbyt skomplikowane ...

mogą istnieć powody, dla przełączenie na solidny system przesyłania wiadomości/pracy, taki jak RabbitMQ lub SQS, szczególnie w przypadku dużego obciążenia. Kolejki wiadomości są nieocenione w odpowiednim kontekście, ale dodają wiele komplikacji/kosztów ogólnych/kodu, jeśli są niepotrzebnie używane.

Najprostszym rozwiązaniem

...ale jeśli twoja jedyna troska uniemożliwia przekroczenie limitu czasu, nie sprawiłabym, że byłaby bardziej skomplikowana, niż musi; można łatwo rozszerzyć lub limity czasu wyłączyć całkowicie za pomocą:

set_time_limit(0); //no time limit, not recommended 
set_time_limit(300); //5 mins 

proponowanego „łańcuchowym” wzór ma sens w zasadzie dlatego, że pozwala precyzyjnie określić, gdzie występują jakieś błędy, ale można to zrobić wszystko wewnątrz tego samego wniosku/funkcjonalność, a nie poleganie na sieci.

Zamiast obsługiwać usterki w jednym zadowalającym miejscu, wymagałoby to dwóch (lub więcej) warstw obsługi błędów: jednej warstwy, która obsługuje indywidualne żądanie, a druga wysyła żądanie.

Zakładając, że praca może być z powodzeniem obsługiwana w jednym żądaniu (lub nawet bez żadnych żądań zdalnych) to Nie, nie jest to "dobre rozwiązanie do stosowania modułowego podejścia z nowymi żądaniami http", ponieważ jesteś dodając niepotrzebnej pracy & złożoność poprzez niepotrzebnych połączeń hTTP/odpowiedzi.”To znaczy wprowadza dodatkowe możliwości porażki, zwłaszcza łączność sieciową/opóźnień, DNS, trudności testowania & debugowanie itd

Oddzielenie do oddzielnych połączeń zdalnych może nawet dodać 10x sieci/opóźnienie uwierzytelniania serwera/serwera i sprawia, że ​​trudniejsze do zrobienia są rozsądne rzeczy, takie jak łączenie się z bazą danych.

Inne sposoby na uproszczenie problemu?

Jeśli to możliwe, warto zastanowić się, dlaczego łańcuch zapytań trwa tak długo - jeśli zoptymalizujesz je, by działały szybciej, możesz uniknąć niepotrzebnego skomplikowania tej części systemu. na przykład Takie sytuacje, jak opóźnienie bazy danych lub niewykorzystywanie pul połączenia z bazą danych może spowodować poważne obciążenie w 10 oddzielnych procesach.

+0

Pobiorę plik CSV, który może być duży od 2 do 20 MB, przekształcę go w tablicę, przeanalizuję i wstawiam do bazy danych, np. 20 000 wkładki. –