Elixir og Phoenix: Skalerbar backend-udvikling · Lektion

Supervisors og applikationsstruktur

Design modstandsdygtige applikationer med supervisors, der overvåger og genstarter processer for at sikre fejltolerance.

Lektion 3 af 411 trin

Supervisors og applikationsstruktur er en gratis Elixir og Phoenix: Skalerbar backend-udvikling-lektion på CoddyKit. Dette er lektion 3 af 4. Du kan læse hele lektionen gratis nedenfor — og derefter øve dig praktisk i browseren med en indbygget kodeeditor og en AI-vejleder, der er tilgængelig døgnet rundt. Den er en del af læringsforløbet i Elixir og Phoenix: Skalerbar backend-udvikling, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. Elixir og Phoenix: Skalerbar backend-udvikling-kurset indeholder 4 lektioner i alt.

Opbygning af robuste systemer

I samtidige applikationer kan tingene gå galt. Processer kan gå ned på grund af uventede fejl eller problemer udefra.

Fejltolerance er et systems evne til at fortsætte driften, selv når komponenter svigter. Elixir bygger på filosofien »lad det gå ned«, hvilket betyder, at fokus ikke er at forhindre hvert eneste nedbrud, men at komme sig på en ordentlig måde efter dem.

Processernes vogtere

Det er her, supervisors kommer ind i billedet! En supervisor er en særlig type proces, der er designet til at overvåge andre processer (dens »børn«).

  • Hvis en underordnet proces går ned, genstarter supervisoren den automatisk.
  • Det sikrer, at din applikation forbliver stabil og tilgængelig.
  • Supervisors udgør rygraden i fejltolerante Elixir-applikationer.

Din første supervisor

Lad os definere et enkelt supervisor-modul. Det bruger adfærden Supervisor, på samme måde som GenServer bruger adfærden GenServer.

Callbacken init/1 er det sted, hvor du definerer de børn, som supervisoren skal overvåge, samt dens genstartsstrategi.

defmodule MySupervisor do
  use Supervisor

  def start_link(init_arg) do
    Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
  end

  @impl true
  def init(_init_arg) do
    # No children yet, just the supervisor itself
    children = []
    Supervisor.init(children, strategy: :one_for_one)
  end
end

# --- Main execution part ---
# This simulates starting the supervisor and checking its status.
# In a real app, this would be part of an Application's start/2.
IO.puts("Attempting to start MySupervisor...")
{:ok, supervisor_pid} = MySupervisor.start_link([])
IO.puts("MySupervisor started with PID: #{inspect(supervisor_pid)}")

# Check if the supervisor process is alive
if Process.alive?(supervisor_pid) do
  IO.puts("Supervisor is alive!")
else
  IO.puts("Supervisor is NOT alive!")
end

Forstå supervisionstrategier

Supervisors har forskellige strategier til håndtering af fejl i underordnede processer:

  • :one_for_one: Genstarter kun den underordnede proces, der gik ned. Dette er standardstrategien og den mest almindelige.
  • :one_for_all: Hvis en underordnet proces går ned, afsluttes alle andre underordnede processer, hvorefter alle processerne genstartes.
  • :rest_for_one: Hvis en underordnet proces går ned, afsluttes og genstartes den samt alle underordnede processer, der blev startet *efter* den.

Valget af den rigtige strategi afhænger af afhængighederne mellem dine processer.

Tilføj underordnede processer

En supervisor skal have børn for at være nyttig! Du definerer børn ved hjælp af Supervisor.child_spec/2, som fortæller supervisoren, hvordan en proces skal startes og administreres.

Her definerer vi en enkel MyWorker-GenServer og tilføjer den som barn til vores supervisor.

defmodule MyWorker do
  use GenServer

  def start_link(_opts) do
    GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
  end

  @impl true
  def init(:ok) do
    IO.puts("MyWorker started!")
    {:ok, %{}}
  end

  @impl true
  def handle_call(:crash, _from, state) do
    IO.puts("MyWorker is crashing!")
    exit(:bad_state) # Simulate a crash
    {:reply, :ok, state} # This line won't be reached
  end
end

defmodule MySupervisorWithWorker do
  use Supervisor

  def start_link(init_arg) do
    Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
  end

  @impl true
  def init(_init_arg) do
    children = [
      # Define our worker as a child process
      Supervisor.child_spec(MyWorker, id: MyWorker)
    ]
    Supervisor.init(children, strategy: :one_for_one)
  end
end

# --- Main execution part ---
IO.puts("Starting supervisor with worker...")
{:ok, supervisor_pid} = MySupervisorWithWorker.start_link([])
IO.puts("Supervisor PID: #{inspect(supervisor_pid)}")

# Get the worker's PID
worker_pid = Process.whereis(MyWorker)
IO.puts("Initial MyWorker PID: #{inspect(worker_pid)}")

if Process.alive?(worker_pid) do
  IO.puts("Worker is alive and supervised.")
else
  IO.puts("Worker did not start correctly.")
end

Fejltolerance i praksis

Lad os nu se supervisoren i aktion! Vi får med vilje vores MyWorker-proces til at gå ned, hvorefter supervisoren automatisk genstarter den.

Bemærk, hvordan arbejderens proces-ID (PID) ændres, hvilket viser, at der blev oprettet en ny proces.

defmodule MyWorker do
  use GenServer

  def start_link(_opts) do
    GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
  end

  @impl true
  def init(:ok) do
    IO.puts("MyWorker started!")
    {:ok, %{}}
  end

  @impl true
  def handle_call(:crash, _from, state) do
    IO.puts("MyWorker is crashing!")
    exit(:bad_state) # Simulate a crash
    {:reply, :ok, state} # This line won't be reached
  end

  def crash_it do
    GenServer.call(__MODULE__, :crash)
  end
