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)时都会用他的这些特性去构建应用。
 
 
优秀的代码是艺术品,它需要精雕细琢!

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

  1. Log4j2分析与实践

    当前网络上关于Log4j2的中文文章比较零散,这里整理了一下关于Log4j2比较全面的一些文章,供广大技术人员参考 Log4j2分析与实践-认识Log4j2 Log4j2分析与实践-架构 Log4j2 ...

  2. 苏宁基于Spark Streaming的实时日志分析系统实践 Spark Streaming 在数据平台日志解析功能的应用

    https://mp.weixin.qq.com/s/KPTM02-ICt72_7ZdRZIHBA 苏宁基于Spark Streaming的实时日志分析系统实践 原创: AI+落地实践 AI前线 20 ...

  3. 《Linux内核分析》实践4

    <Linux内核分析> 实践四--ELF文件格式分析 20135211李行之 一.概述 1.ELF全称Executable and Linkable Format,可执行连接格式,ELF格 ...

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

    深入探讨Android异步精髓Handler 站在源码的肩膀上全解Scroller工作机制 Android多分辨率适配框架(1)- 核心基础 Android多分辨率适配框架(2)- 原理剖析 Andr ...

  5. Gen_server行为分析与实践

    1.简介 Gen_server实现了通用服务器client_server原理,几个不同的客户端去分享服务端管理的资源(如图),gen_server提供标准的接口函数和包含追踪功能以及错误报告来实现通用 ...

  6. AWVS结果分析与实践-XSS

      今天趁着老师接项目,做了一丢丢实践,以下是一点点感触.     都知道AWVS是神器,可是到我手里就是不灵.拿了它扫了一个URL,结果提示XSS漏洞,实践没反应,只好愉快地享受了过程.来看看.   ...

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

    ​ 前言:在分布式环境中,我们经常使用锁来进行并发控制,锁可分为乐观锁和悲观锁,基于数据库版本戳的实现是乐观锁,基于redis或zookeeper的实现可认为是悲观锁了.乐观锁和悲观锁最根本的区别在于 ...

  8. FastText 分析与实践

    一. 前言 自然语言处理(NLP)是机器学习,人工智能中的一个重要领域.文本表达是 NLP中的基础技术,文本分类则是 NLP 的重要应用.在 2016 年, Facebook Research 开源了 ...

  9. 面经手册 · 第17篇《码农会锁,ReentrantLock之AQS原理分析和实践使用》

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 如果你相信你做什么都能成,你会自信的多! 千万不要总自我否定,尤其是职场的打工人.如 ...

随机推荐

  1. GetWindowText和GetDlgItemText的区别

    二者使用方法相同,入口点不一样. 举例: CString str;  /* if (GetDlgItem(IDC_Number1)->GetWindowText(str),str==" ...

  2. #-webkit-autofill##google#启用表单自动填充时,如何覆盖黄色背景

    google和opera浏览器的表单自动填充后,输入框均会变成黄色背景,黑色字体.如下图. 这样的话会与网页的整体设计风格不一致,怎样自定义样式,来覆盖黄色背景. 首先来看看是什么导致的,右键查看元素 ...

  3. Android APP安全评估工具 Drozer - 安装介绍

    一.Drozedr安装 注意事项:安装需要 JavaRuntime Environment (JRE) or Java Development Kit (JDK)环境, 没有安装的请先安装java环境 ...

  4. lua简化cocos2dx的Action动画序列

    情景 今天写代码时,又要写一个很常见的动画,就是变大变小模拟那个弹性的赶脚,很常用但写起来挺麻烦,封装一下用起来就简单多了. 当然我也知道有缓动动画(EaseAction)可以实现反弹效果,但这不是重 ...

  5. 京东商城发现了一枚Bug

    我在京东上买了几本书,发现了一个BUG.. 买书的时候,我选了京东自营的书和京东其他店的书,合在一起购买,填写了开具发票. 然后,京东处理流程是,将上面一笔订单拆分成两笔,然后发票信息没有转到其他店那 ...

  6. SPA与DPA 攻击【转】

    转自:http://blog.sina.com.cn/s/blog_6cb58dbf0102v7ym.html SPA SPA是一种直接解释能量消耗测定值的技术.系统消耗能量的大小随微处理器执行的指令 ...

  7. powerdesigner 15 如何导出sql schema

    PowerDesigner导出所有SQL脚本 操作:Database=>Generate Database PowerDesigner怎么导出建表sql脚本 1 按照数据库类型,切换数据库. D ...

  8. Solr的一些查询参数

    fl: 是逗号分隔的列表,用来指定文档结果中应返回的 Field 集.默认为 “*”,指所有的字段. defType: 指定query parser,常用defType=lucene, defType ...

  9. 对Java不能多继承,只能单继承,却可以实现多个接口的理解

    1.java与C++的不同点在于多继承. Java:不能多继承,只能单继承,但可以实现多个接口 C++:可以实现多继承.例如: class A extends B implements C,D,E { ...

  10. 自定义View(7)draw与onDraw区别

    draw()这个函数本身会做很多事情,可参看源码.         *      1. Draw the background         *      2. If necessary, save ...