EIP / Message Sequence


Message Sequence(メッセージシーケンス)

一言要約

大きなデータは分割して扱う。分割した各メッセージには、あとで元に戻すために必要な情報を含めておく。

Context

あるアプリケーションから別のプロセスに対して巨大なメッセージを送らないといけないが、それが一つのメッセージには入りきらない可能性がある。あるいは、リクエストに対する応答が一つのメッセージに入りきらない可能性がある。


Problem

どんな大きさのデータでも、自由にメッセージングでやりとりしたい。

Force

メッセージのサイズを好きなだけ大きくできるのならありがたいけど、現実的に考えてそれは無理。一つのメッセージで使えるデータ量には制限がある。じゃあ、どうやって切り抜ける?

アプリケーション側で、メッセージング層で扱えるサイズにデータをおさめるようになんとかがんばるというのも一つの手だ。でも、本来やりたかった機能が実現できなくなってしまうかもしれない。リクエストに対する応答が巨大になる場合は、リクエストを小分けにして少しずつ応答を得るという手もある。でも、リクエストの回数が増えるしネットワークのトラフィックも増える。結果があとどれだけ残っているのかも知っておかないといけない。

通販会社を例に考えてみよう。一回の注文が、何個かの箱に別れて送られてくることがある。箱には「1/3」「2/3」「3/3」とかが書いてあって、全部きちんと受け取れたのかどうかがわかるようになっている。メッセージングの世界でも同じことをすればいい。

Solution

巨大なデータをメッセージサイズにあわせて分割する必要に迫られた場合は、データをMessage Sequenceで送信する。各メッセージには、シーケンス識別用のフィールドをつける。

Message Sequenceの識別用フィールドは次の三つ。

  1. シーケンスID(このメッセージ群を、他のメッセージ群を区別する)
  2. ポジションID(このメッセージ群の中で何番目のメッセージなの?)
  3. サイズ、または終了フラグ(メッセージは全部で何個?/メッセージはこの後もまだ続くの?)

個々のメッセージに「このメッセージ群は全部でn個のメッセージからなる」と書いておく方法もあるし、「自分がこのメッセージ群の最後のメッセージかどうか」を書いておく方法もある。次の図は、後者のパターン。

三つのメッセージからなるデータを送ることになったとする。まずは、このメッセージ群を表す一意なIDを用意して、これをシーケンスIDとする。ポジションIDは各メッセージで別々のもの(1、2、3など)をつける。そして、送信側で事前にメッセージ数がわかるのなら、メッセージの総数をつける。ストリーミングの場合とかなら総数はわからないので、最終メッセージ以外は「シーケンス終了フラグ」をfalseにして、最後のメッセージだけフラグを立てる。これだけの情報があれば受信側には十分で、仮にメッセージの到着順がばらばらであっても対応できる。

受信側がMessage Sequenceを期待している場合は、たとえメッセージが一つしかなくてもMessage Sequence形式で送らないといけない。シーケンス識別フィールドなしで送ってしまうと受信側が困ってしまい、Invalid Message Channel行きになる。

シーケンスの一部だけを受け取ったけど全部揃わなかったという場合も、Invalid Message Channelに送らないといけない。

Transactional Clientを使ってシーケンスの送受信をすることもできる。これを使うと、全部のメッセージを送り終えるまでメッセージを配送しないようにできる。受信側でも、メッセージ群を一つのトランザクションで受けるようにして、全部受け取るまでは一切処理しないようにできる。取りこぼしがあったらロールバックする。メッセージングシステムの実装の多くは、トランザクションで送られたメッセージ群は送信時と同じ順番で受け取れるようになっている。これは、受信側でデータをとりまとめる手間を減らすため。

Request-ReplyのレスポンスとしてMessage Sequenceを使う場合は、シーケンスIDとCorrelation Identifierがだいたい同じ意味になる。一つのリクエストに対して複数のレスポンスがある場合はこれらを別々にしておくことになるだろうが、レスポンスが一つだけしかないのなら、レスポンスの識別とシーケンスの識別を別々にすると冗長になる。

Message Sequenceは、Competing ConsumersMessage Dispatcherとは相性が悪い。シーケンス内の個々のメッセージを別の受信者が受け取ってしまったら、お互いに調整し合わない限り誰も元のメッセージを復元できなくなる。Message Sequenceの受信者は単独にしないといけない。

Message Sequenceの代替手段としてClaim Checkが使える。送信側と受信側がデータベースやファイルシステムを共有できるときに、ドキュメントそのものではなくドキュメントへのポインタだけをやりとりする方法だ。

Message Sequenceと似た方法として、Splitterで大きなメッセージを分割して送り、Aggregatorを使って復元したメッセージを受信するというやりかたがある。この方法を使えば、元のメッセージが巨大になってもかまわない。Message Sequenceの場合は、Message Endpoints?の時点でデータを分割してから送信し、受信した後でデータをとりまとめる。

