Supervisores y estructura de aplicaciones
Diseñe aplicaciones resilientes mediante supervisores que monitoricen y reinicien procesos para garantizar la tolerancia a fallos.
Supervisores y estructura de aplicaciones es una lección gratuita de Elixir & Phoenix: Scalable Backend Development en CoddyKit. Esta es la lección 3 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Elixir & Phoenix: Scalable Backend Development, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Elixir & Phoenix: Scalable Backend Development incluye 4 lecciones en total.
Partes de esta lección aún no han sido traducidas y se muestran en inglés.
Building Resilient Systems
In concurrent applications, things can go wrong. Processes might crash due to unexpected errors or external issues.
Fault tolerance is the ability of a system to continue operating even when components fail. Elixir embraces a "let it crash" philosophy, meaning instead of trying to prevent every single crash, it focuses on gracefully recovering from them.
Guardians of Processes
This is where Supervisors come in! A supervisor is a special kind of process designed to monitor other processes (its "children").
- If a child process crashes, the supervisor automatically restarts it.
- This ensures your application remains stable and available.
- Supervisors form the backbone of fault-tolerant Elixir applications.
Your First Supervisor
Let's define a simple supervisor module. It uses the Supervisor behavior, similar to how GenServer uses the GenServer behavior.
The init/1 callback is where you define the children it will supervise and its restart strategy.
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!")
endUnderstanding Supervision Strategies
Supervisors have different strategies for handling child failures:
:one_for_one: Restarts only the child that crashed. This is the default and most common.:one_for_all: If any child crashes, all other children are terminated and then all children are restarted.:rest_for_one: If a child crashes, it and all children started *after* it are terminated and then restarted.
Choosing the right strategy depends on the dependencies between your processes.
Adding Supervised Children
To make a supervisor useful, it needs children! You define children using Supervisor.child_spec/2, which tells the supervisor how to start and manage a process.
Here, we define a simple MyWorker GenServer and add it as a child to our 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.")
endFault Tolerance in Action
Now, let's see the supervisor in action! We'll deliberately crash our MyWorker process, and the supervisor will automatically restart it.
Notice how the worker's Process ID (PID) changes, indicating a new process was spawned.
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).")
endElixir Applications: The Top Level
While supervisors manage individual processes, an Elixir Application is the top-level unit of code and processes in an Elixir system.
It provides a structured way to:
- Group related modules and processes.
- Define how your system starts up and shuts down.
- Manage configuration and dependencies.
The `Application` Behavior
Every Elixir application typically has a main application module that use Application.
The most important callback is start/2, which is invoked when your application starts. This is where you typically start your top-level supervisor, which then recursively starts all other processes in your 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.")
endHierarchical Supervision Trees
For complex applications, you often don't have just one supervisor. You create a supervision tree, where supervisors can supervise other supervisors.
- This allows you to organize your application into logical units.
- Different parts of your system can have different restart strategies.
- If a major component fails, its supervisor can restart it without affecting unrelated parts of the system.
Supervisor Check
Time for a quick check on your understanding of supervisor strategies!
Lesson Summary
Well done! You've learned how Elixir builds resilient applications:
- Supervisors monitor processes and restart them upon failure.
- Different supervision strategies (
:one_for_one,:one_for_all,:rest_for_one) dictate how failures are handled. - Elixir Applications provide the top-level structure, starting supervisors and forming supervision trees.
These concepts are fundamental to building robust, fault-tolerant systems in Elixir.
Preguntas frecuentes
¿La lección «Supervisores y estructura de aplicaciones» es gratis?
Sí — el texto completo de «Supervisores y estructura de aplicaciones» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Elixir & Phoenix: Scalable Backend Development, actualiza a CoddyKit PRO. El curso de Elixir & Phoenix: Scalable Backend Development incluye 4 lecciones en total.
¿Qué aprenderé en «Supervisores y estructura de aplicaciones»?
Diseñe aplicaciones resilientes mediante supervisores que monitoricen y reinicien procesos para garantizar la tolerancia a fallos. Practicas Elixir & Phoenix: Scalable Backend Development con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.
¿Necesito experiencia previa para empezar Elixir & Phoenix: Scalable Backend Development?
No se requiere experiencia previa. Elixir & Phoenix: Scalable Backend Development en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 3 de 4.
¿Cuánto tiempo toma la lección «Supervisores y estructura de aplicaciones»?
La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.
¿Puedo escribir y ejecutar código en esta lección de Elixir & Phoenix: Scalable Backend Development?
Sí. Cada lección de Elixir & Phoenix: Scalable Backend Development incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.
Todas las lecciones de este curso
- Procesos y paso de mensajes en Elixir
- Implementación del comportamiento GenServer
- Supervisores y estructura de aplicaciones
- Trabajo concurrente con Task y Agent