Erlang/OTP 监督者(Supervisor)的作用是负责其子进程的启动,停止和监视。监督者的基本思路是,保持其子进程能正常运行,并在必要时重新启动子进程。
一、Erlang/OTP Supervisor 基本参数
Erlang/OTP Supervisor的基本参数有进程策略,进程id,进程启动函数,进程重启,进程关闭,进程类型,进程模块。
-module(test_sup). -behaviour(supervisor). -export([start_link/1, init/1]). start_link(_) -> io:format("test sup start link~n"), supervisor:start_link({local, ?MODULE}, ?MODULE, []). init([]) -> {ok, {{one_for_one, 1, 60}, % Strategy = {How, Max, Within} [{ test_handler_worker, % Id = internal id {test_server, start, []}, % StartFun = {M, F, A} permanent, % Restart = permanent | transient | temporary 2000, % Shutdown = brutal_kill | int() >= 0 | infinity worker, % Type = worker | supervisor [test_server] % Modules = [Module] | dynamic }] } }.
1、进程策略
Strategy = {How, Max, Within}
意思是在多长时间内(Within)最多允许重启了几次(Max),如何重启(HOW)。Within 和 Max 的设定意义在于限制最大重启频率,这是为了避免反复重启进入死循环。Within 以秒为单位。Max为0表示不重启。
one_for_one | 如果子进程终止,只有这个进程会被重启 |
one_for_all | 如果子进程终止,所有子进程都会被重启 |
rest_for_one | 如果子进程终止,在这个进程重启后,其他所有子进程会被重启 |
simple_one_for_one | 简化one_for_one,所有子进程都动态添加同一种进程的实例 |
one_for_one 用一个列表储存了所有要启动的子进程,并按顺序启动。而simple_one_for_one 用一个进程字典定义所有子进程。所以当一个监督者拥有很多子进程时,遇到进程崩溃,simple_one_for_one 效率会快很多。
2、进程id
Id = internal id
用于监督者区分子进程,必须保证唯一
3、进程启动函数
StartFun = {M, F, A}
用于监督者启动子进程的函数。M代表模块,F代表函数,A代表参数
4、进程重启
Restart = permanent | transient | tempora
表示进程遇到错误之后是否重启
permanent | 遇到任何错误导致进程终止就重启 |
temporary | 进程永远都不重启 |
transient | 只有进程异常终止的时候会被重启 |
Shutdown = brutal_kill | int() >= 0 | infinity
表示采用什么手段来关闭进程
brutal_kill | 立刻强制关闭进程 |
int() >= 0 | 等待多少毫秒后强制关闭进程 |
infinity | 当子进程也是监督者时使用,意思是给足够时间让子进程重启 |
Type = worker | supervisor
告诉监督者子进程是哪种类型的进程,工作进程,还是监督进程?
7、进程模块
Modules = [Module] | dynamic
表示进程运行依赖哪些模块,仅在代码热更新时使用。使用dynamic的情况是当使用了 Erlang/OTP 发布(Release)等功能,使得Erlang/OTP 可以判断在热更新时需要哪些模块
二、Erlang/OTP Supervisor 工作流程
下面以 Erlang/OTP Application 的例子为基础,继续讨论Supervisor 的工作流程。所以这个 Application 就包含了5个结构:应用模块、监督者模块、资源文件、服务端模块、处理模块。
监督者模块(test_sup.erl):
-module(test_sup). -behaviour(supervisor). -export([start_link/1,init/1]). start_link(_) -> io:format("test sup start link~n"), supervisor:start_link({local, ?MODULE}, ?MODULE, []). init([]) -> io:format("test sup init~n"), {ok,{ {one_for_all, 1, 60}, [{ test_handler_worker, { test_server, start, [test_handler] }, permanent, 2000, worker, [test_server,test_handler] }]} }.服务端模块(test_server.erl):
-module(test_server). -behaviour(gen_server). -export([start/1, init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). start(Module) -> io:format("test server start ~p~n", [Module]), gen_server:start_link({local, Module}, ?MODULE, [], []). init([]) -> io:format("test server init~n"), {ok, []}. handle_call({test_handler, test, Name}, _From, State) -> io:format("test server test ~p~n", [Name]), {reply, ok, State}; handle_call(_Request, _From, State) -> io:format("test server call nothing~n"), Reply = ok, {reply, Reply, State}. handle_cast(_Msg, State) -> {noreply, State}. handle_info(_Info, State) -> {noreply, State}. terminate(_Reason, _State) -> ok. code_change(_OldVsn, State, _Extra) -> {ok, State}.处理模块(test_handler.erl):
-module(test_handler). -export([test/1, stop/0]). -define(Server, ?MODULE). test(Name) -> io:format("test handler test~n"), gen_server:call(?Server, {?MODULE, test, Name}). stop() -> io:format("test handler stop~n"), exit(whereis(?Server), kill), io:format("test handler is killed~n").
资源文件(test.app) :
{application,test, [{description,"Test application"}, {vsn,"1.0.0"}, {modules,[test_app,test_handler,test_server,test_sup]}, {registered,[test_app]}, {mod,{test_app,[]}}, {env,[]}, {applications,[kernel,stdlib]}]}.
erl编译这个Application后,执行命令启动:
1> application:start(test). test app start test sup start link test sup init test server start test_handler test server init ok %% 调用处理模块函数: 2> test_handler:test(helloworld). test handler test test server test helloworld ok 3> test_handler:stop(). test handler stop test handler is killed test server start test_handler ok test server init %% 如果在60秒内重复执行以下命令,进程将被彻底关闭: 4> test_handler:stop(). test handler stop test handler is killed ok =INFO REPORT==== 14-Oct-2013::19:25:49 === application: test exited: shutdown type: temporary
由此可见,到了监督者模块(test_sup),监督者模块启动了一个监督者进程,作为监督树的一部分。监督者进程会回调监督者模块的 init/1 函数,根据这个函数的返回值来启动子进程。
init([]) -> io:format("test sup init~n"), {ok,{ {one_for_all, 1, 60}, [{ test_handler_worker, { test_server, start, [test_handler] }, permanent, 2000, worker, [test_server,test_handler] }]} }.
以上参数告诉监督者调用 test_server:start(test_handler) 来启动工作进程,采用 one_for_all 的重启策略,如果60秒内重启超过1次就不再重启子进程,否则就会守护进程的正常运行。