为Net::Server::HTTP子类添加周期性超时处理
在Net::Server::HTTP中添加周期性任务的实现方案
嘿,我来帮你搞定这个问题!要在Net::Server::HTTP的子类里实现15秒一次的周期性数据库检查+子进程处理,有两种比较靠谱的方式,我分别给你拆解一下:
方法一:重写主循环(推荐,不中断请求)
这种方式是通过修改Net::Server的主循环逻辑,让它在等待客户端连接的间隙,定期执行你的任务,不会打断正在处理的HTTP请求,安全性更高。
直接上代码,你可以把你的MyService子类改成这样:
package MyService; use base qw(Net::Server::HTTP); use Time::HiRes qw(time); # 用高精度时间更准确 sub new { my ($class, %args) = @_; my $self = $class->SUPER::new(%args); $self->{last_check_time} = time(); # 初始化上次检查时间 return $self; } sub loop { my $self = shift; while (1) { my $now = time(); # 检查是否到了15秒周期 if ($now - $self->{last_check_time} >= 15) { $self->check_db_and_fork(); $self->{last_check_time} = $now; } # 非阻塞接受连接(没有连接就立刻返回,不卡住循环) $self->accept_nb(); # 如果有客户端连接,处理HTTP请求 if (exists $self->{client}) { $self->process_request(); # 处理完请求后清理客户端句柄 delete $self->{client}; } # 短暂休眠,避免CPU空转 select(undef, undef, undef, 0.1); } } sub check_db_and_fork { my $self = shift; print "[$$] 开始检查数据库消息...\n"; # --- 这里写你的数据库检查逻辑 --- # 比如:my @messages = $db->fetch_pending_messages(); # 假设我们查到了需要处理的消息 my $has_messages = 1; if ($has_messages) { my $pid = fork(); if (!defined $pid) { warn "fork失败: $!"; return; } elsif ($pid == 0) { # 子进程:处理数据库消息 $self->process_db_messages(); exit 0; # 子进程处理完退出 } else { print "[$$] 已fork子进程 $pid 处理消息\n"; } } } sub process_db_messages { my $self = shift; # --- 这里写你的消息处理逻辑 --- print "[$$] 子进程正在处理数据库消息\n"; # 模拟处理耗时 sleep 2; print "[$$] 子进程处理完成\n"; } 1;
然后你的主调用代码需要调整一下app的回调方式(原来的print_client不符合Net::Server::HTTP的规范):
use MyService; my $service = MyService->new(port => 8080); $service->run( app => { "/ping" => sub { my ($req, $res) = @_; $res->content("Pong"); $res->status(200); }, "/status" => sub { my ($req, $res) = @_; $res->content("Good, thanks"); $res->status(200); }, } );
这个方法的优点:
- 不会打断正在处理的HTTP请求,周期性任务只在服务空闲时执行
- 时间控制精准,依赖高精度时间模块,误差很小
- 避免CPU空转,因为有0.1秒的休眠
方法二:使用Alarm信号(简单但有局限性)
如果你不想重写主循环,可以用Perl的alarm信号来实现定时触发,但要注意:如果HTTP请求的处理时间超过15秒,信号会中断当前请求,所以只适合请求处理速度很快的场景。
修改你的子类:
package MyService; use base qw(Net::Server::HTTP); sub pre_loop { my $self = shift; $self->SUPER::pre_loop(@_); # 设置SIGALRM信号处理函数 $SIG{ALRM} = sub { $self->check_db_and_fork(); alarm(15); # 重置闹钟,继续下一个15秒周期 }; alarm(15); # 第一次触发 } # 下面的check_db_and_fork和process_db_messages和方法一完全一样 sub check_db_and_fork { my $self = shift; print "[$$] 开始检查数据库消息...\n"; my $has_messages = 1; if ($has_messages) { my $pid = fork(); if (!defined $pid) { warn "fork失败: $!"; return; } elsif ($pid == 0) { $self->process_db_messages(); exit 0; } else { print "[$$] 已fork子进程 $pid 处理消息\n"; } } } sub process_db_messages { my $self = shift; print "[$$] 子进程正在处理数据库消息\n"; sleep 2; print "[$$] 子进程处理完成\n"; } 1;
这个方法的优缺点:
- 优点:代码更简洁,不需要修改主循环逻辑
- 缺点:如果请求处理时间超过15秒,信号会中断请求,可能导致响应不完整;信号处理函数需要保证是可重入的(不能在里面做复杂的操作)
额外注意事项:
- 子进程回收:Net::Server默认会处理
SIGCHLD信号,自动回收子进程,所以你不需要手动调用waitpid,但如果fork的子进程特别多,可以调整check_for_deadchildren_interval参数(默认是5秒),让服务更频繁地回收僵尸进程。 - 数据库连接:子进程里最好重新建立数据库连接,不要复用父进程的连接(因为fork后父进程的文件描述符会被共享,可能导致连接异常)。
- 日志输出:建议用Net::Server的日志方法(比如
$self->log(INFO => "消息"))代替直接print,这样日志会更规范,和服务的其他日志整合在一起。
内容的提问来源于stack exchange,提问作者srh
相关产品推荐
相关产品推荐

