2016-05-21 12 views
7

Obecnie pracuję nad serwerem mediów na żywo, który pozwoli zwykłym konsumentom przesłać nam wideo na żywo. W naszym obecnym środowisku widzieliśmy transmisje wysyłane do nas w ciągu kilku dni, dlatego pomysł, aby naprawić błąd (lub dodać funkcję) bez odłączania użytkowników, jest niezwykle ważny.Jak działa wymienianie poprawek Erlanga w trakcie działania?

Jednak podczas pisania kodu zdałam sobie sprawę, że wymienianie się gorącym kodem nie ma sensu, chyba że piszę każdy proces tak, że cały stan jest zawsze wykonywany wewnątrz serwera gen_, a wszystkie zewnętrzne moduły, które wywołania gen_server muszą być tak proste jak możliwy.

Weźmy następujący przykład:

-module(server_template). 
-behaviour(gen_server). 

-export([start/1, stop/0]). 
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). 

start() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 

init([]) -> {ok, {module1:new(), module2:new()}}. 

handle_call(Message, From, State) -> {reply, ok, State}. 

handle_cast(any_message, {state1, state2}) -> 
    new_state1 = module1:do_something(state1), 
    new_state2 = module2:do_something(state2), 
    {noreply, {new_state1, new_state2}}. 

handle_info(_Message, _Server) -> {noreply, _Server}. 

terminate(_Reason, _Server) -> ok. 

code_change(_OldVersion, {state1, state2}, _Extra) -> 
    new_state1 = module1:code_change(state1), 
    new_state2 = module2:code_change(state2) 
    {ok, {new_state1, new_state2}} 

Według tego, co udało mi się znaleźć, gdy nowa wersja kodu jest ładowany do aktualnie działającej starcie bez użycia systemu OTP, można uaktualnić do bieżącego kodu wersji poprzez wywołanie twojego modułu jako zewnętrznego wywołania funkcji, czyli my_module:loop(state).

To co widzę, to to, że gdy wykonywana jest funkcja hot swap, funkcja code_change/3 jest wywoływana i aktualizuje stan, więc mogę go użyć, aby upewnić się, że każdy z moich modułów zależnych migruje ostatni stan, w którym dostał mnie do stanu aktualna wersja kodu. Robi to, ponieważ nadzorca wie o uruchomionym procesie, który pozwala na zawieszenie procesu, aby mógł wywołać funkcję zmiany kodu. Wszystko dobrze.

Jednakże, jeśli wywołanie modułu zewnętrznego zawsze wywołuje bieżącą wersję tego modułu, może się to wydawać zerwane, jeśli w połowie funkcji wykonano hot swap. Na przykład mój serwer gen_ jest obecnie w trakcie obsługi obsady any_message, na przykład pomiędzy uruchomieniem module1:do_something() i module2:do_something().

Jeśli jestem zrozumienia rzeczy poprawnie, module2:do_something() będzie teraz nazwać nowo aktualną wersję funkcji do_something, co może potencjalnie oznaczać Jestem przekazując dane nie migrujące ostrożnie do nowej wersji module2:do_something(). Mogłoby to łatwo spowodować problemy, jeśli jest to rekord, który się zmienił, tablica z nieoczekiwaną liczbą elementów lub nawet jeśli na mapie brakuje wartości oczekiwanej przez kod.

Czy nie rozumiem, jak działa ta sytuacja? Jeśli tak jest, wydaje się to wskazywać, że muszę śledzić pewne szczegóły wersji dla dowolnej struktury danych, która może przesuwać granice modułów, a każda funkcja publiczna musi sprawdzić numer wersji i przeprowadzić migrację na żądanie, jeśli to konieczne.

Wydaje się to być bardzo wysokim zleceniem, które wydaje się być podatne na błędy, więc zastanawiam się, czy czegoś brakuje.

+1

Masz rację. OTP sprawia, że ​​aktualizacja kodu jest bardziej kontrolowana. Zawiesza wykonanie kodu zgodnego z OTP, ładuje nową wersję, wywołuje 'code_change/3', a następnie kontynuuje pracę. Jest to o wiele bardziej kontrolowane ulepszanie kodu. Inną rzeczą jest to, że jeśli coś się zawiesza, OTP pozwala na ponowne uruchomienie bardzo szybko, więc jeśli twoje 'module2: do_something/1' ulegnie awarii z nowym formatem danych, może odzyskać.Te rzeczy idą w parze i są wciąż o rząd wielkości prostsze i bardziej niezawodne niż w jakimkolwiek innym środowisku uruchomieniowym. –

+2

W rzeczywistości zawieszenie procesu OTP nie jest dla mnie jasne. Zakładam, że to zawiesza proces po tym, jak trwają jakiekolwiek wywołania 'handle_call/handle_cast', w przeciwnym razie nie byłoby w stanie użyć stanu migrowanego, prawda? – KallDrexx

