銀行口座にアクセスするためのユーザーインターフェイスを提供するWebシステムを使用するとします。 新しい支払い文書を作成し、詳細と金額を指定して、「支出」ボタンをクリックします。 この時点で、クライアントブラウザとリモートサーバーの間のどこかで、接続の問題が発生します。 XHR要求への応答で、ステータスは503です。要求がリモートサーバーによって受信されたかどうか、および実行できなかったかどうかを判断することは明白です。 接続エラーに関する情報がクライアントに表示されます。 同じドキュメントで「保留」を再度クリックし、サーバーがすでにそれを投稿している場合はどうなりますか? ほとんどの場合、そのようなドキュメントが既に投稿されているか、または複製されるという検証エラーをサーバーが出すと仮定できます。 もちろん、すべてはクライアントとサーバーの相互作用の組織に依存します。
かなり単純なプロトコルに基づいて、この種の問題の可能な解決策の1つに注目したいと思います。
このプロトコルは接続の問題をどのように解決しますか?
-クライアント要求はトランザクション*です。 HTTP接続の問題が発生した場合、ユーザーインターフェイスはブロックされます。 定期的に、サービスから応答を受信するまで、障害が発生したトランザクションを送信しようとします。
-要求がサーバーに送信されたが、接続の問題による応答が返されなかった場合、トランザクションは、バックエンドサービスから応答を受信するまでクライアントで再送信されます。 サーバーでは、トランザクションがすでに早く完了していることが判断され、結果キャッシュから結果が返されます。
-トランザクションがサーバーに送信され、接続障害が原因で応答が受信されなかった場合、接続が復元されたときも、サーバーはタスクが完了していないと判断し、対応する応答がクライアントに送信され、サーバーによって計算されるまで実行結果を取得しようとします。
*このコンテキストでは、トランザクションという用語は、サービスへの要求を実行し、実行結果を受け取り、ユーザーインターフェイスに変更を加えるアトミックタスク(またはアトミックタスクセット)を意味します。 障害が発生した場合、送信を繰り返し試行します。
このプロトコルを使用する意味があるのはいつですか?
-クライアントからのすべての要求が1つのポイントを通過して1つのディスパッチャーに送られるようにシステムアーキテクチャが構築されている場合(順番に必要なサービスに分散されます)。
-接続の中断が頻繁に発生する場合。
-長時間実行される同期サーバータスクと要求がある場合、クライアントとサーバー間のインフラストラクチャのどこかでタイムアウトが発生します。 これは、終了するまで接続を保持するタスクを指します。
現在参加しているプロジェクトでは、衛星チャネルを介して海洋の船/プラットフォームからアクセスされるため、頻繁に接続の中断が発生します。 さらに、このシステムは長い間開発されており、長時間実行できる同期サービスが含まれており、プロキシサーバーがタイムアウトに達すると要求がカットされます。 もちろん、そのようなサービスはアーキテクチャの観点からは正しくありませんが、ありのままのものがあります。 このプロトコルの機能は、このようなサービスのリファクタリングの代替ではないかもしれませんが、少なくとも上記の問題の回避策です。
プロトコルの説明

