1. ホーム
  2. javascript

[解決済み] マルチユーザー用ajaxアプリケーションの同時実行安全性を確保する方法

2023-01-25 12:04:38

質問

サーバーからの大量のデータを表示するWebページがあります。通信はajaxで行われます。

ユーザーが操作してこのデータを変更するたびに(ユーザーAが何かの名前を変更したとします)、サーバーにアクションを実行するように伝え、サーバーは新しい変更されたデータを返します。

ユーザーBが同時にページにアクセスし、新しいデータオブジェクトを作成した場合、それは再びajaxを介してサーバーに伝えられ、サーバーはユーザーに対して新しいオブジェクトを返します。

Aのページには、名前を変更したオブジェクトを含むデータがあります。そしてBのページには、新しいオブジェクトを持つデータがあります。サーバー上では、名前は変更されたオブジェクトと新しいオブジェクトの両方が存在します。

複数のユーザーが同時に使用する場合、ページをサーバーと同期させるためのオプションは何ですか?

ページ全体をロックしたり、変更のたびにユーザーに対して全体の状態をダンプしたりするようなオプションは、むしろ避けなければなりません。

役に立つのであれば、この具体例では、Web ページはデータベース上でストアド プロシージャを実行する静的な Webmethod を呼び出します。ストアド プロシージャは、変更されたすべてのデータを返しますが、それ以上は返しません。静的な Web メソッドは、ストアドプロシージャの返事をクライアントに転送します。

バウンティ編集

サーバーとの通信にAjaxを使用し、同時実行の問題を回避するマルチユーザーWebアプリケーションを設計するにはどうすればよいでしょうか?

すなわち、データや状態の破損のリスクなしに、機能およびデータベース上のデータへ同時にアクセスすることです。

どのように解決するのですか?

概要です。

  • 導入
  • サーバーアーキテクチャ
  • クライアント・アーキテクチャ
  • アップデート事例
  • コミット案件
  • コンフリクト案件
  • パフォーマンスとスケーラビリティ

レイノスさん、こんにちは。

私はここで特定の製品について議論するつもりはありません。他の人が言及したものは、すでに見ておくべき良いツールセットです(たぶん、そのリストにnode.jsを追加します)。

アーキテクチャの観点から、あなたはバージョン管理ソフトウェアで見られるのと同じ問題を持っているように見えます。あるユーザーがオブジェクトへの変更をチェックインすると、別のユーザーが同じオブジェクトを別の方法で変更したい=> という衝突が発生します。オブジェクトに対するユーザーの変更を統合すると同時に、タイムリーかつ効率的に更新を配信し、上記のような競合を検出して解決することができなければならないのです。

もし私があなたの立場だったら、このようなものを開発するでしょう。

1. サーバーサイド。

  • アトミック アーティファクト (ページ、ページ上のオブジェクト、オブジェクト内の値) と呼ぶべきものを定義する妥当なレベルを決定します。これは、ウェブサーバー、データベース、キャッシュハードウェア、ユーザー数、オブジェクト数などに依存します。簡単に決定できるものではありません。

  • アトミックアーティファクトが持つそれぞれについて

    • アプリケーションワイドな一意性ID
    • インクリメントされるバージョン ID
    • 書き込みアクセスのためのロック機構 (ミューテックスかもしれません)
    • リングバッファ内の小さな履歴または変更履歴(共有メモリはこれらのためによく働きます)。拡張性は低いですが、単一のキー-バリューペアもOKかもしれません。 http://en.wikipedia.org/wiki/Circular_buffer
  • サーバまたは疑似サーバコンポーネントは 関連する を効率的に配信することができます。Observer-Patternはこのための友人です。

