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

如何限制PHP特定脚本最大同时调用数并实现请求排队?

解决PHP脚本最大并发调用数限制的方案

嘿,这个场景我太熟悉了——要有限制地使用稀缺的Endpoint资源,同时让多余请求排队等待对吧?我给你分享几个生产环境里验证过的方案,按需选就行:

方案一:Redis分布式队列+信号量(推荐多服务器场景)

如果你的服务部署在多台服务器上,Redis是最靠谱的选择,天然支持分布式锁和队列。核心是用Redis信号量控制并发数,用List结构存请求队列:

实现步骤:

  1. 定义最大并发数(比如你的Endpoint数量是5,就设为5)
  2. 客户端请求脚本时,先尝试获取Redis信号量
  3. 成功获取:执行业务逻辑(调用Endpoint),执行完释放信号量,同时触发队列消费
  4. 获取失败:把请求参数存入Redis队列,等待空闲资源

代码示例(用Predis库):

<?php
require 'vendor/autoload.php';

$redis = new Predis\Client([
    'scheme' => 'tcp',
    'host'   => '127.0.0.1',
    'port'   => 6379,
]);

$maxConcurrent = 5; // 你的Endpoint数量
$semaphoreKey = 'script_semaphore';
$queueKey = 'script_request_queue';

// 尝试获取信号量
$currentCount = $redis->incr($semaphoreKey);
if ($currentCount == 1) {
    // 第一次获取,设置过期时间防止脚本挂死导致信号量泄漏
    $redis->expire($semaphoreKey, 300);
}

if ($currentCount <= $maxConcurrent) {
    // 拿到资源,执行业务逻辑
    try {
        // 这里写你的Endpoint调用逻辑
        echo "执行脚本:" . json_encode($_REQUEST) . "\n";
        // 模拟执行时间
        sleep(5);
    } finally {
        // 释放信号量
        $redis->decr($semaphoreKey);
        // 从队列取下一个请求执行(可以用Lua脚本保证原子性,这里简化处理)
        $nextRequest = $redis->lpop($queueKey);
        if ($nextRequest) {
            // 可以用curl或者exec触发下一个请求,比如:
            // exec("php your_script.php " . escapeshellarg($nextRequest) . " &");
            echo "触发队列中的下一个请求:{$nextRequest}\n";
        }
    }
} else {
    // 没拿到资源,存入队列
    $redis->rpush($queueKey, json_encode($_REQUEST));
    echo "请求已加入队列,当前队列长度:" . $redis->llen($queueKey) . "\n";
    // 返回客户端排队提示,比如HTTP 202 Accepted
    http_response_code(202);
}

方案二:文件锁+本地队列(适合单服务器场景)

如果只有一台服务器,不用额外依赖Redis,用文件系统就能实现,成本低:

实现步骤:

  1. 用一个计数器文件记录当前并发数
  2. 用队列文件存等待的请求参数
  3. 操作计数器和队列时必须加文件锁,防止并发冲突

代码示例:

<?php
$maxConcurrent = 5;
$counterFile = __DIR__ . '/counter.lock';
$queueFile = __DIR__ . '/queue.txt';

// 加锁操作计数器
$counterHandle = fopen($counterFile, 'c+');
flock($counterHandle, LOCK_EX);
$currentCount = (int)fread($counterHandle, 10);

if ($currentCount < $maxConcurrent) {
    // 允许执行,更新计数器
    ftruncate($counterHandle, 0);
    fwrite($counterHandle, $currentCount + 1);
    flock($counterHandle, LOCK_UN);
    fclose($counterHandle);

    // 执行业务逻辑
    try {
        echo "执行脚本:" . json_encode($_REQUEST) . "\n";
        sleep(5);
    } finally {
        // 释放计数器
        $counterHandle = fopen($counterFile, 'c+');
        flock($counterHandle, LOCK_EX);
        $currentCount = (int)fread($counterHandle, 10);
        ftruncate($counterHandle, 0);
        fwrite($counterHandle, max(0, $currentCount - 1));
        flock($counterHandle, LOCK_UN);
        fclose($counterHandle);

        // 处理队列
        $queueHandle = fopen($queueFile, 'c+');
        flock($queueHandle, LOCK_EX);
        $queueContent = trim(fread($queueHandle, 1024 * 1024));
        if (!empty($queueContent)) {
            $requests = explode("\n", $queueContent);
            $nextRequest = array_shift($requests);
            // 更新队列文件
            ftruncate($queueHandle, 0);
            fwrite($queueHandle, implode("\n", $requests));
            // 触发下一个请求
            echo "触发队列中的下一个请求:{$nextRequest}\n";
            // exec("php your_script.php " . escapeshellarg($nextRequest) . " &");
        }
        flock($queueHandle, LOCK_UN);
        fclose($queueHandle);
    }
} else {
    // 加入队列
    flock($counterHandle, LOCK_UN);
    fclose($counterHandle);

    $queueHandle = fopen($queueFile, 'a');
    flock($queueHandle, LOCK_EX);
    fwrite($queueHandle, json_encode($_REQUEST) . "\n");
    flock($queueHandle, LOCK_UN);
    fclose($queueHandle);

    echo "请求已加入队列\n";
    http_response_code(202);
}

关键注意事项

  • 超时处理:不管用哪种方案,都要给执行中的脚本设置超时时间,防止资源泄漏(比如Redis信号量设过期,文件锁场景加超时检测)
  • 队列持久化:Redis默认持久化,文件队列也要确保不会丢失(比如用append模式写入)
  • 客户端提示:给排队的客户端返回明确的状态码(比如202 Accepted)和排队位置,提升用户体验

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 07:50:12