elixir 高可用系列(五) Supervisor
概述
OTP 平台的容错性高,是因为它提供了机制来监控所有 processes 的状态,如果有进程出现异常, 不仅可以及时检测到错误,还可以对 processes 进行重启等操作。
有了 supervisor,可以有效的提高系统的可用性,一个 supervior 监督一个或多个应用, 同时, supervior 也可以监督 supervior,从而形成一个监督树,提高整个系统的可用性。
注意 ,supervior 最好只用于监督,不要有其他的业务逻辑处理,越是接近监督树根部的 supervior 就要越简单, 因为 supervior 简单就不容易出错,它是保证系统高可用的关键。
监督者示例
下面,使用 elixir 中提供的 Supervisor 模块,构造简单的监督示例来演示如何提高系统的可用性。
监督策略
监督策略有4种:
- :one_for_one 只重启出错的 process
- :one_for_all 当有 process 出错时,重启所有的 process
- :rest_for_one 重启出错的 process ,以及所有在它之后启动的 process(也就是重启对出错 process 有依赖的 所有 process)
- :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
elixir 高可用系列(五) Supervisor的更多相关文章
- elixir 高可用系列 - 目录
1. elixir 高可用系列(一) Agent 2. elixir 高可用系列(二) GenServer 3. elixir 高可用系列(三) GenEvent 4. elixir 高可用系列(四) ...
- elixir 高可用系列(四) Task
概述 之前学习的 Agent,GenSever以及GenEvent,都是用来管理状态或者处理消息的. 但是在很多时候,我们需要的是执行某个任务,这时如果使用 GenSever 或者 GenEvent, ...
- elixir 高可用系列(三) GenEvent
概述 GenEvent 是事件处理的通用部分的抽象. 通过 GenEvent ,我们给已有的服务 动态 的添加 事件处理. GenEevent 和 GenServer 的区别 之前已经介绍了 GenS ...
- elixir 高可用系列(二) GenServer
概述 如果我们需要管理多个进程,那么,就需要一个专门的 server 来集中监控和控制这些进程的状态,启停等. OTP 平台中的 GenServer 就是对这个 server 通用部分的抽象. 利用 ...
- elixir 高可用系列(一) Agent
概述 elixir 本身是一种 immutable 的语言,默认情况下,进程间是不共享任何状态的,进程之间通过消息来交互. 而 Agent 则封装了一种进程间共享状态的方式,通过这种方式,不用显式的写 ...
- (5.8)mysql高可用系列——MySQL中的GTID复制(实践篇)
一.基于GTID的异步复制(一主一从)无数据/少数据搭建 二.基于GTID的无损半同步复制(一主一从)(mysql5.7)基于大数据量的初始化 正文: [0]概念 [0.5]GTID 复制(mysql ...
- keepalived高可用系列~ keepalived+proxysql
一 简介:介绍下高可用通用的方案 二 目的:一个中间件提供服务,故障后,另一个中间件提供服务 三 手段: 应用keepalived的vrrp_scripts服务 四 具体配置 global_defs ...
- keepalived高可用系列~keepalived+mysql
一 简介:建立读写分离模式 二 keepalived相关配置 vrrp_instance VI_1 { state MASTER // 可修改 interface eth0 virtual_r ...
- 高可用系列之Nginx
1.1Keepalived高可用软件 Keepalived起初是专为LVS设计的,专门用来监控LVS集群系统中各个服务节点的状态,后来又加入了VRRP的功能,因此除了配合LVS服务外,也可以作为其他服 ...
随机推荐
- 利用 Gulp 处理前端工作流程
最近做项目,因为每次做完后都要手动压缩CSS.JS 等文件,压缩后另存为 *.min.xxx. Less 还要手动输入命令进行编译,调整页面也经常要手动刷新页面看效果,很麻烦,就尝试用 gulp 去处 ...
- MFC坐标空间与映射模式
逻辑坐标:使用GDI绘图时使用的坐标系 设备坐标系:实际设备(显示器.打印机)的坐标系,即我们实际看到的坐标系. 坐标空间 在Windows NT/2000中Win32 API中支持以下四层坐标空间: ...
- C语言 malloc calloc realloc alloc 在分配内存时的 区别
malloc : 向堆申请分配内存,不初始化 calloc : 向堆申请分配内存,初始化为0 realloc: 向堆申请分配内存,可调整大小 alloc : 向栈申请内存,不需手动释放
- codeforces731C Socks
C. Socks time limit per test 2 seconds memory limit per test 256 megabytes input standard input outp ...
- Bash Shell实用快捷键
Ctrl-D 相当于Del键,即删除光标所在处的字符 Ctrl-E 相当于End键,即将光标移动到本行末尾 Ctrl-K 用于删除从光标处开始到结尾处的所有字符 Ctrl-L 清屏,相当于clear命 ...
- mysql事务处理用法与实例详解
来源:转载 MySQL的事务支持不是绑定在MySQL服务器本身,而是与存储引擎相关1.MyISAM:不支持事务,用于只读程序提高性能 2.InnoDB:支持ACID事务.行级锁.并发 3.Berke ...
- ORA-12505 错误解决
在Fedora下安装了Oracle 10gR2,安装完成之后,使用netca创建了监听,创建的时候没有使用默认的LISTENER和1521端口,而是使用了LISTENER_DELL和1522端口,终 ...
- PHP 数组函数整理
如果你已经使用了一段时间PHP的话,那么,你应该已经对它的数组比较熟悉了——这种数据结构允许你在单个变量中存储多个值,并且可以把它们作为一个集合进行操作. 经常,开发人员发现在PHP中使用这种数据结构 ...
- AngularJS学习--- 事件处理(Event Handlers) ng-click操作 step 10
本文主要通过介绍ng-click方法来对angularjs中的事件处理方法做个了解. 1.切换目录 git checkout step- npm start 2.效果 点击右边的小图片,那么左边框中将 ...
- C# 使用js正则表达式,让文本框只能输入数字和字母,最大长度5位
使用js正则表达式,让文本框只能输入数字和字母,最大长度5位,只需要加个onkeyup事件,即可简单实现 <asp:TextBox ID="txtBegin" runat=& ...