【问题标题】:Why is my app running out of memory? Unsetting variables, using chunk为什么我的应用程序内存不足?使用块取消设置变量
【发布时间】:2015-03-22 00:27:01
【问题描述】:

我有下面的简单应用程序。我正在关闭 Laravel 中的查询日志记录,我在可能的情况下取消设置,但是在我的 2GB Linode 上的 RAM 用完之前,此功能只会处理大约 800 条记录。我知道我问了很多人,但我似乎看不出我在哪里泄漏了内存。

实际上只有两个主要步骤。

第 1 步 - 将记录从临时表移至生产环境

class ListingMigrator
{
    public function __construct($tempListing, $feed)
    {
        $this->tempListing = $tempListing;
        $this->listing = $this->listingInstance();
        $this->feed = $feed;
    }

    public static function migrateListing($listing, $feed)
    {
        $instance = new static($listing, $feed);
        return $instance->migrate();
    }

    public function migrate()
    {
        $this->addExternalData();
        $this->populateListing();
        $this->processPhotos();
        $this->deleteTempListing();
    }

    private function listingInstance()
    {
        DB::connection()->disableQueryLog();
        $listing = Listing::findByMud($this->tempListing->matrix_unique_id);
        return $listing ?: new Listing;
    }

    private function processPhotos()
    {
        $retsApi = new RetsFeedApi($this->feed);
        /* Initialize Object */
        $rets = $retsApi->findMostRecent();
        $photos = $rets->getPhotosForListing($this->listing->matrix_unique_id);
                        foreach ($photos as $photo)
                        {
                            $uploader = new PhotoProcessor($this->listing, $photo);
                            $uploader->process();
                        }
    }

    private function populateListing()
    {
        DB::connection()->disableQueryLog();
        $this->listing->fill($this->tempListing->toArray());
        $this->listing->imported_at = $this->tempListing->created_at;
        $this->listing->board = $this->tempListing->board;

        return $this->listing->save();
    }

    private function addExternalData()
    {
        // Get Google lattitude and longitude
        $googlecoords = getGoogleMapInfo($this->tempListing->FullAddress, $this->tempListing->City);
        $this->listing->GoogleLat = $googlecoords['GoogleLat'];
        $this->listing->GoogleLong = $googlecoords['GoogleLong'];

        // Add or update the Subdivision Table (helper function)
        $subdivisiondata = SubdivisionUpdate($this->tempListing->board, $this->tempListing->SubCondoName, $this->tempListing->Development);
        $this->listing->SubdivisionID = $subdivisiondata['id'];
    }

    private function deleteTempListing()
    {
        return $this->tempListing->delete();
    }
}

第 2 步 - 下载照片并重新上传到 Amazon S3

class PhotoProcessor
{
  public function __construct(Listing $listing, $photoData)
  {
    $this->bucket       = 'real-estate-listings';
    $this->s3           = App::make('aws')->get('s3');
    $this->tempFileName = 'app/storage/processing/images/retsphotoupload';
    $this->photoData    = $photoData;
    $this->listing      = $listing;
    $this->photo        = new RetsPhoto;
  }

  public function process()
  {
    $this->storeTempFile();
    $this->storeFileInfo();
    $this->buildPhoto();

    $success = $this->pushToS3();

    // if Result has the full URL or you want to build it, add it to $this->photo
    DB::connection()->disableQueryLog();
    $this->listing->photos()->save($this->photo);  
    $this->removeTempFile();
    unset ($this->photoData);
    return $success;
  }

  private function storeTempFile()
  {
    return File::put($this->tempFileName, $this->photoData['Data']) > 0;
  }

  private function storeFileInfo()
  {
    $fileInfo = getimagesize($this->tempFileName);
    // Could even be its own object
    $this->fileInfo = [
    'width'     => $fileInfo[0],
    'height'    => $fileInfo[1],
    'mimetype'  => $fileInfo['mime'],
    'extension' => $this->getFileExtension($fileInfo['mime'])
    ];
  }

  private function buildPhoto()
  {
    $this->photo->number = $this->photoData['Object-ID']; // Storing this because it is relevant order wise
    $this->photo->width  = $this->fileInfo['width'];
    $this->photo->height = $this->fileInfo['height'];
    $this->photo->path   = $this->getFilePath();
  }

