erlang / OTP中的速率限制事件处理程序

时间:2011-08-15 15:18:23

标签: erlang otp

我有一个数据源,它可能以很高的速率产生点,我想对每个点执行一个可能耗时的操作;但我还希望系统在超载时通过丢弃多余的数据点来优雅地降级。

据我所知,使用gen_event永远不会跳过事件。从概念上讲,我希望gen_event做的是在再次运行处理程序之前删除除最新的挂起事件之外的所有事件。

有没有办法用标准OTP做到这一点?还是有充分理由说我不应该这样处理事情?

到目前为止,我所拥有的最好的是使用gen_server并依靠超时来触发昂贵的事件:

-behaviour(gen_server).
init() -> 
    {ok, Pid} = gen_event:start_link(),
    {ok, {Pid, none}}.

handle_call({add, H, A},_From,{Pid,Data}) ->
    {reply, gen_event:add_handler(Pid,H,A), {Pid,Data}}.

handle_cast(Data,{Pid,_OldData}) -> 
    {noreply, {Pid,Data,0}}.  % set timeout to 0 

handle_info(timeout, {Pid,Data}) ->
    gen_event:sync_notify(Pid,Data),
    {noreply, {Pid,Data}}.

这种做法是否正确? (尤其是关于监督?)

2 个答案:

答案 0 :(得分:1)

我不能评论监督,但我会将其作为一个包含过期项目的队列来实现。

我已经实现了一些你可以在下面使用的东西。

我把它变成了gen_server;当你创建它时,你给它一个旧项目的最大年龄。

它的界面是你可以发送它要处理的项目,你可以请求尚未出列的项目它记录它接收每个项目的时间。每次收到要处理的项目时,它都会检查队列中的所有项目,出列并丢弃那些早于最大年龄的项目。 (如果您希望始终遵守最大年龄,则可以在返回排队项目之前过滤队列)

您的数据源会将数据({process_this, Anything})投射到工作队列,而您(可能很慢)的消费者进程会调用(gimme)来获取数据。

-module(work_queue).
-behavior(gen_server).

-export([init/1, handle_cast/2, handle_call/3]).

init(DiscardAfter) ->
  {ok, {DiscardAfter, queue:new()}}.

handle_cast({process_this, Data}, {DiscardAfter, Queue0}) ->
  Instant = now(),
  Queue1 = queue:filter(fun({Stamp, _}) -> not too_old(Stamp, Instant, DiscardAfter) end, Queue0),
  Queue2 = queue:in({Instant, Data}, Queue1),
  {noreply, {DiscardAfter, Queue2}}.

handle_call(gimme, From, State = {DiscardAfter, Queue0}) ->
  case queue:is_empty(Queue0) of
    true ->
      {reply, no_data, State};
    false ->
      {{value, {_Stamp, Data}}, Queue1} = queue:out(Queue0),
      {reply, {data, Data}, {DiscardAfter, Queue1}}
  end.

delta({Mega1, Unit1, Micro1}, {Mega2, Unit2, Micro2}) ->
  ((Mega2 - Mega1) * 1000000 + Unit2 - Unit1) * 1000000 + Micro2 - Micro1.

too_old(Stamp, Instant, DiscardAfter) ->
  delta(Stamp, Instant) > DiscardAfter.

REPL的小演示:

c(work_queue).
{ok, PidSrv} = gen_server:start(work_queue, 10 * 1000000, []).         
gen_server:cast(PidSrv, {process_this, <<"going_to_go_stale">>}),      
timer:sleep(11 * 1000),                                                
gen_server:cast(PidSrv, {process_this, <<"going to push out previous">>}),
{gen_server:call(PidSrv, gimme), gen_server:call(PidSrv, gimme)}.        

答案 1 :(得分:0)

  

有没有办法用标准OTP做到这一点?

没有

  

我有理由不这样处理事情吗?

不,提早超时可以提高整个系统的性能。了解here的方式。

  

这种做法是否正确? (尤其是关于监督?)

不知道,你还没有提供监管守则。


作为第一个问题的一些额外信息:

如果您可以在OTP之外使用第三方库,那么有一些可以添加抢先超时的内容,这就是您所描述的内容。

我熟悉的有两个是dispcount,第二个是chick(我是小鸡的作者,我会尽量不在这里宣传这个项目)。

Dispcount对单个资源非常有用,这些资源只有可以同时运行且不排队的有限数量的作业。你可以阅读here警告很多非常有趣的信息!)。

Dispcount对我不起作用,因为我不得不产生4000多个进程池来处理我的应用程序内部不同队列的数量。我写小鸡因为我需要一种方法来动态增加和减少我的队列长度,以及能够排队请求和拒绝其他人,而不必产生4000多个进程池。

如果我是你,我会首先尝试折扣(因为大多数解决方案都不需要小鸡),然后如果你需要一些更有活力的东西,那么可以响应一定数量的请求的游泳池可以试试小鸡。 / p>