W pierwszej części opisującej rejestry nazw wspomniałem o nodach w elixir. Dziś chciałbym trochę o nich opowiedzieć by oczyścić atmosferę i swoją głowę z tematu. Jak wiemy, elixir bazuje na erlangu a dokładniej na maszynie wirtualnej erlanga – beam. Erlang zaś umożliwia tworzenie rozproszonych systemów – czyli takich które mogą działać na wielu komputerach. I umożliwia to nam kompletnie przezroczyście – nawet nie wiemy czy kod który aktualnie uruchamiamy jest odpalany u nas na komputerze czy gdzie indzie.

Jest to możliwe dzięki architekturze rozwiązania erlanga. Mianowicie każda instancja wirtualnej maszyny to osobny node. Instancja wirtualnej maszyny to nic innego jak działające środowisko/aplikacja. Czyli jak odpalimy trzy iex to trzy VM zostaną utworzone a co za tym idzie trzy nody. I teraz nasz kod może działać nieświadomie i po prostu wykonywać swoją pracę, albo może chcieć się komunikować między różnymi nodami.

Jeżeli pozostajemy w granicach naszego jednego node, to nas nic nie interesuje. Nadajemy nazwy GenServer tak jak chcemy, korzystamy z atomów i innych udogodnień tak jak chcemy. Nic nie musimy robić wszystko będzie działało.

Jeżeli zaś chcemy tworzyć aplikację rozproszoną, to już nody muszą się ze sobą komunikować. Domyślnie w erlangu jest tak, że jeżeli mój node A wie o node B a B wie o node C, to noda A wie o node C. To znaczy, że każdy node wie o każdym. Ma to swoje plusy bo z miejsca jesteśmy wstanie się komunikować i działać na wielu nodach, ale też ma minusy w postaci tego, że każdy node wymaga osobnego portu, uruchamianie dużej liczby nodów może spowodować obniżenie wydajności całego rozwiązania.

Najlepsze jest to, że nodey są niezależne od siebie, posiadają swoje procesy i swoje atomy. Jednak istniej możliwość wywołania procesu na innym node niż nasz aktualny.  Więc mamy niezłe możliwości pracy rozproszonej. I to wszystko z out of the box.

Dla przykładu, otwórzmy sobie dwa okna cmd i odpalmy następujące komendy (jedna komenda na okno)

iex --sname right
iex --sname left

Po pierwsze zobaczymy, że nasz iex trochę inaczej wygląda:

iex(right@gutek-pc)1>

Teraz ma on nazwę! Po drugie, nazwa składa się z dwóch członów:

right @ gutek-pc

Gdzie right to jest to co my nadaliśmy, a @gutek-pc to nazwa komputera. Tutaj może być IP lub inna nazwa lokalnie rozpoznawalna. iex zaś odpaliliśmy komendą --sname która pozwala nam podać nazwę skróconą node bez podania lokalizacji. By podać IP lub pełną nazwę, należy wykorzystać --name:

iex --name [email protected]

Jeżeli jednak wykorzystamy --name to musimy się liczyć, że działamy już na fully qualified hostnames a to znaczy, że wszystkie operacje musimy tak wykonywać i nasze wszystkie serwisy musza też spełniać dany schemat.

Następnie w każdym oknie wykonajmy polecenie:

Node.list
Node.self

Pierwsze zwróci nam listę nodów już wykrytych, drugi zaś zwróci nam naszą nazwę hosta. Jeżeli otworzymy iex normalnie bez podania nazwy noda to dostaniemy:

iex(1)> Node.self
:nonode@nohost

Mając dwa okna możemy się pobawić i na przykład sprawdzić, czy działa nam komunikacja pomiędzy naszymi nodami. W tym celu wykonujemy jedną z dwóch operacji: ping albo połączenie.

Node.ping :"left@gutek-pc"

Zwróci nam :pong jak się udało, :pang jak się nie udało.

Zamiast tego możemy też wykonać polecenie:

Node.connect :"left@gutek-pc"

Które zwróci :true jak się udało, :ignore jeżeli próbujemy podłączyć się jak mamy odpalony iex bez nadania nazwy lub :false jak się nie udało.

Teraz jeżeli w wpiszemy Node.list w oknach to otrzymamy w obydwóch informacje o tym, że się widzą nawzajem.

Teraz uzyskajmy odpowiedź z naszej lewej strony:

iex(right@gutek-pc)1> Node.spawn :"left@gutek-pc", fn -> IO.puts Node.self end
left@gutek-pc
#PID<9390.100.0>

Tak o to możemy wykonywać jakiś kod po stronie innego node. Stwórzmy prosty moduł w left:

defmodule How, do: def dy, do: IO.puts "Howdy! #{Node.self}"

Jeżeli spróbujemy go odpalić na naszym right to nam to nie zadziała:

iex(right@gutek-pc)3> How.dy
** (UndefinedFunctionError) function How.dy/0 is undefined (module How is not available)
    How.dy()

Ale! :) odpalmy coś takiego:

iex(right@gutek-pc)1> Node.spawn_link :"left@gutek-pc", fn -> How.dy end

Podsumowanie

Fajne jest to, że erlang nam dużo daje z miejsca. Jest to dostępne od ręki i do prostego wykorzystania przez nas wtedy, kiedy tego potrzebujemy. Oczywiście to nie jest wszystko takie piękne jak to wygląda i są z tym pomniejsze problemy i różne rozwiązania.

Jednak zasada taka, że każda maszynka wirtualna to node, a każdy node może ze sobą rozmawiać bezpośrednio i jest to przezroczyste dla nas, programistów jest moim zdaniem super rozwiązaniem. Pewnie wrócę kiedyś do node by dać bardziej szczegółowy opis co i jak. Jednak na razie jako wstęp do tematu powinno być ok.

Czy coś powinienem jednak dodać teraz lub opisać? Czy może coś nie tak napisałem? Może już teraz powinienem skoncentrować się na zaawansowanych aspektach node i komunikacji a nie później? Dzięki!