Erlang gen_server重新加载

By AverageJoeWang
 标签:

本篇文章是翻译的,原文链接

编写gen_servers已经成为一个习惯。我们在我们的应用程序中有几十个gen_servers,但是当我们进一步扩展服务器的行为时,我们开始搔头了。我们哪个服务器的响应时间最短?处理请求需要多长时间?实施这些指标可能涉及更改我们所有服务器中的代码。通过公共服务接口(CSI),我们可以在一个地方管理我们的服务器的行为和度量。

使用CSI的路

介绍

在Erlang应用程序中,通常我们有一些服务通信来处理来自客户端的传入请求。大多数时候,这些服务是通过编写gen_server代码实现的。其中一些可能是处理器密集型,其他可能是I / O密集型。当我们面临体系减缓的局面时,很难找到性能下降的根本原因。我们可能会知道哪个服务(或gen_server)占用了导致瓶颈的资源,但另一方面,如果我们能够衡量所有服务的基本度量,则寻求根本原因将会更容易。

在每个服务中实施指标需要时间,并带有不符合的风险,因为不同的开发人员可能会有不同的方法和想法来实现一个共同的度量。

如果我们有一个通用的gen_server,所有的服务符合,实现所有服务的常见行为 将意味着创建一次代码,然后根据需要多次重用它。

因此,我们可以使用genealised gen_server来编写一个gen_server来处理请求,通过调用我们的回调模块来实现这些功能。

公共业务接口(CSI)

简而言之,CSI位于服务API和服务逻辑之间。当请求被提出时,服务API调用CSI并告诉它如何处理请求。然后,CSI接受订单,调用服务逻辑(回调模块),并使用服务逻辑返回的值来响应呼叫者。

所以我们有两件事要考虑:

  • 处理传入请求的方式。
  • 处理请求的回调模块的行为。

处理传入请求

任何传入请求的呼叫流程如下:呼叫者执行servicename:function(Parameters)呼叫以获得服务的结果。在servicename.erl文件(它是服务的API)中,所有函数都具有相同的格式,以便能够调用csi:calltype(Parameters)CSI服务器的API。有几种可用的呼叫类型,稍后介绍。

对于给定的服务,这是设置API所需要的。

在回调模块中处理请求

由于CSI服务器是实现服务的gen_server,因此通过调用回调模块,将在service.erl API中进行的调用放在其handle_call函数中function(Parameters)。回调模块将结果返回给将其转发给原始请求者的CSI gen_server。我们假设回调模块名称是servicename_service.erl。下图显示了呼叫流程:

CSI gen_server

服务API模块是my.erl,业务逻辑在my_service.erl中。服务的通用部分在csigenserver.erl中。

一个很小的例子。

为了快速了解CSI的使用情况,我们将实施一个名为em_service的小型服务,提供三种功能:

  • process_foo(Atom). - 返回atom hello_world。
  • process_too_long(Atom). - 睡眠很长时间来测试超时终止功能
  • process_crashing(Atom). - 抛出异常

em_service.erl回调模块有两个主要部分:

1.行为功能