2. クライアントサイド

  • 上記のサーバーに長時間 HTTP 接続できる、または軽量なポーリングを使用する javascript クライアントです。

  • 接続された javascript クライアントが、監視されている成果物の履歴の変更を通知したときに、サイトのコンテンツをリフレッシュする javascript の成果物更新コンポーネントです。(再び、オブザーバー パターンが良い選択かもしれません)

  • アトミックアーティファクトの変更を要求し、mutex ロックを取得しようとする javascript のアーティファクトコミッターコンポーネントです。これは、既知のクライアント側のアーティファクトのバージョン ID と現在のサーバー側のアーティファクトのバージョン ID を比較することによって、アーティファクトの状態がほんの数秒前に他のユーザーによって変更されたかどうかを検出します (javascript クライアントとコミット プロセスの遅延が影響します)。

  • javascript のコンフリクトソルバーは、人間による「どちらが正しいか」の判断を可能にします。あなたは、ユーザーに「誰かがあなたより速かったので、あなたの変更を削除しました」と伝えたくはないでしょう。私はあなたの変更を削除しました。泣けよ..."。技術的な差分や、よりユーザーフレンドリーな解決策など、多くの選択肢があるように思います。

では、どのようにロールバックするのでしょうか...

ケース1:更新のための親切なシーケンス図。

  • ブラウザがページをレンダリングする
  • javascript は、それぞれが少なくとも 1 つの値フィールド、一意、およびバージョン ID を持つアーティファクトを見ます。
  • javascript のクライアントが開始され、見つかったバージョンから始まる見つかったアーティファクトの履歴を見るように要求します(古い変更には興味がありません)。
  • サーバーはリクエストを記録し、履歴を継続的にチェックおよび/または送信します。
  • 履歴エントリには、クライアントが独立してポーリングできるようにするための単純な通知 "artifact x has changed, client pls request data" または完全なデータセット "artifact x has changed to value foo" を含めることができます。
  • javascript の artifact-updater は、更新されたことがわかるとすぐに新しい値をフェッチするためにできることをします。新しい ajax リクエストを実行するか、javascript クライアントからフィードを取得します。
  • ページの DOM-content が更新され、任意でユーザーに通知されます。履歴の監視は継続されます。

ケース2:次はコミットです。

  • アーティファクトコミッターはユーザーの入力から希望する新しい値を知り、サーバーに変更要求を送信します。
  • サーバーサイドのミューテックスが取得される
  • サーバーは、"ヘイ、私はバージョン123からアーティファクトxの状態を知っている、私はそれを値foo pls.に設定させてください"を受け取ります。
  • サーバー側のアーティファクト x のバージョンが 123 と等しい (小さくできない) 場合、新しい値が受け入れられ、124 という新しいバージョン ID が生成されます。
  • 新しい状態情報 "updated to version 124" とオプションで新しい値 foo は、アーティファクト x のリングバッファ (変更履歴/ヒストリー) の先頭に配置されます。
  • サーバーサイドのミューテックスが解放されました。
  • 要求しているアーティファクトのコミッターは、新しい ID と共にコミット確認を受け取ることができれば満足です。
  • 一方、サーバーサイドのサーバーコンポーネントは、接続されているクライアントにリングバッファをポーリング/プッシュし続けます。アーティファクト x のバッファを監視しているすべてのクライアントは、通常の遅延時間内に新しい状態情報と値を取得します(ケース 1 を参照)。

ケース 3: コンフリクトの場合。

  • アーティファクトコミッターはユーザーの入力から希望する新しい値を知り、サーバーに変更要求を送信します。
  • 一方、別のユーザーが同じアーティファクトを正常に更新しました (ケース 2 を参照)。しかし、さまざまな遅延のため、このことはまだ別のユーザーには知られていません。
  • そのため、サーバーサイドのミューテックスが取得されます (または "より速い" ユーザーが変更をコミットするまで待機されます)。
  • サーバーは "ヘイ、私はバージョン 123 からアーティファクト x の状態を知っているので、それを値 foo." に設定させてください。
  • サーバー側では、現在、アーティファクト x のバージョンはすでに 124 です。要求しているクライアントは、上書きされる値を知ることができません。
  • 明らかにサーバーは変更要求を拒否しなければならず (神に介入する上書きの優先順位を考慮しない)、ミューテックスを解放し、新しいバージョン ID と新しい値をクライアントに直接送り返すのに十分親切なのです。
  • 拒否されたコミット リクエストと、変更を要求したユーザーがまだ知らない値に直面した場合、javascript のアーティファクト コミッターは競合リゾルバーを参照し、ユーザーに問題を表示し説明します。
  • ユーザーは、スマートなコンフリクト リゾルバ JS によっていくつかのオプションが提示され、値を変更するための再試行が許可されます。
  • ユーザーが正しいと思う値を選択すると、プロセスはケース 2 (または他の誰かがより速かった場合はケース 3) からやり直されます。

