web-dev-qa-db-ja.com

同時に安全になるようにマルチユーザーAjax Webアプリケーションを設計する方法

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

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

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

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

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

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

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

バウンティ編集:

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

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

94
Raynos

概要:

  • イントロ
  • サーバーアーキテクチャ
  • クライアントのアーキテクチャ
  • ケースを更新
  • コミットケース
  • 紛争事例
  • パフォーマンスとスケーラビリティ

こんにちはRaynos、

ここでは特定の製品については説明しません。他の人が言及したのは、すでに調べておくのに適したツールセットです(おそらくnode.jsをそのリストに追加してください)。

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

私があなたの靴にいたなら、私はこのようなものを開発します:

1.サーバー側:

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

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

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

2.クライアント側:

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

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

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

  • 人間が正しい変更を決定できるようにするjavascript競合ソルバー。 「誰かがあなたよりも速かったです。変更を削除しました。泣きます。」とユーザーに伝えたくないかもしれません。かなり技術的な差分や、よりユーザーフレンドリーなソリューションからの多くのオプションが考えられます。

それでは、どのように転がりますか...

ケース1:更新のための一種のシーケンス図:

  • ブラウザレンダリングページ
  • javascriptは、それぞれ少なくとも1つの値フィールド、unique-idおよびversion-idを持つアーティファクトを「見ます」
  • javascriptクライアントが開始され、見つかったバージョンから始まる見つかったアーティファクトの履歴を「監視」するよう要求します(古い変更は面白くない)
  • サーバープロセスはリクエストを記録し、履歴を継続的にチェックおよび/または送信します
  • 履歴エントリには、「アーティファクトxが変更され、クライアントplsがデータを要求する」という簡単な通知が含まれる場合があります。
  • 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回、毎秒5回のリクエストを作成します。 (Apache?)と(php?)を "軽量"スターターとして十分に構成しない場合、これはインフラストラクチャにとってかなり残酷な場合があります。ポーリング間隔の長さよりもはるかに短い時間実行されるように、サーバー側でポーリング要求を最適化することが望ましいです。そのランタイムを半分に分割すると、システム全体の負荷が最大50%低下する可能性があります。
  • HTTP経由のプッシュ(Webworkerがサポートするには遠すぎると仮定)には、常に各ユーザーで利用可能なApache/lighthttpdプロセスが1つ必要です。これらのプロセスとシステムの合計メモリ用に予約されている常駐メモリは、発生する非常に確実なスケーリング制限の1つです。接続のメモリフットプリントを削減する必要があります。また、これらのそれぞれで実行される連続CPUおよびI/O作業の量を制限する必要があります(多くのスリープ/アイドル時間が必要です)

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

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

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

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

「創造的な」微調整

  • クライアントがポーリングしており、多くのユーザーが同じアーティファクトを見る傾向がある場合は、それらのアーティファクトの履歴を静的ファイルとして公開して、Apacheがキャッシュできるようにしますが、アーティファクトが変更されたときにサーバーサイドで更新します。これにより、PHP/memcacheが要求から一部ゲームから除外されます。 Lighthttpdは、静的ファイルの提供に非常に効率的です。
  • cotendo.comなどのコンテンツ配信ネットワークを使用して、アーティファクト履歴をプッシュします。プッシュレイテンシは大きくなりますが、スケーラビリティは夢です
  • ユーザーがJavaまたはflash(?)を使用して接続する実サーバー(HTTPを使用しない)を作成します。1つのサーバースレッドで多数のユーザーにサービスを提供する必要があります。 (または委任)必要な作業。分岐プロセスまたは追加のサーバーを介して拡張できます。ただし、mutexはグローバルに一意である必要があります。
  • 負荷シナリオに応じて、アーティファクトIDの範囲ごとにフロントエンドサーバーとバックエンドサーバーをグループ化します。これにより、永続メモリをより適切に使用できるようになり(データベースにすべてのデータが格納されない)、ミューテックスをスケーリングできるようになります。ただし、JavaScriptは複数のサーバーへの接続を同時に維持する必要があります。

これがあなた自身のアイデアの出発点になることを願っています。もっと多くの可能性があると確信しています。この投稿に対する批判や機能強化を歓迎している以上、wikiは有効になっています。

クリストフ・ストラセン

157

これは古い質問であることは知っていますが、私はただチャイムインしたいと思っていました。

OT(operational transforms) は、同時かつ一貫したマルチユーザー編集の要件に適しているようです。 Googleドキュメントで使用される技術 (およびGoogle Waveでも使用されます):

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

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

サーバーとクライアント間の通信にBrowserChannelを使用します(WebSocketのサポートは機能しているはずです-以前はSocket.IOを介して行われていましたが、Socket.ioの開発者の問題のために削除されました)。ただし、現時点ではビットがまばらです。

13
victorhooi

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

5
Chris Baker

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

将来、WebSocketsと呼ばれるこれを行う簡単な方法がありますが、標準の現在の状態に関するセキュリティ上の懸念があるため、いつその標準がプライムタイムの準備が整うかはまだわかりません。

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

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

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

これを同時に編集するのは本当に簡単なことではありません。なぜなら、遅延のためにウェブ上でアトミックな操作を行うことができないからです。多分 この記事 は、このトピックについてさらに学ぶのに役立ちます。

3
Jannes

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

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

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

「この単純なパターン(リアクティブな計算+リアクティブなデータソース)には幅広い適用性があります。プログラマーは、サブスクライブ/リサブスクライブコールを記述し、適切なタイミングで呼び出されるようにすることで節約されます。エラーが発生しやすいロジックを備えたアプリケーション。」

2
mb.

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

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

Meteorは非常に単純なので、少なくともアイデアを盗むためにそれを見てみるとよいでしょう。

1

これらのウィキペディアのページは、 並行性 および 並行計算 の学習に視点を追加するのに役立ちます ajaxウェブアプリケーションプル または プッシュ 状態 イベント[〜#〜] eda [〜#〜]メッセージメッセージングパターン 基本的に、メッセージは変更イベントおよび同期要求に応答するチャネルサブスクライバーに複製されます。

同時ウェブベースの多くの形式があります 共同ソフトウェア

多くの etherpad-liteのHTTP APIクライアントライブラリ 、a 共同リアルタイムエディター があります。

Django-realtime-playground は、 Socket.io などのさまざまなリアルタイムテクノロジーを使用して、Djangoにリアルタイムチャットアプリを実装します。

AppEngineとAppScaleの両方が AppEngine Channel API ;を実装します Google Realtime API とは異なります。これは googledrive/realtime-playground で示されています。

1
Wes Turner

サーバー側プッシュ テクニックはここに行く方法です。 コメット はバズワードです(またはそうでしたか?)。

特定の方向性は、サーバースタックと、あなた/それがどれだけ柔軟かによって大きく異なります。可能であれば、 socket.io をご覧ください。これは、websocketのクロスブラウザ実装を提供します。これにより、サーバーとの双方向通信が非常に合理化され、サーバーがプッシュできるようになります。クライアントの更新。

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

0
davin