实施服务需要一些家务管理。当服务服务器通过start()start_link()函数调用启动时,init_service()在回调模块中被调用。在这里,服务初始化其全局状态并返回一个{ok, ServiceState}元组。这个`ServiceState’用于在开始处理每个请求时传递全局状态。

当请求到达服务器时,它调用回调模块的init(Args, ServiceState)函数来初始化处理线程。Args参数与进行服务请求调用时使用的参数相同。它返回{ok, RequestState}。这是在处理特殊请求时使用的。例如,这里可以从池中获取数据库连接,并作为RequestState的一部分传回。

init返回{ok, RequestState}我们的CSI gen_server调用将处理请求的函数。

当处理请求时,我们的gen_server终于terminate(Reason, RequestState)在处理单个请求后调用回调函数进行清理。

terminate_service()是对应的init_service()。当服务被终止时,它被调用,所以这是在服务关闭之前执行清理的最后一个地方。

2.服务功能

所有服务函数都有两个参数 - 调用函数时传递的参数(当需要多个参数时可以是列表或元组)以及初始化调用期间初始化的请求处理的状态。

服务功能的实现到em_service.erl

-module(em_service).
-behaviour(csi_server).

%% General state of the service
-record(em_state,{}).

%% Lifecycle State for every requests'
-record(em_session_state,{}).

-export([init_service/1,
         init/2,
         terminate/2,
         terminate_service/2]).

-export([process_foo/2,
         process_too_long/2,
         process_crashing/2]).


%% ====================================================================
%% Behavioural functions
%% ====================================================================
init_service(_InitArgs) ->
    {ok,#em_state{}}.

init(_Args,_ServiceState) ->
    {ok,#em_session_state{}}.

terminate(_Reason,_State) ->
    ok.

terminate_service(_Reason,_State) ->
    ok.

%% ====================================================================
%% Service functions
%% ====================================================================
process_foo(_Args,State) ->
    {hello_world,State}.

process_too_long(_Args,State) ->
    timer:sleep(100000),
    {long_job_fininshed,State}.

process_crashing(Args,State) ->
    A = Args - Args,
    {A,State}.

服务函数返回一个包含的元组{Result, NewState}

到目前为止,我们已经实现了业务逻辑。我们来看看如何为服务创建API。

服务API

一个服务的入口点与gen_server实现非常相似。有一些内务管理功能,如start,start_link,stop,然后公开的功能被声明。

启动服务时,应启动其startstart_link功能。由于我们的服务使用CSI基础服务器,因此需要告知CSI服务器,本地注册服务使用什么名称,以及哪个模块实现回调函数以及服务的功能。所以startstart_link有两个参数,服务名称和服务模块。在我们的例子中,后者是上面创建的em_service.erl

业务逻辑通过CSI服务器调用csi:call_p(?SERVICE_NAME, function, Args)。那么CSI服务器会调用您的服务模块功能来执行操作。你可能已经认识到,我们使用call_p而不是简单的call()。服务器可以通过几种方式来处理请求。它可以进行并行或序列化请求处理。当我们使用call_p(), CSI服务器产生一个单独的进程来处理请求。在这个过程中,回调模块的init(),function()terminate()如上述那样将被调用。call_p()用于并发请求处理。call_s()是类似的,但提供了序列化处理。两种方式,只要回调函数返回,请求者就被阻止。

有一些其他的方式可以打电话给一个服务,这里我只提一个。如果call_p()我们不使用post_p()它,它将立即返回一个包含Pid的元组和为请求生成的进程的引用,然后结果将被发送回Erlang消息中的请求者。

为了简单起见,我们将call_p()只看看。

-module(em).

-define(SERVICE_NAME,em_service).
-define(SERVICE_MODULE,em_service).

%% ====================================================================
%% API functions
%% ====================================================================
-export([start/0,
         start_link/0,
         stop/0]).

-export([process_foo/1,
         process_too_long/1,
         process_crashing/1]).


start() -> csi:start(?SERVICE_NAME,?SERVICE_MODULE).
start_link() -> csi:start_link(?SERVICE_NAME,?SERVICE_MODULE).

stop() -> csi:stop(?SERVICE_NAME).

process_foo(Atom) -> csi:call_p(?SERVICE_NAME,process_foo,[Atom]).
process_too_long(Atom) -> csi:call_p(?SERVICE_NAME,process_too_long,[Atom]).
process_crashing(Atom) -> csi:call_p(?SERVICE_NAME,process_crashing,[Atom]).

对于更复杂的服务,您可能需要查看csi.erl中的CSI本身的API,csi_service.erl来了解如何实现更复杂的服务以及csi_server.erl,其中出现所有的魔法。

扩展CSI功能

如果服务服务器使用通用的CSI框架,则可以轻松添加新功能。我们需要实现的唯一的地方是CSI本身,所有使用它的服务将具有这种新功能。

快速开始的例子

克隆资源库,转到其目录。

$ cd example
$ make run

利用一个erlang shell。这是怎么玩呢?

(em_server@127.0.0.1)1> em:start().
    {ok,<0.83.0>}
    (em_server@127.0.0.1)2> em:
    module_info/0       module_info/1       process_crashing/1
    process_foo/1       process_too_long/1  start/0
    start_link/0        stop/0
    (em_server@127.0.0.1)2> em:process_foo(test).
    hello_world
    (em_server@127.0.0.1)3> em:process_too_long(test).
    {error,timeout_killed}
    (em_server@127.0.0.1)4> em:process_crashing(test).
    {error,exception}
    (em_server@127.0.0.1)5> 19:36:30.489 [error] Exception in service when calling em_service:process_crashing([test]). error:badarith. Stacktrace:[{em_service,process_crashing,2,[{file,"src/em_service.erl"},{line,46}]},{csi_server,process_service_request,8,[{file,"src/csi_server.erl"},{line,402}]},{proc_lib,init_p_do_apply,3,[{file,"proc_lib.erl"},{line,237}]}]
    19:36:30.490 [error] CRASH REPORT Process <0.89.0> with 0 neighbours exited with reason: exception in csi_server:process_service_request/8 line 425

    (em_server@127.0.0.1)5> csi:stats_get_all(em_service).
    [{{response_time,process_foo},{1,106,106.0,106,106}}]
    (em_server@127.0.0.1)6> csi:services
    services/0         services_status/0
    (em_server@127.0.0.1)6> csi:services_status().
    [{{registered_name,em_service},
      {csi_service_state,em_service,em_service,
                         {em_state},
                         true,36888,40985,csi_stats,
                         [all],
                         [],
                         [{response_time,[{"last_nth_to_collect",10},
                                          {"normalize_to_nth",8}]}]}},
     {{registered_name,csi_service},
      {csi_service_state,csi_service,csi_service,
                         {csi_service_state},
                         true,20500,24597,csi_stats,
                         [all],
                         [],
                         [{response_time,[{"last_nth_to_collect",10},
                                          {"normalize_to_nth",8}]}]}}]
    (em_server@127.0.0.1)7> em:stop().
    ok
    (em_server@127.0.0.1)8>

如果你使用lager,这里是你在console.log中找到的顺序如下:

2015-08-04 19:35:35.973 [info] <0.7.0> Application lager started on node 'em_server@127.0.0.1'
    2015-08-04 19:35:35.980 [info] <0.7.0> Application csi started on node 'em_server@127.0.0.1'
    2015-08-04 19:35:35.980 [info] <0.7.0> Application em started on node 'em_server@127.0.0.1'
    2015-08-04 19:35:35.984 [info] <0.7.0> Application runtime_tools started on node 'em_server@127.0.0.1'
    2015-08-04 19:36:30.489 [error] <0.89.0>@csi_server:process_service_request:415 Exception in service when calling em_service:process_crashing([test]). error:badarith. Stacktrace:[{em_service,process_crashing,2,[{file,"src/em_service.erl"},{line,46}]},{csi_server,process_service_request,8,[{file,"src/csi_server.erl"},{line,402}]},{proc_lib,init_p_do_apply,3,[{file,"proc_lib.erl"},{line,237}]}]
    2015-08-04 19:36:30.490 [error] <0.89.0> CRASH REPORT Process <0.89.0> with 0 neighbours exited with reason: exception in csi_server:process_service_request/8 line 425

从哪里去?

CSI具有衡量服务性能的附加功能。一个WombatOAM插件也有可视化的功能水平的指标。有关完整说明,请访问:GitHub

总结

随着gen_server推广服务器的行为,CSI是对gen_server-s进行泛化使用常用模式编写服务的下一步。