我正在使用Net :: AMQP :: RabbitMQ和fork()遇到令人困惑的行为。如果我......
...第二条消息实际上并未发送到RabbitMQ(并且不会抛出任何错误)。我做了很多测试,包括在发送之前检查$connection->is_connected()
。我的实验中有一些有趣的花絮:
$connection->open_channel( $newChannelId )
来电会挂起。waitpid
),则表示已成功发送。我正在寻找一种方法来检测当分叉子项退出时此连接不再有效,并强制断开连接/重新连接。我在系统中各种其他模块使用的perl模块中缓存连接,但我不知道是否/何时其他模块fork()
并行工作。我无法可靠地设置$SIG{CHLD}
处理程序并在收到信号时断开连接,因为其他代码可能会覆盖我的处理程序。我唯一的防弹选项是丢弃缓存并连接每条消息,但这会大大降低发布速度(降低30倍)。
此脚本演示了此问题(发布到名为'广播'的主题交换):
#!/usr/bin/perl
use strict;
use Net::AMQP::RabbitMQ;
use JSON -support_by_pp;
my $connection;
my $channelId = 0;
sub sendToRabbit {
my ($message) = @_;
print "Sending message $message->{message}\n";
my $contentType = 'application/json';
my $payload = encode_json $message;
$connection->publish($channelId, 'test.route', $payload, { exchange => 'broadcast', force_utf8_in_header_strings => 1 }, { 'content_type' => $contentType });
print "Sent!\n";
}
sub main {
print "Starting...\n";
$connection = Net::AMQP::RabbitMQ->new();
$connection->connect('localhost', { user => 'guest', password => 'guest', port => 5672 });
$connection->channel_open(++$channelId);
print "Connected!\n";
# send first message
sendToRabbit({ message => 'body 1' });
# fork child
my $child = fork();
if(!$child) {
# child
sleep(1);
print "child exiting...\n";
exit(0);
}
else {
# parent
waitpid($child, 0);
}
print "parent continuing...\n";
# send second message - this will not be actually sent.
sendToRabbit({ message => 'body 2' });
# allow I/O to settle...
sleep(1);
}
main();
感谢 ikegami 揭示解决方案!
在我的RabbitMQ管理对象中,我已经在connect()
例程中注入了一些代码,这些例程允许我选择性地跳过不自己调用connect()
的分叉子代的析构函数。这似乎有预期的效果。
# Connect to RabbitMQ and create a channel
sub connect {
my ($self) = @_;
$self->{pid} = $$;
# if we redefined the destructor and connect is called, we need to revert
# it so it can be redefined again properly
no warnings qw( redefine );
if($self->{original_destructor}) {
# reset original destructor
*Net::AMQP::RabbitMQ::DESTROY = $self->{original_destructor};
delete $self->{original_destructor};
}
# define alternate destructor so forked children that do not call "connect" do
# not destroy our connection
{
$self->debug("Overridding constructor...");
$self->{original_destructor} = Net::AMQP::RabbitMQ->can('DESTROY');
# only destroy the connection if the current pid is the owner's pid
my $new_destructor = sub { if($self->{pid} eq $$) { $self->debug("Destroying $_[0]!\n"); $self->{original_destructor}->(); } };
*Net::AMQP::RabbitMQ::DESTROY = $new_destructor;
}
my $connection = Net::AMQP::RabbitMQ->new();
$connection->connect('localhost', { user => $self->{username}, password => $self->{password}, port => $PORT, vhost => $VHOST });
$self->{connection} = $connection;
$self->{channel} = $self->createChannel();
1;
}
答案 0 :(得分:3)
子节点是父节点的克隆,父节点与子节点共享的文件句柄。作为父母的副本,孩子有$connection
的副本。当子进入时,该对象被销毁,调用它的析构函数,向RabbitMQ发送命令以关闭连接。
你可以通过添加
来看到这一点{
my $old_destructor = Net::AMQP::RabbitMQ->can('DESTROY');
my $new_destructor = sub { print("Destroying $_[0]!\n"); $old_destructor->(); };
no warnings qw( redefine );
*Net::AMQP::RabbitMQ::DESTROY = $new_destructor;
}
可能的解决方案:
exec
该文件。exec
使用该参数重新启动。 PS - 不要自己编写fork
+ exec
代码。至少使用open3
。
sub spawn {
open(local *CHILD_STDIN, '<', '/dev/null') or die $!;
return open3('<&CHILD_STDIN', '>&STDOUT', '>&STDERR', @_);
}
sendToRabbit({ message => 'body 1' });
my $pid = spawn('child.pl');
waitpid($pid, 0);
sendToRabbit({ message => 'body 2' });