ラーメン店の伝票の列
ラーメン店の厨房には、注文の伝票が順に並びます。ホールの人が伝票を後ろに足し、料理人が先頭の1枚を取って作ります。
SQS のキューは、メッセージについて同じことをします。ネットショップは、新しい注文をキューに入れ、ワーカーが1つずつ取り出します。このレッスンでは、1つのメッセージを、送信から削除まで追います。
このレッスンのゴール
メモ
メッセージが、送信から削除までに、どうなるかを説明できる。
キーワード
| ことば | いみ |
|---|---|
| キュー | 処理を待つメッセージの列 |
| メッセージ | キューで待つ、1つの文字のかたまり |
| 受信 | 作業するメッセージを、キューに求めること |
| 可視性タイムアウト | 受信したメッセージが、隠れている時間 |
| レシートハンドル | 受信したメッセージを削除するための札 |
| 少なくとも1回 | メッセージは2回届くことがあるが、0回にはならない |
学生が入力するセルは、このプレビューでは空白です。
受信するとメッセージが届く
send_message は、文字をキューに入れます。答えには MessageId があります。receive_message は、メッセージを求めます。答えには、Messages というリストがあります。
各メッセージは、文字の Body と、ReceiptHandle を持ちます。本文はただの文字です。多くの店は、本文に JSON を入れ、ワーカーが json.loads で辞書に戻します。
例: receive の答え
| 項目 | 中身 | 例 |
|---|---|---|
| Messages | メッセージ1つにつき、辞書が1つ入ったリスト | 2つのメッセージ |
| MessageId | 送ったときにキューが付けた id | m-1 |
| Body | 送った文字 | {"order": 1, "item": "ramen"} |
| ReceiptHandle | この受信を削除するための札 | h-1 |
| ApproximateReceiveCount | 受信された回数。文字で入る | "1" |
学生が入力するセルは、このプレビューでは空白です。
実行: receive の答えを読む
import json
answer = {
"Messages": [
{"MessageId": "m-1", "ReceiptHandle": "h-1",
"Body": "{\"order\": 1, \"item\": \"ramen\"}",
"Attributes": {"ApproximateReceiveCount": "1"}},
{"MessageId": "m-2", "ReceiptHandle": "h-2", "Body": "gyoza",
"Attributes": {"ApproximateReceiveCount": "1"}},
]
}
for message in answer["Messages"]:
print(message["MessageId"], message["Body"])
order = json.loads(answer["Messages"][0]["Body"])
print("品物:", order["item"])
# ループが ReceiptHandle も表示するように変えましょう。
受信したメッセージは隠れる
受信しても、メッセージは消えません。可視性タイムアウトの間、隠れるだけで、取り込んだキューでは30秒です。隠れている間、ほかのワーカーは受信できません。
作業が終わったら、ReceiptHandle を付けて delete_message を呼びます。そのときはじめて、メッセージが消えます。先にワーカーが止まると、タイムアウトが終わり、メッセージはまた見えて、別のワーカーが受け取れます。
例: キューの属性
| 属性 | 値 | 意味 |
|---|---|---|
| VisibilityTimeout | "30" | 受信したメッセージが隠れる秒数 |
| ApproximateNumberOfMessages | "3" | 受信を待っているメッセージの数 |
| ApproximateNumberOfMessagesNotVisible | "0" | 受信のあとで隠れているメッセージの数 |
| MessageRetentionPeriod | "345600" | メッセージを保つ秒数で、4日 |
学生が入力するセルは、このプレビューでは空白です。
実行: 隠れて、また見える
TIMEOUT = 30
received_at = {"m-1": 100}
def is_visible(message_id, now):
if message_id not in received_at:
return True
return now >= received_at[message_id] + TIMEOUT
for now in [100, 120, 130, 160]:
print(now, is_visible("m-1", now))
print("m-2 は受信されていない:", is_visible("m-2", 100))
# TIMEOUT を 60 に変えましょう。m-1 が見えるのは、いつからですか。
よくある間違い
注意
受信すると、メッセージが取り除かれると考える人が多いです。隠れるだけです。delete_message を呼ばないと、タイムアウトのあとメッセージが戻り、もう一度処理されます。削除は、作業が終わってからです。
少なくとも1回は、重複もある
キューは、削除されるまでメッセージを保つので、同じメッセージが2回届くことがあります。ワーカーが作業を終えて、削除の前に止まる場合です。
受信が、求めた数より少ないメッセージや、0件を返すこともあります。空の答えには Messages がないので、読む前に、あるかどうかを調べます。重複しても害がないように、ワーカーを書きましょう。
実行: 重複を飛ばす
deliveries = ["m-1", "m-2", "m-1", "m-3"]
done = []
for message_id in deliveries:
if message_id in done:
print(message_id, "2回目: 飛ばす")
else:
done.append(message_id)
print(message_id, "初めて: 作業する")
empty = {}
if "Messages" in empty:
print("メッセージの数:", len(empty["Messages"]))
else:
print("メッセージの数: 0")
# 代わりに empty["Messages"] を試しましょう。何が起きますか。
クラウドラボで試す
クラウドラボは、S3、SQS、DynamoDB の練習用の複製です。バーの ラボを起動 を押してから、下の各コマンドの 実行 を押します。本物の AWS コマンドラインを使うので、同じ言葉が本物のアカウントでも通じます。削除したあとでキューをもう一度たずねると、何も表示されません。キューは空に見えます。この手順は練習です。下の課題はふつうの Python のままなので、ラボなしでも解けます。
作り物のキーとラボのアドレスを設定する
export AWS_ACCESS_KEY_ID=test AWS_SECRET_ACCESS_KEY=test AWS_DEFAULT_REGION=us-east-1 AWS_ENDPOINT_URL=http://cloud:4566ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
キューを作る
Q=$(aws sqs create-queue --queue-name orders --query QueueUrl --output text)ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
メッセージを送る
aws sqs send-message --queue-url $Q --message-body "order 1"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
受け取って返事を保存する
aws sqs receive-message --queue-url $Q | tee reply.jsonここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
受信ハンドルを取っておく
H=$(grep -o '"ReceiptHandle": "[^"]*"' reply.json | cut -d'"' -f4)ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
メッセージを削除する
aws sqs delete-message --queue-url $Q --receipt-handle "$H"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。