Do you want to pick up from where you left of?
Take me there

OTP Supervisors

Supervisors 是一种特殊的进程:专门来监控其他的进程。supervisors 能够自动重启出错的子进程,从而编写容错性高的程序。

配置

Supervisors 的魔力主要在 Supervisor.start_link/2 函数。除了能启动 supervisor 和子进程之外,它还允许我们设置管理子进程的策略。

通过 OTP 并发 课程中实现的 SimpleQueue,我们开始本节课程:

使用 mix new simple_queue --sup 命令,我们创建了拥有 supervisor 树的新项目。SimpleQueue 的代码因为放在 lib/simple_queue.ex 而 supervisor 的代码我们将添加到 lib/simple_queue/application.ex 中。

子进程通过列表的方式定义,要么是模块名称的列表:

defmodule SimpleQueue.Application do
  use Application

  def start(_type, _args) do
    children = [
      SimpleQueue
    ]

    opts = [strategy: :one_for_one, name: SimpleQueue.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

要么是元组的列表,如果你还想包含配置项的话:

defmodule SimpleQueue.Application do
  use Application

  def start(_type, _args) do
    children = [
      {SimpleQueue, [1, 2, 3]}
    ]

    opts = [strategy: :one_for_one, name: SimpleQueue.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

如果我们运行 iex -S mix 这个命令,我们就会看到 SimpleQueue 已经自动启动了:

iex> SimpleQueue.queue
[1, 2, 3]

如果我们的 SimpleQueue 进程崩溃了,或者被中止了,Supervisor 会自动重启这个进程,就像什么事情都没有发生过一样。

策略

目前有三种不同的重启策略可以使用:

子进程 Specification

当 supervisor 进程启动后,它必须知道如何 start/stop/restart 它的子进程。每个子模块都应该拥有 child_spec/1 函数来定义好这些行为。宏 use GenServeruse Supervisoruse Agent 的使用,会自动为我们定义好这些行为(SimpleQueue 使用了 use GenServer,所以我们不需要修改这个模块)。但是,如果你需要自己定义这些行为,你要在 child_spec/1 函数中返回一个选项映射:

def child_spec(opts) do
  %{
    id: SimpleQueue,
    start: {__MODULE__, :start_link, [opts]},
    shutdown: 5_000,
    restart: :permanent,
    type: :worker
  }
end

DynamicSupervisor

Supervisors 通常在应用启动的时候,伴随着子进程而启动。但是,有时候,被监管的子进程在应用启动的时候,还是未知的(比如,我们可能在 web 应用中,启动了一个新的进程来处理用户到我们网站的连接)。这种情况下,我们需要的是一个能按需启动子进程的 Supervisor。而 DynamicSupervisor 正是用来处理这种场景的。

因为我们并不指定子进程,我们只需要为 supervisor 定义好运行时的选项就可以了。DynamicSupervisor 只支持 :one_for_one 这种监管策略:

options = [
  name: SimpleQueue.Supervisor,
  strategy: :one_for_one
]

DynamicSupervisor.start_link(options)

然后,我们需要使用 start_child/2 函数来动态启动新的 SimpleQueue 子进程。这个函数接收一个 supervisor 和 子进程 specification 作为参数(再次强调,SimpleQueue 使用了 use GenServer,所以子进程的 specification 已经定义好了):

{:ok, pid} = DynamicSupervisor.start_child(SimpleQueue.Supervisor, SimpleQueue)

Task Supervisor

Tasks 有它们自己特殊的 Supervisor,叫做 Task.Supervisor。它是专门为动态创建的任务而设计的 supervisor,内部实际使用的是 DynamicSupervisor

Setup

Task.Supervisor 使用起来和其他的 supervisors 没有任何区别:

children = [
  {Task.Supervisor, name: ExampleApp.TaskSupervisor, restart: :transient}
]

{:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one)

SupervisorTask.Supervisor 主要的不同是 Task.Supervisor 的默认重启策略是 :temporary(绝不重启子任务)。

受监管的 Tasks

Supervisor 启动之后,我们可以使用 start_child/2 函数来创建受监管的 task:

{:ok, pid} = Task.Supervisor.start_child(ExampleApp.TaskSupervisor, fn -> background_work end)

如果我们的任务过早地崩溃掉,它会被自动启动。这个功能在处理大量涌来的请求或者后台工作的时候非常有用。

Caught a mistake or want to contribute to the lesson? Edit this lesson on GitHub!