96

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

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

ユーザーBが同時にページにアクセスし、新しいデータオブジェクトを作成すると、ajaxを介してサーバーに再度通知され、サーバーはユーザーの新しいオブジェクトを返します。

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

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

ページ全体をロックしたり、変更のたびに状態全体をユーザーにダンプしたりするなどのオプションは、かなり避けられます。

それが役立つ場合、この特定の例では、Webページはデータベースでストアドプロシージャを実行する静的Webメソッドを呼び出します。ストアドプロシージャは、変更したデータを返しますが、それ以上は返しません。次に、静的Webメソッドは、ストアドプロシージャの戻り値をクライアントに転送します。

バウンティ編集:

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

つまり、データや状態の破損のリスクなしに、機能とデータベース上のデータへの同時アクセス

4

8 に答える 8

158

概要:

  • はじめに
  • サーバーのアーキテクチャ
  • クライアントのアーキテクチャ
  • ケースの更新
  • ケースをコミットする
  • 紛争事件
  • パフォーマンスとスケーラビリティ

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

ここでは、特定の製品については説明しません。他の人が言及したことは、すでに見てみるのに適したツールセットです (そのリストに node.js を追加するかもしれません)。

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

私があなたの立場だったら、次のようなものを開発します。

1. サーバー側:

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

  • 各アトミック アーティファクトには、次のものがあります。

    • アプリケーション全体の一意の ID
    • 増分するバージョン ID
    • 書き込みアクセス用のロック機構 (おそらくミューテックス)
    • リングバッファ内の小さな履歴または「変更ログ」(共有メモリはそれらに適しています)。拡張性は低くなりますが、単一のキーと値のペアでも問題ない場合があります。http://en.wikipedia.org/wiki/Circular_bufferを参照
  • 関連する変更ログを接続ユーザーに効率的に配信できるサーバーまたは疑似サーバー コンポーネント。Observer-Pattern はこのための友達です。

2. クライアント側:

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

  • 接続された JavaScript クライアントが監視対象の成果物履歴の変更を通知したときに、サイトのコンテンツを更新する JavaScript 成果物更新コンポーネント。(ここでも観察者パターンが良い選択かもしれません)

  • ミューテックス ロックを取得しようとして、アトミック アーティファクトの変更を要求する可能性がある JavaScript アーティファクト コミッター コンポーネント。既知のクライアント側のアーティファクト バージョン ID と現在のサーバー側のアーティファクト バージョン ID を比較することにより、アーティファクトの状態がほんの数秒前に別のユーザーによって変更されたかどうか (javascript クライアントのレイテンシとコミット プロセスの要因) を検出します。

  • どの変更が正しいかを人間が判断できるようにする JavaScript コンフリクト ソルバー。ユーザーに「誰かがあなたより速かった。あなたの変更を削除しました。泣いてください」とだけ言いたくないかもしれません。かなり技術的な差分またはよりユーザーフレンドリーなソリューションからの多くのオプションが可能であるようです。

では、どのように転がるのだろうか...

ケース 1: 更新用シーケンス図の種類:

  • ブラウザーがページをレンダリングする
  • javascript は、それぞれが少なくとも 1 つの値フィールド、unique-id および version-id を持つアーティファクトを「認識」します。
  • javascript クライアントが開始され、見つかったバージョンから始まる、見つかったアーティファクトの履歴を「監視」するように要求します (古い変更は興味深いものではありません)。
  • サーバープロセスはリクエストを記録し、履歴を継続的にチェックおよび/または送信します
  • 履歴エントリには、クライアントが個別にポーリングできるようにする「アーティファクト x が変更されました。クライアント pls はデータを要求します」という単純な通知、または「アーティファクト x が値 foo に変更されました」という完全なデータセットが含まれる場合があります。
  • javascript artifact-updater は、更新されたことがわかるとすぐに新しい値を取得するためにできることを行います。新しい ajax リクエストを実行するか、javascript クライアントからフィードを受けます。
  • ページの DOM コンテンツが更新され、オプションでユーザーに通知されます。ヒストリーウォッチングは続きます。

