25、Erlang 应用开发:从告警管理到系统启动

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. 设置告警:
1> alarm_handler:set_alarm(tooHot).
  1. 安装自定义告警处理程序:
2> gen_event:swap_handler(alarm_handler,
{alarm_handler, swap},
{my_alarm_handler, xyz}).
  1. 再次设置告警:
3> alarm_handler:set_alarm(tooHot).
  1. 清除告警:
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. 启动监督者:
1> sellaprime_supervisor:start_in_shell_for_testing().
  1. 进行有效查询:
2> area_server:area({square,10}).
  1. 进行无效查询:
3> area_server:area({rectangle,10,20}).
  1. 再次进行有效查询:
4> area_server:area({square,25}).
  1. 生成小质数:
5> prime_server:new_prime(20).
  1. 生成大质数:
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().
  1. 加载应用:
2> application:load(sellaprime).
  1. 查看已加载的应用:
3> application:loaded_applications().
  1. 启动应用:
4> application:start(sellaprime).
  1. 停止应用:
5> application:stop(sellaprime).
  1. 卸载应用:
6> application:unload(sellaprime).
  1. 查看已加载的应用:
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 应用程序。在实际开发中,我们可以根据具体需求对代码进行修改和扩展,以满足不同的业务场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值