elixir 高可用系列(五) Supervisor

简介:

概述

OTP 平台的容错性高,是因为它提供了机制来监控所有 processes 的状态,如果有进程出现异常, 不仅可以及时检测到错误,还可以对 processes 进行重启等操作。

有了 supervisor,可以有效的提高系统的可用性,一个 supervior 监督一个或多个应用, 同时, supervior 也可以监督 supervior,从而形成一个监督树,提高整个系统的可用性。

注意 ,supervior 最好只用于监督,不要有其他的业务逻辑处理,越是接近监督树根部的 supervior 就要越简单, 因为 supervior 简单就不容易出错,它是保证系统高可用的关键。

监督者示例

下面,使用 elixir 中提供的 Supervisor 模块,构造简单的监督示例来演示如何提高系统的可用性。

监督策略

监督策略有4种:

  1. :one_for_one 只重启出错的 process
  2. :one_for_all 当有 process 出错时,重启所有的 process
  3. :rest_for_one 重启出错的 process ,以及所有在它之后启动的 process(也就是重启对出错 process 有依赖的 所有 process)
  4. :simple_one_for_one 类似 :one_for_one ,但是 supervior 只能包含一个 process

监督策略的转换非常简单,下面演示2种监督策略的示例:

one for one

defmodule PseudoServerA do
  use GenServer

  def start_link(state, opts \\ []) do
    GenServer.start_link(__MODULE__, state, opts)
  end

  def handle_call(:display, _from, []) do
    {:reply, 'ServerA PID: ' ++ :erlang.pid_to_list(self()), []}
  end

  def handle_cast(:err, []) do
    {:stop, "stop ServerA", []}
  end
end

defmodule PseudoServerB do
  use GenServer

  def start_link(state, opts \\ []) do
    GenServer.start_link(__MODULE__, state, opts)
  end

  def handle_call(:display, _from, []) do
    {:reply, 'ServerB PID: ' ++ :erlang.pid_to_list(self()), []}
  end

  def handle_cast(:err, []) do
    {:stop, "stop ServerB", []}
  end
end

defmodule PseudoServerC do
  use GenServer

  def start_link(state, opts \\ []) do
    GenServer.start_link(__MODULE__, state, opts)
  end

  def handle_call(:display, _from, []) do
    {:reply, 'ServerC PID: ' ++ :erlang.pid_to_list(self()), []}
  end

  def handle_cast(:err, []) do
    {:stop, "stop ServerC", []}
  end
end

defmodule SupervisorTest do
  import Supervisor.Spec

  def init() do
    children = [
      worker(PseudoServerA, [[], [name: :server_a]]),
      worker(PseudoServerB, [[], [name: :server_b]]),
      worker(PseudoServerC, [[], [name: :server_c]])
    ]

    # Start the supervisor with children
    Supervisor.start_link(children, strategy: :one_for_one)
  end

end

测试方式:

$ iex -S mix

