如何在Perl中处理AnyEvent,RabbitMQ(心跳)和长时间运行的工作?

时间:2016-01-18 10:56:39

标签: multithreading perl rabbitmq fork anyevent

我正在实现一个用于分布式cronjob执行的系统(所谓的 cron计算集群)。当行动时间到来时,Cronjobs应排队到消息队列(RabbitMQ)。另一方面(集群的节点/工作者)是一个Perl守护程序,利用AnyEvent::RabbitMQ从消息队列中接收一个cronjob / task / message,处理任务并请求另一个cronjob / task / message来自消息队列等等。

我使用RabbitMQ的心跳功能,该功能与AnyEvent::RabbitMQ一起实现,以帮助RabbitMQ识别断开的连接。

不要介意心跳间隔的实际值!我也有很长时间的工作需要几天。因此,将间隔设置为最长的cronjob将不是一种选择。

请参阅以下代码段,以便在Perl守护程序worker中执行实际的cronjob。它是在'AnyEvent->计时器'中实现的,不是为了消息而使用DoSing RabbitMQ。由于RabbitMQ的consume被禁止(由管理层),因此使用了此方法。

sub _timer_tick {

  $rabbitmq_channel->get(
    queue      => 'job_queue',
    on_success => sub {
      my ($amqp_method) = @_;
      if ( not $amqp_method->{empty} ) {
        pause_timer();
        progress_job($amqp_method);
        resume_timer();
      }
    },
    on_failure => sub { $quit_programm->send( 'RABBITMQ_ERROR', @_ ) },
  );

  return;
}

progress_job()是解析消息的位置,并且将执行作业。 pause_timer()resume_timer()控制触发AnyEvent->timer的{​​{1}}。

_timer_tick()

第一个长时间运行的作业进入,系统“崩溃”,出现各种错误消息。有时会抛出'Unknown channel id:1',有时会抛出'Channel已经关闭'。所以我做了'哑调试'(试图弄乱配置)并发现当use Capture::Tiny 'capture'; sub progress_job { my ($amqp_method) = @_; my $job = decode_json( $amqp_method->{body}->to_raw_payload() ); my ( $stdout, $stderr, $exit ) = capture { system $job->{execute}; }; return; } 间隔比heartbeat内的时间短时,会抛出这些错误。经过一番思考后才有意义。 progress_job()是一个阻塞子例程,AnyEvent无法继续向RabbitMQ发送心跳包。

我在解决阻塞热问题时的第一个想法是在子进程中分叉并执行progress_job()AnyEvents documentation on FORK指出当在子级内无法访问事件系统(例如通过AnyEvent)时,使用progress_job()是保存的。 接下来想:好的,没有访问事件系统所以我可以做fork。 但是:计时器应该在fork返回后恢复(resume_timer())。理论上progress_job()将在resume_timer()之后立即调用,而不是在fork()之后调用。所以我停止了我的实施。

我的问题:如何解决最后一点?如何在progress_job()(或换句话说分叉的孩子)返回后resume_timer()? 由于分叉,我无法将progress_job()放在子项内,并且事件系统不是线程安全的。

1 个答案:

答案 0 :(得分:3)

除非使用支持AE的呼叫阻止程序,否则AE无法处理事件。 system不支持AE。请改用AnyEvent::Util中的run_cmd