システムAの更新内容を、別のサーバにあるシステムBにも反映させるためにデータ送る、というケースを考えます。
主流はWeb APIかMOMを使う方法かと思います。(俯瞰的な話は、20日目のこざけさんが書いてくれる、はず)
しかし、A,B間で同時に一貫性を保たなくても良いケースは、企業間システム連携ではよくあります。
CAP定理でいうところの可用性+分断耐性をとりにいくパターンです。が、最大数秒程度で結果整合性は保ちにいきます。
システム間の接続
非同期ながら、ほぼリアルタイムでデータを反映していくので、応答性の高い接続手段が求められます。だが中間経路をHTTPでないプロトコルを通すハードルが高かったり、ファイヤーウォールなどで長時間接続を切られたりする問題があるので、私はよくWebSocketを使います。
だいたいの構成は以下のようになります。同期エージェント間をWebSocketでつなぎ、データの転送を行います。A→B間の接続がWebSocketです。
全体の構成は下記図のようになります。システムAでデータ変更があったら、変更差分テーブルにも同時に変更内容を書き込みます。これが成功したら、同期エージェントに通知し、未処理の変更データを抽出しシステムBに送信する。という仕組みです。システムBで正常に反映ができたら、変更差分の該当レコードを送信済みにします。
サーバBからのレスポンスが戻ってこない場合、再送をします。更新処理に冪等性があるのは稀なので、すでに処理済みであれば対象のトランザクションをスキップするように設計します。
このため、転送データ内で一意になるようにトランザクションIDをふり、BのテーブルにもトランザクションIDを格納しておくようにします。
さてこの設計で考慮すべきは、システムBがダウンしていた場合、復旧し次第再送しますが、その責任をAが負っているところです。これはシステムAとシステムBのオーナーの力関係によっては、もめることがあります。
サーバ間のWebSocket実装(Java)
WebSocketの標準仕様JSR-356はクライアントも用意されていて、サーバ間のWebSocket実装は非常に簡単になっています。
@ServerEndpoint("/")
public class WebsocketProvider {
@OnOpen
public void onOpen(Session session, EndpointConfig endpointConfig) {
logger.debug("Client " + session + " open");
}
@OnMessage
public void onBinaryMessage(ByteBuffer msg, Session session) {
logger.debug("ReceiveMessage: " + msg);
}
@OnClose
public void onClose(Session session, CloseReason closeReason) {
logger.debug("Client " + session + " closed for" + closeReason);
}
}
いくつか実装の方法はありますが、サーバ側と同じようにイベントハンドラ的に実装します。
@ClientEndpoint
public class WebSocketEndpoint extends Endpoint {
@OnOpen
public void onOpen(Session session, EndpointConfig config) {
this.session = session;
session.addMessageHandler(new MessageHandler.Whole<ByteBuffer>() {
@Override
public void onMessage(ByteBuffer buf) {
}
});
}
}
EEサーバでなくてもUndertow, Jetty, Tomcatの上で実行可能です。
接続方向の逆転
前述の問題を解消するには、接続の方向を逆転させることです。AからBへ送るのではなくて、BからAへ変更データを取りに行くという方式です。
変更データに順序保証されたシーケンシャルな番号をふっておき、Bは何番まで処理できたかを管理しておきます。そしてBからAに、「○番以降の変更データをくれ」とリクエストします。ここでWebSocketが役に立って、変更がなければBの要求を待たせておいて、変更があった瞬間にAからBに送ることができるようになります。
大きなメリットは、データを転送する際のトランザクションがシステムBだけに閉じられていることです。データ転送エラーになっても、A側は何もすることはなく、B側から再度とりにいくだけです。
Clojureでの実装
ulon-colonというプロダクトで、逆向きWebSocket転送を実装しています。
まとめ
設計がいまいち複雑になってしまうなー、というときは発想を逆転させるとうまくいく…かもしれません。