+1

Zostało to wyjaśnione tutaj (w podsekcji aktualizacji): http://erlang.org/doc/design_principles/release_handling.html#id78465 Release Handler używa 'sys: suspend/1,2',' sys: change_code/4,5 ', i' sys: resume/1,2', aby zawiesić, uaktualnić, a następnie wznowić proces. – Amiramix

Odpowiedz

7

Tak, masz całkowitą rację. Nikt nie powiedział, że wymiana hot-code jest łatwa. Pracowałem dla firmy telekomunikacyjnej, w której wszystkie aktualizacje kodu były wykonywane w systemie na żywo (dzięki czemu użytkownicy nie są rozłączeni w trakcie połączenia). Zrobienie tego dobrze oznacza uważne rozważenie wszystkich wspomnianych scenariuszy i przygotowanie kodu na każdą porażkę, a następnie testowanie, a następnie rozwiązywanie problemów, testowanie i tak dalej. Aby przetestować go poprawnie, potrzebowałbyś systemu działającego pod starą wersją pod obciążeniem (np. W środowisku testowym), a następnie wdrażając nowy kod i sprawdzając, czy nie występują awarie.

W tym konkretnym przykładzie wspomnianym w pytaniu najprostszym sposobem rozwiązania tego problemu jest napisanie dwóch wersji module2:do_something/1, jednej akceptującej stary stan i przyjmującej nowy stan. Następnie zajmujemy się odpowiednio starym stanem, np. konwersję do nowego stanu.

Aby to działało będzie trzeba także zapewnić, że nowa wersja module2 jest wdrażany zanim jakikolwiek moduł ma szansę wywołać ją z nowego państwa:

  1. Jeśli aplikacja zawierająca module2 jest Zależność innej aplikacji release_handler spowoduje uaktualnienie tego modułu w pierwszej kolejności.

  2. przeciwnym razie może trzeba podzielić wdrożenie na dwie części, po pierwsze uaktualnienie wspólne funkcje, tak aby mogły one obsługiwać nowe państwo, a następnie wdrażania nowych wersji gen_servers i inne moduły, które sprawiają, że połączenia do module2.

  3. Jeśli nie używasz modułu obsługi wydania, możesz ręcznie określić, w jakiej kolejności są ładowane moduły.

Jest to również powód, dla którego w Erlang jest advised to avoid circular dependencies w wywołaniach funkcji między modułami, np. gdy modA wywołuje funkcję w modB, która wywołuje inną funkcję w modA.

przypadku modernizacji wykonywanych za pomocą uchwytu zwalniającego można sprawdzić kolejność, w jakiej release_handler uaktualni modułów na starym systemie w pliku relup że release_handler generuje na podstawie old and new release. Jest to plik tekstowy zawierający wszystkie instrukcje dotyczące aktualizacji, np remove (w celu usunięcia modułów), load_object_code (załadować nowy moduł), load, purge itp

Należy pamiętać, że nie istnieje ścisły wymóg, aby wszystkie aplikacje muszą być zgodne Zasady OTP dla zamiany gorącego kodu do działania, jednak użycie zestawu gen_server i odpowiedniego stosu supervisor znacznie ułatwia obsługę tego zadania zarówno programistom, jak i programom obsługi wersji.

Jeśli nie korzystasz z wersji OTP, nie możesz dokonać aktualizacji za pomocą procedury obsługi wersji, ale nadal możesz na siłę ponownie załadować moduły do ​​swojego systemu i zaktualizować je do nowej wersji. Działa to dobrze, dopóki nie trzeba dodawać/usuwać aplikacji Erlang, ponieważ w tym celu definicja wydania musiałaby zostać zmieniona, a tego nie można zrobić w systemie na żywo bez obsługi procedury obsługi wersji.

1

Obsługa zwolnienia wywołuje sys:suspend, która wysyła komunikat do serwera gen_. Serwer będzie kontynuował przetwarzanie żądań, dopóki nie obsłuży wiadomości zawieszenia, kiedy w zasadzie po prostu siedzi i czeka. Nowa wersja modułu jest następnie ładowana do systemu, wywoływana jest sys:change_code, która nakazuje serwerowi wywołanie wywołania zwrotnego code_change w celu przeprowadzenia aktualizacji, a następnie serwer ponownie siedzi i czeka. Kiedy program obsługi wydania wywołuje sys:resume, wysyła wiadomość do serwera, który nakazuje jej przywrócenie do pracy i ponowne przetwarzanie przychodzących wiadomości.

Obsługa zwalniania odbywa się w tym samym czasie dla wszystkich serwerów zależnych od modułu. Tak więc najpierw wszystkie są zawieszone, a następnie nowy moduł jest załadowany, a następnie wszyscy są proszeni o aktualizację się, a następnie w końcu wszyscy są proszeni o wznowienie pracy.

Powiązane problemy