• Supervisor行为分析和实践


    转载的这里 http://www.cnblogs.com/liuweiccy/p/4622075.html

    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)时都会用他的这些特性去构建应用。
  • 相关阅读:
    [记录点滴] 一个解决Lua 随机数生成问题的办法
    [源码解析] 从TimeoutException看Flink的心跳机制
    [记录点滴] OpenResty中Redis操作总结
    [记录点滴] 小心 Hadoop Speculative 调度策略
    [源码解析] GroupReduce,GroupCombine 和 Flink SQL group by
    第28 章 : 理解容器运行时接口 CRI
    第27 章 : Kubernetes 安全之访问控制
    第26 章 : 理解 CNI 和 CNI 插件
    第25 章 : Kubernetes 网络模型进阶
    第24 章 : Kubernetes API 编程利器:Operator 和 Operator Framework
  • 原文地址:https://www.cnblogs.com/ziyouchutuwenwu/p/4104174.html
Copyright © 2020-2023  润新知