Aplicatii distribuite in Erlang

Categorii: Programare

05-Jun-2017 15:58 - 366 vizionari

Inca mai invat Erlang si ma minunez de cat de simplu, de elegant si de robust este acest limbaj combinat cu OTP (Open Telecom Platform).

Aplicand un exemplu simplu din capitolul Distributed OTP Applications, din cartea Learn you some Erlang for great good, am creat un program in Python care sa genereze automat configuratia si scripturile (fisiere .cmd in Windows) necesare pornirii unei aplicatii distribuite pe o lista de noduri date.

Observatii:

- pe un calculator pot exista mai multe noduri Erlang

- numele nodurilor trebuie sa contina numele real al calculatorului in format: nume_nod@nume_calculator

- lista de noduri Erlang pornite pe un calculator se afla cu comanda: epmd -names

- nodurile sunt pornite cu numele scurt, parametrul -sname numele_nodului pentru erl.exe

- numai aplicatiile Erlang pot fi distribuite (nu orice program Erlang), adica aplicatia trebuie sa aiba un fisier .app specific

- se pot porni toate nodurile cu aceeasi configuratie (cfg.config), asa am generat scripturile din programul in Python

- configuratia de pornire a nodului defineste ce aplicatii se pot executa distribuit

- aplicatia distribuita se executa numai pe un nod, iar celelalte noduri sunt in asteptare

- nu este obligatoriu ca toate nodurile sa fie pornite pentru ca aplicatia sa porneasca si sa inceapa executia

- daca nodul pe care se executa aplicatia se opreste, unul din nodurile in asteptare porneste dupa un timp executia aplicatiei

- programul in Python genereaza si fisierul .app (minim si de test) necesar pornirii aplicatiei Erlang

- programul in Python genereaza scripturi pentru pornirea a trei noduri (n1, n2, n3) locale si a un nod pe alt calculator numit quad. Numele nodului este test@quad

- programul in Python mai genereaza si un script (_send_msg_all_nodes.cmd) care trimite un mesaj aleator la toate nodurile. Daca aplicatia se ruleaza pe un nod, afiseaza pe ecran mesajul receptionat. Am testat aici executia distribuita, inregistrarea unui proces accesibil dupa nume (distr_app_pid) si comunicarea intre noduri diferite existente pe calculatoare diferite.

- se pare ca prioritatea nodurilor e data de ordinea nodurilor din configuratia cu care a pornit nodul

- pentru replicarea experimentului sunt necesare numai doua fisiere: app1.erl si programul in Python

- inainte de executia scriptului in Python este bine sa se stearga toate fisierele generate anterior: del *.cmd *.beam *.app *.config

Aplicatia care se executa distribuit pe mai multe noduri, app1.erl,  este formata dintr-un singur fisier Erlang cu comportament de aplicatie si de supervizor (o simpla demonstratie):


%
% app1.erl
%
% Aplicatie distribuita pe mai multe noduri
%
% se executa pe un nod si se muta automat executia 
% pe alt nod daca nodul unde se executa se opreste
%

-module(app1).

-behaviour(application).
-behaviour(supervisor).

%% Application callbacks
-export([start/2, stop/1]).
-export([start_link/1, init/1, start_myloop/1, myloop/1]).

%% ===================================================================
%% Application callbacks
%% ===================================================================
start(_StartType, _StartArgs) ->
       %aici porneste aplicatia
       io:format("0 - START app1:start/2 Type:~p Args=~p~n",[_StartType,_StartArgs]),
       Rez = start_link(_StartArgs),
       io:format("0 - END   app1:start/2 ~n"),
       Rez.

start_link([Cnt]) ->
       io:format("1 - app1:start_link/0~n",[]),
       supervisor:start_link({local, ?MODULE}, ?MODULE, [Cnt]). %argumente pt init

stop(_State) ->
       io:format("app1:stop/1~n",[]),
       ok.

init([Cnt]) ->
       io:format("2 - app1:init self=~p~n",[self()]),
       Children = {?MODULE,{?MODULE, start_myloop, [Cnt]}, permanent, 5000, worker, dynamic},
       %Children = 	#{id => nume_id_app1, start => {?MODULE, start_myloop, [Cnt]}, restart => permanent,
       % shutdown => brutal_kill, type => worker, modules => [app1]},
       Processes = [Children],
       MaxRestart = 5, MaxTime = 1000,
       Strategy = {one_for_one, MaxRestart, MaxTime},
                {ok, {Strategy, lists:flatten(Processes)}}.

