首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用php-fpm/Nginx下载脚本导致CPU高负载

使用php-fpm/Nginx下载脚本导致CPU高负载
EN

Stack Overflow用户
提问于 2013-11-17 03:44:39
回答 2查看 1.2K关注 0票数 0

我有一个很好的文件共享使用的服务器,但我在下载脚本时遇到了问题。我使用运行在nginx上的PHP-FPM。

服务器规格:

代码语言:javascript
复制
2x Intel Xeon E5
CPU: 92GB RAM
10x2TB (RAID6)
And we use 1 SSD disk for CashCade

当我在apache服务器上运行这个脚本时,它工作得很好,但我想在nginx服务器上运行它,因为apache占用了很多内存。(RAM)但是当我在nginx上运行这个脚本时,真的发生了一些事情-它占用了CPU的30%,只需要一次下载!请注意,下载开始后3-4分钟后,CPU负载恢复正常(但下载仍在继续)。

这是LINUX中的“顶端”,当我下载的时候…

我不知道为什么,PHP-FPM脚本占用了大量CPU资源。脚本:

代码语言:javascript
复制
class ResumeDownload {
    private $file;
    private $name;
    private $boundary;
    private $delay = 0;
    private $size = 0;

    function __construct($file, $delay = 0) {
        if (! is_file($file)) {
            header("HTTP/1.1 400 Invalid Request");
            die("<h3>File Not Found</h3>");
        }

        $this->size = filesize($file);
        $this->file = fopen($file, "r");
        $this->boundary = md5($file);
        $this->delay = $delay;
        $this->name = basename($file);
    }

    public function process() {
        $ranges = NULL;
        $t = 0;
        if ($_SERVER['REQUEST_METHOD'] == 'GET' && isset($_SERVER['HTTP_RANGE']) && $range = stristr(trim($_SERVER['HTTP_RANGE']), 'bytes=')) {
            $range = substr($range, 6);
            $ranges = explode(',', $range);
            $t = count($ranges);
        }

        header("Accept-Ranges: bytes");
        header("Content-Type: application/octet-stream");
        header("Content-Transfer-Encoding: binary");
        header(sprintf('Content-Disposition: attachment; filename="%s"', $this->name));

        if ($t > 0) {
            header("HTTP/1.1 206 Partial content");
            $t === 1 ? $this->pushSingle($range) : $this->pushMulti($ranges);
        } else {
            header("Content-Length: " . $this->size);
            $this->readFile();
        }

        flush();
    }

    private function pushSingle($range) {
        $start = $end = 0;
        $this->getRange($range, $start, $end);
        header("Content-Length: " . ($end - $start + 1));
        header(sprintf("Content-Range: bytes %d-%d/%d", $start, $end, $this->size));
        fseek($this->file, $start);
        $this->readBuffer($end - $start + 1);
        $this->readFile();
    }

    private function pushMulti($ranges) {
        $length = $start = $end = 0;
        $output = "";

        $tl = "Content-type: application/octet-stream\r\n";
        $formatRange = "Content-range: bytes %d-%d/%d\r\n\r\n";

        foreach ( $ranges as $range ) {
            $this->getRange($range, $start, $end);
            $length += strlen("\r\n--$this->boundary\r\n");
            $length += strlen($tl);
            $length += strlen(sprintf($formatRange, $start, $end, $this->size));
            $length += $end - $start + 1;
        }
        $length += strlen("\r\n--$this->boundary--\r\n");
        header("Content-Length: $length");
        header("Content-Type: multipart/x-byteranges; boundary=$this->boundary");
        foreach ( $ranges as $range ) {
            $this->getRange($range, $start, $end);
            echo "\r\n--$this->boundary\r\n";
            echo $tl;
            echo sprintf($formatRange, $start, $end, $this->size);
            fseek($this->file, $start);
            $this->readBuffer($end - $start + 1);
        }
        echo "\r\n--$this->boundary--\r\n";
    }

    private function getRange($range, &$start, &$end) {
        list($start, $end) = explode('-', $range);

        $fileSize = $this->size;
        if ($start == '') {
            $tmp = $end;
            $end = $fileSize - 1;
            $start = $fileSize - $tmp;
            if ($start < 0)
                $start = 0;
        } else {
            if ($end == '' || $end > $fileSize - 1)
                $end = $fileSize - 1;
        }

        if ($start > $end) {
            header("Status: 416 Requested range not satisfiable");
            header("Content-Range: */" . $fileSize);
            exit();
        }

        return array(
                $start,
                $end
        );
    }

    private function readFile() {
        while ( ! feof($this->file) ) {
            echo fgets($this->file);
            flush();
            usleep($this->delay);
        }
    }

    private function readBuffer($bytes, $size = 1024) {
        $bytesLeft = $bytes;
        while ( $bytesLeft > 0 && ! feof($this->file) ) {
            $bytesLeft > $size ? $bytesRead = $size : $bytesRead = $bytesLeft;
            $bytesLeft -= $bytesRead;
            echo fread($this->file, $bytesRead);
            flush();
            usleep($this->delay);
        }
    }
}

download.php (我在其中运行脚本)

代码语言:javascript
复制
// ... some code that get the file details from extrenal Database...

$fileArr = $query->fetch_assoc();
$file = 'uploads/' . $download['fileid'] . '/' . $fileArr['name'];

if(file_exists($file)) {
    $mysqli->close();
    require 'class.download.php';
    set_time_limit(0);
    $download = new ResumeDownload($file, 0); //delay about in microsecs 
    $download->process();
}
EN

回答 2

Stack Overflow用户

发布于 2013-11-17 07:37:31

我不知道为什么,但是PHP-FPM脚本占用了很多CPU。

是什么让你觉得这是个问题?当服务器不受CPU限制时,程序将尽可能快地运行,尽可能多地使用可用的CPU。只有当服务器不能足够快地处理请求时,高CPU使用率才是真正重要的,因为所有脚本都在竞争使用CPU资源,并阻止彼此运行。

您的脚本正在尽可能快地运行(即尽可能多地使用CPU ),因为您已经告诉它:

代码语言:javascript
复制
$download = new ResumeDownload($file, 0); //delay about in microsecs

也就是说,您的下载脚本不会等待,因此它会尽可能快地循环以将数据推送到网络连接中。

很明显,服务器将数据推送到网络连接的速度比通过互联网发送数据的速度更快,所以很多时候你的脚本只是循环,等待网络连接能够发送数据--正如' top‘输出顶部的高空闲时间所证明的那样。

你可以通过设置延迟来让脚本真正休眠,或者你可以使用Nginx来完全删除x-accel中的负载。这里有一个配置:Serve large file with PHP and nginx X-Accel-Redirect,它在你实际受CPU限制的情况下会更有效率。

票数 1
EN

Stack Overflow用户

发布于 2017-05-27 15:43:06

增加此方法中的延迟:

代码语言:javascript
复制
 $download = new ResumeDownload($file, 500); //delay about in microsecs

或者使用nginx x-accel-redirect代替ResumeDownload类

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/20022966

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档