| theme | style | paginate | footer | backgroundImage | marp |
|---|---|---|---|---|---|
uncover |
.small-text {
font-size: 0.75rem;
letter-spacing: 1px;
font-family: "Times New Roman", Tahoma, Verdana, sans-serif;
}
section {
font-size: 28px;
letter-spacing: 1px !important;
}
li {
font-size: 28px;
letter-spacing: 1px !important;
}
p.quote {
line-height: 38px;
}
q {
font-size: 32px;
letter-spacing: 1px !important;
}
cite {
text-align: right;
font-size: 28px;
margin-top: 12px;
margin-bottom: 128px;
}
code.language-elixir {
font-size: 80%;
background: #f2f0ed;
color: #080808;
}
li code,
h4 code,
p code {
color: #089808;
background-image: linear-gradient(to bottom, #E0EAFC, #CFDEF3);
border-radius: 15px;
}
section {
letter-spacing: 1px !important;
}
span.hljs-comment,
span.hljs-quote,
span.hljs-meta {
color: #3d3f8f;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-tag,
.hljs-name {
color: #096a6a;
}
.hljs-attribute,
.hljs-selector-id {
color: #ffffb6;
}
.hljs-string,
.hljs-selector-attr,
.hljs-selector-pseudo,
.hljs-addition {
color: #489707;;
}
.hljs-subst {
color: #526228;
}
.hljs-regexp,
.hljs-link {
color: #e9c062;
}
.hljs-title,
.hljs-section,
.hljs-type,
.hljs-doctag {
color: #724b99;
}
.hljs-symbol,
.hljs-bullet,
.hljs-variable,
.hljs-template-variable,
.hljs-literal {
color: #b90f0f;
}
.hljs-number,
.hljs-deletion {
color:#ff73fd;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: bold;
}
|
true |
Курс по Elixir 2023, ФМИ |
linear-gradient(to bottom, #E0EAFC, #CFDEF3) |
true |
- Какво е BEAM Node?
- Какво е OTP Application?
- Какво е Supersvision дърво?
iex -S mixmix run --no-haltelixir -S mix run --no-halt- Mix компилира кода и го слага на правилно място.
- Mix генерира app file и оправя зависимостите.
- Mix знае пътищата, които са ни нужни, и как да стартира проекта.
- Има как да правим release, който да се пуска в production и не ползва Mix.
- OTP Release е нещо, което може да се стартира:
- Чрез инсталиран Erlang (не трябва Elixir);
- Или дори без Erlang (енкапсулира ERTS в самия Release).
- Elixir идва командата
mix releaseза компилиране на release
- Какво означава да сме дистрибутирани?
- Node-ове и функции за свързване и комуникация.
- Проблемите на дистрибутираните системи.
- Дистрибутираност значи, че програмата ни се изпълнява на две или повече виртуални машини.
- Elixir ни дава добри инструменти за постигане на това.
- Един node е една виртуална машина.
- Всеки node може да е отворен или затворен за комуникация с други nodes.
- Как да видим името на node-a си?
node()
#=> :nonode@nohost- Как да направим node-а си 'жив'?
Node.alive?
#=> false
Node.start(:meddle, :shortnames)
#=> {:ok, #PID<0.115.0>}
node()
#=> :"meddle@meddlandr"
Node.alive?
#=> true- Това е OTP модул, който се ползва вътрешно от
Nodeмодула, за да направи node дистрибутиран. - Като цяло дърво от процеси, които знаят как да комуникират с отдалечени nodes и техните процеси.
pid-ът, който се връща отNode.start, е на supervisor на това дърво.- Когато тези процеси вървят, node-ът придобива име и може да бъде намерен в мрежата.
iex --sname njichev
#=> Erlang/OTP 24 [erts-12.0] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1] [jit]
#=> Interactive Elixir (1.14.3) - press Ctrl+C to exit (type h() ENTER for help)
#=> iex(njichev@meddlandr)1>iex --name slavi@127.0.0.1
#=> Erlang/OTP 24 [erts-12.0] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1] [jit]
#=> Interactive Elixir (1.14.3) - press Ctrl+C to exit (type h() ENTER for help)
#=> iex(slavi@127.0.0.1)1>- Списък на свързаните node-ове.
Node.list()
#=> []- Свързване на node-ове.
# @meddle
Node.connect(:ivan@meddlandr)
#=> true
Node.list()
#=> [:ivan@meddlandr]- Връзките между node-ове са симетрични и транзитивни.
# @ivan
Node.list()
#=> [:meddle@meddlandr]
# @njichev
Node.connect(:ivan@meddlandr)
#=> true
Node.list()
#=> [:ivan@meddlandr, :meddle@meddlandr]- Най-малката единица за deployment в Kubernetes е pod.
- Всяко приложение се изпълнява в контейнер в някой pod.
- При deployment имената на pod-овете се променят (но не винаги).
- При deployment нашето приложение може да получи различен IP адрес.
- Това прави формирането на клъстър по-трудно
config :libcluster,
topologies: [
k8s: [
strategy: Elixir.Cluster.Strategy.Kubernetes,
config: [
mode: :dns,
kubernetes_node_basename: "sanbase",
kubernetes_selector: "app=sanbase",
polling_interval: 10_000
]
]
]- Библиотека за автоматично формиране на BEAM клъстери
- Предоставя различни механизми:
- Стандарните Erlang механизми: epmd, .hosts.erlang
- Multicast UDP gossip протокол
- Чрез използване на Kubernetes API и конфигуриране на етитек (label)
- Чрез Rancher
# @ivan
Node.ping(:njichev)
#=> :pang
Node.ping(:njichev@meddlandr)
#=> :pong
Node.ping(:"slavi@127.0.0.1")
#=> [error] ** System NOT running to use fully qualified hostnames **
#=> ** Hostname 127.0.0.1 is illegal **
:pang- Съдържа списък от познати node-ове, които биха могли да се намерят в мрежата.
- Файлът .hosts.erlang:
'192.168.3.10'.
'192.168.3.11'.
'192.168.3.12'.
- Функциата
:net_adm.names/0ще листне всичко от там:
:net_adm.names()
#=> {:ok, [{'b', 45325}, {'a', 33599}]}- Използва се sys.config за задаване на node-ове, които искаме да са online.
- Ако използваме
sync_nodes_optionalсе чака даденото време вsync_nodes_timeoutи приложението тръгва:
[{kernel,
[
{sync_nodes_optional, ['b@127.0.0.1', 'c@127.0.0.1']},
{sync_nodes_timeout, 30000}
]}
].- Използва същата идея като синхронизацията.
- За един и същ application се стартират няколко node-a.
- С команди като:
iex --name a@127.0.0.1 --erl "-config da.sys.config" -S mix- Такъв setup ще се нуждае от sys.config конфигурации като:
[{kernel,
[{distributed, [{ecio, 5000, ['a@127.0.0.1', {'b@127.0.0.1', 'c@127.0.0.1'}]}]},
{sync_nodes_mandatory, ['b@127.0.0.1', 'c@127.0.0.1']},
{sync_nodes_timeout, 20000}
]
}
].- Описаният
distributedApplication ще върви само на един node. - Ако този node падне, един от другите ще стартира application-а.
- Ако се появи най-приоритетният node в списъка, той ще поеме контрола в две стъпки:
- Node-ът, който изпълнява Application-а, ще го спре
- Приоритетният node ще стартира Application-a.
- Късите имена са за локална (localhost) употреба. Не се използва DNS.
- Дългите имена са за мрежа и могат да съдържат достъпен IP адрес или domain name
- Основата на комуникацията между node-ове са процесите.
Node.spawnи неговите версии:
Node.spawn(:ivan@meddlandr, fn -> IO.puts(node()) end)
#=> #PID<13382.125.0>
#=> ivan@meddlandr# @slavi
defmodule DB do
def tell_us_secret do
IO.puts("Познавам всички и всеки!")
end
end# @meddle
Node.spawn(:slavi@meddlandr, DB, :tell_us_secret, [])
#output: Познавам всички и всеки!
#=> #PID<9473.144.0>- Разбира се има
Node.spawn_linkиNode.spawn_monitor. - Това са процеси, които наистина вървят на отдалечения node.
- Наследяват group_leader-а на процеса, който ги е създал.
- Ако първият аргумент на
Node.spawn*не еnode(), то IO операциите ще изпращат съобщения по мрежата към този node, от който е извиканNode.spawn*.
- Ако първият аргумент на
# @meddle
Node.spawn(:ivan@meddlandr, fn -> Process.sleep(60_000); IO.puts(node()) end)
#=> #PID<13382.126.0>
Process.group_leader
#=> #PID<0.66.0>
# @ivan
Process.info(pid(0, 129, 0), :group_leader)
#=> {:group_leader, #PID<11629.66.0>} # Забележете, че първият елемент на pid не е 0!- Къде ще се отпечата "Hey" и защо?
# a@127.0.0.1 (ECIO)
device_pid = Process.whereis(ECIO.Device)
#=> #PID<0.234.0>
:global.register_name(ECIO.Device, device_pid)
#=> :yes
# b@127.0.0.1 (Asynch)
IO.puts(:global.whereis_name(ECIO.Device), "Hey")
#=> :ok- Разбира се има и
:global.unregister_name/1по атом. - Глобалният регистър е достъпен за всички connected nodes.
- Можем да наблюдаваме node кога става offline.
- Същата функция с
falseще спре наблюдението. - Процесът извикал тази функция с
trueтрябва да приема съобщението{:nodedown, <nodename>}.
# @meddle
Node.monitor(:njichev@meddlandr, true)
#=> true
# Kill @njichev!
flush()
#=> {:nodedown, :njichev@meddlandr}- Използва се Erlang модула
:rpc(Remote procedure call). - Отдолу продължава да работи с процеси.
:rpc.call/4- Извиква отдалечена функция и връща резултат:
# @meddle
:rpc.call(:slavi@meddlandr, DB, :tell_us_secret, [])
#=> Познавам всички и всеки!
#=> :ok:rpc.cast/4- Извиква отдалечена функция асинхронно:
# @meddle
:rpc.cast(:ivan@meddlandr, Enum, :map, [[1, 2, 3], fn x -> Process.sleep(10_000); IO.inspect(self()); x * x end])
#=> true
#=> #PID<0.144.0>
#=> #PID<0.144.0>
#=> #PID<0.144.0>
# @ivan, докато се принтира:
Process.alive?(pid("0.144.0"))
#=> true- Функциите
async_call/4иyield/1:
# @ivan
pid = :rpc.async_call(:meddle@meddland,
Enum,
:sort,
[(1..2_000_000) |> Enum.shuffle])
#=> #PID<0.115.0>
# Това няма да забие текущия процес докато резултата е готов.
:rpc.yield(pid)
# Ще чака за резултат, или ако е готов ще го върне- Функцията
nb_yield/4(non blocking yield).
# @meddle
pid = :rpc.async_call(:slavi@meddland, Process, :sleep, [50_000])
#=> #PID<0.108.0>
# По подразбиране timeout-ът е нула.
# Пробва и ако няма резултат - :timeout
:rpc.nb_yield(pid)
#=> :timeout
:rpc.nb_yield(pid, 1_000)
#=> :timeout
:rpc.nb_yield(pid, 50_000)
#=> {:value, :ok}- Функциите
multicall/4иeval_everywhere
# @slavi
Node.list()
#=> [:meddle@meddland, :ivan@meddland]
:rpc.multicall([Node.self() | Node.list()], Node, :self, [])
#=> {[:slavi@meddland, :meddle@meddland, :ivan@meddland], []}- Когато стартираме node с име или пък му дадем име впоследствие, той ще се свърже към програма, наречена EPMD (Erlang Port Mapper Daemon).
- Такава програма върви на всеки компютър на който има поне един 'жив' Erlang или Elixir node.
- EPMD е нещо като сървър за имена, който позволява регистриране, комуникация и връзка между node-ове.
- EPMD map-ва имена на node-ове към машинни адреси.
- Програмата пази само name частта от name@host, защото си знае хоста.
- Ако няма вървящ EPMD и стартираме node с име, автоматично се стартира.
- Портът му по подразбиране е 4369, но може да се конфигурира друг.
- Не е добра идея да ползваме друг порт, защото Ericsson са го регистрирали официално за EPMD и би трябвало да е свободен.
iex --sname andi --erl \
"-kernel inet_dist_listen_min 54300 inet_dist_listen_max 54400"# @meddle
:net_adm.names()
#=> {:ok, [{'meddle', 38657}, {'ivan', 46597}, {'slavi', 33253}, {'andi', 41137}]}# @ivan
Node.spawn(:meddle@meddlandr, fn -> send(pid(0, 110, 0), "Hello from ivan!") end)
#=> #PID<11629.200.0>
# @meddle
flush()
#=> "Hello from ivan!"
#=> :ok- Това, което
Node.spawn/2връща еpid, но изглежда малко странно. - Свикнали сме първото число да е 0, а тук не е.
- Това е така, защото първото число на pid-а е свързано с node-а, на който процеса му се изпълнява.
- Ако процесът върви на текущия node, то винаги е 0.
- Ако обаче процесът върви на друг node, числото уникално ще идентифицира този друг node.
- Тази стойност за един и същи node ще е различна на различни node-ове, свързани с него.
- Първото число на pid показва на кой node върви процеса.
- Второто е брояч, а третото допълнение към този брояч.
- Когато минем максималния брой процеси за дадения node, третото число се увеличава с едно.
binary_pid = :erlang.term_to_binary(self())
#=> <<131, 88, 100, 0, 16, 109, 101, 100, 100, 108, 101, 64, 109, 101, 100, 100,
#=> 108, 97, 110, 100, 114, 0, 0, 0, 110, 0, 0, 0, 0, 100, 56, 62, 180>>- Винаги започват с 131
- Вторият байт е tag (в 1 байт) - за pid-ове : 88 (103 за по-стар формат)
<<131, tag::binary-size(1), rest::binary >> = binary_pid
#=> <<131, 88, 100, 0, 16, 109, 101, 100, 100, 108, 101, 64, 109, 101, 100, 100,
#=> 108, 97, 110, 100, 114, 0, 0, 0, 110, 0, 0, 0, 0, 100, 56, 62, 180>>- За атомите, след tag байта следва закодирана стойността на атома.
- След името на node-a имаме 4 * 3 байта, които ще разгледаме след малко.
name_size = byte_size(rest) - (4*3)
#=> 19
<<131, tag::binary-size(1), binary_atom::binary-size(name_size), rest::binary >> = binary_pid
#=> <<131, 88, 100, 0, 16, 109, 101, 100, 100, 108, 101, 64, 109, 101, 100, 100,
#=> 108, 97, 110, 100, 114, 0, 0, 0, 110, 0, 0, 0, 0, 100, 56, 62, 180>>- Името е атом, но във вложени term-ове нямаме 131 за начало.
- Атомите се кодират по няколко начина, ATOM_EXT, което е deprecated се ползва все още в pid-ове.
- Тагът на ATOM_EXT e 100, след това имаме 2 байта дължина на името и след това името:
<<100, atom_len::integer-size(16), name::binary-size(atom_len)>> = binary_atom
#=> <<100, 0, 16, 109, 101, 100, 100, 108, 101, 64, 109, 101, 100, 100, 108, 97,
#=> 110, 100, 114>>
name
#=> "meddle@meddlandr"- Последните 12 байта са 3 integer-a
- id-то е номерът на pid-a (2-рото му число)
- serial e третото число на pid-a
- creation е дата в unix int, кога е бил създаден node-a.
<<
131,
tag::binary-size(1),
binary_atom::binary-size(name_size),
id::integer-size(32),
serial::integer-size(32),
creation::integer-size(32)
>> = binary_pid- Всеки term може да се кодира и си има формат.
- Това включва и функции, така могат да се пращат на други node-ове.
- Или да се съхраняват да речем в база данни за изпълнение по-късно.
elixir --pipe-to pipe logs --name a@127.0.0.1 -S mix run --no-halt
iex --name b@127.0.0.1 --remsh a@127.0.0.1 --hidden- На мрежата не винаги може да се разчита. Node-ове могат да изчезнат.
- Мрежата може да бъде бавна от време на време. Резултатите от извиквания могат да се забавят.
- Bandwidth. Малки и прости съобщения!
- Security. Това трябва да си го постигнем сами!
- Топологията на мрежата не е константа - имена и локации - трябва да внимаваме.
- Рядко ние имаме пълен контрол над физическите машини в мрежата.
- Транспортът е скъп. Малки, прости съобщения!
- Мрежата няма определен формат. Трябва ние да определим формат и протокол.









