Erlang设计原则Behaviour

标准 Erlang/OTP 行为有

Behaviour 功能
gen_server 用于实现 C/S 结构中的服务端
gen_fsm 用于实现有限状态机
gen_event 用于实现事件处理功能
supervisor 用于实现监督树中的督程
gen_statem 新版本中的有限状态机实现

平时使用最多的是gen_serversupervisor

gen_server

erlang gen_server的使用:以银行账户服务为例

bank_account.erl如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
%%%-------------------------------------------------------------------
%%% @author Flowsnow
%%% @copyright (C) 2017, <COMPANY>
%%% @doc
%%% http://www.kongqingquan.com/archives/403
%%% @end
%%% Created : 13. 十一月 2017 17:19
%%%-------------------------------------------------------------------
-module(bank_account).
-author("Flowsnow").
%% 指定behaviour
-behaviour(gen_server).
%% 回调接口
-export([init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
code_change/3,
terminate/2
]).
%% API
-export([start_link/3,
withdraw/2,
deposit/2,
print/1,
stop/1]).
-record(account,{
id :: integer(), %% ID
name :: string(), %% 帐号名
money :: integer() %% 帐号余额
}).
-define(PRINT(Msg),io:format(Msg ++ "\n")).
-define(PRINT(Format,Msg),io:format(Format ++ "\n",Msg)).
init([Id, Name, Money]) ->
?PRINT("bank account init,ID:~w,Name:~p,Money:~w",[Id,Name,Money]),
State = #account{id = Id, name = Name, money = Money},
{ok,State}.
%% asynchronous operation
%% handle_cast接收gen_server:cast消息
%% 打印帐号信息
handle_cast(print, State = #account{id = Id, name = Name, money = Money}) ->
?PRINT("account info, id: ~w, name: ~p, money: ~w", [Id, Name, Money]),
{noreply, State};
%% 返回stop,进程将停止,调用terminate
handle_cast(stop, State) ->
?PRINT("handle cast stop"),
{stop, normal, State}.
%% synchronous operation
%% handle_call接收gen_server:call消息
%% 取款
handle_call({withdraw, Num}, _From, State = #account{name = Name,money = Money}) when Num > 0, Num =< Money ->
NewMoney = Money - Num,
NewState = State#account{money = NewMoney},
?PRINT("~p withdraw:~w,NewMoney:~w",[Name, Num, NewMoney]),
{reply, true, NewState};
%% 存款
handle_call({deposit, Num}, _From, State = #account{name = Name,money = Money}) ->
NewMoney = Money + Num,
NewState = State#account{money = NewMoney},
?PRINT("~p withdraw:~w,NewMoney:~w",[Name, Num, NewMoney]),
{reply, true, NewState}.
%% handle_info,处理直接发给进程的消息
handle_info(Info, State) ->
?PRINT("handle_info receive msg:~p",[Info]),
{noreply,State}.
code_change(_OldVsn, State, _Extra) ->
{ok,State}.
%% 进程停止时,回调terminate
terminate(Reason, #account{id = ID,name = Name,money = Money}) ->
?PRINT("process stop,Reason:~p",[Reason]),
?PRINT("account Info,ID:~w,Name:~p,Money:~w",[ID,Name,Money]),
ok.
%% 开启帐号进程,将回调init/1函数,返回{ok,Pid}
start_link(Id, Name, Money) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [Id, Name, Money], []).
withdraw(Pid,Num) ->
gen_server:call(Pid,{withdraw,Num}).
deposit(Pid,Num) ->
gen_server:call(Pid,{deposit,Num}).
print(Pid) ->
gen_server:cast(Pid,print).
stop(Pid) ->
gen_server:cast(Pid,stop).

supervisor

SupFlags参数{Type, Times, Sec}

  • Type: 重启策略
    • one_for_one: 一个子进程终止,只重启该进程,在init的时候会启动参数内的子进程
    • simple_one_for_one: 同one_for_one,但是在init的时候不会启动子进程,需要动态调用启动
    • one_for_all: 一个子进程终止,将重启所有子进程
    • rest_for_one: 一个子进程终止,将按顺序重启这个子进程和之后顺序的子进程
  • Times: 次数(监控频率)
  • Sec: 秒数(监控频率),如果在Sec秒内重启次数超过Times,则终止所有进程,并终止监控树,将由父进程决定它的命运

ChildSpec参数{Id, StartFunc, Restart, Shutdown, Type, Modules}

  • Id 子进程ID标识符
  • StartFunc = {M, F, A}: 子程序启动入口
  • Restart: 重启方案
    • permanent: 如果app终止了,整个系统都会停止工作(application:stop/1除外)。
    • transient: 如果app以normal的原因终止,没有影响。任何其它终止原因都谁导致整个系统关闭。
    • temporary: app可以以任何原因终止。只产生报告,没有其它任何影响。
  • Shutdown: 终止策略
    • brutal_kill: 无条件终止
    • 超时值(毫秒): 终止时,如果超时,则强制终止
    • infinity: 如果子进程是监控树,设置为无限大,等待其终止为止
  • Type:
    • worker: 普通子进程
    • supervisor: 子进程是监控树
  • Modules:
    • dynamic: 当子进程是gen_event
    • [Module]: 当子进程是监控树、gen_server或者gen_fsm,表示回调模块名称

使用宏定义解决重复的ChildSpec指定:

1
-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}).

cdn控制服务器的sup模块示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
-module(control_server_sup).
-behaviour(supervisor).
-include("ctrl_internal.hrl").
%% API
-export([start_link/0, start_child/0]).
%% Supervisor callbacks
-export([init/1]).
%% Helper macro for declaring children of supervisor
-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}).
%% ===================================================================
%% API functions
%% ===================================================================
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
start_child() ->
supervisor:start_child(?MODULE, ?CHILD(ctrl_concurrency_mgr, worker)),
supervisor:start_child(?MODULE, ?CHILD(ctrl_hls_writer_mgr, worker)),
supervisor:start_child(?MODULE, ?CHILD(ctrl_record_writer_mgr, worker)),
supervisor:start_child(?MODULE, ?CHILD(ctrl_live_mgr, worker)),
supervisor:start_child(?MODULE, ?CHILD(ctrl_live_analyser_mgr, worker)).
%% ===================================================================
%% Supervisor callbacks
%% ===================================================================
init([]) ->
timer:apply_after(1000, control_server_sup, start_child, []),
{ok, ErlOdbcConnectStr} = application:get_env(erl_odbc_connect_str),
{ok, _} = qk_odbc_pool:start_odbc_pool(?ERL_ODBC_POOL_ID, ErlOdbcConnectStr, 1),
Children = [
?CHILD(ctrl_cluster, worker),
?CHILD(ctrl_plan, worker),
?CHILD(ctrl_amqp_agent, worker)
],
{ok, {{one_for_one, 10, 10}, Children}}.

start_child动态添加子进程和start_link添加的监控树的区别在于:监控树退出并重启后,动态添加的子进程会丢失。


参考:

  1. OTP Design Principles: Gen_Server Behaviour
  2. Erlang-Supervisor Behaviour
  3. OTP Design Principles: Supervisor Behaviour
  4. erlang supervisor(监控树)的重启策略
  5. OTP设计原则——第三部分
捐赠:喜欢就请我喝一杯