クライアント部
-バックエンドサービスへの各リクエストはトランザクションにラップされます
-単一のブラウザウィンドウ内の各トランザクションは、一意のトランザクション識別子によって特徴付けられます。 たとえば、新しいトランザクションのIDは、前のトランザクションのIDをインクリメントすることで計算されます。
-一意のブラウザウィンドウ識別子が各リクエストに追加されます。 これは、複数のブラウザウィンドウが同じWebセッション内で機能し、トランザクション識別子が単一のブラウザウィンドウ内で一意であるためです。 詳細は後述します。
-接続の問題(HTTPステータス応答コード4xx、5xx、またはクライアントタイムアウト)の場合、要求と応答は接続エラーハンドラーに送信されます。
-接続エラーハンドラーはユーザーインターフェイスをブロックし、特定の時間間隔で実行するトランザクションを再度送信します。これにより、対応するメッセージがユーザーに表示されます。
-再接続すると、ユーザーインターフェイスのロックが解除され、受信した応答の通常の処理が続行されます。
サーバー側
-リクエストがディスパッチャに到達すると、ブラウザウィンドウIDとトランザクションIDがディスパッチャから決定されます。
-応答キャッシュは、ユーザーWebセッションのサーバーオブジェクトから取得されます。
-応答キャッシュ、ブラウザウィンドウID、トランザクションIDに基づいて、トランザクションが以前に完了したかどうかが判断されます。
-トランザクションが以前に完了した場合、ディスパッチャは結果を返します。
-トランザクションが進行中の場合、サーバーはこれに関する情報をクライアントに返します。
-トランザクションがまだ実行されていないと判断された場合:
1)トランザクションが処理された特定のブラウザウィンドウIDおよびトランザクションIDのラベルがキャッシュに配置されます
2)ディスパッチャは、処理のためにトランザクションから適切なサービスに操作を転送し、結果を受け取ります
3)ディスパッチャーは、トランザクションの結果を、それぞれブラウザーウィンドウIDとトランザクションIDの応答キャッシュに入れます
4)ディスパッチャは、トランザクションの結果をレスポンダに送信します。
-ユーザーのWebサーバーセッションが廃止されると、セッションオブジェクトの削除の一部としてキャッシュが削除されます。
-応答は、キャッシュ内の応答の境界ライフタイムに達すると、キャッシュからクリアされます。 これは、ユーザーセッションが長い場合のメモリオーバーフローを防ぐために必要です。
-ライフタイムの期限が切れた後にキャッシュから割り当てられたトランザクションの結果を取得しようとすると、クライアントに関連情報が送信されます。
実装の詳細の説明
クライアント側JavaScript、SmartClient 6.5
-SmartClientでは、RPCManager.handleErrorメソッド(応答、要求)を使用して、すべてのRPCエラーがデフォルトでキャッチされます。
次のように再定義し、接続問題のインターセプトを追加します
..
if (response.status == isc.RPCResponse.STATUS_TRANSPORT_ERROR || response.status == isc.RPCResponse.STATUS_SERVER_TIMEOUT) {
isc.RPCManager.handleHttpError(response, request);
}
..
RPCManager.handleHttpError(応答、要求)メソッドを実装し、ユーザーインターフェイスを(問題に関するメッセージと次の試行までのカウントダウンで)ブロックして、トランザクションを再送信します。
Webセッション内でブラウザー識別子の一意性を確保する必要があることに注意してください。 ブラウザ識別子は静的変数RPCManager.clientIdで定義され、クラスが初期化されるとisc.timeStamp()タイムスタンプに設定されます。
各リクエストにclientIdを追加するには、すべてのRPCリクエストがSmartClientに送信されるメソッドを再定義します
isc.RPCManager.addClassMethods({
..
sendRequestOriginal : RPCManager.sendRequest,
sendRequest : function (request) {
if (request.params == null) {
request.params = new Object();
}
request.params.clientId = RPCManager.clientId;
isc.RPCManager.sendRequestOriginal(request);
}
..
Javaバックエンド、SmartClient DMI
SmartClientでは、クライアントとサーバーの相互作用は、内部プロトコルSmartClient DMI(直接メソッド呼び出し)に従って編成されます。 サーバー側のJava実装があります。
ここで、エントリポイントはcom.isomorphic.servlet.IDACallサーブレットで、特定のDMIサービスへのリクエストをディスパッチします。
このクラスから継承し、web.xmlで実装を定義します
public void processRequest(HttpServletRequest request, HttpServletResponse response)
オーバーライド
サーブレットのソースコードがない場合は、ファイルクラスをいつでも逆コンパイルできることに注意してください。 このサーブレットで何が起こっているかを正確に理解し、SmartClient DMIの処理に機能を実装するためにこれが必要になります。
HTTPリクエストから、クライアントによって送信されるブラウザ識別子を取得します
request.getParameter(CLIENT_ID)
request.getSession()
介してセッションオブジェクトを取得します。
セッションオブジェクトは、このセッションのフレームワーク内のサーバーへのリクエストの多くのフロー間で共有されるため、慎重に作業する必要があることに注意してください。 必要に応じて、セッション呼び出しで同期ブロックに参加します。
レスポンスキャッシュは、
setAttribute()/getAttribute()
属性としてセッションオブジェクトに保存します
キャッシュ構造は次のとおりです。
Map<Long, Map<Long, CacheTransaction>> cache
キーは、セッション内のブラウザIDです
値はトランザクションカードであり、キーはトランザクションIDであり、値はトランザクション操作の結果を持つオブジェクトです。
Collections.synchronizedMap()
を使用して、スレッドセーフな方法でこれらのカードを作成します
アトミックコールのみがここでスレッドセーフになるため、必要に応じて同期ブロックについても忘れないでください。
public class CacheTransaction {
private long transactionNum;
private long requestTimestamp;
private Map<Long, Object> responses;
..
ここで、
Map<Long, Object> responses
は、要求に対するサービス応答のマップです。
ここでのキーはリクエストハッシュで、値はレスポンスのオブジェクトです。
このプロトコルを実装する際には、パフォーマンスとキャッシュで使用されるメモリに注意してください! キャッシュ内の可用性について各リクエストがチェックされます。 この実装例では、各セッションには独自のキャッシュがあるため、これは問題になりません。 キャッシュ内のオブジェクトの有効期間を設定することにより、使用メモリを調整できます。 また、リクエストをキャッシュに保存する必要はありません。リクエストのハッシュをキーとして、レスポンスを値として保存すると便利です。 ここで、要求と応答とは、HttpRequestとHttpResponseではなく、ターゲットサービスの要求と応答のオブジェクトを意味します。
スケーラブルなソリューションの場合、セッションオブジェクトがあることに注意してください。 したがって、ノード間のセッションのレプリケーションまたはスティッキーセッションによる負荷分散のいずれかを整理します。