end

defmodule MySupervisorWithWorker do
  use Supervisor

  def start_link(init_arg) do
    Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
  end

  @impl true
  def init(_init_arg) do
    children = [
      Supervisor.child_spec(MyWorker, id: MyWorker)
    ]
    Supervisor.init(children, strategy: :one_for_one)
  end
end

# --- Main execution part ---
IO.puts("Starting supervisor with worker...")
{:ok, _supervisor_pid} = MySupervisorWithWorker.start_link([])

worker_pid_before_crash = Process.whereis(MyWorker)
IO.puts("Worker PID before crash: #{inspect(worker_pid_before_crash)}")

# Crash the worker
IO.puts("Attempting to crash the worker...")
MyWorker.crash_it()
:timer.sleep(100) # Give supervisor a moment to restart

worker_pid_after_crash = Process.whereis(MyWorker)
IO.puts("Worker PID after crash: #{inspect(worker_pid_after_crash)}")

if worker_pid_before_crash != worker_pid_after_crash && Process.alive?(worker_pid_after_crash) do
  IO.puts("Worker was restarted by the supervisor! New PID detected.")
else
  IO.puts("Worker was NOT restarted, or PID remained the same (unexpected).")
end

Elixir-applikationer: Det øverste niveau

Mens supervisors administrerer individuelle processer, er en Elixir-applikation den øverste enhed af kode og processer i et Elixir-system.

Den giver en struktureret måde at:

  • Gruppere relaterede moduler og processer.
  • Definere, hvordan systemet starter og lukker ned.
  • Administrere konfiguration og afhængigheder.

`Application`-adfærden

Alle Elixir-applikationer har typisk et hovedmodul, der bruger use Application.

Den vigtigste callback er start/2, som kaldes, når applikationen starter. Det er her, du typisk starter din øverste supervisor, som derefter rekursivt starter alle andre processer i dit system.

defmodule MyApp.Application do
  use Application

  # This is the entry point for your application.
  # It starts the top-level supervisor.
  @impl true
  def start(_type, _args) do
    children = [
      # In a real app, you'd start your main supervisor here.
      # For example: Supervisor.child_spec(MySupervisorWithWorker, id: MySupervisorWithWorker)
    ]

    # Start a supervisor that will supervise other processes/supervisors
    opts = [strategy: :one_for_one, name: MyApp.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

# --- Main execution part ---
# This part simulates how an application would be started.
# In a real Mix project, 'mix run --no-halt' would call MyApp.Application.start/2
IO.puts("Simulating application start...")
{:ok, pid} = MyApp.Application.start(:normal, [])
IO.puts("Application top-level supervisor started with PID: #{inspect(pid)}")

if Process.alive?(pid) do
  IO.puts("Application supervisor is active.")
else
  IO.puts("Application supervisor failed to start.")
end

Hierarkiske supervisionstræer

I komplekse applikationer har du ofte ikke kun én supervisor. Du opretter et supervisionstræ, hvor supervisors kan overvåge andre supervisors.

  • Det giver dig mulighed for at organisere applikationen i logiske enheder.
  • Forskellige dele af systemet kan have forskellige genstartsstrategier.
  • Hvis en vigtig komponent svigter, kan dens supervisor genstarte den uden at påvirke andre, uafhængige dele af systemet.

Tjek af supervisorer

Tid til et hurtigt tjek af din forståelse af supervisionstrategier!

Opsummering af lektionen

Godt gået! Du har lært, hvordan Elixir bygger robuste applikationer:

  • Supervisors overvåger processer og genstarter dem ved fejl.
  • Forskellige supervisionstrategier (:one_for_one, :one_for_all, :rest_for_one) bestemmer, hvordan fejl håndteres.
  • Elixir-applikationer udgør den øverste struktur, starter supervisors og danner supervisionstræer.

Disse begreber er grundlæggende for at bygge robuste og fejltolerante systemer i Elixir.

Gratis at komme i gang

Lær Elixir med en AI-underviser — gratis

Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.

Kurser
12
Lektioner
48

Ofte stillede spørgsmål

Er lektionen “Supervisors og applikationsstruktur” gratis?

Ja — hele teksten til “Supervisors og applikationsstruktur” kan læses gratis her på nettet. Hvis du vil øve dig interaktivt med en indbygget kodeeditor og en AI-vejleder døgnet rundt og få adgang til resten af Elixir og Phoenix: Skalerbar backend-udvikling-kurset, skal du opgradere til CoddyKit PRO. Elixir og Phoenix: Skalerbar backend-udvikling-kurset indeholder 4 lektioner i alt.

Hvad lærer jeg i “Supervisors og applikationsstruktur”?

Design modstandsdygtige applikationer med supervisors, der overvåger og genstarter processer for at sikre fejltolerance. Du øver dig i Elixir og Phoenix: Skalerbar backend-udvikling med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.

Skal jeg have erfaring for at begynde på Elixir og Phoenix: Skalerbar backend-udvikling?

Der kræves ingen tidligere erfaring. Elixir og Phoenix: Skalerbar backend-udvikling på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 3 af 4.

Hvor lang tid tager lektionen “Supervisors og applikationsstruktur”?

De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.

Kan jeg skrive og køre kode i denne Elixir og Phoenix: Skalerbar backend-udvikling-lektion?

Ja. Alle Elixir og Phoenix: Skalerbar backend-udvikling-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.

Alle lektioner i dette kursus

  1. Elixir-processer og meddelelsesudveksling
  2. Implementering af GenServer-adfærd
  3. Supervisors og applikationsstruktur
  4. Samtidigt arbejde med Task og Agent
← Tilbage til Elixir og Phoenix: Skalerbar backend-udvikling