Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Demo 3: Sistemas distribuídos com Elixir

sorry-dave/distributed_elixir.livemd

Demo 3: Sistemas distribuídos com Elixir

Mix.install(
  [
    {:kino, "~> 0.14.1"},
    {:kino_flame, github: "hugobarauna/kino_flame"}
  ]
)

Processo

Processo é a unida mínima de concorrência

self()
pid = self()
IO.puts("Esse código está sendo executado dentro do processo: #{inspect(pid)}")

Criando novos processos com spawn

current_process_pid = self()
child_process_pid = spawn(fn -> 
  "pong" 
end)

IO.inspect(current_process_pid, label: "PID do processo corrente")
IO.inspect(child_process_pid, label: "PID do processo filho")

# :ok

Processos se comunicam por passagem de mensagem

Envia mensagem message para processo com identificador pid

send(pid, message)

Recebe mensagem message de um outro processo e faz alguma coisa

receive do
  message -> # do something
end
Kino.Process.render_seq_trace(fn ->
  # inicia um novo processo
  child_process_pid =
    spawn(fn ->
      receive do
        {:ping, caller_pid} -> send(caller_pid, :pong)
      end
    end)

  # pega o pid do processo rodando
  parent_process_pid = self()

  # envia uma mensagem para o processo child_process_pid
  send(child_process_pid, {:ping, parent_process_pid})

  receive do
    :pong -> :it_worked!
  end
end)

Location transparency

Processos podem se comunicar de modo transparente em relação a que nó (instância máquina virtual / máquina) eles estão rodando.

Mesma API para se comunicar com um processo rodando na máquina local ou em qualquer máquina do cluster:

send(pid, :message_name)

Vamos iniciar um uma instância da máquina virtual do Erlang em outra máquina

Kino.start_child(
  {FLAME.Pool,
   name: :runner,
   code_sync: [
     start_apps: true,
     sync_beams: Kino.beam_paths(),
     compress: false,
     copy_paths: [],
     verbose: true
   ],
   min: 1,
   max: 1,
   max_concurrency: 10,
   boot_timeout: :timer.minutes(3),
   idle_shutdown_after: :timer.minutes(1),
   timeout: :infinity,
   track_resources: true,
   log: :info,
   backend:
     {FLAME.FlyBackend,
      cpu_kind: "shared",
      cpus: 1,
      memory_mb: 2048,
      env: %{"LIVEBOOK_COOKIE" => Node.get_cookie()}}}
)
remote_node = 
  Node.list(:hidden)
  |> Enum.filter(&String.contains?(Atom.to_string(&1), "flame"))
  |> List.first()
remote_process_pid =
  Node.spawn(remote_node, fn ->
    receive do
      :node_and_pid_info ->
        IO.puts("Remote process #{inspect(self())} \n\trunning on node #{node()}\n")
    end
  end)

Kino.nothing()
local_process_pid =
  spawn(fn ->
    receive do
      :node_and_pid_info ->
        IO.puts("Local process #{inspect(self())} \n\trunning on node #{node()}\n")
    end
  end)

Mesma API para se comunicar com um processo rodando na máquina local ou em qualquer máquina do cluster:

send(pid, :message_name)
send(remote_process_pid, :node_and_pid_info)
send(local_process_pid, :node_and_pid_info)

Kino.nothing()
IO.puts("Local process #{inspect(self())}\n\trunning on node #{node()}")

Kino.nothing()

FLAME: elastic scale by wrapping a code inside a function

defmodule Echo do
  def hello(message) do
    IO.puts("ECHO FROM\n\tNODE: #{node()}\n\tMESSAGE: #{message}")
  end
end
message = "Sorry, Dave"
Echo.hello(message)
Kino.start_child(
  {FLAME.Pool,
   name: :elastic_pool,
   code_sync: [
     start_apps: true,
     sync_beams: Kino.beam_paths(),
     compress: false,
     copy_paths: [],
     verbose: true
   ],
   min: 0,
   max: 4,
   max_concurrency: 1,
   boot_timeout: :timer.minutes(3),
   idle_shutdown_after: :timer.minutes(1),
   timeout: :infinity,
   track_resources: true,
   log: :info,
   backend:
     {FLAME.FlyBackend,
      cpu_kind: "shared",
      cpus: 2,
      memory_mb: 1024,
      env: %{"LIVEBOOK_COOKIE" => Node.get_cookie()}}}
)
FLAME.call(:elastic_pool, fn ->
  message = "Sorry, Dave"
  Echo.hello(message)
end)
FLAME.call(
  :elastic_pool,
  fn ->
    message = "Sorry, Dave"
    Echo.hello(message)
  end
)