Erlang OTP应用程序设计

时间:2011-03-11 22:02:14

标签: architecture erlang otp

我正在努力解决OTP开发模式,因为我将一些代码转换为OTP应用程序。

我实际上是在制作一个网络抓取工具,而我只是不知道在哪里放置实际工作的代码。

我有一位主管,负责启动我的工作人员:

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

init(_Args) ->          
  Children = [
    ?CHILD(crawler, worker)
  ],  
  RestartStrategy = {one_for_one, 0, 1},
  {ok, {RestartStrategy, Children}}.

在此设计中,Crawler Worker负责执行实际工作:

-behaviour(gen_server).

start_link() ->
  gen_server:start_link(?MODULE, [], []).

init([]) ->
  inets:start(),        
  httpc:set_options([{verbose_mode,true}]), 
  % gen_server:cast(?MODULE, crawl),
  % ok = do_crawl(),
  {ok, #state{}}.

do_crawl() ->
  % crawl!
  ok.

handle_cast(crawl}, State) -> 
  ok = do_crawl(),
  {noreply, State};

do_crawl 会产生大量处理通过http抓取工作的进程和请求。

问题,最终是:实际抓取应该在哪里发生?从上面可以看出,我一直在试验触发实际工作的不同方式,但仍然缺少一些对于整合事物的方式至关重要的概念。

注意:部分OTP管道是为了简洁而省略的 - 管道就在那里,系统全部挂在一起

3 个答案:

答案 0 :(得分:11)

如果我的问题出错,我道歉。

我可以提出一些建议,以指导您朝着正确的方向(或我认为正确的方向):

1(相当小,但仍然很重要)我建议将inets启动代码从该worker中取出并将其放入应用程序statup代码(appname_app.erl)。据我所知,你正在使用钢筋模板,所以你应该有这些。

2现在,进入必要部位。为了充分利用OTP的 supervisor 模型,假设您想要生成大量的爬虫,那么使用 simple_one_for_one 会很有意义。主管而不是 one_for_one (阅读http://www.erlang.org/doc/man/supervisor.html以获取更多详细信息,但必不可少的部分是:simple_one_for_one - 简化的one_for_one主管,其中所有子进程都是动态添加相同进程类型的实例,即运行相同的代码。)因此,您不必仅启动一个监督过程,而是实际指定一个排序的“模板” - 关于如何启动正在执行的工作进程。这种类型的每个工作人员都是使用主管:start_child / 2 - http://erldocs.com/R14B01/stdlib/supervisor.html?i=1&search=start_chi#start_child/2开始的。在你明确启动它们之前,这些工人都不会开始。

2.1根据您的抓取工具的性质,您可能需要评估工作人员所需的重启策略类型。现在,在您的模板中,您将其设置为永久性(但您有一种不同类型的受监督子项)。您可以选择以下选项:

 Restart defines when a terminated child process should be restarted. A permanent child process should always be restarted, 
 a temporary child process should never be restarted and a transient child process should be restarted only if it terminates 
 abnormally, i.e. with another exit reason than normal.

所以,你可能希望得到类似的东西:

 -behaviour(supervisor).
 -define(CHILD(I, Type, Restart), {I, {I, start_link, []}, Restart, 5000, Type, [I]}).

 init(_Args) ->          
     Children = [
          ?CHILD(crawler, worker, transient)
     ],  
     RestartStrategy = {simple_one_for_one, 0, 1},
    {ok, {RestartStrategy, Children}}.

我冒昧地为这些孩子建议暂时重启,因为这对于这类工作人员是有意义的(如果他们没有完成工作则重新启动,如果他们正常完成则不重启)

2.2一旦您处理了上述项目,您的主管将处理任意数量的动态添加的工作流程;并且它将监视并重新启动(如果需要)每个,这为您的系统稳定性和可管理性增加了很多。

3现在,一个工人流程。我假设每个爬虫都有一些特定的状态,它可能在任何给定的时刻。出于这个原因,我建议使用gen_fsm(有限状态机,更多关于它们在http://learnyousomeerlang.com/finite-state-machines处可用)。这样,您动态添加到主管的每个gen_fsm实例都应该在 init / 1 中使用http://erldocs.com/R14B01/stdlib/gen_fsm.html?i=0&search=send_even#send_event/2向自己发送一个事件。

单独的东西:

   init([Arg1]) ->
       gen_fsm:send_event(self(), start),
       {ok, initialized, #state{ arg1 = Arg }}.

   initialized(start, State) ->
       %% do your work
       %% and then either switch to next state {next_state, ...
       %% or stop the thing: {stop, ...

请注意,您的工作可以包含在此gen_fsm流程中,也可以考虑根据您的特定需求为其生成单独的流程。

如果认为有必要,您可能希望为抓取的不同阶段设置多个州名。

无论哪种方式,希望这将有助于以某种OTP方式设计您的应用程序。如果您有任何问题,请告诉我。如果有必要,我很乐意添加一些内容。

答案 1 :(得分:3)

您实际上是否正在跟踪gen_server中的任何状态?

如果答案是肯定的,那么看起来你正在以正确的方式做事。请注意,由于消息是序列化的,因此通过上述实现,您无法同时进行两次爬网。如果您需要并发抓取,请参阅我的问题here的答案。

如果答案是否定的话,那么您可以放弃服务器和主管,只需使用应用程序模块查看here所示的任何初始化代码。

最后,lhttpcibrowse被认为是inets的更好替代品。我在广告服务器上使用lhttpc进行制作,效果很好。

答案 2 :(得分:2)

我对此问题的解决方案是查看Erlang Solutions“作业”应用程序,该应用程序可用于调度作业(即请求页面)并让单独的系统处理每个作业,绑定并发等等。

然后,您可以将新网址提供给流程crawl_sched_mgr,该流程会过滤网址,然后生成新的工作。您也可以让请求者自己执行此操作。

如果你不想使用工作,Yurii的建议就是你要走的路。