Supervisor行为分析和实践

1.简介

Erlang要编写高容错性、稳定性的系统,supervisor就是用来解决这一问题的核心思想。通过建立一颗监控树,来组织进程之间的关系,通过确定重启策略、子进程说明书等参数信息来确定佣程与督程的行为,以及在发生故障时的处理办法。简单介绍supervisor的API:

     start_link(Module, Args) -> startlink_ret()

     start_link(SupName, Module, Args) -> startlink_ret()

用来启动一颗监控树,它会调用Module:init(Args)来获取监控树的配置信息,SupName代表监控树的名字,默认就是pid()。

start_child(SupRef, ChildSpec) -> startchild_ret()

用来向监控树SupRef动态的添加子进程,ChildSpec为子进程的规格说明。

 terminate_child(SupRef, Id) -> Result

用来终止一个正在运行的子进程。注意:如果这个督程是simple_one_for_one类型的 id = pid(),如果是其他类型 id 就是规范说明书的Id。

delete_child(SupRef, Id) -> Result

用来删除一个已经停止的子进程,但不包括临时(temporary)子进程,临时子进程一旦停止就立即删除。对督程是simple_one_for_one无效,因为当终止子进程时,子进程相关的信息就被删除了,而不是修改状态,详情supervisor源码。

restart_child(SupRef, Id) -> Result

用来重启一个停止的并且可以重启的子进程。临时(temporary)子进程就没有意义。对督程是simple_one_for_one无效。

which_children(SupRef) -> [{Id, Child, Type, Modules}]

列出监控数的所有子进程。注意:当监控树有巨大数量的子进程时,调用该方法容易造成内存溢出。

count_children(SupRef) -> PropListOfCounts

统计子进程的数量。返回列表:[{specs, int()}, {active, int()}, {supervisors, int()}, {workers ,int()}]

  check_childspecs(ChildSpecs) -> Result

检验某种规格的子进程是否存在。

2.分析

要怎样构造一颗监控树,佣程与督程各自有什么特征,存在什么联系,init()中子进程参数说明,以及监控树所采用的重启策略,重启频率说明了这一切。

2.1 重启策略

 one_for_one:一个子进程停止只重启该子进程

one_for_all:一个子进程停止重新重启所有子进程
  rest_for_one:针对一个子进程列表,一个子进程停止,停止列表中该子进程及后面的子进程,并依次重启这些子进程

simple_one_for_one:其重启策略同one_for_one,但是必须是同类型的子进程,必须动态加入。

2.2 最大重启频率

最大重启频率(maximum restart frequency),是指针对最大重启次数:MaxR,最大重启时间:MaxT,即在MaxT时间内,最多能重启MaxR次,若超过这个频率,整个监控树将终止。

2.3 子进程说明

子进程说明书的模版:

 1 child_spec() = {Id,StartFunc,Restart,Shutdown,Type,Modules}
 2 Id = term()
 3 StartFunc = {M,F,A}
 4 M = F = atom()
 5 A = [term()]
 6 Restart = permanent | transient | temporary
 7 Shutdown = brutal_kill | int()>0 | infinity
 8 Type = worker | supervisor
 9 Modules = [Module] | dynamic
10 Module = atom()

Id:子进程的唯一标识符,当supervisor为非simple_one_for_one类型时,在terminate_child/2,restart_child/2,delete_child/2中Id参数。

StartFunc:子进程的启动函数。

Restart:重启类型

  permanent:子进程总是被重启

  transient:子进程在正常退出的情况下可以被重启

  temporary:子进程在任何情况下,都不被重启,该重启类型的子进程在终止后,就会立即删除不能够再重启,因此restart_child/2,delete_child/2对该类型的子进程无效

    Shutdown:关闭时间

      brutal_kill:将会立即无条件的终止子进程,通过exit(pid(), kill).

  int()>0:在时限范围内将会通过exit(pid(),shutdown)正常关闭子进程,等待信息返回;若超出时限范围消息未返回消息将会通过exit(pid(),kill)立即终止子进程

  infinity:当子进程为另一颗监控树时,会给与子监控树足够的时间来关闭;也可以给工作进程设置该参数,但是需要注意,该监控树的终止取决于该子进程,并且他的清理结果必须始终返回。(未验证)

