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


95

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

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

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

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

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

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

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

バウンティ編集:

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

つまり、機能やデータベース上のデータへの同時アクセスで、データや状態の破損のリスクがありません。



クライアントの状態をシリアル化し、次にajax経由でサーバーに通知するのが私の状態です。更新する必要があるのはオプションです。ただし、クライアントは、あらゆる情報を1か所で更新する方法を知っている必要があります。
Raynos、2011年

1
ユーザーエンドの同時実行性に対する最良のソリューションは、単なるプッシュバリアントの1つではありませんか?Webソケット、彗星など
davin

@davinかなりいいかもしれません。しかし、私は彗星に慣れておらず、クロスブラウザサポートのためのWebソケットはありません。
レイノス

2
ブラウザー間のサポートに適したパッケージがあり、特にjWebSocketや他の多くのものもありますが、特にsocket.ioをお勧めします。あなたはsocket.ioの道を行く場合は、すべてのフレームワークのようなNode.jsのグッズの種類、および(クライアント側)テンプレート化エンジンなどを組み込むことができます
デーヴィン

回答:


157

概要:

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

こんにちはレイノス、

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

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

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

1.サーバー側:

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

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

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

2.クライアント側:

  • 上記のサーバーへの長時間のHTTP接続、または軽量ポーリングを使用できる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に等しい(それ以上にすることはできない)場合、新しい値が受け入れられ、新しいバージョンID 124が生成されます。
  • 「バージョン124に更新された」新しい状態情報と、オプションで新しい値fooが、アーティファクトxのリングバッファ(changelog / history)の先頭に配置されます。
  • サーバーサイドミューテックスがリリースされました
  • アーティファクトコミッターをリクエストすると、新しいIDとともにコミット確認を受け取ります。
  • その間、サーバー側のサーバーコンポーネントは、リングバッファを接続されたクライアントにポーリング/プッシュし続けます。アーティファクトxのバッファーを監視しているすべてのクライアントは、通常の待機時間内に新しい状態情報と値を取得します(ケース1を参照)。

ケース3:競合の場合:

  • アーティファクトコミッターはユーザー入力から目的の新しい値を認識し、サーバーに変更要求を送信します
  • その間、別のユーザーが同じアーティファクトを正常に更新しましたが(ケース2を参照)、レイテンシがさまざまであるため、これはまだ他のユーザーにはわかりません。
  • したがって、サーバー側のミューテックスが取得されます(または、「より高速な」ユーザーが変更をコミットするまで待機します)。
  • サーバーは「ねえ、バージョン123からアーティファクトxの状態を知っています。値fooに設定させてください。」
  • サーバーサイドでは、アーティファクトxのバージョンはすでに124です。要求しているクライアントは、彼が上書きする値を知ることができません。
  • 明らかに、サーバーは変更要求を拒否する必要があり(神の介入による上書きの優先順位は考慮されません)、ミューテックスを解放し、新しいバージョンIDと新しい値を直接クライアントに送り返すのに十分親切です。
  • 拒否されたコミット要求と、変更を要求するユーザーがまだ知らない値に直面した場合、JavaScriptアーティファクトコミッターは、ユーザーに問題を表示して説明する競合リゾルバーを参照します。
  • Smart Conflict-Resolver JSによっていくつかのオプションが提示されたユーザーは、値を変更する別の試行を許可されます。
  • ユーザーが正しいと考える値を選択すると、プロセスはケース2(または他の誰かがより速い場合はケース3)からやり直します。

パフォーマンスとスケーラビリティに関する一言

HTTPポーリングとHTTP "プッシュ"

  • ポーリングは、許容可能なレイテンシと見なすものは何でも、1秒あたり1回、1秒あたり5回のリクエストを作成します。(Apache?)と(php?)を「軽量」のスターターとして十分に構成しない場合、これはインフラストラクチャに対してかなり残酷な場合があります。ポーリング間隔の長さよりはるかに短い時間で実行されるように、サーバー側でポーリング要求を最適化することが望ましいです。そのランタイムを半分に分割することは、システム全体の負荷を最大50%削減することを意味します。
  • HTTP経由でプッシュ1は、Apache / lighthttpdユーザーごとに使用可能なプロセス持っているあなたを必要とします(仮定webworkersはあまりにも遠く、それらをサポートするためにオフになっている)すべての時間を。これらの各プロセス用に予約されている常駐メモリとシステムの合計メモリは、遭遇する非常に確実なスケーリング制限の1つになります。接続のメモリフットプリントを削減する必要があるだけでなく、これらのそれぞれで実行される継続的なCPUおよびI / O作業の量を制限する必要があります(多くのスリープ/アイドル時間が必要です)。

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

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

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

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

「クリエイティブ」な微調整

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

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

クリストフ・ストレイゼン


1
@ChristophStrasenユーザーごとに1つのスレッドに依存しないnode.jsなどのイベントサーバーを確認します。これにより、プッシュ技術をより少ないメモリ消費で処理できます。node.jsサーバーとTCP WebSocketsに依存することは、スケーリングの助けになると思います。ただし、クロスブラウザーのコンプライアンスは完全に台無しになります。
レイノス

