PHPでGuzzleHttpを使用して並列処理(同時実行数の制御)の確認
概要
知りたかったこと
- GuzzleHttpでPoolの使用時に設定するconcurrency(並列処理数)の制御
- 具体的には、全部で10リクエスト投げる処理でconcurrencyに2を設定して実行した場合、以下のどちらになるか調査
試した環境
- lumen: v5.5.2
- GuzzleHttp: 6.3.3(lumen導入時に一緒に入ったもの)
試したサンプルコード
routes/web.php
$router->get('/wait', 'SampleController@wait');
app/Http/Controllers/SampleController.php
- リクエストパラメータの秒数分Sleep後にレスポンスを返す関数
public function wait(Request $request) { $wait = $request->input("sec"); sleep((int)$wait); return "ok" . $wait; }
app/Console/Kernel.phpの$commandsに追加
- API呼び出しをartisanを呼び出すため、実行クラスを追加
protected $commands = [
\App\Console\Commands\Sample\ApiCaller::class,
];
app/Console/Commands/Sample/ApiCaller.php
<?php namespace App\Console\Commands\Sample; use Illuminate\Console\Command; use GuzzleHttp\Pool; use GuzzleHttp\Client; use GuzzleHttp\Psr7\Request; class ApiCaller extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'apicall'; /** * The console command description. * * @var string */ protected $description = 'API呼び出し処理'; /** * API呼び出し処理 */ public function handle() { $this->multiReq(); } private function multiReq() { $uri = [ 'http://localhost/api/wait?sec=1', 'http://localhost/api/wait?sec=10', 'http://localhost/api/wait?sec=1', 'http://localhost/api/wait?sec=1', 'http://localhost/api/wait?sec=1', 'http://localhost/api/wait?sec=1', 'http://localhost/api/wait?sec=1', 'http://localhost/api/wait?sec=1', 'http://localhost/api/wait?sec=1', 'http://localhost/api/wait?sec=1', ]; $client = new Client(); $requests = function () use ($uri) { foreach ($uri as $u) { yield new Request('GET', $u); } }; $pool = new Pool($client, $requests(), [ 'concurrency' => 2, 'fulfilled' => function ($response, $index) { // this is delivered each successful response \Log::info("成功::index::" . $index); }, 'rejected' => function ($reason, $index) { // this is delivered each failed request \Log::info("失敗::index::" . $index); }, ]); // Initiate the transfers and create a promise $promise = $pool->promise(); // Force the pool of requests to complete. $promise->wait(); } }
実行コマンド
php artisan apicall
結果
- 以下のようなログが出力される
- 2つ目(indexが1)の10秒掛かるリクエストが返されるまでに3つ目のリクエストが投げられなか確認したところ、レスポンス待たずにどんどんリクエストしているので並列処理としては常時2つを保つように動いていることがわかった
[2018-12-05 17:17:34] lumen.INFO: 成功::index::0 [] [] [2018-12-05 17:17:36] lumen.INFO: 成功::index::2 [] [] [2018-12-05 17:17:38] lumen.INFO: 成功::index::3 [] [] [2018-12-05 17:17:40] lumen.INFO: 成功::index::4 [] [] [2018-12-05 17:17:42] lumen.INFO: 成功::index::5 [] [] [2018-12-05 17:17:43] lumen.INFO: 成功::index::1 [] [] [2018-12-05 17:17:44] lumen.INFO: 成功::index::7 [] [] [2018-12-05 17:17:44] lumen.INFO: 成功::index::6 [] [] [2018-12-05 17:17:45] lumen.INFO: 成功::index::8 [] [] [2018-12-05 17:17:45] lumen.INFO: 成功::index::9 [] []
Azure Data Studioの設定方法(SQLの実行計画)
概要
- Azure SQL Databaseを使っていて、Azure Data Studio(MSが提供しているクライアントツール)を試してみた
- 実行計画のところだけ、ハマったのでメモ
バージョン
- OS:10.12.6
- Azure Data Stduio:1.1.3
インストール
以下のサイトからダウンロードしてインストールするだけ!
DB接続
既にAzure SQL Databaseが用意されていれば、以下のサイトを参考にして接続情報を入力するだけ!
Widget追加(ダッシュボード)
- これまで、Azure Portal上で確認していた遅いクエリなどをクライアントツールからも確認可能となる
- 設定しておいて損はないと思うので、以下のサイトを参考にして設定するだけ!
実行計画
- 参考にするサイトは、Widget追加と同じ
- 参考サイトだと、実行計画の確認するときに「Explain」ボタンがクリックするようになっているが、デフォルトだと「Explain」ボタンが表示されていない
「Explain」ボタンはデフォルト非表示になっているで以下の流れで表示するように設定する
- Azure Data Studioを起動しDBに接続する
- 「Command + Shift + P」でコマンドパレットを開く
- コマンドパレットから「Preferences: Open User Settings」を開く
- ユーザー設定画面で、「workbench.enablePreviewFeatures」を検索
- 左側の鉛筆アイコンをクリックし、「false」から「true」へ変更
- trueを設定すると、以下のスクショのように右側に反映される
Command + Nとかでクエリのページを新規で開くと、「Explain」ボタンが表示されている
Nuxt.jsの開発サーバでHot Module Reloading (HMR)のエラー対策
概要
- Nuxt.jsのテンプレートを使ったプロジェクト
- 「yarn run dev」コマンドで開発サーバ起動(デフォルトの3000ポート)
- コンソールログに「hot-update.json timed out」というエラー出力
- ホットリローディングができない
バージョン
- node:10.12.0
- nuxt.js:2.0.0
- nuxtjs/pwa:2.6.0
- vue-cli:2.9.6
- yarn:1.10.1
対応方法
- 開発サーバのポートを変更
- package.jsonのscriptsを変更
"scripts": { "dev": "PORT=3333 nuxt", },
- 特に3000番ポートは使用していなかったので、なぜこれでうまくいくようになったかは不明。。。
参考サイト(開発サーバのポート変更)
Nuxt.jsでPWAをGitHub Pagesにコマンドでデプロイする流れ
概要
バージョン
- node:10.12.0
- nuxt.js:2.0.0
- nuxtjs/pwa:2.6.0
- push-dir:0.4.1
- vue-cli:2.9.6
- yarn:1.10.1
参考記事
方法
Nuxt.jsでプロジェクト生成
vue init nuxt-community/starter-template nuxt-pwa
PWAモジュールを追加
- プロジェクトのルートディレクトリでpwaモジュールを追加
yarn add @nuxtjs/pwa
- nuxt.config.jsに「modules」と「manifest」を追記
modules: [ '@nuxtjs/pwa', ], manifest: { name: 'yasutomog nuxt pwa', lang: 'ja' },
- .gitignoreに追記
sw.*
ルートディレクトリ下のstaticディレクトリにアプリアイコンを追加
- android-chrome-192x192.png
- android-chrome-512x512.png
- apple-touch-icon-120x120.png
- apple-touch-icon-152x152.png
- apple-touch-icon-180x180.png
- apple-touch-icon-60x60.png
- apple-touch-icon-76x76.png
- apple-touch-icon.png
- favicon-16x16.png
- favicon-32x32.png
- favicon.ico
- msapplication-icon-144x144.png
- mstile-150x150.png
GitHubの準備
push-dirを追加
- ルートディレクトリで以下を実行
yarn add push-dir
- nuxt.config.jsに「routerBase」を追記
const routerBase = process.env.DEPLOY_ENV === 'GH_PAGES' ? { router: { base: '/nuxt-pwa/' } } : {} module.exports = { ...routerBase,
"scripts": { "deploy": "push-dir --dir=dist --branch=gh-pages --cleanup", "build:gh-pages": "DEPLOY_ENV=GH_PAGES nuxt build", "generate:gh-pages": "DEPLOY_ENV=GH_PAGES nuxt generate" },
PWAの生成とデプロイ
yarn run generate:gh-pages yarn run deploy
GitHub Pagesを確認
AjaxのCSVダウンロード(Excelでの文字化け対応)
概要
- 既に色々なところであがっているトピックなので今更だが、Excelの特定バージョンでのみ文字化けするということがあったので、対策をメモ
- 一般的にCSVをExcelで開く時の文字化け対策としては以下2つ。
- 今回はUTF-8(BOM付き)で対応していたところ、以下のExcelバージョンでのみ日本語が文字化け(同じ2007でも違うバージョンだと問題なく表示できていた、、)
- Excel 2007(12.0.4518.1014)
- ダウンロード処理のざっくりとした流れは以下。
- ajaxでPOST通信
- サーバ側からファイル内容を返す
- JavaScriptでBlobを使いファイルとそのリンクを作成
- aタグを作ってリンクを紐づけ、clickイベント実行
対応
- JavaScriptでShift_JISに変換してファイルを作成するように変更することで、文字化けしていたExcelでも正しく表示できました。
ファイル内容の文字コードを変更するために、encoding.js(1.0.29)を追加しました。
// ダウンロード用Ajax通信のコールバック(サンプルコード) let rt = response.responseText, strRt = Encoding.stringToCode(rt), arrRt = Encoding.convert(strRt, "SJIS", "UNICODE"), u8a = new Uint8Array(arrRt), blob = new Blob([u8a], { 'type' : 'text/csv;charset=sjis;' }), blobUrl = window.URL.createObjectURL(blob), a = document.createElement('a'); a.href = blobUrl; a.download = 'hoge.csv'; a.click();
Lumen(Laravel)のQueue管理クラス(DB)の拡張方法
概要
- Lumenを使用したWebアプリを運用している中で、Queue(DB)を使用
- DBは、Azure SQL Databaseを使用
- Azure SQL Databaseでは一時的に負荷が上がると、強制的に接続を切断しDB側の再構成が走る
DB接続をキャッシュしたり、コネクションプーリングなどを使用している場合、古いDB接続が利用されるとDB接続エラーが発生する docs.microsoft.com - LumenのQueue(DB)では、DB接続をキャッシュして使用しているため、デフォルトのまま使用しているとDB負荷が上がったときにDB接続エラーが連続して発生するようになる yasutomo.hatenablog.com
前提
詳細
Illuminate\Queue\DatabaseQueueを継承したクラスを作成
<?php namespace App\Libs; use Illuminate\Queue\DatabaseQueue; class CustomDatabaseQueue extends DatabaseQueue { // DB再接続時のwait秒 const RETRY_WAIT_SEC = 10; // DB再接続用のSQL STATEコード const RETRY_SQL_STATE = ["08S01", "HY000"]; /** * DBへの再接続処理を加えたoverride関数 * * @see DatabaseQueue::pop() * @param string $queue * @return \Illuminate\Contracts\Queue\Job|null * @throws \Exception|\Throwable */ public function pop($queue = null) { $queue = $this->getQueue($queue); try { try { // DB負荷が高くなるとAzure SQL Databaseは、強制的に切断しDB再構築する // Lumenの標準実装では、DB接続インスタンスを保持して使いまわしているため // DB再構築後も、切断されたDB接続インスタンスを使用しようとするため // キュー管理テーブルを参照するタイミングでDB接続エラーが発生し続ける $this->database->beginTransaction(); } catch (\Throwable $e) { \Log::info($e); if (property_exists($e, 'errorInfo')) { $sqlstate = $e->errorInfo[0]; $sqlerrcd = $e->errorInfo[1]; \Log::info("SQLSTATE::" . $sqlstate); \Log::info("SQLERRCD::" . $sqlerrcd); // DBのコネクションプーリングを使用していない場合でも // 連続して接続エラーが発生するケースがある(Azureサポート) // MSサイトから再接続までには最低でも5秒は間隔を空けることを推奨しているので // 余裕をもって10秒後に再接続する処理 // https://docs.microsoft.com/ja-jp/azure/sql-database/sql-database-develop-error-messages if (in_array($sqlstate, self::RETRY_SQL_STATE)) { \Log::info('キュー管理テーブル参照時にDB接続エラーが発生したので、10秒後にDB再接続します。'); sleep(10); $this->database->reconnect(); $this->database->beginTransaction(); \Log::info('キュー管理テーブルの再接続に成功しました。'); } else { throw $e; } } else { throw $e; } } if ($job = $this->getNextAvailableJob($queue)) { return $this->marshalJob($queue, $job); } $this->database->commit(); } catch (\Throwable $e) { $this->database->rollBack(); throw $e; } } }
Illuminate\Queue\Connectors\DatabaseConnectorを継承したクラスを作成
<?php namespace App\Libs; use Illuminate\Queue\Connectors\DatabaseConnector; /** * キュー管理用のDBコネクタクラス * * Class CustomDatabaseConnector * @package App\Libs */ class CustomDatabaseConnector extends DatabaseConnector { /** * returnするクラスを変更したoverride関数 * * @see DatabaseConnector::connect() * @param array $config * @return \Illuminate\Contracts\Queue\Queue */ public function connect(array $config) { return new CustomDatabaseQueue( $this->connections->connection($config['connection'] ?? null), $config['table'], $config['queue'], $config['retry_after'] ?? 60 ); } }
App\Providers\AppServiceProviderの改修
<?php namespace App\Providers; use Illuminate\Support\ServiceProvider; use App\Libs\CustomDatabaseConnector; class AppServiceProvider extends ServiceProvider { /** * Register any application services. * * @return void */ public function register() { // キュー管理のDBコネクタ登録 $queueManager = $this->app['queue']; $queueManager->addConnector('customdatabase', function () { return new CustomDatabaseConnector($this->app['db']); }); } }
api/config/queue.php
<?php return [ /* |-------------------------------------------------------------------------- | Default Queue Driver |-------------------------------------------------------------------------- | | The Laravel queue API supports a variety of back-ends via an unified | API, giving you convenient access to each back-end using the same | syntax for each one. Here you may set the default queue driver. | | Supported: "null", "sync", "database", "beanstalkd", "sqs", "redis" | */ 'default' => env('QUEUE_DRIVER', 'customdatabase'), /* |-------------------------------------------------------------------------- | Queue Connections |-------------------------------------------------------------------------- | | Here you may configure the connection information for each server that | is used by your application. A default configuration has been added | for each back-end shipped with Laravel. You are free to add more. | */ 'connections' => [ 'customdatabase' => [ 'connection' => 'hoge', 'driver' => 'customdatabase', 'table' => 'QueueJobs', 'queue' => 'default', 'retry_after' => 60, ] ], ];
artisanコマンドを変更
- php artisan queue:work customdatabase