ケース 2: コミットする場合:

  • アーティファクト コミッターは、ユーザー入力から目的の新しい値を認識し、変更要求をサーバーに送信します
  • サーバー側のミューテックスが取得されます
  • サーバーは、「ねえ、バージョン 123 からのアーティファクト x の状態を知っています。値 foo pls に設定させてください。」を受け取ります。
  • アーティファクト x のサーバー側のバージョンが 123 よりも小さい場合は、新しい値が受け入れられ、124 の新しいバージョン ID が生成されます。
  • 「バージョン 124 に更新された」新しい状態情報と、オプションで新しい値 foo が、アーティファクト x のリングバッファ (changelog/history) の先頭に配置されます。
  • サーバー側のミューテックスが解放されました
  • アーティファクト コミッターを要求すると、新しい 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 経由でプッシュするには (Web ワーカーがサポートするには遠すぎると仮定します)、ユーザーごとに 1 つの apache/lighthttpd プロセスを常に使用できるようにする必要があります。これらのプロセスごとに予約されている常駐メモリとシステムの合計メモリは、遭遇する非常に確実なスケーリング制限の 1 つです。接続のメモリ フットプリントを減らすことが必要であり、これらのそれぞれで実行される継続的な CPU および I/O 作業の量を制限する必要があります (多くのスリープ/アイドル時間が必要です)。

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

  • データベースとファイルシステムを忘れてください。頻繁なポーリングのために、ある種の共有メモリベースのバックエンドが必要になります (クライアントが直接ポーリングしない場合は、実行中の各サーバー プロセスがポーリングします)。
  • memcache を使用すると、より適切にスケーリングできますが、それでも高価です
  • コミット用のミューテックスは、負荷分散のために複数のフロントエンド サーバーが必要な場合でも、グローバルに機能する必要があります。

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

  • ポーリングしているか「プッシュ」を受信して​​いるかに関係なく、監視されているすべてのアーティファクトの情報を 1 つのステップで取得するようにしてください。

「創造的な」微調整

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

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

クリストフ・シュトラーセン

于 2011-02-17T16:32:18.727 に答える
13

これは古い質問であることは知っていますが、私はただチャイムを鳴らすだけだと思いました.

OT (運用変換)は、同時かつ一貫したマルチユーザー編集の要件に適しているようです。これは、Google Docs で使用されている手法です(Google Wave でも使用されていました)。

