やりたい事
- 毎日、スクレイピング処理を実行
- スクレイピングした結果をGoogleスプレッドシートに書き込む
- 大量の処理が走るので、PHPメモリエラーになる可能性が高い
- スプレッドシートの結果を毎日、バックアップする
- スプレッドシートの結果を毎日、メールで送る
やった事
- ①スケジューラの設定
- ②バッチコマンドクラスの作成
- ③ジョブキューの仕組みを実装
- ④GASでスプレッドシート書き込みの処理を実装
- ⑤supervisorの設定
0.各キーワード説明
Job Queueとは?
ジョブキューとはジョブをキューで管理するものです。
キューとはFIFO(First In First Out)を実現するデータ構造です。
キューに登録されたモノは、キューに登録した順に処理されます。
参考:http://tech.voyagegroup.com/archives/495474.html
全体構成
①スケジューラの設定
Kernel.phpに朝9時でバッチを動くように設定
<?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
/**
* The Artisan commands provided by your application.
*
* @var array
*/
protected $commands = [
//
];
/**
* Define the application's command schedule.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*/
protected function schedule(Schedule $schedule)
{
// バッチを実行
$schedule->command('batch:daily')->dailyAt('09:00');
}
/**
* Register the commands for the application.
*
* @return void
*/
protected function commands()
{
$this->load(__DIR__.'/Commands');
require base_path('routes/console.php');
}
}
②バッチコマンドクラスの作成
コマンドでの処理
- 本日分のジョブキューを詰める
- 前日分のジョブの実行結果をメールで送る
<?php
namespace App\Console\Commands;
use App\Entities\JobStatus;
use App\Entities\Project;
use App\Jobs\ProcessGoogleSearch;
use App\Mail\DailyBatchResultMail;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Mail;
class dailyBatchCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'batch:daily';
/**
* The console command description.
*
* @var string
*/
protected $description = '1日1回実行されるバッチ用コマンド';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
* @throws \Exception
*/
public function handle()
{
$error = $this->SendJobStatusesByEmail();
if ($error !== null) {
throw new \Exception($error);
}
$error = $this->updateIsActive();
if ($error !== null) {
throw new \Exception($error);
}
$this->refreshQueue();
$projects = Project::where('is_active','=', 1)->get();
$this->registeredJobStatuses($projects);
$this->pushQueue($projects);
}
// 以下、省略
}
③ジョブキューの仕組みを実装
※サンプルコードなので、細かい部分は省略。
<?php
namespace App\Jobs;
use App\Entities\JobStatus;
use App\Entities\Project;
use App\Enums\JobState;
use App\Services\CustomSearchService;
use App\Services\SearchService;
use App\Services\SpreadsheetService;
use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Log;
class ProcessSearch implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* 最大試行回数
*
* @var int
*/
public $tries = 0;
/**
* 詳細検索するかどうか(TRUE)
*/
const IS_DETAIL_SEARCH_TRUE = 1;
/**
* 詳細検索するかどうか(FALSE)
*/
const IS_DETAIL_SEARCH_FALSE = 0;
private $project;
private $jobStatus;
private $searchService;
private $customSearchService;
private $spreadsheetService;
/**
* Create a new job instance.
*
* @param Project $project
* @param JobStatus $jobStatus
*/
public function __construct(Project $project, JobStatus $jobStatus)
{
$this->project = $project;
$this->jobStatus = $jobStatus;
$this->searchService = new SearchService();
$this->customSearchService = new CustomSearchService();
$this->spreadsheetService = new SpreadsheetService();
}
/**
* Execute the job.
*
* @return void
* @throws \Exception
*/
public function handle()
{
// 一部、処理を省略
// JobStatusの更新
$jobStatus = $this->jobStatus;
$jobStatus->status = \App\Enums\JobState::RUNNING;
$jobStatus->started_at = Carbon::now();
$jobStatus->save();
$parameter = [];
$keyword = $jobStatus->keyword;
if (!empty($keyword)) {
$searchCount = $this->project->search_count;
// 検索を実行
// サービスクラスの具体的な処理は省略
$parameter['searchResults'][] = $this->customSearchService
->execute($keyword, $searchCount, self::IS_DETAIL_SEARCH_TRUE, $jobStatus);
sleep(mt_rand(config('variables.google_search_min_wait'), config('variables.google_search_max_wait')));
}
// GAS側で使う
$parameter['sheetName'] = $this->project->name;
$parameter['searchCount'] = $searchCount;
// スプレッドシート書き出し
[$spreadSheetUrl, $error] = $this->spreadsheetService->outputSpreadsheet($parameter);
// JobStatusの更新
if (empty($error)) {
$jobStatus->status = JobState::SUCCESS;
$jobStatus->spreadsheet_url = $spreadSheetUrl;
} else {
$jobStatus->status = JobState::FAILED;
}
$jobStatus->finished_at = Carbon::now();
$jobStatus->save();
}
/**
* 失敗したジョブの処理
*
* @param \Exception $exception
* @return void
*/
public function failed(\Exception $exception)
{
// JobStatusの更新
$jobStatus = JobStatus::where('project_id', $this->project->id)->first();
$jobStatus->status = \App\Enums\JobState::FAILED;
$jobStatus->finished_at = Carbon::now();
$jobStatus->save();
// 失敗の通知をユーザーへ送るなど…
\Log::error($exception->getMessage());
}
}
④GASでスプレッドシート書き込みの処理を実装
- PHPからパラメータを取得
- Excelファイルを作成して、メール送信
- Excelファイルを作成して、Googleドライブにバックアップ
⑤supervisorの設定
まとめ
- 大量の処理が走るような機能があった際に、Job Queueを使うとPHPメモリエラーを防げる可能性がある。
- メールの大量送信やスクレイピング、高速でAPIを叩くような処理には向いてそう。
参考記事
-
https://speakerdeck.com/bumptakayuki/laravel-job-queuede-batutiyun-yong-sitahua
自分の発表資料も参考により具体的に書いてみました。