# 启动 supervisor 及其监督的3个 process 
iex(1)> SupervisorTest.init
{:ok, #PID<0.145.0>}

# 启动后, 3个 process 的 PID 如下
iex(2)> GenServer.call(:server_a, :display)
'ServerA PID: <0.146.0>'
iex(3)> GenServer.call(:server_b, :display)
'ServerB PID: <0.147.0>'
iex(4)> GenServer.call(:server_c, :display)
'ServerC PID: <0.148.0>'

# 通过消息 :err 让 serverA 出错
iex(5)> GenServer.cast(:server_a, :err)
:ok
iex(6)>
14:47:53.119 [error] GenServer :server_a terminating
** (stop) "stop ServerA"
Last message: {:"$gen_cast", :err}
State: []

nil

# serverA 出错后,再次查看3个process的PID,发现 supervisor 只重启了 serverA,符合策略 :one_for_one
iex(7)> GenServer.call(:server_a, :display)
'ServerA PID: <0.155.0>'
iex(8)> GenServer.call(:server_b, :display)
'ServerB PID: <0.147.0>'
iex(9)> GenServer.call(:server_c, :display)
'ServerC PID: <0.148.0>'

one_for_all

我们换一种监督策略试试看,只需要将上面的代码

# Start the supervisor with children
Supervisor.start_link(children, strategy: :one_for_one)

改成

# Start the supervisor with children
Supervisor.start_link(children, strategy: :one_for_all)

测试步骤 和 one_for_one 一样:

$ iex -S mix

# 启动 supervisor 及其监督的3个 process 
iex(1)> SupervisorTest.init
{:ok, #PID<0.145.0>}

# 启动后, 3个 process 的 PID 如下
iex(2)> GenServer.call(:server_a, :display)
'ServerA PID: <0.146.0>'
iex(3)> GenServer.call(:server_b, :display)
'ServerB PID: <0.147.0>'
iex(4)> GenServer.call(:server_c, :display)
'ServerC PID: <0.148.0>'

# 通过消息 :err 让 serverA 出错
iex(5)> GenServer.cast(:server_a, :err)
:ok
iex(6)>
14:55:16.183 [error] GenServer :server_a terminating
 ** (stop) "stop ServerA"
 Last message: {:"$gen_cast", :err}
 State: []

 nil

# serverA 出错后,再次查看3个process的PID,发现 supervisor 重启了所有 process,符合策略 :one_for_all
iex(7)> GenServer.call(:server_a, :display)
'ServerA PID: <0.153.0>'
iex(8)> GenServer.call(:server_b, :display)
'ServerB PID: <0.154.0>'
iex(9)> GenServer.call(:server_c, :display)
'ServerC PID: <0.156.0>'

监督树

监督者并不是一维的,监督者也可以监督其它监督者,从而形成树状的监督关系。

修改上面的测试代码如下:(只修改了 Supervisor 的部分)

defmodule PseudoServerA do
  use GenServer

  def start_link(state, opts \\ []) do
    GenServer.start_link(__MODULE__, state, opts)
  end

  def handle_call(:display, _from, []) do
    {:reply, 'ServerA PID: ' ++ :erlang.pid_to_list(self()), []}
  end

  def handle_cast(:err, []) do
    {:stop, "stop ServerA", []}
  end
end

defmodule PseudoServerB do
  use GenServer

  def start_link(state, opts \\ []) do
    GenServer.start_link(__MODULE__, state, opts)
  end

  def handle_call(:display, _from, []) do
    {:reply, 'ServerB PID: ' ++ :erlang.pid_to_list(self()), []}
  end

  def handle_cast(:err, []) do
    {:stop, "stop ServerB", []}
  end
end

defmodule PseudoServerC do
  use GenServer

  def start_link(state, opts \\ []) do
    GenServer.start_link(__MODULE__, state, opts)
  end

  def handle_call(:display, _from, []) do
    {:reply, 'ServerC PID: ' ++ :erlang.pid_to_list(self()), []}
  end

  def handle_cast(:err, []) do
    {:stop, "stop ServerC", []}
  end
end

defmodule SupervisorBranch do
  import Supervisor.Spec

  def start_link(state) do
    children = [
      worker(PseudoServerA, [[], [name: :server_a]]),
      worker(PseudoServerB, [[], [name: :server_b]]),
    ]

    Supervisor.start_link(children, strategy: :one_for_one)
  end

end

defmodule SupervisorRoot do
  import Supervisor.Spec

  def init() do
    children = [
      supervisor(SupervisorBranch, [[name: :supervisor_branch]]),
      worker(PseudoServerC, [[], [name: :server_c]])
    ]

    # Start the supervisor with children
    Supervisor.start_link(children, strategy: :one_for_all)
  end

end

测试流程如下:

# 启动 根 监督者 
iex(1)> SupervisorRoot.init
{:ok, #PID<0.149.0>}

# 启动后,查看 3 个process 的PID
iex(2)> GenServer.call(:server_a, :display)
'ServerA PID: <0.151.0>'
iex(3)> GenServer.call(:server_b, :display)
'ServerB PID: <0.152.0>'
iex(4)> GenServer.call(:server_c, :display)
'ServerC PID: <0.153.0>'

# 通过消息 :err 让 serverA 出错
iex(5)> GenServer.cast(:server_a, :err)
:ok
iex(6)>
15:31:15.846 [error] GenServer :server_a terminating
 ** (stop) "stop ServerA"
 Last message: {:"$gen_cast", :err}
 State: []

 nil

 # serverA 出错后,因为它的监督者 SupervisorBranch 的策略是 :one_for_one,所以只重启了 serverA
 iex(7)> GenServer.call(:server_a, :display)
 'ServerA PID: <0.158.0>'
 iex(8)> GenServer.call(:server_b, :display)
 'ServerB PID: <0.152.0>'
 iex(9)> GenServer.call(:server_c, :display)
 'ServerC PID: <0.153.0>'

 # 通过消息 :err 让 serverC 出错
 iex(10)> GenServer.cast(:server_c, :err)
 :ok

 15:31:35.264 [error] GenServer :server_c terminating
 ** (stop) "stop ServerC"
 Last message: {:"$gen_cast", :err}
 State: []

 # serverC 出错后,因为它的监督者 SupervisorRoot 的策略是 :one_for_all,所以所有的 proocess 都重启了
 iex(11)> GenServer.call(:server_a, :display)
 'ServerA PID: <0.166.0>'
 iex(12)> GenServer.call(:server_c, :display)
 'ServerC PID: <0.168.0>'
 iex(13)> GenServer.call(:server_b, :display)
 'ServerB PID: <0.167.0>'

通过监督树,我们可以给不同的 process 分组,然后让每个组有不同的监督策略。

总结

有了监督机制,可以及时的把握所有 process 的状态,通过监督树,还可以加入不同恢复机制。 因此,用好 Supervisor 模块,可以极大提高系统的可用性。

Supervisor 模块详细内容可以参见:http://elixir-lang.org/docs/stable/elixir/Supervisor.html

来源:http://blog.iotalabs.io/



本文转自wang_yb博客园博客,原文链接:http://www.cnblogs.com/wang_yb/p/5564459.html,如需转载请自行联系原作者


目录
相关文章
|
虚拟化 Windows
Hyper-v虚拟机安装Windows 11失败问题处理(一)
Hyper-v虚拟机安装Windows 11失败问题处理
3562 0
Hyper-v虚拟机安装Windows 11失败问题处理(一)
|
XML JavaScript 搜索推荐
如何在 Vue 中进行 SEO 优化?
如何在 Vue 中进行 SEO 优化?
526 5
|
Python
Python中遇到奇怪的错误ValueError: bad marshal data
不是代码出的问题,是*.pyc文件被改动了。解决方法,删除所有*.pyc文件再运行
5077 0
|
3月前
|
Java 测试技术 API
2025 年 Java 开发者必知的最新技术实操指南全览
本指南涵盖Java 21+核心实操,详解虚拟线程、Spring Boot 3.3+GraalVM、Jakarta EE 10+MicroProfile 6微服务开发,并提供现代Java开发最佳实践,助力开发者高效构建高性能应用。
598 4
|
9月前
|
前端开发 Java 测试技术
语音app系统软件源码开发搭建新手启蒙篇
在移动互联网时代,语音App已成为生活和工作的重要工具。本文为新手开发者提供语音App系统软件源码开发的启蒙指南,涵盖需求分析、技术选型、界面设计、编码实现、测试部署等关键环节。通过明确需求、选择合适的技术框架、优化用户体验、严格测试及持续维护更新,帮助开发者掌握开发流程,快速搭建功能完善的语音App。
|
开发框架 前端开发 .NET
AbpHelper CLI积累与实战分享
【9月更文挑战第19天】AbpHelper CLI 是一个强大的工具,用于提升开发人员使用 ABP 框架时的效率。它能快速生成代码、确保代码规范性,并执行自动化任务如数据库迁移和种子数据插入。本文分享了如何利用 AbpHelper CLI 快速初始化项目、开发模块及注意事项,帮助开发者提高效率和代码质量。
188 7
|
监控 网络协议 网络安全
识别DDoS攻击
【10月更文挑战第12天】识别DDoS攻击
449 16
|
机器学习/深度学习 TensorFlow 算法框架/工具
使用Python实现深度学习模型:迁移学习与领域自适应教程
【7月更文挑战第3天】 使用Python实现深度学习模型:迁移学习与领域自适应教程
369 0
|
开发框架 JSON 前端开发
利用过滤器Filter和特性Attribute实现对Web API返回结果的封装和统一异常处理
利用过滤器Filter和特性Attribute实现对Web API返回结果的封装和统一异常处理
|
机器学习/深度学习 数据可视化 算法
umap:一个小巧而强大的Python库,探索高维数据的降维与可视化
umap:一个小巧而强大的Python库,探索高维数据的降维与可视化
743 0