为啥我的 PHP 脚本在生成缩略图时会停止?

Posted

技术标签:

【中文标题】为啥我的 PHP 脚本在生成缩略图时会停止?【英文标题】:Why is my PHP script stopping when generating thumbnails?为什么我的 PHP 脚本在生成缩略图时会停止? 【发布时间】:2021-08-21 20:09:14 【问题描述】:

我是一名 php 初学者,我正在尝试构建一个图库页面,该页面将输出文件夹中所有 195 个图像的缩略图。这些图像平均每个 8 MB。这是目前的情况:

脚本文件夹中的php.ini

allow_url_fopen = On
display_errors = On
enable_dl = Off
file_uploads = On
max_execution_time = 999
max_input_time = 999
max_input_vars = 1000
memory_limit = 999M
post_max_size = 516M
session.gc_maxlifetime = 1440
session.save_path = "/var/cpanel/php/sessions/ea-php74"
upload_max_filesize = 512M
zlib.output_compression = Off

PHP/html 代码

<?php
DEFINE('UPLOAD_DIR', 'sources/');
DEFINE('THUMB_DIR', 'thumbs/');

function GenerateThumbnail($src, $dest)

    $Imagick = new Imagick($src);
    
    $bigWidth = $Imagick->getImageWidth();
    $bigHeight = $Imagick->getImageHeight();
    
    $scalingFactor = 230 / $bigWidth;
    $newheight = $bigHeight * $scalingFactor;
    
    $Imagick->thumbnailImage(230,$newheight,true,true);
    $Imagick->writeImage($dest);
    $Imagick->clear();
    
    return true;    


// Get list of files in upload dir
$arrImageFiles = scandir(UPLOAD_DIR);

// Remove non-images
$key = array_search('.', $arrImageFiles);
if ($key !== false) 
    unset($arrImageFiles[$key]);

$key = array_search('..', $arrImageFiles);
if ($key !== false) 
    unset($arrImageFiles[$key]);

$key = array_search('.ftpquota', $arrImageFiles);
if ($key !== false) 
    unset($arrImageFiles[$key]);

$key = array_search('thumbs', $arrImageFiles);
if ($key !== false) 
    unset($arrImageFiles[$key]);

?><!DOCTYPE HTML>
<html lang="fr">
<head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Select Image</title>
</head>
    <body>
<?php
foreach($arrImageFiles as $imageFile)

        $thumbFullPath =  THUMB_DIR . "th_" . $imageFile;
        $imageFullPath = UPLOAD_DIR . $imageFile;

        if (! file_exists($thumbFullPath))  
                   
            GenerateThumbnail($imageFullPath, $thumbFullPath);
        
        
        echo "<img alt='' src='" . $thumbFullPath . "'>";
    

?>              
    </body>
</html>

我不知道如何解决的两个问题是:

在生成所有缩略图之前(在 30-50 个缩略图之后),脚本似乎在某个点停止。没有记录错误 页面没有加载到浏览器中直到脚本停止,然后我可以看到(不完整的)输出

这是我已经尝试过的:

使用输出缓冲(可能因为新手不正确) 将输出连接到一个变量中并在末尾回显(无变化) 使用不同的方法生成缩略图(imagecopyresample、imagescale、ImageMagick 等),这只会略微改变成功缩略图的数量。

感谢您的任何想法,我有点迷路了。

【问题讨论】:

看起来服务器超时有问题。过程停止需要多长时间? @DavidPauli -- 感谢您的评论。加载时间变化很大。我测试了页面加载时间,得到:48s、123s、57s、115s。我很困惑! “脚本文件夹中的php.ini” 为什么那个文件会有影响? @Olivier - 感谢您的参与。正如我所说,我是一个新手,我理解(可能是错误的?)在那里放置一个 php.ini 文件可以让我修改一些参数(执行时间,内存限制等)你有什么建议吗?我渴望学习。 @Kerans 当 PHP 作为 CGI 运行时,您可以将 .user.ini 文件放在目录中以覆盖设置(如 here 所述)。当 PHP 作为 Apache 模块运行时,只能使用 .htaccess 文件。在任何情况下,请始终致电phpinfo() 来检查配置。 【参考方案1】:

问题肯定是超时或资源稀缺(内存或类似的东西)。 This article 显示 ImageMagick 导致每个图像使用 315.03 MB 内存和 47% 的 CPU 使用率。如果您在foreach 循环中循环浏览处理器中的图像,这可能会复合/泄漏。图像转换在服务器资源上可能很昂贵。这也可能是您的某个图像文件或其他一些奇怪的问题。

Ajax

解决这些问题的方法是小批量处理这些问题。创建一个简单的 ajax/php 循环 - 为 ajax 使用一个 html.php 页面并为处理使用一个单独的文件,如下所示:

thumb-display.php