パフォーマンスとスケーラビリティに関するいくつかの言葉

HTTP ポーリングと HTTP プッシングの比較。

  • ポーリングは、1 秒に 1 回、1 秒に 5 回など、あなたが許容できる遅延と考えるとおりに、リクエストを作成します。これは、(Apache?) と (php?) を十分に構成して軽量なスターターを作らないと、インフラストラクチャにとってかなり残酷なことになります。サーバーサイドでポーリング要求を最適化し、ポーリング間隔の長さよりもはるかに短い時間で実行されるようにすることが望まれます。この実行時間を半分にすることは、システム全体の負荷を最大50%まで下げることを意味します。
  • HTTPでプッシュする場合(ウェブワーカーが遠く離れていてサポートできないと仮定して)、各ユーザーに1つのapache/lighthttpdプロセスを利用できるようにする必要があります。 常に . これらのプロセスのそれぞれに予約された常駐メモリとシステムの総メモリは、あなたが遭遇する非常に確実なスケーリング限界のひとつとなるでしょう。接続のメモリ フットプリントを削減することが必要であり、また、これらの各プロセスで行われる連続的な CPU および I/O 作業の量を制限する必要があります (多くのスリープ/アイドル時間が必要です)。

バックエンドのスケーリング

  • データベースやファイルシステムを忘れ、あなたは が必要です。 頻繁にポーリングするための共有メモリベースのバックエンド (クライアントが直接ポーリングしない場合、実行中の各サーバー プロセスがポーリングします)
  • memcache を使用すると、より良いスケーリングが可能ですが、それでも高価です。
  • コミット用のmutexは、ロードバランスのために複数のフロントエンドサーバを持ちたい場合でも、グローバルに動作する必要があります。

フロントエンド・スケーリング

  • ポーリングしているかプッシュを受信しているかにかかわらず、ウォッチしているすべてのアーティファクトの情報を 1 つのステップで取得するようにします。

クリエイティブな微調整

  • クライアントがポーリングしていて、多くのユーザーが同じ成果物を見る傾向がある場合、それらの成果物の履歴を静的ファイルとして公開し、apache がそれをキャッシュし、成果物が変更されるとサーバー側でそれを更新できるようにしてみてはいかがでしょうか。これにより、PHP/memcacheはリクエストのためのゲームから解放されます。Lighthttpd は静的ファイルの提供において非常に効率的です。
  • cotendo.com のようなコンテンツデリバリーネットワークを使用して、アーティファクトの履歴をそこにプッシュします。プッシュのレイテンシは大きくなりますが、スケーラビリティは夢のようです。
  • ユーザーが java や flash(?) を使って接続する、本物のサーバーを書く (HTTP は使わない)。あなたは、1 つのサーバー スレッドで多くのユーザーにサービスを提供することに対処する必要があります。開いているソケットを循環させ、必要な作業を行う(または委任する)。プロセスをフォークしたり、より多くのサーバを起動することでスケーリングできる。ミューテックスはグローバルにユニークであり続けなければなりません。
  • 負荷シナリオに応じて、フロントエンドとバックエンドのサーバーを artifact-id 範囲でグループ化します。これは、永続的なメモリのより良い使用を可能にし (データベースはすべてのデータを持っていません)、ミューテックスをスケーリングすることを可能にします。しかし、JavaScriptは、同時に複数のサーバーへの接続を維持しなければなりません。

これがあなた自身のアイデアの出発点になることを願っています。もっとたくさんの可能性があると確信しています。 この投稿に対する批判や改良を歓迎します。

クリストフ・シュトレーゼン