Type:子进程的类型 worker | supervisor

Modules : 回调模块

如果子进程是supervisor、gen_server、gen_fsm则Modules是回调模块name的列表,如果为gen_event则为dynamic。(未验证)

3.实例

3.1 simple_one_for_one实例

 1 -module(add_sup).
 2
 3 -behaviour(supervisor).
 4
 5 -export([start_link/0, start_child/0]).
 6 -export([init/1]).
 7
 8 -define(SERVER, ?MODULE).
 9
10 start_link() ->
11 supervisor:start_link({local, ?SERVER}, ?MODULE, []).
12 start_child() ->
13 supervisor:start_child(?MODULE, []).
14
15 init([]) ->
16 RestartStrategy = simple_one_for_one,
17 MaxRestarts = 2,
18 MaxSecondsBetweenRestarts = 100,
19
20 SupFlags = {RestartStrategy, MaxRestarts, MaxSecondsBetweenRestarts},
21
22 Restart = permanent,
23 Shutdown = 20000,
24 Type = worker,
25
26 AChild = {add2, {add2, start_link, []},
27 Restart, Shutdown, Type, [add2]},
28
29 {ok, {SupFlags, [AChild]}}.

通过调用start_child/0来动态的加入子进程。

add2模块的启动函数(注意:因为simple_one_for_one要求加入的同类型的子进程,因此启动函数没有名字):

1 start_link() ->
2      gen_server:start_link(?MODULE, [], []).

add_sup就是simple_one_for_one的监控树,其动态加入的子进程都没有名字:

通过terminate_child/2可以终止add_sup的子进程,终止后的子进程将被删除,不能重启,因此delete_child/2、restart_child/2对simple_one_for_one类型的监控树的子进程无效。

3.2 普通子进程添加到监控树

普通子进程代码:

 1 -module(common).
 2 -author("Administrator").
 3
 4 -export([start_link/0, start_loop/2]).
 5
 6 start_link() ->
 7 Res = proc_lib:start_link(?MODULE, start_loop, [self(), ?MODULE]), %%启动一个普通进程
 8 Res.
 9
10 start_loop(Parent,Name) ->
11 register(Name, self()), %%给普通进程命名,否则默认是pid()。
12 proc_lib:init_ack(Parent, {ok, self()}),
13 loop().
14
15 loop()->
16 receive
17 Args ->
18 io:format("args:~p~n",[Args])
19 end.

在上面创建一个普通进程的过程中可以用proc_lib:start_link,是同步的创建子进程。注意:不能用spawn创建一个在监控树下的进程,这会导致创建的进程在终止后,不能被监控树重启。

下面是子进程的规格:

1 {common, {common, start_link, []}, permanent, 10000, worker, [common]}

创建的监控树:

普通子进程的终止与重启:

3.3 重启动态添加的子进程

对于监控树A的一个子进程是另一颗监控树B,监控树B有多个动态加入的子进程,若B重启后那么动态加入的子进程将不复存在,若B重启后需要想重新加入这些子进程,一、记录动态加入子进程的信息,当监控树重启后再动态加入以前的子进程;二、改进supervisor的实现,可以重启动态加入的子进程(未实践,rabbitmq中对supervisor修改来实现这个功能)。

下面实例针对simple_one_for_one的监控树简单实现的方法一:

1.单独一个子进程创建ets标

1 init([]) ->
2 {ok, ets:new(proc_ets,[duplicate_bag, public, named_table, {keypos,1}])}.

当前监控树

