You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为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秒,信号会中断请求,可能导致响应不完整;信号处理函数需要保证是可重入的(不能在里面做复杂的操作)

额外注意事项:

  1. 子进程回收:Net::Server默认会处理SIGCHLD信号,自动回收子进程,所以你不需要手动调用waitpid,但如果fork的子进程特别多,可以调整check_for_deadchildren_interval参数(默认是5秒),让服务更频繁地回收僵尸进程。
  2. 数据库连接:子进程里最好重新建立数据库连接,不要复用父进程的连接(因为fork后父进程的文件描述符会被共享,可能导致连接异常)。
  3. 日志输出:建议用Net::Server的日志方法(比如$self->log(INFO => "消息"))代替直接print,这样日志会更规范,和服务的其他日志整合在一起。

内容的提问来源于stack exchange,提问作者srh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 08:11:52