LoginSignup
0
1

More than 3 years have passed since last update.

microsoft/azure-storage-blob のリトライ機能を試す

Posted at

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 は本番運用していると割と頻繁に接続エラーが起きる印象があるので、公式ライブラリでリトライ機能を簡単に使えるのは嬉しい。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1