SQS(Amazon Simple Queue Service)について纏めます。
1.SQS概要
- サービス間でのメッセージのやり取りを管理するキューのこと。
- 複数のキューに分散してメッセージを管理するのでFIFO(First in First out)は必ず成立するとは限らない
- 一つのキューを複数サーバに冗長化して保持する。
- キューにメッセージを入れるサービスをプロデューサ、キューからメッセージを取り出すサービスをプロデューサという
1.2 SQSを使用するメリット
もし、プロデューサーとコンシューマの間にSQSが存在せず、直接メッセージのやり取りをしていたとするとします。処理量が増えてきてコンシューマを増やしたとするとプロデューサに新しいコンシューマへメッセージを送信するために毎回必要な情報を登録する必要が出てきます。しかし、間にSQSがあればコンシューマを増やしたとしても新しいコンシューマがSQSからメッセージを取得すればよいという情報さえあれば大丈夫です。
このように密結合だったものを疎結合にできるのがSQSのメリットです。
疎結合にすることで負荷が変化した際にAutoScallingなどで調整することが可能になります。
1.3 キューへのメッセージ送信
キューへメッセージを送信する際にはSendMessage操作を行う。
SendMessageには以下のパラメータを指定できる。
- QueueUrl(必須)
メッセージを送信したいキューのURL。 - MessageBody(必須)
送信したい内容。 - DelaySeconds(任意)
メッセージはキューに登録された後、ここで指定した時間が経過すると処理可能になる。
指定しないとキュー作成時に設定したデフォルト値を使用する。 - MessageAttributes(任意)
- 属性名、データ型、値で構成される。
- メッセージにタイムスタンプや署名、地理情報などの付加情報をつけることができる。
- 属性名、データ型、値すべてに最大で256文字以内という制限がある。
- データ型はString、Number、Binaryが選択できる。
また、キューには一度に最大10個のメッセージを送信することが可能。
1.4 キューからメッセージ受信
キューからメッセージを受信する際にはreceiveMessage操作を行う。
receiveMessageではキューのURLを指定する。
キューの中の特定のメッセージを指定することはできない(キューの中の先頭のデータが渡されるため)。
キューからのメッセージ受信動作にはショートポーリングとロングポーリングが存在する。
- ショートポーリング(デフォルト)
前述した通り、SQSでは複数のキューに分散してメッセージを保持する分散キューと同じメッセージを複数のキューで保持する冗長化が行われている。
5つのキューに分散してメッセージを保持したいた場合、下図のように全てのキューの内容が同じである保証はなく、1つのキューにすべてのメッセージが入っていることも保証されない。
このような状態でメッセージ取得時に特定のキューをランダムに選択し、そのキューからメッセージを取得する方法がショートポーリングである。
ランダムに選択されるのでキューに登録されているメッセージの数が少ない場合、メッセージが登録されていないキューを選択してしまい、receiveMessageの結果が取得できない可能性もある。
結果が0件だった場合はリトライされるのでリクエスト回数が多くなり、料金にも影響が出る。
0件でもレスポンスを返すのでリクエストを投げると結果は直ぐに返ってくる。
ショートポーリングを使用するのはメッセージ取得時の待機時間0秒の場合である。待機時間は下記の方法で設定できる。- receiveMessage操作時に指定できるwaitTimeSecondsを0で指定する
- waitTimeSecondsを指定しなかった場合キュー作成時に指定できるReceiveMessageWaiteTimeSecondsが使用されるがこれを0にする
どちらの方法も設定は任意であり、設定しなかった場合は0がデフォルト値となるのでショートポーリングがデフォルトの方法となる。
- ロングポーリング(推奨)
特定のキューからメッセージを取り出すショートポーリングと違い、すべてのキューを確認し、処理可能となった(設定された待機時間を超過した)メッセージが存在する場合と接続タイムアウトになった場合のみ結果を返す。
つまり、タイムアウトにならない限りは必ず何かしらのメッセージを返し、0件で応答することはない。
結果が0件の場合に返答しないのでSQS使用時のコストを削減できるため、ショートポーリングよりも推奨される方法である。
1.5 各種プロパティ
- 可視性タイムアウト(Default Visibility Timeout)
SQSは自動でメッセージの削除を行わないのでコンシューマがSQSからメッセージを取得してもSQSからメッセージは消えない。
これはコンシューマがメッセージの取得に失敗した際に再取得できるようにするため。
しかし、そのままだと同じメッセージを別のコンシューマが取得してしまう可能性がある。
これを防ぐために可視性タイムアウトを設定する。
これは設定された時間の間、他のコンシューマがそのメッセージを取得できなくするもの。
これをメッセージの処理時間よりも長く設定しておくことで処理が完了してからSQSに残っているメッセージを削除できる。
デフォルトは30秒。 - メッセージ保持期間(Message Retention Period)
キューに登録されたメッセージは明示的に削除処理を行われない限りデフォルトで4日間保持する。
この値は1分~14日間の間で設定可能。 - 配信遅延時間(Delivery Delay)
遅延時間を設定するとキューに登録されたメッセージは設定した時間の間コンシューマから読み込まれなくなる。
デフォルトは0秒で最大15分まで設定可能。 - メッセージ待機時間(Recieve Message Wait Time)
メッセージを取得を指定時間だけ待機する。
例えば10秒で設定した場合はコンシューマは一度メッセージの取得を行ってから10秒待機して再度メッセージを取得します。
デフォルトは0秒(ショートポーリング)。20秒まで設定可能。
この設定以外にコンシューマがメッセージを取得する際にも待機時間設定を指定できる。(wait_time_seconds)これが設定された場合はコチラが優先される。
2 デザインパターン
2.1 Queuing Chain
SQSのQueuing Chainパターンを使用することで非同期でシステム同士を結合することができます。
下記の例だと3つのサービスがあり、1の結果を2が受け取って処理を行い、その結果を3が受け取って処理を行うとします。
サービス同士が直接データ(メッセージ)をやり取りするのではなく、間にSQSを挟むのがQueuing Chainです。
こうすることでサービス同士の結合を疎にして非同期で処理することができます。
サービス同士の結合が疎になればサービスAだけをスケールアップするといったことも可能になります。
2.2 デッドレターキュー
デザインパターンではないかも。
サービスがキューからメッセージを取り出して処理しようとしたが途中でエラーになった場合、そのメッセージを処理しないと他のメッセージを処理することができなくなる。
この対応策として
- 処理できないメッセージを削除する
- 処理できないメッセージを他の場所に退避させる
という2つの対策が考えられるが、デッドレターキューとは2つ目の処理できないメッセージを退避させるキューを指します。
SQSではメッセージの処理に失敗した場合は再度そのメッセージの処理を試みます。
しかし、maxReceiveCountというSQS設定で指定した回数を超えるとデッドレターキューに送信されます。
デッドレターキューに入れられたメッセージはエラーの原因の解析などに使用されます。
デッドレターキューを使用する上で注意点としてメッセージの保持期間があります。
SQSではキュー作成時にメッセージの保持期間を設定することができます。(Message Retention Period)
この保持期間を超えているかどうかはメッセージが持つタイムスタンプによって判断されますが、デッドレターキューに入れられてもタイムスタンプは変化しません。
デッドレターキューの保持期間は通常のキューの保持期間より長くするべきです。
例えば、キューA(保持期間:5日)とデッドレターキューD(保持期間:3日)が存在し、Aで処理できなかったメッセージをDに入れる場合、下記のような現象が起こりえます。
- 8/1にメッセージをAに登録(メッセージのタイムスタンプは8/1になります。)
- 8/6に処理しようとしたが、エラーが発生して処理できなかった。
- このメッセージをDに入れて解析しようとしたが、既にDの保持期間を過ぎているのでDで保持することはできない。
上記のようなケースが発生しないようにデッドレターキューを使用する場合は通常のキューよりも保持期間を長くするのがベストプラクティスです。
2.3 AutoScalling
キューに登録されているメッセージの数が多く(少なく)なって閾値を超えるとコンシューマ側に設定しているEC2の数をAutoScallingで調整可能。
2.4 ファンアウト
これでサービス1が動くとその結果を受けてサービス2と3が動作する環境を非同期で実装可能です。
よく出される例としてサービス1は注文を受け付けるサービスで受注の結果をサービス2と3が受け取ります。
2では注文処理の続きを実施し、
3では注文記録を分析するといったことが可能になります。
コマンドから実践
今回は簡略化してファンアウト構成にはせずにSNSをputするSQSは1つの構成で作成します。
- SQSを作成
キューの名前はリージョン内で一意になる必要があるそうです。
aws sqs create-queue --queue-name myQueue
SNSで指定するために作成したSQSのArnを取得します。
aws sqs get-queue-attributes \
> --queue-url https://ap-northeast-1.queue.amazonaws.com/[AWSアカウントID]/myQueue \
> --attribute-names QueueArn
- SNSトピックを作成
aws sns create-topic --name my_topic_SQS
-
トピックからSQSにメッセージ送信の許可をつける
トピックからSQSへメッセージを送信するにはポリシーの設定が必要です。
こちらを参考にマネジメントコンソールから設定しました。
https://docs.aws.amazon.com/ja_jp/sns/latest/dg/SendMessageToSQS.html#SendMessageToSQS.sqs.permissions -
サブスクライブ設定
今回はメッセージの送信先はSQSなのでプロトコルにSQS、
エンドポイントに先ほど調べたSQSのArnを指定します。
aws sns subscribe \
--topic-arn arn:aws:sns:ap-northeast-1:[AWSアカウントID]:my_topic_SQS \
--protocol sqs \
--notification-endpoint arn:aws:sqs:ap-northeast-1:[AWSアカウントID]:myQueue
- トピックにメッセージを登録
aws sns publish \
--topic-arn arn:aws:sns:ap-northeast-1:[AWSアカウントID]:my_topic_SQS \
--subject "test1" \
--message "Hello World1"
aws sns publish \
--topic-arn arn:aws:sns:ap-northeast-1:[AWSアカウントID]:my_topic_SQS \
--subject "test1" \
--message "Hello World2"
aws sns publish \
--topic-arn arn:aws:sns:ap-northeast-1:[AWSアカウントID]:my_topic_SQS \
--subject "test1" \
--message "Hello World3"
aws sns publish \
--topic-arn arn:aws:sns:ap-northeast-1:[AWSアカウントID]:my_topic_SQS \
--subject "test1" \
--message "Hello World4"
aws sns publish \
--topic-arn arn:aws:sns:ap-northeast-1:[AWSアカウントID]:my_topic_SQS \
--subject "test1" \
--message "Hello World5"
aws sns publish \
--topic-arn arn:aws:sns:ap-northeast-1:[AWSアカウントID]:my_topic_SQS \
--subject "test1" \
--message "Hello World6"
6.SQSに登録されたメッセージを取得
aws sqs receive-message \
--queue-url https://ap-northeast-1.queue.amazonaws.com/[AWSアカウントID]/myQueue