Operational Transforms を使用するための JS ベースのライブラリがあります。ShareJS ( http://sharejs.org/ ) は、Google Wave チームのメンバーによって作成されています。

また、必要に応じて、ShareJS 上に構築された完全な MVC Web フレームワークである DerbyJS ( http://derbyjs.com/ ) も用意されており、これらすべてが実行されます。

サーバーとクライアント間の通信に BrowserChannel を使用します (そして、WebSockets のサポートが進行中であると私は信じています。以前は Socket.IO 経由でサポートされていましたが、Socket.io に関する開発者の問題のために削除されました)。ただし、現時点では少しまばらです。

于 2013-07-08T02:04:05.223 に答える
5

各データセットに時間ベースの修正スタンプを追加することを検討します。したがって、db テーブルを更新する場合は、それに応じて変更されたタイムスタンプを変更します。AJAX を使用すると、クライアントの変更されたタイムスタンプをデータ ソースのタイムスタンプと比較できます。ユーザーが遅延している場合は、表示を更新します。このサイトが質問を定期的にチェックして、回答を入力している間に他の誰かが回答したかどうかを確認する方法と同様です.

于 2011-02-15T17:23:45.957 に答える
3

データベースに変更が加えられるとすぐに、プッシュ手法 (Comet またはリバース Ajax とも呼ばれます) を使用してユーザーに変更を反映する必要があります。これに現在利用できる最良の手法は Ajax ロング ポーリングのようですが、すべてのブラウザーでサポートされているわけではないため、フォールバックが必要です。幸いなことに、これを処理するソリューションが既にあります。その中には、orbited.org と前述の socket.io があります。

将来的には、これを行うためのより簡単な方法である WebSockets が登場する予定ですが、標準の現在の状態に関するセキュリティ上の懸念があるため、その標準がいつ公開されるかはまだわかりません。

新しいオブジェクトを使用して、データベースに同時実行の問題があってはなりません。ただし、ユーザーがオブジェクトを編集するとき、サーバーには、その間にオブジェクトが編集または削除されたかどうかを確認するロジックが必要です。オブジェクトが削除された場合、解決策は簡単です。編集を破棄するだけです。

しかし、複数のユーザーが同じオブジェクトを同時に編集しているときに、最も難しい問題が発生します。ユーザー 1 と 2 が同時にオブジェクトの編集を開始すると、両方とも同じデータに対して編集を行います。ユーザー 2 がまだデータを編集している間に、ユーザー 1 が行った変更が最初にサーバーに送信されるとします。次に 2 つのオプションがあります。ユーザー 1 の変更をユーザー 2 のデータにマージするか、ユーザー 2 にデータが古いことを伝え、データがサーバーに送信されるとすぐにエラー メッセージを表示するかです。後者はここではあまりユーザーフレンドリーなオプションではありませんが、前者は実装が非常に困難です。

これを初めて実現した数少ない実装の 1 つは、Google に買収されたEtherPadでした。その後、彼らは Google Docs と Google Wave で EtherPad の技術の一部を使用したと思いますが、確かなことはわかりません。Google は EtherPad もオープンソース化したので、何をしようとしているのかによっては、一見の価値があるかもしれません。

レイテンシーのために Web 上でアトミック操作を行うことができないため、この同時編集を行うのは実際には容易ではありません。この記事は、このトピックについて詳しく知るのに役立つかもしれません。

于 2011-02-17T00:19:25.700 に答える
2

これらすべてを自分で書こうとするのは大変な作業であり、正しく行うのは非常に困難です。1 つのオプションは、クライアントをデータベースと同期させ、クライアント同士をリアルタイムで同期させるために構築されたフレームワークを使用することです。

Meteor フレームワークがこれをうまく行うことがわかりました ( http://docs.meteor.com/#reactivity )。

「Meteor はリアクティブ プログラミングの概念を採用しています。これは、コードを単純な命令型スタイルで記述できることを意味し、コードが依存するデータが変更されるたびに結果が自動的に再計算されます。」

「この単純なパターン (リアクティブな計算 + リアクティブなデータ ソース) には幅広い適用性があります。プログラマーは、unsubscribe/resubscribe 呼び出しを記述し、それらが適切なタイミングで呼び出されることを確認することから解放され、データ伝播コードのクラス全体を排除します。エラーが発生しやすいロジックを持つアプリケーション。」

于 2013-07-08T04:51:13.387 に答える
1

誰もMeteorについて言及していないなんて信じられない。これは確かに新しく未熟なフレームワークですが (公式には 1 つの DB しかサポートしていません)、ポスターが説明しているようなマルチユーザー アプリからすべての面倒な作業と思考を取り除きます。実際、マルチユーザーのライブ更新アプリを構築することはできません。簡単な要約は次のとおりです。

  • すべてが node.js (JavaScript または CoffeeScript) にあるため、クライアントとサーバーの間で検証などを共有できます。
  • Websocket を使用しますが、古いブラウザーにフォールバックできます
  • バックグラウンドでサーバーに送信される変更を使用して、ローカル オブジェクトへの即時更新 (つまり、UI がきびきびしているように感じられる) に焦点を当てています。混合更新を簡単にするために、アトミック更新のみが許可されます。サーバーで拒否された更新はロールバックされます。
  • おまけとして、ライブ コードのリロードを処理し、アプリが大幅に変更された場合でもユーザーの状態を保持します。

Meteor は非常にシンプルなので、少なくともアイデアを盗むために見てみることをお勧めします。

于 2013-07-08T03:09:45.377 に答える
0

サーバー側のプッシュ手法は、ここで使用する方法です。コメットは流行語です。

どの方向に進むかは、サーバー スタックと、その柔軟性に大きく依存します。可能であれば、websocket のクロスブラウザー実装を提供するsocket.ioを見てみたいと思います。これは、サーバーとの双方向通信を非常に合理的に行う方法を提供し、サーバーが更新をクライアントにプッシュできるようにします。

特に、ライブラリの作成者によるこのデモンストレーションを参照してください。これは、説明した状況をほぼ正確に示しています。

于 2011-02-15T18:34:23.747 に答える