我可以优化这个脚本,用大量数据更新~6000行

时间:2016-03-11 16:07:52

标签: laravel eloquent guzzle

我需要在数据库中更新~5-6k $items。每个项目都需要HTTP请求才能从页面获取数据。在HTTP GET请求中,我获得了大量的数组(~500-2500),我只需要插入那些不在数据库中的行。在我的流浪苏格兰威士忌盒上,我目前的剧本(每2-4分钟一件)需要花费很多时间。

简化示例:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Http\Requests;
use GuzzleHttp\Client;
use App\Item;
use App\ItemHistory;
use Carbon\Carbon;

use DB;

class UpdateController extends Controller
{
    public function getStart() {
        // Don't cancel the script
        ignore_user_abort(true);
        set_time_limit(0);

        $client = new Client();
        $items = Item::where('updated_at', '<=', Carbon::now()->subDay())->get();

        foreach($items as $item) {
            $response = $client->request('GET', 'API_URL');
            // get the body
            $body = $response->getBody()->getContents();

            $hugeArray = $body['history']; // can be from 100 to 5 000 lines and I use regex to get the "history" array from the body
            $arrayCollection = collect($hugeArray);

            foreach($arrayCollection->take(-100) as $row) { // I take the last 100 since each row = 1 hour, so I get items in the last 100 hours
                $date = new \DateTime($row['created_at']);
                if( ! ItemHistory::whereItemId($item->id)->whereSoldAt($date)->count()) { // Checking if it already exists
                    // I insert the new rows..
                    $history = new ItemHistory;
                    // ....
                    $history->save();
                }
            }
        }
    }
}

我实际上抓取数据并使用正则表达式来查找正文响应中的数组。 难道我做错了什么?它需要很长时间,直到它移动到下一个$item

1 个答案:

答案 0 :(得分:1)

我可以提供简化的答案 - 同步执行,对象水合和批量数据库查询。

考虑以下示例:

$requests = function () use ($items) {
    foreach ($items as $item) {
        yield new GuzzleHttp\Psr7\Request($method, $uri);
    }
};

$client = new GuzzleHttp\Client();

foreach ($requests() as $request) {
    $client->sendAsync($request)
        ->then(
            function(Psr7\Http\Message\ResponseInterface) {
                // process the response into array;

                return $arrayFromResponse;
        })
        ->then(
            function ($unfilteredArray) {
                // filter the array as necessary

                return $filteredArray;
        })
        ->then(
            function($filteredArray) { 
                // create the array for bulk insert / update

                return $sqlArray;
        })
        ->then(
            function($sqlArray) {
                // perform bulk db operations.
            }
        );
}
  1. 同步Http查询 - 上面的示例突出显示了Guzzle的一些异步功能,同时突破了处理步骤。您上面链接的代码是同步的。执行请求,等待响应,处理响应,rince&amp;重复。异步Http请求将确保在处理其他信息时下载数据。我应该注意,您的结果会有所不同,具体取决于您的特定用例,可能会增加资源使用量。

  2. 对象水合 - 也就是当你执行查询时你的ORM正在做什么,它返回一个对象实例(而不是一个数组),是耗时和内存密集的。 @orcamius(Doctrine的开发人员之一)在subject上撰写了一篇相当技术性的文章。虽然这不是Eloquent特定的,但它确实提供了对所有ORM的幕后操作的深入了解。代码段会执行其中的许多操作(参考$itemHistory$historyItem::where)。

  3. 批量数据库操作 - 众所周知的事实是数据库操作很慢。当与物体水合相结合时,这个时间进一步增加。使用1000x记录和1000x插入执行单个插入更好。为此,必须将代码从使用ORM修改为直接使用DB表。如docs

  4. 中所示,可以DB::table('itemHistory')->insert($arrayOfValues)执行批量插入

    更新:虽然未显示,但then()的方法签名为then(callable $fulfilled, callable $onError)。如果出现问题,您可以执行类似

    的操作
    // promise returned from a request
    $p->then(
        function (Psr\Http\Message\ResponseInterface $response) use ($p)
            if ($response->getResponseCode() >= 400) {
                $p->cancel();
            }
            //perform processing
            return $someArray;
        },
        function (RequestException $e) {
            echo $e->getMessage() . "\n";
            echo $e->getRequest()->getMethod();
        })
    ->then(
        function($someArray) use ($p) {
            // filter or other processing
        });
    

    有关Guzzle's Promises的更多信息可以在Github Repo

    中找到