Erlang OTP 应用程序设计
Posted
技术标签:
【中文标题】Erlang OTP 应用程序设计【英文标题】:Erlang OTP application design 【发布时间】:2011-07-13 19:51:00 【问题描述】:在将一些代码转换为 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 管道 - 管道就在那里,系统都挂在一起
【问题讨论】:
【参考方案1】:我对这个问题的解决方案是研究 Erlang Solutions 的“作业”应用程序,该应用程序可用于调度作业(即请求页面)并让单独的系统处理每个作业、绑定并发等等。
然后,您可以将新 url 输入到进程 crawl_sched_mgr
中,该进程会过滤 url,然后生成新作业。你也可以让请求者自己做。
如果你不想使用工作,Yurii 的建议是要走的路。
【讨论】:
嗯,工作可能没问题,但我认为询问是关于如何使用 OTP 来解决这个问题,因此我写了答案。 非常正确。我只是想提出一个替代方案。【参考方案2】:如果我把你的问题弄错了,我深表歉意。
我可以提出一些建议来指导您朝着正确的方向前进(或者我认为是正确的方向:)
1(相当次要,但仍然很重要)我建议从该工作程序中获取 inets 启动代码并将其放入应用程序状态代码 (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 主管,其中所有子进程都是动态添加的相同进程类型的实例,即运行相同代码。)。因此,您实际上将指定一种“模板”,而不是只启动一个进程进行监督——关于如何启动正在执行实际工作的工作进程。这种类型的每个工人都是使用 supervisor: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.
我冒昧地建议 transient 为这些孩子重新启动,因为这对这类工人有意义(如果他们未能完成工作则重新启动,如果他们正常完成则不要重新启动)
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 方式设计您的应用程序。如果您有任何问题,请告诉我,如有必要,我很乐意补充。
【讨论】:
您可能还想考虑 ibrowse 或 lhttpc,因为众所周知,erlang 发行版中的 httpc 不是那么好 小心 gen_fsm ...它的语义通常过于严格。仅在完全适合时使用。大多数时候 gen_server 更合适。 我无法表达我多么感激你在推特上特意写了这篇文章!【参考方案3】:您实际上是否在跟踪您的 gen_server 中的任何状态?
如果答案是肯定的,那么看起来您正在以正确的方式做事。请注意,由于消息是序列化的,因此通过上述实现,您不能同时进行两次爬网。如果您需要并发爬取,请参阅我的问题here 的答案。
如果答案是否定的,那么您可能会放弃服务器和主管,而只需将应用程序模块用于任何初始化代码,如 here 所示。
最后,lhttpc 和 ibrowse 被认为是 inets 的更好替代品。我在广告服务器的生产环境中使用 lhttpc,效果很好。
【讨论】:
哪种方法是正确的?你如何从 gen_server 调用函数?以上都没有实际工作。 re inets,它似乎最近发生了一些变化,并且似乎是可用选项中功能最全的。不过会检查建议,干杯。 我不明白呼叫甚至没有成功。查看您的实际代码会很有帮助,因此我可以指出问题所在。简而言之,您应该从服务器导出一个函数,例如crawl/1
(我假设它需要一个 URL)。该函数将简单地执行:gen_server:cast(?MODULE, crawl, Url).
。这将触发您的处理程序:handle_cast(crawl, Url, State)
,这将反过来调用do_crawl(Url)
。查看一些 OTP 示例将有助于您了解整个过程。以上是关于Erlang OTP 应用程序设计的主要内容,如果未能解决你的问题,请参考以下文章
Erlang / OTP并发编程实战 Erlang程序设计.第2版 PDF分享