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 子进程说明

        子进程说明书的模版:
 child_spec() = {Id,StartFunc,Restart,Shutdown,Type,Modules}
Id = term()
StartFunc = {M,F,A}
M = F = atom()
A = [term()]
Restart = permanent | transient | temporary
Shutdown = brutal_kill | int()>0 | infinity
Type = worker | supervisor
Modules = [Module] | dynamic
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实例

 -module(add_sup).

 -behaviour(supervisor).

 -export([start_link/0, start_child/0]).
-export([init/1]). -define(SERVER, ?MODULE). start_link() ->
supervisor:start_link({local, ?SERVER}, ?MODULE, []).
start_child() ->
supervisor:start_child(?MODULE, []). init([]) ->
RestartStrategy = simple_one_for_one,
MaxRestarts = 2,
MaxSecondsBetweenRestarts = 100, SupFlags = {RestartStrategy, MaxRestarts, MaxSecondsBetweenRestarts}, Restart = permanent,
Shutdown = 20000,
Type = worker, AChild = {add2, {add2, start_link, []},
Restart, Shutdown, Type, [add2]}, {ok, {SupFlags, [AChild]}}.

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

add2模块的启动函数(注意:因为simple_one_for_one要求加入的同类型的子进程,因此启动函数没有名字):
 start_link() ->
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 普通子进程添加到监控树

普通子进程代码:

 -module(common).
-author("Administrator"). -export([start_link/0, start_loop/2]). start_link() ->
Res = proc_lib:start_link(?MODULE, start_loop, [self(), ?MODULE]), %%启动一个普通进程
Res. start_loop(Parent,Name) ->
register(Name, self()), %%给普通进程命名,否则默认是pid()。
proc_lib:init_ack(Parent, {ok, self()}),
loop(). loop()->
receive
Args ->
io:format("args:~p~n",[Args])
end.
在上面创建一个普通进程的过程中可以用proc_lib:start_link,是同步的创建子进程。注意:不能用spawn创建一个在监控树下的进程,这会导致创建的进程在终止后,不能被监控树重启。
下面是子进程的规格:
 {common, {common, start_link, []}, permanent, 10000, worker, [common]}
创建的监控树:

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

3.3 重启动态添加的子进程

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

下面实例针对simple_one_for_one的监控树简单实现的方法一:
1.单独一个子进程创建ets标
 init([]) ->
{ok, ets:new(proc_ets,[duplicate_bag, public, named_table, {keypos,1}])}.

当前监控树

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

 -module(add_sup).
-include("ets_arg.hrl").
-behaviour(supervisor). -export([start_link/0, start_child/1]).
-export([init/1]). -define(SERVER, ?MODULE). -spec(start_link() ->
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
start_link() ->
{ok, Pid} = supervisor:start_link({local, ?SERVER}, ?MODULE, []),
case ets:lookup(?ETS, ?MODULE) of
Object -> load_dynamic_proc(Object) %% 动态的加入存储的子进程
end,
{ok, Pid}. start_child(_Type) ->
{ok, Pid} = supervisor:start_child(?MODULE, []),
case _Type of
restart -> ok;
_->ets:insert(?ETS,{?MODULE, ?SIMPLE, []}) %% 存储动态加入子进程的信息
end,
{ok, Pid}. init([]) ->
RestartStrategy = simple_one_for_one,
MaxRestarts = 2,
MaxSecondsBetweenRestarts = 100, SupFlags = {RestartStrategy, MaxRestarts, MaxSecondsBetweenRestarts}, Restart = permanent,
Shutdown = brutal_kill,
Type = worker, AChild = {add2, {add2, start_link, []},
Restart, Shutdown, Type, [add2]}, {ok, {SupFlags, [AChild]}}. load_dynamic_proc([])->
ok;
load_dynamic_proc([H|T]) ->
start_child(restart),
load_dynamic_proc(T),
{ok, H}.
当前监控树
动态加入的子进程的信息表
 
监控树add_sup重启后,重新动态加入的相同类型的子进程

4.总结

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

最新文章

  1. How to get Timer Job History
  2. Deep Learning 23:dropout理解_之读论文“Improving neural networks by preventing co-adaptation of feature detectors”
  3. zabbix_agent端 key
  4. php 实现qq第三方登录
  5. iOS8 推送注册方式改变的问题
  6. 迁移应用数据库到MySQL Database on Azure
  7. Picker组件封装
  8. 工作单元(Unit of Work)
  9. UVa 10652 (简单凸包) Board Wrapping
  10. 【转】Java编程之字符集问题研究
  11. Central Europe Regional Contest 2012 Problem J: Conservation
  12. NET基础课-- 类型基础(NET之美)
  13. java Timer 使用小结
  14. javascript 工作必知(四) 类型转换
  15. #include<> 和#include“”的区别
  16. 笔记:Spring Cloud Hystrix 服务容错保护
  17. .NET Core微服务之服务间的调用方式(REST and RPC)
  18. angular学习笔记(三)
  19. Vmware的虚拟机示例进入BIOS方法
  20. Thymeleaf+SpringMVC,如何从模板中获取数据(转)

热门文章

  1. Python十分钟学会
  2. POSIX、XNU
  3. .NET中使用OleDb读取Excel
  4. Android处理Bitmap的一些方法
  5. javascript中 的 + RegExp['\x241'] 怎么理解
  6. INDIGO STUDIO神器!快速创建WEB、移动应用的交互原型工具【转】
  7. C# 将字符串转化成流,将流转换成字符串
  8. Python得到前面12个月的数据,Python得到现在时间 前一年的数据,
  9. psycopg2.pool – Connections pooling / psycopg2.pool – 连接池 / postgresql 连接池
  10. WINCE设备开机灰屏问题(很怪异)