Microsoft 公式の Azure Storage Blob 操作用ライブラリ microsoft/azure-storage-blob
のリトライ機能を試してみたのでメモ。
microsoft/azure-storage-blob - Packagist
https://packagist.org/packages/microsoft/azure-storage-blob
azure-storage-php/azure-storage-blob at master · Azure/azure-storage-php
https://github.com/Azure/azure-storage-php/tree/master/azure-storage-blob
環境
試した環境は以下。
% php -v
PHP 7.4.5 (cli) (built: Jun 9 2020 14:02:33) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
with Zend OPcache v7.4.5, Copyright (c), by Zend Technologies
インストール
% mkdir azure-storage-ex && cd $_
% composer require microsoft/azure-storage-blob
検証時点(2021.03.05) では以下のバージョンが入った。
% composer show
guzzlehttp/guzzle 7.2.0 Guzzle is a PHP HTTP client library
guzzlehttp/promises 1.4.0 Guzzle promises library
guzzlehttp/psr7 1.7.0 PSR-7 message implementation that also provides common utility methods
microsoft/azure-storage-blob 1.5.2 This project provides a set of PHP client libraries that make it easy to access Microsoft Azure Storage Blob APIs.
microsoft/azure-storage-common 1.5.1 This project provides a set of common code shared by Azure Storage Blob, Table, Queue and File PHP client libraries.
psr/http-client 1.0.1 Common interface for HTTP clients
psr/http-message 1.0.1 Common interface for HTTP messages
ralouphie/getallheaders 3.0.3 A polyfill for getallheaders.
使ってみる
まずは普通に使うコードを書く。
コンテナの一覧を出力するだけのもの。
<?php
require_once 'vendor/autoload.php';
use MicrosoftAzure\Storage\Blob\BlobRestProxy;
$connectionString = getenv("AZURE_STORAGE_CONNECTION_STRING");
$restProxy = BlobRestProxy::createBlobService($connectionString);
$containers = $restProxy->listContainers()->getContainers();
foreach($containers as $container) {
echo "Container name: {$container->getName()} url:{$container->getUrl()}", PHP_EOL;
}
上記が正常に実行されることを確認したら、 iptables
を使ってサーバとの通信をブロック。
% sudo iptables -A OUTPUT -p tcp -d XXXXX.blob.core.windows.net --dport 443 -j REJECT
後で削除するので追加したルールの番号を調べておく(num 列の値。ここでは 1 だった)。
sudo iptables -nL --line-numbers
・・・
Chain OUTPUT (policy ACCEPT)
num target prot opt source destination
1 REJECT tcp -- 0.0.0.0/0 XXX.XXX.XXX.XXX tcp dpt:443 reject-with icmp-port-unreachable
・・・
再度実行。
当然だが今度は GuzzleHttp\Exception\ConnectException
が発生することを確認。
% php blob-test.php
PHP Fatal error: Uncaught GuzzleHttp\Exception\ConnectException: cURL error 7: Failed to connect to XXXXX.blob.core.windows.net port 443: 接続を拒否されました (see https://curl.haxx.se/libcurl/c/libcurl-errors.html) for https://XXXXX.blob.core.windows.net/?comp=list in /home/akishin/src/php/php7/azure-storage-ex/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php:210
Stack trace:
#0 /home/akishin/src/php/php7/azure-storage-ex/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php(158): GuzzleHttp\Handler\CurlFactory::createRejection()
#1 /home/akishin/src/php/php7/azure-storage-ex/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php(110): GuzzleHttp\Handler\CurlFactory::finishError()
#2 /home/akishin/src/php/php7/azure-storage-ex/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php(236): GuzzleHttp\Handler\CurlFactory::finish()
#3 /home/akishin/src/php/php7/azure-storage-ex/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php(168): GuzzleHttp\Handler\CurlMultiHandler->processMe in /home/akishin/src/php/php7/azure-storage-ex/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php on line 210
リトライ処理の追加
リトライ機能についてのドキュメントは以下にある。
Retrying failures
https://github.com/Azure/azure-storage-php#retrying-failures
以下のような感じで RetryMiddleware
を追加するだけ。
ちなみに Blob だけでなく、Storage Table や Storage Queue の場合でも同じ。
<?php
require_once 'vendor/autoload.php';
use MicrosoftAzure\Storage\Blob\BlobRestProxy;
use MicrosoftAzure\Storage\Common\Middlewares\RetryMiddlewareFactory;
$connectionString = getenv("AZURE_STORAGE_CONNECTION_STRING");
$retryMiddleware = RetryMiddlewareFactory::create(
RetryMiddlewareFactory::GENERAL_RETRY_TYPE, # リトライのロジック
5, # リトライ回数
1000, # リトライ間隔
RetryMiddlewareFactory::EXPONENTIAL_INTERVAL_ACCUMULATION, # リトライ間隔の増加方法
true # 接続エラー時も再試行する
);
$optionsWithMiddlewares = [
'middlewares' => [
$retryMiddleware,
],
];
$restProxy = BlobRestProxy::createBlobService($connectionString, $optionsWithMiddlewares);
try {
$start = microtime(true);
$containers = $restProxy->listContainers()->getContainers();
foreach($containers as $container) {
echo "Container name: {$container->getName()} url:{$container->getUrl()}", PHP_EOL;
}
} finally {
$elapsed = microtime(true) - $start;
echo "elapsed: [{$elapsed}]", PHP_EOL;
}
2021.03.05 時点での不具合
これで実行するとリトライしてくれるかと思いきや何度試しても即エラーで終了。
デフォルトでは GuzzleHttp\Exception\ConnectException
の場合はリトライしないようだが、 RetryMiddlewareFactory::create
時の $retryConnect
オプションに true
を指定しているのでリトライしてくれるはず。
プリントデバッグしたりして調べてみると、どうやら以下の if 文で常に 1 つ目の if に入ってしまい false が返っている模様。
instanceof
演算子で ConnectException
の場合に true
が返ることを期待しているようだがその通りに動いていないっぽい。
GuzzleHttp の例外階層を調べると以下のようになっている。
. \RuntimeException
└── TransferException (implements GuzzleException)
└── RequestException
├── BadResponseException
│ ├── ServerException
│ └── ClientException
├── ConnectException
└── TooManyRedirectsException
しかしコードの方を見てみると、 どうも guzzlehttp 7 で例外の継承階層が変わった模様。
6.5
https://github.com/guzzle/guzzle/blob/6.5/src/Exception/ConnectException.php
7.0
https://github.com/guzzle/guzzle/blob/7.0/src/Exception/ConnectException.php
仕方ないので、一旦 guzzlehttp を 6 系に落として試すことにする。
composer.json を以下のように変更。
{
"require": {
"microsoft/azure-storage-blob": "^1.5",
"guzzlehttp/guzzle": "^6.0"
}
}
変更を保存したら update 実行。
% composer update
guzzlehttp のバージョンは 6.5.5 になった。
% composer show
guzzlehttp/guzzle 6.5.5 Guzzle is a PHP HTTP client library
guzzlehttp/promises 1.4.0 Guzzle promises library
guzzlehttp/psr7 1.7.0 PSR-7 message implementation that also provides common utility methods
microsoft/azure-storage-blob 1.5.2 This project provides a set of PHP client libraries that make it easy to access Microsoft Azure Storage Blob APIs.
microsoft/azure-storage-common 1.5.1 This project provides a set of common code shared by Azure Storage Blob, Table, Queue and File PHP client libraries.
psr/http-message 1.0.1 Common interface for HTTP messages
ralouphie/getallheaders 3.0.3 A polyfill for getallheaders.
symfony/polyfill-intl-idn v1.22.1 Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions
symfony/polyfill-intl-normalizer v1.22.1 Symfony polyfill for intl's Normalizer class and related functions
symfony/polyfill-php72 v1.22.1 Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions
再度検証
再度先程のサンプルコードを実行すると、最初とは変わり結構時間かかってからエラーが返ってくるようになったのでリトライは実行されてるっぽい。
試しにリトライ中に別ターミナルから iptables
のルールを削除してみる。
% sudo iptables -D OUTPUT 1
しばらく待っていると正常にレスポンスが返ってきた。
ちゃんとリトライしてくれてるっぽい。
Azure Storage は本番運用していると割と頻繁に接続エラーが起きる印象があるので、公式ライブラリでリトライ機能を簡単に使えるのは嬉しい。