私は完全に同意し、私の記事がホイールの再発明を促さないことを願っています!一部のホイールは少し新しいですが、知られ始めたばかりであり、十分に説明されていないため、中級レベルのソフトウェアアーキテクトは、特定のアイデアについてそのアプリケーションを判断できます。私見では。Node.jsは、「ダミー用」の本に値するものです;)。確かに買います。
Christoph Strasen

2
+500あなたはこれに挑戦的に挑戦しました。それは素晴らしい答えです。
Raynos、2011

1
@luqmaanこの回答は2011年2月のものです。WebSocketはまだ目新しいもので、8月頃にChromeでプレフィックスなしでリリースされました。しかし、Cometとsocket.ioは問題ありませんでした。これは、よりパフォーマンスの高いアプローチの提案にすぎないと思います。
Ricardo Tomasi 2013

1
また、Node.jsが快適ゾーン(または運用チームの快適ゾーンですが、質問のビジネスコンテキストについては確信している)から少し離れている場合は、JavaベースのサーバーでSocket.ioを使用することもできます。TomcatとJettyはどちらも、サーバープッシュの種類のセットアップ(例:wiki.eclipse.org/Jetty/Feature/Continuationsを参照)のスレッドレス接続をサポートしています
Tomas

13

これは古い質問であることは知っていますが、私はただ口をそろえると思いました。

OT(操作変換)は、並行して一貫したマルチユーザー編集の要件に適しているようです。これは、Googleドキュメントで使用されている手法です(Google Waveでも使用されていました)。

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

また、必要に応じて、完全なMVC Webフレームワークが用意されています。ShareJS上に構築されたDerbyJS (http://derbyjs.com/)が、すべてを代行してくれます。

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


5

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


それは便利な点です。また、データベースの「LastEdited」フィールドを設計のポイントから理解するのにも役立ちます。
レイノス

丁度。このサイトは「ハートビート」を使用します。つまり、AJAXリクエストをサーバーに送信するx時間ごとに、チェックするデータのIDと、そのデータに対して変更されたタイムスタンプを渡します。それでは、質問#1029にいるとします。AJAXリクエストごとに、サーバーは質問#1029の変更されたタイムスタンプのみを確認します。クライアントが古いバージョンのデータを持っていることが判明した場合は、新しいコピーでハートビートに応答します。その後、クライアントはページをリロード(更新)するか、新しいデータについて警告する何らかのメッセージをユーザーに表示できます。
Chris Baker、

変更されたスタンプは、現在の「データ」をハッシュして、反対側のハッシュと比較するよりもはるかに優れています。
レイノス

1
クライアントとサーバーは、不整合を回避するために、まったく同じ時間にアクセスできる必要があることに注意してください。
祈りの祈り2013

3

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

将来的には、WebSocketと呼ばれるこれを行う簡単な方法が存在しますが、標準の現在の状態に関するセキュリティ上の懸念があるため、標準がいつ完成するかはまだわかりません。

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

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

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

レイテンシのためにWebでアトミック操作を行うことができないため、これを同時に編集することは実際には簡単ではありません。多分この記事はあなたがトピックについてもっと学ぶのを助けるでしょう。


2

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

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

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

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


1

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

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

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


1
私は、特定の種類のアプリでのDerbyとMeteorのアイデアが本当に好きです。ドキュメント/レコードの所有権とアクセス許可は、実際には解決されていない実際の問題のほんの一部です。また、その80%を本当に簡単にし、他の20%に時間をかけすぎているという長年のMSの世界から来た私は、そのようなPFM(純粋な魔術)ソリューションを使用することをためらっています。
Tracker1 2013

1

これらのWikipediaのページはについての学習に視点を追加助けるかもしれ並行処理並行コンピューティング設計するためのAjax Webアプリケーションのいずれかのことを引っ張るかがされて押された状態イベントEDAメッセージ、メッセージングパターンを。基本的に、メッセージは、変更イベントと同期要求に応答するチャネルサブスクライバーに複製されます。

並行Webベースのコラボレーションソフトウェアには多くの形式があります

協調型リアルタイムエディタであるetherpad-liteには、いくつかのHTTP APIクライアントライブラリがあります

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

AppEngineとAppScaleはどちらもAppEngine Channel APIを実装しています。これは、googledrive / realtime-playgroundで示されているGoogle Realtime APIとは異なります


0

サーバー側のプッシュ手法は、ここに行く方法です。彗星は流行語です(またはそうでしたか?)。

特定の方向性は、サーバースタック、およびユーザー/その柔軟性に大きく依存します。可能であれば、サーバーとの双方向通信を行うための非常に効率的な方法を提供し、サーバーがクライアントに更新をプッシュできるようにする、Webソケットのクロスブラウザー実装を提供するsocket.ioを見てみます。

特に、ライブラリの作者によるこのデモを参照してください。これは、あなたが説明する状況をほぼ正確に示しています。


これは、縮小による問題を軽減するための優れたライブラリですが、アプリケーションの設計方法に関する高レベルの情報を探していました
Raynos

1
なお、socket.io(およびSignalR)は、ファーストクラスの選択肢としてwebsocketを使用するフレームワークですが、彗星、ロングポーリング、フラッシュソケット、foreverframesなどの他の手法を使用するための互換性のあるフォールバックを備えています。
Tracker1 2013
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.