PHPでSQS

# 前置き
SQS使った事なかったのでPHPから連携させて使ってみた。

# 環境
**Amazon Linux 2**
**PHP 7.2.27** (たぶん7系であればOK)
**AWS SDK 3.133** (たぶん3系であればOK)

Amazon Linuxでは脳死でyumでphpを入れると5系になってしまう。
この状態でcomposerでAWS SDKを入れようとすると2.8になる。
するとAWS SDKを使ったPHPのプログラム実行時に、credentialがなんちゃかってエラーが出たりして全然出来ないので、PHPを7にあげて…とかしてハマった。

# SQSって
AWSのキューのサービス。
FIFO(ファースト・インファーストアウト)って基本情報の勉強とかしてると出てくるやつ。
つまり入れた順番で情報を取り出せる。

Aのシステムでキューへメッセージ(データ)を格納する。
Bのシステムでキューを覗きに行って、メッセージがあったら取ってくる。
という利用イメージ。

料金は
[Amazon SQS の料金はどのように計算されますか?](https://aws.amazon.com/jp/sqs/pricing/)
によると、
>毎月最初の 100 万件のリクエストまでは無料です。

>無料利用枠を超えた後の 100 万件のリクエストあたりの料金 (月単位)
> 標準キュー  0.40USD (0.0000004USD/リクエスト)
> FIFO キュー  0.50USD (0.0000005USD/リクエスト)
との事。

1秒に1回のリクエストだと
1*60*60*24 = 86,400リクエスト
1ヶ月を30日とすると
86400*30 = 2,592,000リクエスト

なので3秒に1回くらいのペースなら無料枠を超えないのかなというイメージ。
たぶん実際にはキューに入れる側のリクエスト、取得する側のリクエスト、さらに取得済みのメッセージを消すリクエスト、とかも発生するはずなので、工夫しないと簡単にリクエスト数が跳ね上がってしまう。

とはいえ標準キューで無料枠を100万リクエストオーバーしたとしても0.4USDって事は1ドル100円として40円なので大した事はない。

ところで料金表のところで標準キューとFIFOキューって表記がある。
落ち着け、そもそもキューってFIFOじゃないのって話なのだが、SQSの場合は標準キューだと必ずしもインした順番どおりにアウトされない事があるらしい。(キューとはなんぞやって頭を悩ませる案件)

そういうSQS特有の概念も含めて、だいたいこのへんを眺めればざっくり概要はつかめる気がする。
[Amazon SQSを使う前に知っておきたい基本的なこと](https://qiita.com/tomoya_ozawa/items/dc0286cdb30763eab174)

ちなみにメッセージがキューに入っているかなぁって確認する側に関して。
たぶんSQSにロングポーリングを設定するのが良いと思われる。
[Amazon SQS メッセージキューのロングポーリングの有効化](https://docs.aws.amazon.com/ja_jp/sdk-for-java/v1/developer-guide/examples-sqs-long-polling.html)

要するに、メッセージ確認する側は何もしなくても、SQSにメッセージ確認のリクエストを投げると、
・メッセージがあれば即レスポンス
・メッセージが無い場合は最大20秒までメッセージが来るのを待ってレスポンス
 (もちろん待ってる途中でメッセージが入ればその時点でレスポンス)
という動きをSQS側がしてくれるようになる。
(リクエスト側で気にするのはリクエスト自体のタイムアウト設定くらい…？)

リアルタイム性を求める場合は、ずっとメッセージが来なくても20秒に1回リクエストが発生するだけになるのでお財布にも優しい。
(そもそもそこまでリアルタイムにこだわらなければ取得側のリクエスト周期自体を伸ばせばいい話なのだけど。)

# 環境構築

日が経ってるのでうろ覚えの記憶を頼りにhistoryを見つつなので間違ってたらすまんとだけ

とりま

```
sudo su -
```
で管理ユーザになる

PHP 7.2をインストール
ハマったのでとりあえず片っ端からそれらしいPHPモジュールをいれてみた(爆
もしかすると不必要なやつもあるかもしれないが、いったんhistoryから拾ってきたやつを書き連ねる

```
yum install -y php72 php72-php php72-php-fpm php72-php-mbstring php72-php-devel php72-php-gd
```

AWS SDKをインストールするため、[ここらへん](https://qiita.com/kakijin/items/02364adacf36410f449e)を参考にcomposerをインストール
めんどくさかったので作業ディレクトリ作ってそこでやった

```
curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer
```

AWS SDKをインストール
[Installing the AWS SDK for PHP Version 3](https://docs.aws.amazon.com/sdk-for-php/v3/developer-guide/getting-started_installation.html)

```
composer require aws/aws-sdk-php
```

ハマってたときにcomposerのupdateでこけたりしてムキーってなってこのへんの対応をしたけど、普通にupdateいらなかったかもしれないしそもそもmicro以外のインスタンスタイプなら起きないかもなので参考程度に貼っておく
[EC2のmicro instanceでcomposer updateがコケる場合のメモ](https://qiita.com/takaaki-mizuno/items/fc1b9ef513609cab7eb9)

# PHPからメッセージの送信
AWS SDK使ったりAWS CLIでコマンド叩く人にはお馴染みのあれだが
PHPからのリクエスト時に当然認証は入るので、事前に「プログラムによるアクセス」を許可したユーザをIAMで用意しておかないといけない。
例のアクセスキーとシークレットアクセスキーを用意するあれ

PHPのソースはここらへんを参考に

[AWS SDK for PHP から AWS SQSを使ってみた](http://rabbitfoot141.hatenablog.com/entry/2018/08/26/125535)

[AWS SDK for PHPでAmazon SQSを使う](https://qiita.com/ykono/items/0961edeb492c32b28281)

標準キューとFIFOキューでは若干違うらしい。
今回はFIFOキューでやった。

###メッセージの送信処理

```php:sendMessage.php
<?php
require_once 'vendor/autoload.php';
use Aws\Sqs\SqsClient;
use Aws\Exception\AwsException;

$queueUrl = "https://sqs.ap-northeast-1.amazonaws.com/xxxxxxxx/yyyyyyyy.fifo";

try{
    $client = new SqsClient([
        'profile' => 'default',
        'region' => 'ap-northeast-1',
        'version' => 'latest',
    ]);

    $fifo_params = [
        'QueueUrl' => $queueUrl,
        'MessageBody' => "xxxxxx", // ここにメッセージを設定
        'MessageGroupId' => 'test', // よく分かってない
        'MessageDeduplicationId' => time(), // よく分かってない
    ];

    $client->sendMessage($fifo_params);
} catch(AwsException $e){
    var_dump($e->getMessage());
}
```

これを実行後にAWSコンソールからSQSを確認するとメッセージが入っている。

### メッセージの受信処理

```php:getMessage.php
<?php
require_once 'vendor/autoload.php';
use Aws\Sqs\SqsClient;
use Aws\Exception\AwsException;

$queueUrl = "https://sqs.ap-northeast-1.amazonaws.com/xxxxxxxx/yyyyyyyy.fifo";

try{
    $client = new SqsClient([
        'profile' => 'default',
        'region' => 'ap-northeast-1',
        'version' => 'latest',
    ]);
// ここまではsendMessageと一緒

// 継続的にメッセージを受け取りに行く場合にはここでループの記述
// while(true){

    // メッセージ受け取り
    $result = $client->receiveMessage(array(
        'QueueUrl'        => $queueUrl,
        'WaitTimeSeconds' => 5, // ロングポーリングする秒数(最大20秒)
        'MaxNumberOfMessages' => 10, // 同時に取ってくるメッセージ数
    ));

    // メッセージの受け取り処理をループにしていた場合はメッセージの中身が空ならここでcontinueさせる
    // if(!$result->search('Messages[]'))continue;

    // メッセージを1件づつ取得し、取得済みメッセージは都度削除リクエストを投げる
    foreach ($result->search('Messages[]') as $message) {
        $queueHandle = $message['ReceiptHandle'];
        echo $message['Body'];

        // 対象メッセージの削除
        $client->deleteMessage([
            'QueueUrl' => $queueUrl,
            'ReceiptHandle' => $queueHandle,
        ]);
    }
// メッセージの受け取りループをしていた場合はここまで
// }
} catch(AwsException $e){
    var_dump($e->getMessage());
}
```

# 結論
受信側でSQSをロングポーリングしつつ、メッセージ送信処理を実行したところ、受信側では体感1秒もかからないうちに受信出来た。
使える場面は十分ありそうな手応え。

# おまけ

## 可視性タイムアウトについて
SQSに「test」という名前のキューを作成
「test」キューに対しメッセージ「AAA」を送信

$client->receiveMessageのタイミングで、「AAA」に対し、「ReceiptHandle」と呼ばれるセッションIDみたいなものが設定払い出される。(ReceiptHandleはメッセージに対し1:1。)
そしてReceiptHandleの有効期限中は、「AAA」は不可視、要は他からは見えない、いわば専有状態になる。
(たぶん、ReceiptHandleの有効期限＝「可視性タイムアウト」)

メッセージを受信したプロセスは、そのReceiptHandleを使用すれば不可視になっているメッセージに対する操作を行える。(削除とか可視性タイムアウト時間の変更とかとか)


若干ハマったのが
**可視性タイムアウトを0秒に設定するとメッセージが消せない()**
受信側の処理が途中で落ちた場合に、再取得のため再度リクエストを投げてまだ不可視だったらめんどくさいなと思い、思い切って0秒にしてみたところ上記の状態になった。
(ReceiptHandleの有効期限が切れてるんだから当然デスヨネ)

なお再リクエスト時にまだ不可視の問題は、ReceiptHandleを使って対象メッセージの有効期限を0秒にすると即可視状態になるので、それで解決した。

可視性タイムアウト0秒は、おそらく
「メッセージ自体の有効期限がくるまでメッセージはずっと可視状態で残り続ける」
という要件じゃないと使えない設定だと思われる。
そんなものある…？最新のニュース一覧とかには使えるか…？
## FIFOキューをPHPから作成
```php:createQueue.php
<?php
require 'vendor/autoload.php';
use Aws\Sqs\SqsClient;
use Aws\Exception\AwsException;

try{
    $client = new SqsClient([
        'profile' => 'default',
        'region' => 'ap-northeast-1',
        'version' => 'latest',
    ]);

    $client->createQueue([
        'QueueName' => 'omake.fifo',
        'Attributes' => ['FifoQueue' => 'true'],
    ]);


} catch(AwsException $e){
    var_dump($e->getMessage());
}
```

## FIFOキューをAWS CLI ver2から作成
```
aws2 sqs create-queue --queue-name omake.fifo --attributes FifoQueue=true
```