<script>
const batchSize = 5; // number of images per send
function doThumb(nextIndex) 
    $.ajax(
      url: "thumb-process.php",
      method: "post",
      data: nextIndex: nextIndex, batchSize: batchSize,
      dataType: "json"
    ).done(function(response) 
      if (response.thumbs) 
         response.thumbs.forEach( th => $('.thumbs').append('<img  src="'+th+'" />') ) ;
      
      if (response.finished) alert('finished')
      else 
        console.log('processed batch index #' + (nextIndex-batchSize + ' - ' + nextIndex);
        nextIndex += batchSize;
        setTimeout(() =>  doThumb(nextIndex) , 1000);
      
    ).error(function (e) 
       console.error('Error happened on batch index #' + (nextIndex-batchSize + ' - ' + nextIndex, ' - details: ', e);
    )
    ;


$(document).ready(() =>  doThumb(0);  )
</script>


<div class='thumb-list'></div>

thumb-process.php

function GenerateThumbnail($src, $dest)

    $Imagick = new Imagick($src);
    $bigWidth = $Imagick->getImageWidth();
    $bigHeight = $Imagick->getImageHeight();
    $scalingFactor = 230 / $bigWidth;
    $newheight = $bigHeight * $scalingFactor;
    $Imagick->thumbnailImage(230,$newheight,true,true);
    $Imagick->writeImage($dest);
    $Imagick->clear();
    return true;    


// Get list of files in upload dir
$arrImageFiles = scandir(UPLOAD_DIR);

// Remove non-images
$key = array_search('.', $arrImageFiles);
if ($key !== false) 
    unset($arrImageFiles[$key]);

$key = array_search('..', $arrImageFiles);
if ($key !== false) 
    unset($arrImageFiles[$key]);

$key = array_search('.ftpquota', $arrImageFiles);
if ($key !== false) 
    unset($arrImageFiles[$key]);

$key = array_search('thumbs', $arrImageFiles);
if ($key !== false) 
    unset($arrImageFiles[$key]);
$nextIndex = $_POST['nextIndex'];
$max = min( ($nextIndex + $_POST['batchSize']), count($arrImageFiles)-1);
$thumbs = [];
while($nextIndex < $max)    
  $imageFile = $arrImageFiles[$nextIndex ];
  $thumbFullPath =  THUMB_DIR . "th_" . $imageFile;
  $imageFullPath = UPLOAD_DIR . $imageFile;
  if (!$imageFile) 
    $output = array('finished' => 1) ;
    die(json_encode($output));
  
  if (! file_exists($thumbFullPath))             
    GenerateThumbnail($imageFullPath, $thumbFullPath);
  
  $thumbs[]= $thumbFullPath ;
  $nextIndex++;


$output = array('thumbs' => $thumbs);
  
if ($nextIndex >= count($arrImageFiles)-1) $output['finished'] = 1 ;

echo json_encode($output);

.. 坐下来看看你的控制台。如果您遇到超时或其他错误,您会看到 nextIndex 它被阻塞,您可以使用该数字而不是 0 作为起始索引重新加载您的页面。

当然,您可以在 thumb-display.php 中将所有文件路径收集到一个数组中,并通过 ajax 发送每个文件路径(这样就不必每次都重新计算该路径中的所有文件),但我个人感觉更好通过帖子而不是图像路径发送数字。如果您希望发送大量文件路径而不是索引,请告诉我#。

【讨论】:

感谢 Kinglish,这听起来像是一个实用的解决方案。我没有得到的是 - 为什么在脚本停止之前根本没有输出?我怎样才能找到达到的限制(内存或其他)?我有一个 cPanel,如果有帮助的话。感谢您的宝贵时间! @Kerans - 你可以在你的 PHP 文件中初始化 error_reporting - ***.com/questions/1053424/… - 我知道 cpanel 过去也有查看错误日志的方法。我的解决方案的好处是它是可扩展的——可以处理 10,000 张图像,这很好。它比直接的 PHP 循环需要更长的时间,但在您的服务器上更容易并且更容易排除故障 @Kerans 我对 ImageMagik 做了更多的研究(见新的第一段),还编辑了我的脚本以允许您可以在 javascript 中设置的批处理(而不是一个一个)。欢呼【参考方案2】:

@kinglish 的答案很适合通过 ajax 加载它们,但我认为这不是正确的方法。这减轻了服务器的一些处理,因为它不会一次全部运行它们,但如果您希望许多用户访问 195 张图像,它仍然不是一个好的解决方案

这里最大的问题是您试图根据请求处理图像,因此每个页面加载都会请求并重新创建所有 195 个图像。这对性能不利,并且您网站上的 10 位用户可能只需刷新几次页面就会使您的服务器崩溃,除非您为此支付超过 10 美元/月的费用。

所以,这里有一个更好的解决方案。

仅在 198 个图像的循环上运行图像处理脚本服务器端。 您可以查看如何运行命令来执行 php 文件,或者如果您通过 SSH 连接到 linux 机器,则只需执行“php yourfile.php”即可。

该脚本将比通过浏览器请求更快地运行和处理图像。 [不知道具体情况,就是这样]

将图像存储到“缩略图”目录,然后像往常一样通过标签加载它们。 TADA 问题解决了。您只需以这种方式处理一次,其余时间仅将它们提供给客户。 是的,您现在同时拥有原始大小和缩略图版本,这感觉不对,但执行起来比尝试在每个请求上重新生成它们要好得多。

添加一些缓存 [将它们加载到 S3,或使用 cloudflare 来缓存它们],您的网站将与任何网站一样快。

【讨论】:

以上是关于为啥我的 PHP 脚本在生成缩略图时会停止?的主要内容,如果未能解决你的问题,请参考以下文章

使用 C# System.Drawing 生成缩略图时居中裁剪图像

PHP获取远程图片并调整图像大小(转)

性能:视频缩略图/截图生成

找到缩略图时如何获取实际图像?

html如何设置鼠标悬停在缩略图时,上面的大图切换为该缩略图的大图?

图片上传有效,缩略图歪斜?