Erlang 应用开发:从告警管理到系统启动
1. 告警管理
1.1 错误日志处理
在处理错误日志时,无需手动删除错误报告,因为轮换机制会自动删除旧的错误日志。若想保留所有错误日志,需定期轮询错误日志并提取所需信息。
1.2 告警处理代码
以下是使用 OTP 告警处理程序的代码示例:
-module(my_alarm_handler).
-behaviour(gen_event).
%% gen_event callbacks
-export([init/1, handle_event/2, handle_call/2,
handle_info/2, terminate/2]).
%% init(Args) must return {ok, State}
init(Args) ->
io:format("*** my_alarm_handler init:~p~n",[Args]),
{ok, 0}.
handle_event({set_alarm, tooHot}, N) ->
error_logger:error_msg("*** Tell the Engineer to turn on the fan~n"),
{ok, N+1};
handle_event({clear_alarm, tooHot}, N) ->
error_logger:error_msg("*** Danger over. Turn off the fan~n"),
{ok, N};
handle_event(Event, N) ->
io:format("*** unmatched event:~p~n",[Event]),
{ok, N}.
handle_call(_Request, N) -> Reply = N, {ok, N, N}.
handle_info(_Info, N) -> {ok, N}.
terminate(_Reason, _N) -> ok.
1.3 操作步骤
以下是启动系统、生成告警、安装告警处理程序等操作的步骤:
1. 启动 Erlang 并加载 SASL:
$ erl -boot start_sasl -config elog3
- 设置告警:
1> alarm_handler:set_alarm(tooHot).
- 安装自定义告警处理程序:
2> gen_event:swap_handler(alarm_handler,
{alarm_handler, swap},
{my_alarm_handler, xyz}).
- 再次设置告警:
3> alarm_handler:set_alarm(tooHot).
- 清除告警:
4> alarm_handler:clear_alarm(tooHot).
1.4 读取日志
使用
rb
模块读取错误日志:
1> rb:start([{max,20}]).
2> rb:list().
3> rb:show(1).
4> rb:show(2).
2. 应用服务器
2.1 质数服务器
以下是质数服务器的代码:
-module(prime_server).
-behaviour(gen_server).
-export([new_prime/1, start_link/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
new_prime(N) ->
%% 20000 is a timeout (ms)
gen_server:call(?MODULE, {prime, N}, 20000).
init([]) ->
%% Note we must set trap_exit = true if we
%% want terminate/2 to be called when the application
%% is stopped
process_flag(trap_exit, true),
io:format("~p starting~n",[?MODULE]),
{ok, 0}.
handle_call({prime, K}, _From, N) ->
{reply, make_new_prime(K), N+1}.
handle_cast(_Msg, N) -> {noreply, N}.
handle_info(_Info, N) -> {noreply, N}.
terminate(_Reason, _N) ->
io:format("~p stopping~n",[?MODULE]),
ok.
code_change(_OldVsn, N, _Extra) -> {ok, N}.
make_new_prime(K) ->
if
K > 100 ->
alarm_handler:set_alarm(tooHot),
N = lib_primes:make_prime(K),
alarm_handler:clear_alarm(tooHot),
N;
true ->
lib_primes:make_prime(K)
end.
2.2 面积服务器
以下是面积服务器的代码,其中包含一个故意设置的错误:
-module(area_server).
-behaviour(gen_server).
-export([area/1, start_link/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
area(Thing) ->
gen_server:call(?MODULE, {area, Thing}).
init([]) ->
%% Note we must set trap_exit = true if we
%% want terminate/2 to be called when the application
%% is stopped
process_flag(trap_exit, true),
io:format("~p starting~n",[?MODULE]),
{ok, 0}.
handle_call({area, Thing}, _From, N) -> {reply, compute_area(Thing), N+1}.
handle_cast(_Msg, N) -> {noreply, N}.
handle_info(_Info, N) -> {noreply, N}.
terminate(_Reason, _N) ->
io:format("~p stopping~n",[?MODULE]),
ok.
code_change(_OldVsn, N, _Extra) -> {ok, N}.
compute_area({square, X}) -> X*X;
compute_area({rectonge, X, Y}) -> X*Y.
3. 监督树
3.1 监督树类型
监督树有两种类型:
-
一对一监督树
:如果一个工作进程崩溃,它将由监督者重新启动。
-
一对多监督树
:如果任何一个工作进程死亡,所有工作进程将被终止,然后重新启动。
3.2 监督者代码
以下是
sellaprime
监督者的代码:
-module(sellaprime_supervisor).
-behaviour(supervisor).
% see erl -man supervisor
-export([start/0, start_in_shell_for_testing/0, start_link/1, init/1]).
start() ->
spawn(fun() ->
supervisor:start_link({local,?MODULE}, ?MODULE, _Arg = [])
end).
start_in_shell_for_testing() ->
{ok, Pid} = supervisor:start_link({local,?MODULE}, ?MODULE, _Arg = []),
unlink(Pid).
start_link(Args) ->
supervisor:start_link({local,?MODULE}, ?MODULE, Args).
init([]) ->
%% Install my personal error handler
gen_event:swap_handler(alarm_handler,
{alarm_handler, swap},
{my_alarm_handler, xyz}),
{ok, {{one_for_one, 3, 10},
[{tag1,
{area_server, start_link, []},
permanent,
10000,
worker,
[area_server]},
{tag2,
{prime_server, start_link, []},
permanent,
10000,
worker,
[prime_server]}
]}}.
3.3 工作进程规范
工作进程规范是一个元组,其格式如下:
{Tag, {Mod, Func, ArgList},
Restart,
Shutdown,
Type,
[Mod1]}
各参数含义如下:
| 参数 | 含义 |
| ---- | ---- |
| Tag | 用于引用工作进程的原子标签 |
| {Mod, Func, ArgList} | 监督者用于启动工作进程的函数 |
| Restart | 重启策略,可选值为
permanent
、
transient
或
temporary
|
| Shutdown | 关闭时间,即工作进程终止的最大时间 |
| Type | 被监督进程的类型,可选值为
worker
或
supervisor
|
| [Mod1] | 子进程的回调模块名称 |
3.4 监督树流程图
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
A[sellaprime_supervisor]:::process --> B[area_server]:::process
A --> C[prime_server]:::process
4. 启动系统
4.1 启动步骤
以下是启动系统、进行查询等操作的步骤:
1. 启动 Erlang 并加载 SASL:
$ erl -boot start_sasl -config elog3
- 启动监督者:
1> sellaprime_supervisor:start_in_shell_for_testing().
- 进行有效查询:
2> area_server:area({square,10}).
- 进行无效查询:
3> area_server:area({rectangle,10,20}).
- 再次进行有效查询:
4> area_server:area({square,25}).
- 生成小质数:
5> prime_server:new_prime(20).
- 生成大质数:
6> prime_server:new_prime(120).
4.2 监督策略验证
当面积服务器崩溃时,监督者会检测到并重新启动它,所有操作都会记录在错误日志中。可以使用
rb
模块查看错误日志:
1> rb:start([{max,20}]).
2> rb:list().
9> rb:show(5).
10> rb:show(2).
10> rb:show(1).
5. 应用
5.1 应用资源文件
创建一个扩展名为
.app
的文件,包含应用的信息:
{application, sellaprime,
[{description, "The Prime Number Shop"},
{vsn, "1.0"},
{modules, [sellaprime_app, sellaprime_supervisor, area_server,
prime_server, lib_primes, my_alarm_handler]},
{registered,[area_server, prime_server, sellaprime_super]},
{applications, [kernel,stdlib]},
{mod, {sellaprime_app,[]}},
{start_phases, []}
]}.
5.2 回调模块
创建一个与
.app
文件中
mod
字段同名的回调模块:
-module(sellaprime_app).
-behaviour(application).
-export([start/2, stop/1]).
start(_Type, StartArgs) ->
sellaprime_supervisor:start_link(StartArgs).
stop(_State) -> ok.
5.3 启动和停止应用
以下是启动、停止和卸载应用的操作步骤:
1. 查看已加载的应用:
1> application:loaded_applications().
- 加载应用:
2> application:load(sellaprime).
- 查看已加载的应用:
3> application:loaded_applications().
- 启动应用:
4> application:start(sellaprime).
- 停止应用:
5> application:stop(sellaprime).
- 卸载应用:
6> application:unload(sellaprime).
- 查看已加载的应用:
7> application:loaded_applications().
通过以上步骤,我们可以创建一个完整的 OTP 应用,实现系统的启动、停止和管理。同时,利用监督树和错误日志机制,确保系统的稳定性和可维护性。
6. 总结与实际应用建议
6.1 整体流程回顾
为了更清晰地理解整个 Erlang 应用开发过程,我们可以将各个步骤整理成如下表格:
| 步骤 | 操作内容 | 代码示例 |
| ---- | ---- | ---- |
| 1 | 错误日志处理 | 无需手动删除,可定期轮询提取信息 |
| 2 | 告警管理 | 编写
my_alarm_handler
模块,设置和清除告警 |
-module(my_alarm_handler).
-behaviour(gen_event).
%% gen_event callbacks
-export([init/1, handle_event/2, handle_call/2,
handle_info/2, terminate/2]).
%% init(Args) must return {ok, State}
init(Args) ->
io:format("*** my_alarm_handler init:~p~n",[Args]),
{ok, 0}.
handle_event({set_alarm, tooHot}, N) ->
error_logger:error_msg("*** Tell the Engineer to turn on the fan~n"),
{ok, N+1};
handle_event({clear_alarm, tooHot}, N) ->
error_logger:error_msg("*** Danger over. Turn off the fan~n"),
{ok, N};
handle_event(Event, N) ->
io:format("*** unmatched event:~p~n",[Event]),
{ok, N}.
handle_call(_Request, N) -> Reply = N, {ok, N, N}.
handle_info(_Info, N) -> {ok, N}.
terminate(_Reason, _N) -> ok.
| 3 | 应用服务器开发 | 编写
prime_server
和
area_server
模块 |
%% prime_server 模块
-module(prime_server).
-behaviour(gen_server).
-export([new_prime/1, start_link/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
new_prime(N) ->
%% 20000 is a timeout (ms)
gen_server:call(?MODULE, {prime, N}, 20000).
init([]) ->
%% Note we must set trap_exit = true if we
%% want terminate/2 to be called when the application
%% is stopped
process_flag(trap_exit, true),
io:format("~p starting~n",[?MODULE]),
{ok, 0}.
handle_call({prime, K}, _From, N) ->
{reply, make_new_prime(K), N+1}.
handle_cast(_Msg, N) -> {noreply, N}.
handle_info(_Info, N) -> {noreply, N}.
terminate(_Reason, _N) ->
io:format("~p stopping~n",[?MODULE]),
ok.
code_change(_OldVsn, N, _Extra) -> {ok, N}.
make_new_prime(K) ->
if
K > 100 ->
alarm_handler:set_alarm(tooHot),
N = lib_primes:make_prime(K),
alarm_handler:clear_alarm(tooHot),
N;
true ->
lib_primes:make_prime(K)
end.
%% area_server 模块
-module(area_server).
-behaviour(gen_server).
-export([area/1, start_link/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
area(Thing) ->
gen_server:call(?MODULE, {area, Thing}).
init([]) ->
%% Note we must set trap_exit = true if we
%% want terminate/2 to be called when the application
%% is stopped
process_flag(trap_exit, true),
io:format("~p starting~n",[?MODULE]),
{ok, 0}.
handle_call({area, Thing}, _From, N) -> {reply, compute_area(Thing), N+1}.
handle_cast(_Msg, N) -> {noreply, N}.
handle_info(_Info, N) -> {noreply, N}.
terminate(_Reason, _N) ->
io:format("~p stopping~n",[?MODULE]),
ok.
code_change(_OldVsn, N, _Extra) -> {ok, N}.
compute_area({square, X}) -> X*X;
compute_area({rectonge, X, Y}) -> X*Y.
| 4 | 监督树构建 | 编写
sellaprime_supervisor
模块 |
-module(sellaprime_supervisor).
-behaviour(supervisor).
% see erl -man supervisor
-export([start/0, start_in_shell_for_testing/0, start_link/1, init/1]).
start() ->
spawn(fun() ->
supervisor:start_link({local,?MODULE}, ?MODULE, _Arg = [])
end).
start_in_shell_for_testing() ->
{ok, Pid} = supervisor:start_link({local,?MODULE}, ?MODULE, _Arg = []),
unlink(Pid).
start_link(Args) ->
supervisor:start_link({local,?MODULE}, ?MODULE, Args).
init([]) ->
%% Install my personal error handler
gen_event:swap_handler(alarm_handler,
{alarm_handler, swap},
{my_alarm_handler, xyz}),
{ok, {{one_for_one, 3, 10},
[{tag1,
{area_server, start_link, []},
permanent,
10000,
worker,
[area_server]},
{tag2,
{prime_server, start_link, []},
permanent,
10000,
worker,
[prime_server]}
]}}.
| 5 | 系统启动与测试 | 启动 Erlang,加载应用,进行查询操作 |
$ erl -boot start_sasl -config elog3
1> sellaprime_supervisor:start_in_shell_for_testing().
2> area_server:area({square,10}).
3> area_server:area({rectangle,10,20}).
4> area_server:area({square,25}).
5> prime_server:new_prime(20).
6> prime_server:new_prime(120).
| 6 | 应用管理 | 编写
.app
文件和回调模块,启动、停止和卸载应用 |
%% sellaprime.app 文件
{application, sellaprime,
[{description, "The Prime Number Shop"},
{vsn, "1.0"},
{modules, [sellaprime_app, sellaprime_supervisor, area_server,
prime_server, lib_primes, my_alarm_handler]},
{registered,[area_server, prime_server, sellaprime_super]},
{applications, [kernel,stdlib]},
{mod, {sellaprime_app,[]}},
{start_phases, []}
]}.
%% sellaprime_app 模块
-module(sellaprime_app).
-behaviour(application).
-export([start/2, stop/1]).
start(_Type, StartArgs) ->
sellaprime_supervisor:start_link(StartArgs).
stop(_State) -> ok.
%% 启动、停止和卸载应用操作
1> application:loaded_applications().
2> application:load(sellaprime).
3> application:loaded_applications().
4> application:start(sellaprime).
5> application:stop(sellaprime).
6> application:unload(sellaprime).
7> application:loaded_applications().
6.2 实际应用中的注意事项
在实际应用开发过程中,还需要注意以下几点:
-
错误日志大小
:确保错误日志足够大,以满足数天或数周的操作需求。定期检查错误日志,调查所有错误。
-
监督策略选择
:根据应用的实际需求选择合适的监督策略,如一对一或一对多监督树。
-
代码复用
:在编写服务器代码时,可以通过复制粘贴现有代码并修改模块名来提高开发效率,但要注意检查和修改代码中的错误。
-
告警处理
:合理设置告警条件,确保在系统出现异常时能够及时通知相关人员。
6.3 未来扩展建议
为了进一步完善应用,可以考虑以下扩展:
-
性能优化
:对质数生成算法进行优化,提高生成大质数的效率。
-
功能扩展
:添加更多的服务器功能,如用户管理、订单处理等。
-
监控系统
:引入监控系统,实时监控服务器的运行状态,及时发现和处理潜在问题。
6.4 开发流程总结流程图
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
A[错误日志处理]:::process --> B[告警管理]:::process
B --> C[应用服务器开发]:::process
C --> D[监督树构建]:::process
D --> E[系统启动与测试]:::process
E --> F[应用管理]:::process
通过以上的介绍和总结,我们详细了解了 Erlang 应用开发的全过程,包括告警管理、应用服务器开发、监督树构建、系统启动和应用管理等方面。掌握这些知识和技能,能够帮助我们开发出稳定、可靠的 Erlang 应用程序。在实际开发中,我们可以根据具体需求对代码进行修改和扩展,以满足不同的业务场景。
37

被折叠的 条评论
为什么被折叠?