start_myloop(Cnt) ->
       Pid = spawn_link(?MODULE, myloop, [Cnt]),
       register(distr_app_pid, Pid),
       {ok, Pid}.

myloop(Cnt) ->
       receive
             Msg -> io:format("Msg=~p~n",[Msg])
       after 1000 ->
             %io:format("Timeout~n",[])
             timeout
       end,
       {H,M,S} = time(),
       io:format("Node=~p Cnt=~p Self=~p Time=~p:~p:~p~n", [node(), Cnt, self(), H,M,S]),
       %timer:sleep(1000),
       myloop(Cnt+1).

Programul Python care genereaza fisierele necesare executiei distribuite:


'''
        Program de generare fisiere necesare executiei distribuite
        a unei aplicatii (app1) pe un sistem Erlang OTP

        Nu conteaza numele fisierului, importante sunt fisierele generate

'''

import string
import socket

hostname, aliaslist, ipaddrlist = socket.gethostbyname_ex(socket.gethostname())

def main():
    app_name = "app1"
    start_cnt = 3000 #argument dat la start aplicatie
    TimeOutBeforeRestart = 500 #ms - cat asteapta pana sa repornesca aplicatia
    SyncNodesMaxTime = 3000 #ms - cat timp verifica daca alte noduri sunt pornite
    Cookie = "Magic123Cookie" #pentru accesul securizat la fiecare nod
    #nodurile trebuie sa fie de tip: nume_nod@nume_calculator, altfel nu merge
    nodes = "n1@{0},n2@{0},n3@{0},test@quad".format(hostname)
    distributed_config_file = "cfg.config"

    open("{}.app".format(app_name), "wt").write(
'''
%%minimul de definitii pentru %(app_name)s
{application, %(app_name)s,
    [
    {mod, {%(app_name)s,[%(start_cnt)d]}}
    ]}.
''' % vars())

    open(distributed_config_file, "wt").write(
'''
[{kernel,[
    {distributed,
        [{%(app_name)s, %(TimeOutBeforeRestart)d,
        [{%(nodes)s}]}]
        },
    %%{sync_nodes_mandatory, [%(nodes)s]},
    {sync_nodes_optional, [%(nodes)s]},
    {sync_nodes_timeout, %(SyncNodesMaxTime)d}
    ]
}].
''' % vars())

    cmd_send_msg = "@echo off"
    for node in nodes.split(","):
        filename = "_start_node_{}.cmd".format(node)
        cmd_send_msg += '''
echo Mesaj pentru nodul %s
erl -noshell -setcookie %s -sname msgNode ^
    -eval "{distr_app_pid,%s}!'Hello node %s %%RANDOM%% - %%DATE%%',erlang:halt()"
''' % (node, Cookie, node, node)
        open(filename, "wt").write(
'''@echo off
title node {0}
:loop
    cls
    erl -make
    erl -sname {0} -config {1} -setcookie {2} -eval application:start({3})
goto loop
'''.format(node, distributed_config_file, Cookie, app_name))

    cmd_send_msg += "pause\n"
    open("_send_msg_all_nodes.cmd","wt").write(cmd_send_msg)

if __name__ == "__main__":
    try:
        main()
    except Exception as ex:
        print ex
        raw_input("Enter")

Aplicatia app1.erl nu se executa simultan pe toate nodurile si nu obtine nici un avantaj de viteza din executia distribuita, singurul castig este ca infrastructura OTP garanteaza ca aplicatia porneste in timpul TimeOutBeforeRestart (definit in fisierul Python la 500 ms) si se executa pe cel putin unul din noduri.

Astfel Erlang este solutia cea mai potrivita si cea mai matura (peste 20 de ani incepand cu 1993) pentru sisteme robuste care sa functioneze 99,999% din timp.



Ultimele pagini: RSS

Alte adrese de Internet

Categorii

Istoric



Contorizari incepand cu 9 iunie 2014:
Flag Counter

Atentie: Continutul acestui server reprezinta ideile mele si acestea pot fi gresite.