2.动态加入子进程,并记录信息;重启子监控树,并动态加入子进程

 1 -module(add_sup).
 2 -include("ets_arg.hrl").
 3 -behaviour(supervisor).
 4
 5 -export([start_link/0, start_child/1]).
 6 -export([init/1]).
 7
 8 -define(SERVER, ?MODULE).
 9
10 -spec(start_link() ->
11 {ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
12 start_link() ->
13 {ok, Pid} = supervisor:start_link({local, ?SERVER}, ?MODULE, []),
14 case ets:lookup(?ETS, ?MODULE) of
15 Object -> load_dynamic_proc(Object)            %% 动态的加入存储的子进程
16 end,
17 {ok, Pid}.
18
19 start_child(_Type) ->
20 {ok, Pid} = supervisor:start_child(?MODULE, []),
21 case _Type of
22 restart -> ok;
23 _->ets:insert(?ETS,{?MODULE, ?SIMPLE, []}) %% 存储动态加入子进程的信息
24 end,
25 {ok, Pid}.
26
27 init([]) ->
28 RestartStrategy = simple_one_for_one,
29 MaxRestarts = 2,
30 MaxSecondsBetweenRestarts = 100,
31
32 SupFlags = {RestartStrategy, MaxRestarts, MaxSecondsBetweenRestarts},
33
34 Restart = permanent,
35 Shutdown = brutal_kill,
36 Type = worker,
37
38 AChild = {add2, {add2, start_link, []},
39 Restart, Shutdown, Type, [add2]},
40
41 {ok, {SupFlags, [AChild]}}.
42
43 load_dynamic_proc([])->
44 ok;
45 load_dynamic_proc([H|T]) ->
46 start_child(restart),
47 load_dynamic_proc(T),
48 {ok, H}.

当前监控树

动态加入的子进程的信息表

监控树add_sup重启后,重新动态加入的相同类型的子进程

4.总结

supervisor是erlang四个行为模式之一,但是实质上gen_server实现提供了在业务上的支持。supervisor为编写可容错,高稳定性提供了支持,构建的监控树体系功能强大、易于理解,结构多样。但是当顶层监控进程崩溃,整个系统将崩溃。不能重启子进程为监控进程动态加入的子进程。在构建任何应用(Application)时都会用他的这些特性去构建应用。

博客地址:http://www.cnblogs.com/liuweiccy/p/4622075.html

源码地址:https://github.com/liuweiccy/test_sup

优秀的代码是艺术品,它需要精雕细琢!

时间: 2024-11-08 14:52:47

Supervisor行为分析和实践的相关文章

Log4j2分析与实践

当前网络上关于Log4j2的中文文章比较零散,这里整理了一下关于Log4j2比较全面的一些文章,供广大技术人员参考 Log4j2分析与实践-认识Log4j2 Log4j2分析与实践-架构 Log4j2分析与实践-配置示例 Log4j2分析与实践-配置详解 Log4j2分析与实践-Lookups Log4j2分析与实践-Appenders Log4j2分析与实践-Layouts Log4j2分析与实践-Filters Log4j2分析与实践-异步Logger Log4j2分析与实践-无垃圾地写日志

Gen_server行为分析与实践

1.简介 Gen_server实现了通用服务器client_server原理,几个不同的客户端去分享服务端管理的资源(如图),gen_server提供标准的接口函数和包含追踪功能以及错误报告来实现通用的服务器,同时可以作为OTP监控树的一部分. Gen_server函数与回调函数之间的关系: 1 gen_server module Callback module 2 ----------------- --------------- 3 gen_server:start_link ----->

[测试技术思考] 软件可测性分析和实践

软件测试中可测性一般是指对系统的可控性.可观测性进行的评估,借以反映系统设计.实现对测试的友好程度和相应的测试成本.可测性在测试阶段会对系统的测试成本及关联产品代码的Patch次数产生重大影响.如何提高可测性成为软件生命周期特别是前期(设计阶段.coding阶段)重要的一环. 本文带领大家探索在实际项目中可测性相关的实战经验和对应的改进措施. 1 提高可测性的切入点 可测性的评估和改进最早开始于两个阶段: a. 新项目的设计阶段: b. 已有项目新功能.新策略的提测阶段. 这些是提高团队设计的系

基于redis的分布式锁的分析与实践

转:https://my.oschina.net/wnjustdoit/blog/1606215 前言:在分布式环境中,我们经常使用锁来进行并发控制,锁可分为乐观锁和悲观锁,基于数据库版本戳的实现是乐观锁,基于redis或zookeeper的实现可认为是悲观锁了.乐观锁和悲观锁最根本的区别在于线程之间是否相互阻塞. 那么,本文主要来讨论基于redis的分布式锁算法问题. 从2.6.12版本开始,redis为SET命令增加了一系列选项(SET key value [EX seconds] [PX

自定义View系列教程04--Draw源码分析及其实践

通过之前的详细分析,我们知道:在measure中测量了View的大小,在layout阶段确定了View的位置. 完成这两步之后就进入到了我们相对熟悉的draw阶段,在该阶段真正地开始对视图进行绘制. 按照之前的惯例,我们来瞅瞅View中draw( )的源码 public void draw(Canvas canvas) { final int privateFlags = mPrivateFlags; final boolean dirtyOpaque = (privateFlags & PFL

Gen_event行为分析和实践

1.简介 Gen_event实现了通用事件处理,通过其提供的标准接口方法以及回调函数,在OTP里面的事件处理模块是由一块通用的事件管理器和任意数量的事件处理器,并且这些事件处理器可以动态的添加和删除.一个事件可以用来记录error,alarm,info, warning等信息.一个事件管理器可以安装0,1,N个事件处理器,当一个事件管理器接受到一个事件的通知时,这个事件将会被所有的已安装的事件处理器处理(如图). 事件管理器实质上是{Module, State}组成的列表,每个Module是一个

手动SQL注入原理分析与实践

代码仓库 本文所用代码的代码库地址: 点击这里前往Github仓库 了解SQL注入 定义 SQL注入攻击(SQL Injection),简称注入攻击,是Web开发中最常见的一种安全漏洞.可以用它来从数据库获取敏感信息,或者利用数据库的特性执行添加用户,导出文件等一系列恶意操作,甚至有可能获取数据库乃至系统用户最高权限. 原理 造成SQL注入的原因是因为程序没有有效过滤用户的输入,使攻击者成功的向服务器提交恶意的SQL查询代码,程序在接收后错误的将攻击者的输入作为查询语句的一部分执行,导致原始的查

Gearman介绍、原理分析、实践改进

gearman是什么? 它是分布式的程序调用框架,可完成跨语言的相互调用,适合在后台运行工作任务.最初是2005年perl版本,2008年发布C/C++版本.目前大部分源码都是(Gearmand服务job Server)C++,各个API实现有各种语言的版本.PHP的Client API与Worker API实现为C扩展,在PHP官方网站有此扩展的中英文文档. gearman架构中的三个角色 client:请求的发起者,工作任务的需求方(可以是C.PHP.Java.Perl.Mysql udf等

分享一次失败的项目实践经验

一.开篇 最近在网上看到了一款canvas实现网页涂鸦效果的作品,感觉这个效果比较奇特而且在以前没有学习canvas这样的功能是不可思议的,所以本人秉着程序员的那一份执着,花了两三个小时的时间来研究了一下canvas涂鸦作品的代码,发现里面代码比较精辟,但是美中不足的是有些代码的结构会比较的混乱,让人感觉层次上面有点不太分明.所以本人就打算对这个代码结构进行重构使其更具有可读性.但是理想是丰满的现实是骨感的,经过这一次代码的分析和实践下来,对我也是打击蛮大的,以前从来都是认为前端只要能够熟悉调试