Как работят надзорниците в Elixir

В Erlang и Elixir надзорните органи са процеси, които управляват дъщерни процеси и ги рестартират, ако възникне грешка. Тази статия разглежда по-отблизо прилагащите надзорници в Elixir.

Но преди да преминете към самите супервизори, е добре да се запознаете с модулите gen_server и supervisor. Можете да се справите и без това, ако вече сте работили с еквивалентни модули Elixir, тъй като те просто предават повиквания към модулите Erlang, без да променят поведението си.

Нека започнем с пример от самата документация на Elixir:

В горния пример методът start_link създава супервизор, а методът init реализира обратното повикване, използвано от поведението от Supervisor. Нека разгледаме по-отблизо Supervisor.

Нека да разгледаме поведението. Операторът use Supervisor се разширява по време на компилиране в поведението, посочено в макроса __using__. Нека да разгледаме кода на макроса __using__ от модула Supervisor в източниците на Elixir .

По време на това писане макросът __using__ изглеждаше така:

@Behaviour проверява супервизора за необходимите функции за обратно извикване и операторът за импортиране зарежда допълнителни функции в MyApp.Supervisor от модула Supervisor.Spec. Тук са дефинирани функциите на работник и надзор. .

Накратко, инструкцията use Supervisor добавя няколко нови функции към проекта и следи за наличието на необходимите функции за обратно извикване.

Супервайзорът започва с извикване на MyApp.Supervisor.start_link. Той се делегира на функцията Supervisor.start_link чрез предаване на връзка към себе си (с помощта на макроса __MODULE__).

Нека да разгледаме внедряването на Supervisor.start_link:

Обърнете внимание, че модулът Elixir делегира повиквания към модула Erlang: supervisor. Но това не е причина за паника! Повярвайте ми и разгледайте източника на Erlang .

Търсейки start_link, ще срещнете оператор за експортиране, който ви позволява да използвате функция извън модула, спецификация, описваща типовете данни, налични за дадена функция, и всъщност самата им реализация:

Виждате ли? Вече нещо познато. Просто изпълняваме gen_server, без да навлизаме в подробности за това как работи. Основното нещо, което трябва да научите тук, е, че надзорните органи са изградени върху модула gen_server, който очаква да види многобройни обратни обаждания.

Методът init представлява много по-голям интерес. Имайте предвид, че въпреки че MyApp.Supervisor съдържа внедряването на функцията init, следващата функция няма да бъде извикана. Ако се върнете към изпълнението на функцията start_link в модула: supervisor от Erlang, ще забележите, че параметърът self е предаден към него. Това означава, че: supervisor.init е функцията, която търсите.

Погледнете изходния му код:

Първо се извиква функцията Mod: init (Args), която от своя страна извиква функцията MyApp.Supervisor.init. Нека да разгледаме още веднъж кода:

Не забравяйте, че работникът и надзорникът са помощници от Supervisor.Spec. Нека не бием около храста, а да преминем към най-важното, тоест, нека разгледаме по-отблизо как всичко това е приложено в Erlang. worker връща child_spec и контролира кортеж по следния начин:, child_specs >> .

Второ, supervisor.init извиква process_flag, след което изходният сигнал се прихваща. Това е ключов момент, за да се демонстрира как и кога ръководителят решава да рестартира процеса. Накратко, преди убиването, процесът изпраща изходен сигнал към всички процеси, свързани с него. Извикването process_flag прихваща сигнала и вместо това изпраща съобщение до процеса. По-долу ще покажем как процесът на надзорник използва стойността from_pid, за да идентифицира убития процес и да го рестартира.

И така, разгледахме функцията supervisor.init и научихме за прихващането на изходните сигнали. Сега да преминем към инициализиране на държавни и дъщерни процеси. Тъй като в този пример не се използват супервизори: simple_one_for_one, ние ще обърнем внимание само на инициализацията на дъщерните процеси init_children .

Малко отклонение: State # state.name се отнася до променливата State като държавен запис и "издърпва" полето за име от него. Записите са повече или по-малко структуриран тип данни, тъй като се съхраняват като кортежи от типа (подобно на типа enum на други езици). Записите са просто начин да отделите позицията на полето от стойността му. Ето как изглежда изходният код за влизането в състоянието в началото:

Можете да видите, че държавният запис съдържа поле с име, а изразът SupName = State # state.name просто се отнася до кортежа на състоянието като държавен запис, като изважда и съхранява полето, свързано с name в SupName .

Що се отнася до функцията check_startspec, тя проверява данните и поставя спецификациите, получени от MyApp.Supervisor.init, в записа (източник).

Същността на функцията: supervisor.init_children е да извика функцията start_children:

Наблюдава се малка рекурсия: взема се всеки дъщерен процес и се извиква функцията do_start_child:

Erlang apply е метод за динамично извикващи функции (като изпращане в Ruby или прилагане/обаждане в Javascript). Той динамично извиква функцията за обратно извикване, дефинирана в Supervisor.Spec.worker: по конвенция функцията start_link се извиква от работния модул. И накрая, той извиква функцията report_progress и предава информационно събитие за стартирането на нов процес от надзора на Erlang мениджъра на събития, наречен error_logger (не е добро име).

Половината битка е свършена. Продължавай.

За да обобщим горното, разгледахме как се стартират надзорници, прихващат се изходни сигнали и се създават дъщерни процеси. Но как се рестартират дъщерни процеси? Спомняте ли си как process_flag улавя изходните сигнали и ги превръща в кортежи? А фактът, че надзорните органи са изградени на базата на библиотеката gen_server? gen_server обработва всички съобщения с изключение на типове обаждания/гласове, използвайки функцията handle_info (повече за това тук). Ето как надзорният орган обработва изходните сигнали от дъщерни процеси.

Това показва как създава съобщение, съдържащо идентификатора на дъщерния процес (pid) и причината за прекратяването му, и предава тези данни на метода restart_child! Победа! Функцията do_start_child има много общо с предварително разглежданата функция restart_child, така че нека оставим въпроса за нейното изпълнение за независимо проучване. Ако ви е интересно как надзорните органи прилагат своите стратегии за прекратяване, погледнете функцията: gen_server.stop, която делегира повиквания към обратно повикване, наречено прекратяване .

Нека обобщим. Elixir използва общоприетия модел на надзорник, който се основава на интерфейса Erlang: надзорен модул, изграден върху библиотеката gen_server. Настройките от Elixir се предават на: supervisor модула, който стартира дъщерни процеси въз основа на тях. Супервайзорът управлява процесите на детето чрез прихващане на техните изходни сигнали и преобразуване на тези сигнали в съобщения. Той реализира функцията за обратно извикване handle_info, която приема изходни съобщения и рестартира необходимия работник. И последното нещо, което си струва да се спомене, е, че отчетите се извеждат в Erlang event manager: error_logger .

Веднъж или два пъти седмично изпращаме топли писма за Elixir: преводи на най-интересните статии, преди да се появят в публичното пространство, съобщения за събития и вкусни бонуси.

Не забравяйте да потвърдите пощата си, като щракнете върху връзката в писмото, в противен случай няма да можем да споделим полезността с вас.