Erlang OTP 应用程序设计

9

我在将一些代码转换为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}}.

在这个设计中,爬虫工作者负责实际的工作。
-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个回答

12

如果我误解了你的问题,我很抱歉。

以下是一些建议,可以指导你朝正确的方向前进(或者我认为是正确的方向 :))

1(相对不重要,但仍然重要)我建议将inets启动代码从worker中移出,并将其放入应用程序启动代码(appname_app.erl)中。据我所知,您正在使用rebar模板,因此您应该有这些。

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监督者,其中所有子进程都是同一进程类型的动态添加实例,即运行相同的代码)。因此,您将实际上指定一个“模板”-关于如何启动正在执行真正工作的工作进程。每个这种类型的worker都使用supervisor:start_child/2启动-http://erldocs.com/R14B01/stdlib/supervisor.html?i=1&search=start_chi#start_child/2。在您明确启动它们之前,这些worker都不会启动。

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
你可能还想考虑使用ibrowse或lhttpc,因为erlang分发中的httpc被认为不太好。 - Yurii Rashkovskii
小心使用gen_fsm...它的语义常常太过严格。只有当完全匹配时才使用它。大多数情况下,使用gen_server更为适合。 - Peer Stritzinger
我无法表达我有多感激你在Twitter上不辞辛劳地写下这篇文章! - Toby Hede

4

你的gen_server实际上是否跟踪了任何状态?

如果是肯定的,看起来你正在正确地做事情。请注意,由于消息被序列化,使用上面的实现,您不能同时进行两个爬取。如果需要并发爬取,请参见我的问题here的答案。

如果否,则可能可以摆脱服务器和监视器,并仅使用应用程序模块进行任何初始化代码,如here所示。

最后,lhttpcibrowse被认为是inets的更好选择。我在我的广告服务器上使用lhttpc生产环境中运行良好。


哪种方式才是正确的呢?你如何从gen_server中调用函数?上述两种方法都实际无法工作。 - Toby Hede
关于re inets,它似乎最近有一些变化,并且似乎是可用选项中功能最全面的。不过我会查看建议,谢谢。 - Toby Hede
我不明白为什么调用没有成功。如果能看到您的实际代码,我就可以指出问题。简而言之,您应该从服务器导出一个函数,例如 crawl/1(我假设它需要一个URL参数)。该函数将简单地执行:gen_server:cast(?MODULE, {crawl, Url})。这将触发处理程序的情况:handle_cast({crawl, Url}, State),然后调用do_crawl(Url)。看一些OTP示例将有助于您了解整个过程。 - David Weldon

3
我的解决方案是研究Erlang Solutions的“jobs”应用程序,该应用程序可用于安排工作(即请求页面),并让单独的系统处理每个工作,限制并发等。
然后,您可以将新的网址馈送到进程“crawl_sched_mgr”中,该进程会过滤网址,然后生成新的工作。您也可以让请求者自己执行此操作。
如果您不想使用jobs,则Yurii的建议是可行的。

工作可能还好,但我认为问题更多是关于如何使用OTP,因此我写了这个答案。 - Yurii Rashkovskii
非常正确。我只是想提出一个替代方案。 - I GIVE CRAP ANSWERS

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接