Examples


大きなドキュメントの転送

巨大なドキュメントをやりとりするときは、ドキュメントを分割してからそれぞれをメッセージとして送る。たとえばMSMQでは、メッセージのサイズが最大4MBまでになっている。MSMQでマルチパートメッセージを送信する方法についてはDesigning Applications with MSMQに説明がある。

複数のアイテムを返す問い合わせ

たとえば、ある著者のすべての著書を問い合わせるリクエストを考えよう。結果は大量になるかもしれないので、個別のメッセージに分けることになるだろう。各メッセージには、その応答の元になった問い合わせやシーケンス内での位置、そして結果の総数を含める。

分散問い合わせ

複数の受信者が分担して処理する問い合わせを考えよう。もし各パーツの並び順に意味があるのなら、応答を返すときにそれを指定しておかないとうまく復元できない。自分が受け取ったメッセージが全体の何番目なのかを受信者側が知らないといけないし、その情報を応答に含めないといけない。

JMSや.NET

JMSや.NETには、Message Sequenceをサポートする組み込みプロパティが存在しない。なので、シーケンス識別フィールドをアプリケーションが自前で実装しないといけない。JMSなら、アプリケーションがヘッダに独自のプロパティを定義できるので、それを使えばいい。.NETの場合はそうはいかない。しかし、メッセージの本文にプロパティを含めることはできる。受信側のアプリケーションでこれらの情報を読み出すときには、ヘッダに書かれていたほうがずっと処理がシンプルになる。


Webサービス: Multiple Asynchronous Responses

Webサービスの標準規格は、非同期メッセージングをうまくサポートできていない。でもW3C的には、(※2003年時点で)何か考え始めているっぽい。

Web Services Architecture Usage Scenarios

いろんなシナリオが考えられているが、その一つがMultiple Asynchronous Responses。SOAPヘッダのmessage-idとresponse-toフィールドでリクエストとレスポンスの相関を表し、sequence-numberとtotal-in-sequenceフィールドでシーケンスを識別する。

SOAPリクエスト

<?xml version="1.0" ?>
<env:Envelope xmlns:env="http://www.w3.org/2002/06/soap-envelope">
  <env:Header>
    <n:MsgHeader xmlns:n="http://example.org/requestresponse">
      <n:MessageId>uuid:09233523-345b-4351-b623-5dsf35sgs5d6</n:MessageId> <!-- ←ここ -->
    </n:MsgHeader>
  </env:Header>
  <env:Body>
    ........
  </env:Body>
</env:Envelope>

最初のレスポンス

<?xml version="1.0" ?>
<env:Envelope xmlns:env="http://www.w3.org/2002/06/soap-envelope">
  <env:Header>
    <n:MsgHeader xmlns:n="http://example.org/requestresponse">
      <!-- MessageIdはレスポンスのIDなので、メッセージごとに変わる -->
      <!-- ResponseToは、同じシーケンスのメッセージならどれも同じになる -->
      <n:MessageId>uuid:09233523-567b-2891-b623-9dke28yod7m9</n:MessageId>
      <n:ResponseTo>uuid:09233523-345b-4351-b623-5dsf35sgs5d6</n:ResponseTo>
    </n:MsgHeader>
    <s:Sequence xmlns:s="http://example.org/sequence">
      <!-- 1/5 -->
      <s:SequenceNumber>1</s:SequenceNumber>
      <s:TotalInSequence>5</s:TotalInSequence>
    </s:Sequence>
  </env:Header>
  <env:Body>
    ........
  </env:Body>
</env:Envelope>

最後のレスポンス

<?xml version="1.0" ?>
<env:Envelope xmlns:env="http://www.w3.org/2002/06/soap-envelope">
  <env:Header>
    <n:MsgHeader xmlns:n="http://example.org/requestresponse">
      <!-- MessageIdはレスポンスのIDなので、メッセージごとに変わる -->
      <!-- ResponseToは、同じシーケンスのメッセージならどれも同じになる -->
      <n:MessageId>uuid:40195729-sj20-pso3-1092-p20dj28rk104</n:MessageId>
      <n:ResponseTo>uuid:09233523-345b-4351-b623-5dsf35sgs5d6</n:ResponseTo>
    </n:MsgHeader>
    <s:Sequence xmlns:s="http://example.org/sequence">
      <!-- 5/5 -->
      <s:SequenceNumber>5</s:SequenceNumber>
      <s:TotalInSequence>5</s:TotalInSequence>
    </s:Sequence>
  </env:Header>
  <env:Body>
    ........
  </env:Body>
</env:Envelope>

担当者のつぶやき

言われるまでもない、ごく当たり前の内容かな?(高木)

みんなの突っ込み