  private function getFilePath()
  {
    $path   = [];
    if ($this->listing->City == NULL)
    {
      $path[] = Str::slug('No City');
    }
    else
    {
      $path[] = Str::slug($this->listing->City, $separator = '-'); 
    }

    if ($this->listing->Development == NULL)
    {
      $path[] = Str::slug('No Development');
    }
    else
    {
      $path[] = Str::slug($this->listing->Development, $separator = '-');  
    }

    if ($this->listing->Subdivision == NULL)
    {
      $pathp[] = Str::slug('No Subdivision');
    }
    else
    {
      $path[] = Str::slug($this->listing->Subdivision, $separator = '-');  
    }

    if ($this->listing->MLSNumber == NULL)
    {
      $pathp[] = Str::slug('No MLSNumber');
    }
    else
    {
      $path[] = Str::slug($this->listing->MLSNumber, $separator = '-');
    }

      $path[] = $this->photoData['Object-ID'].'.'.$this->fileInfo['extension'];

      return strtolower(join('/', $path));
  }

  private function pushToS3()
  {
    return $this->s3->putObject([
      'Bucket'     => $this->bucket,
      'Key'        => $this->photo->path,
      'ContentType'=> $this->fileInfo['mimetype'],
      'SourceFile' => $this->tempFileName
    ]);
  }

  private function getFileExtension($mime)
  {
    // Use better algorithm than this
    $ext = str_replace('image/', '', $mime);
    return $ext == 'jpeg' ? 'jpg' : $ext;
  }

  private function removeTempFile()
  {
    return File::delete($this->tempFileName);
  }
}

编辑以显示 RetsPhoto

class RetsPhoto extends Eloquent {

    protected $table = 'rets_property_photos';

    public function listing() {

        return $this->belongsTo('Listing', 'matrix_unique_id', 'matrix_unique_id');
    }

}

编辑 #2:分块调用 这是在应用程序/命令中,唯一的东西是下面的 fire() 函数:

public function fire()
{
    // Turn off query logging
    DB::connection()->disableQueryLog();

    $feeds = RetsFeed::where('active','=',1)->get();
    foreach ($feeds as $feed)
    {

        $class = "TempListing{$feed->board}";

        $listings = $class::orderBy('MatrixModifiedDT','desc');

        $listings->chunk(50, function($listings) use($feed) {
            $listings->each(function($listing) use ($feed) {
                ListingMigrator::migrateListing($listing,$feed);
                echo "Feed: $feed->board\r\n";
                echo "SubcondoName: $listing->SubCondoName\r\n";
                echo "Development: $listing->Development\r\n";
                echo "\r\n";
            });
        });
    }

}

【问题讨论】:

  • 我有一种感觉getimagesize将资源加载到内存中。
  • @Marty 我该怎么说?
  • 我现在正试图确定,以及如何释放那段记忆。
  • 您可以尝试使用imagecreatefromjpeg 并从中获取图像大小,然后在其上使用imagedestroy,看看是否会有所不同。
  • 这与我有关... $this->photo = new RetsPhoto;请使用该课程更新帖子。

标签: php laravel laravel-4 eloquent


【解决方案1】:

我想我已经明白了。

  1. 您的系统将所有照片数据保存在内存中。正如未设置 ($this->photoData) 所见证的那样;

    1. 问题是需要先完成进程功能。您的应用程序不太可能处理任何照片,因此当您不断从文件系统中抓取它们时,您甚至在处理一张照片之前都会耗尽内存。

要确认这一点,只需获取 1 个不使用块方法的文件。 我对 Laravel 不是很熟悉,它可能会同时抓取所有文件并吃掉内存。

您可以使用 memory_get_usage(true) 进行一些跟踪,以准确找出 ram 是从哪里吃掉的。我建议先分析火法。

【讨论】:

  • 我不知道。它为临时表中的一条记录提取所有照片,并将该记录迁移到主表,下载图像并将其重新上传到 S3,然后继续。它一次下载一张图片。
猜你喜欢
  • 1970-01-01
  • 2013-12-21
  • 2010-10-10
  • 2010-10-08
  • 2016-03-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-27
相关资源
最近更新 更多