web-dev-qa-db-fra.com

Comment concevoir une application Web ajax multi-utilisateurs pour une sécurité simultanée

J'ai une page Web qui montre une grande quantité de données provenant du serveur. La communication se fait via ajax.

Chaque fois que l'utilisateur interagit et modifie ces données (disons que l'utilisateur A renomme quelque chose), il dit au serveur de faire l'action et le serveur renvoie les nouvelles données modifiées.

Si l'utilisateur B accède à la page en même temps et crée un nouvel objet de données, il le dira à nouveau au serveur via ajax et le serveur reviendra avec le nouvel objet pour l'utilisateur.

Sur la page de A, nous avons les données avec un objet renommé. Et sur la page de B, nous avons les données avec un nouvel objet. Sur le serveur, les données ont à la fois un objet renommé et un nouvel objet.

Quelles sont mes options pour garder la page synchronisée avec le serveur lorsque plusieurs utilisateurs l'utilisent simultanément?

Des options telles que le verrouillage de la page entière ou le vidage de l'état entier à l'utilisateur à chaque changement sont plutôt évitées.

Si cela aide, dans cet exemple spécifique, la page Web appelle une méthode Web statique qui exécute une procédure stockée sur la base de données. La procédure stockée renverra toutes les données qu'elle a modifiées et pas plus. La méthode Web statique transmet ensuite le retour de la procédure stockée au client.

Bounty Edit:

Comment concevez-vous une application Web multi-utilisateurs qui utilise Ajax pour communiquer avec le serveur mais évite les problèmes de concurrence?

C'est à dire. accès simultané aux fonctionnalités et aux données d'une base de données sans risque de corruption des données ou de l'état

94
Raynos

Présentation:

  • Intro
  • Architecture de serveur
  • Architecture client
  • Mettre à jour le cas
  • Valider le cas
  • Cas de conflit
  • Performance et évolutivité

Salut Raynos,

Je ne discuterai aucun produit particulier ici. Ce que d'autres ont mentionné est un bon ensemble d'outils à consulter déjà (peut-être ajouter node.js à cette liste).

D'un point de vue architectural, vous semblez avoir le même problème qui peut être vu dans le logiciel de contrôle de version. Un utilisateur enregistre une modification d'un objet, un autre utilisateur souhaite modifier le même objet d'une autre manière => conflit. Vous devez intégrer les modifications des utilisateurs aux objets tout en étant en mesure de fournir des mises à jour en temps opportun et efficacement, en détectant et en résolvant les conflits comme celui ci-dessus.

Si j'étais à votre place, je développerais quelque chose comme ça:

1. Côté serveur:

  • Déterminez un niveau raisonnable auquel vous définiriez ce que j'appellerais des "artefacts atomiques" (la page? Objets sur la page? Valeurs à l'intérieur des objets?). Cela dépendra de vos serveurs Web, de la base de données et du matériel de mise en cache, du nombre d'utilisateurs, du nombre d'objets, etc. Pas une décision facile à prendre.

  • Pour chaque artefact atomique:

    • un identifiant unique à l'échelle de l'application
    • un incrément de version-id
    • un mécanisme de verrouillage pour l'accès en écriture (mutex peut-être)
    • une petite histoire ou "changelog" à l'intérieur d'un ringbuffer (la mémoire partagée fonctionne bien pour ceux-ci). Une seule paire clé-valeur peut également être OK mais moins extensible. voir http://en.wikipedia.org/wiki/Circular_buffer
  • Un serveur ou un composant de pseudo-serveur capable de fournir efficacement les pertinents changelogs à un utilisateur connecté. Observer-Pattern est votre ami pour cela.

2. Côté client:

  • Un client javascript qui peut avoir une connexion HTTP longue durée avec ledit serveur ci-dessus, ou utilise une interrogation légère.

  • Un composant de mise à jour d'artefacts javascript qui actualise le contenu des sites lorsque le client javascript connecté notifie les modifications de l'historique des artefacts surveillés. (encore une fois, un modèle d'observation pourrait être un bon choix)

  • Un composant javascript d'artefact-committer qui peut demander de changer un artefact atomique, essayant d'acquérir un verrou mutex. Il détectera si l'état de l'artefact a été modifié par un autre utilisateur quelques secondes auparavant (latence du client javascript et des facteurs de processus de validation) en comparant l'artefact-version-id connu côté client et l'actuel id-version-artefact côté serveur.

  • Un solutionneur de conflits javascript permettant une décision humaine qui change est la bonne décision. Vous ne voudrez peut-être pas simplement dire à l'utilisateur "Quelqu'un a été plus rapide que vous. J'ai supprimé votre modification. Allez pleurer.". De nombreuses options de différences plutôt techniques ou de solutions plus conviviales semblent possibles.

Alors, comment ça roulerait ...

Cas 1: sorte de diagramme de séquence pour la mise à jour:

  • La page de rendu du navigateur
  • javascript "voit" des artefacts qui ont chacun au moins un champ de valeur, unique et un identifiant de version
  • le client javascript démarre, demandant de "regarder" l'historique des artefacts trouvés à partir de leurs versions trouvées (les changements plus anciens ne sont pas intéressants)
  • Le processus serveur note la demande et vérifie et/ou envoie en permanence l'historique
  • Les entrées d'historique peuvent contenir de simples notifications "l'artefact x a changé, le client demande les données" permettant au client d'interroger indépendamment ou des ensembles de données complets "l'artefact x a changé en valeur foo"
  • javascript artifact-updater fait ce qu'il peut pour récupérer de nouvelles valeurs dès qu'elles sont connues pour avoir été mises à jour. Il exécute de nouvelles requêtes ajax ou est alimenté par le client javascript.
  • Le contenu DOM des pages est mis à jour, l'utilisateur est éventuellement averti. L'observation de l'histoire se poursuit.

Cas 2: Maintenant, pour commettre:

  • artefact-committer connaît la nouvelle valeur souhaitée à partir de l'entrée utilisateur et envoie une demande de changement au serveur
  • acquisition d'un mutex côté serveur
  • Le serveur reçoit "Hé, je connais l'état de l'artefact x à partir de la version 123, permettez-moi de le mettre à la valeur foo pls."
  • Si la version côté serveur de l'artefact x est égale (ne peut pas être inférieure) à 123, la nouvelle valeur est acceptée, un nouvel identifiant de version de 124 est généré.
  • Les nouvelles informations d'état "mises à jour vers la version 124" et éventuellement la nouvelle valeur foo sont placées au début du tampon d'anneau de l'artefact x (journal des modifications/historique)
  • le serveur mutex est publié
  • le demandeur de l'artefact est heureux de recevoir une confirmation de validation avec le nouvel identifiant.
  • pendant ce temps, le composant serveur côté serveur continue d'interroger/de pousser les tampons annulaires vers les clients connectés. Tous les clients qui regardent le tampon de l'artefact x obtiendront les nouvelles informations d'état et la valeur dans leur latence habituelle (voir le cas 1.)

Cas 3: pour les conflits:

  • committer d'artefact connaît la nouvelle valeur souhaitée à partir de l'entrée utilisateur et envoie une demande de changement au serveur
  • en attendant, un autre utilisateur a mis à jour le même artefact avec succès (voir le cas 2.) mais en raison de diverses latences, cela n'est pas encore connu de notre autre utilisateur.
  • Ainsi, un mutex côté serveur est acquis (ou attendu jusqu'à ce que l'utilisateur "le plus rapide" valide sa modification)
  • Le serveur reçoit "Hé, je connais l'état de l'artefact x de la version 123, permettez-moi de le mettre à la valeur foo."
  • Sur le côté serveur, la version de l'artefact x est désormais 124 déjà. Le client demandeur ne peut pas connaître la valeur qu'il remplacerait.
  • Évidemment, le serveur doit rejeter la demande de changement (sans compter les priorités de remplacement par Dieu), libère le mutex et est assez aimable pour renvoyer le nouvel ID de version et la nouvelle valeur directement au client.
  • confronté à une demande de validation rejetée et à une valeur que l'utilisateur demandeur de changement ne connaissait pas encore, le committer d'artefact javascript fait référence au résolveur de conflit qui affiche et explique le problème à l'utilisateur.
  • L'utilisateur, étant présenté avec quelques options par le JS intelligent de résolution de conflit, est autorisé une autre tentative de changer la valeur.
  • Une fois que l'utilisateur a sélectionné une valeur qu'il juge correcte, le processus recommence à partir du cas 2 (ou du cas 3 si quelqu'un d'autre était plus rapide, encore)

Quelques mots sur les performances et l'évolutivité

Sondage HTTP vs HTTP "pousser"

  • L'interrogation crée des demandes, une par seconde, 5 par seconde, tout ce que vous considérez comme une latence acceptable. Cela peut être assez cruel pour votre infrastructure si vous ne configurez pas assez bien vos (Apache?) Et (php?) Pour être des démarreurs "légers". Il est souhaitable d'optimiser la demande d'interrogation côté serveur afin qu'elle s'exécute bien moins longtemps que la longueur de l'intervalle d'interrogation. Diviser ce temps d'exécution en deux pourrait bien signifier une réduction de la charge totale de votre système jusqu'à 50%,
  • Pousser via HTTP (en supposant que les travailleurs Web sont trop éloignés pour les prendre en charge) vous obligera à avoir un processus Apache/lighthttpd disponible pour chaque utilisateur tout le temps . La mémoire résidente réservée à chacun de ces processus et la mémoire totale de vos systèmes seront une limite d'échelle très certaine que vous rencontrerez. Il sera nécessaire de réduire l'empreinte mémoire de la connexion, ainsi que de limiter la quantité de travail continu du processeur et des E/S effectués dans chacun d'eux (vous voulez beaucoup de temps de veille/inactif)

mise à l'échelle du backend

  • Oubliez la base de données et le système de fichiers, vous aurez besoin une sorte de backend basé sur la mémoire partagée pour les interrogations fréquentes (si le client n'interroge pas directement, alors chaque processus serveur en cours le fera)
  • si vous optez pour memcache, vous pouvez mieux évoluer, mais c'est toujours cher
  • Le mutex pour les validations doit fonctionner globalement même si vous souhaitez avoir plusieurs serveurs frontaux pour équilibrer la charge.

mise à l'échelle frontale

  • que vous interrogiez ou receviez des "push", essayez d'obtenir des informations pour tous les artefacts observés en une seule étape.

réglages "créatifs"

  • Si les clients interrogent et que de nombreux utilisateurs ont tendance à regarder les mêmes artefacts, vous pouvez essayer de publier l'historique de ces artefacts sous forme de fichier statique, ce qui permet à Apache de le mettre en cache, tout en le rafraîchissant côté serveur lorsque les artefacts changent. Cela élimine PHP/memcache du jeu pour les requêtes. Lighthttpd est très efficace pour servir des fichiers statiques.
  • utilisez un réseau de diffusion de contenu comme cotendo.com pour y diffuser l'historique des artefacts. La latence de poussée sera plus grande mais l'évolutivité est un rêve
  • écrire un vrai serveur (n'utilisant pas HTTP) auquel les utilisateurs se connectent en utilisant Java ou flash (?). Vous devez gérer le fait de servir de nombreux utilisateurs dans un même thread de serveur. Faire défiler les sockets ouverts, faire (ou déléguer) le travail requis. Peut évoluer via des processus de bifurcation ou le démarrage de plusieurs serveurs. Les mutex doivent cependant rester uniques à l'échelle mondiale.
  • En fonction des scénarios de charge, groupez vos serveurs frontaux et principaux par plages d'ID d'artefact. Cela permettra une meilleure utilisation de la mémoire persistante (aucune base de données ne possède toutes les données) et permet de mettre à l'échelle le mutexage. Votre javascript doit cependant maintenir des connexions à plusieurs serveurs en même temps.

J'espère que cela peut être un début pour vos propres idées. Je suis sûr qu'il y a beaucoup plus de possibilités. Je suis plus que satisfait de toute critique ou amélioration de ce message, le wiki est activé.

Christoph Strasen

157
Christoph Strasen

Je sais que c'est une vieille question, mais je pensais que j'allais juste sonner.

OT (transformations opérationnelles) semble être un bon ajustement pour vos besoins en matière d'édition multi-utilisateurs simultanée et cohérente. C'est une technique utilisée dans Google Docs (et a également été utilisée dans Google Wave):

Il existe une bibliothèque basée sur JS pour utiliser les transformations opérationnelles - ShareJS ( http://sharejs.org/ ), écrite par un membre de l'équipe Google Wave.

Et si vous le souhaitez, il existe un framework Web MVC complet - DerbyJS ( http://derbyjs.com/ ) construit sur ShareJS qui fait tout pour vous.

Il utilise BrowserChannel pour la communication entre le serveur et les clients (et je pense que le support WebSockets devrait être en cours - il était là auparavant via Socket.IO, mais a été retiré en raison des problèmes du développeur avec Socket.io) Les documents pour débutants sont un peu clairsemée pour le moment, cependant.

13
victorhooi

J'envisagerais d'ajouter un tampon modifié en fonction du temps pour chaque ensemble de données. Donc, si vous mettez à jour des tables db, vous devez modifier l'horodatage modifié en conséquence. En utilisant AJAX, vous pouvez comparer l'horodatage modifié du client avec l'horodatage de la source de données - si l'utilisateur est jamais derrière, mettez à jour l'affichage. Similaire à la façon dont ce site vérifie périodiquement une question pour voir si quelqu'un d'autre a répondu pendant que vous tapez une réponse.

5
Chris Baker

Vous devez utiliser des techniques Push (également connues sous le nom de Comet ou Ajax inversé) pour propager les modifications à l'utilisateur dès qu'elles sont apportées à la base de données. La meilleure technique actuellement disponible pour cela semble être l'interrogation longue Ajax, mais elle n'est pas prise en charge par tous les navigateurs, vous avez donc besoin de solutions de rechange. Heureusement, il existe déjà des solutions qui gèrent cela pour vous. Parmi eux se trouvent: orbited.org et le socket.io déjà mentionné.

À l'avenir, il y aura un moyen plus facile de le faire qui s'appelle WebSockets, mais on ne sait pas encore quand cette norme sera prête pour les heures de grande écoute, car il existe des problèmes de sécurité concernant l'état actuel de la norme.

Il ne devrait pas y avoir de problèmes de concurrence dans la base de données avec de nouveaux objets. Mais lorsqu'un utilisateur modifie un objet, le serveur doit disposer d'une logique qui vérifie si l'objet a été modifié ou supprimé entre-temps. Si l'objet a été supprimé, la solution est, encore une fois, simple: jetez simplement la modification.

Mais le problème le plus difficile apparaît lorsque plusieurs utilisateurs modifient le même objet en même temps. Si les utilisateurs 1 et 2 commencent à modifier un objet en même temps, ils effectueront tous les deux leurs modifications sur les mêmes données. Supposons que les modifications apportées par l'utilisateur 1 soient d'abord envoyées au serveur pendant que l'utilisateur 2 modifie toujours les données. Vous avez alors deux options: vous pouvez essayer de fusionner les modifications de l'utilisateur 1 dans les données de l'utilisateur 2 ou vous pouvez dire à l'utilisateur 2 que ses données sont obsolètes et lui afficher un message d'erreur dès que ses données sont envoyées au serveur. Cette dernière option n'est pas très conviviale ici, mais la première est très difficile à mettre en œuvre.

EtherPad , l'une des rares implémentations qui a vraiment réussi à faire les choses pour la première fois, a été acquise par Google. Je pense qu'ils ont ensuite utilisé certaines des technologies d'EtherPad dans Google Docs et Google Wave, mais je ne peux pas le dire avec certitude. Google a également ouvert EtherPad, donc cela vaut peut-être le coup d'œil, selon ce que vous essayez de faire.

Ce n'est vraiment pas facile de faire ce montage simultanément, car il n'est pas possible de faire des opérations atomiques sur le Web en raison de la latence. Peut-être cet article vous aidera à en savoir plus sur le sujet.

3
Jannes

Essayer d'écrire tout cela vous-même est un gros travail, et il est très difficile de bien faire les choses. Une option consiste à utiliser un cadre conçu pour maintenir les clients synchronisés avec la base de données et entre eux en temps réel.

J'ai trouvé que le framework Meteor le faisait bien ( http://docs.meteor.com/#reactivity ).

"Meteor adopte le concept de programmation réactive. Cela signifie que vous pouvez écrire votre code dans un style impératif simple, et le résultat sera automatiquement recalculé chaque fois que les données changent dont votre code dépend."

"Ce modèle simple (calcul réactif + source de données réactive) a une large applicabilité. Le programmeur est sauvé de l'écriture des appels de désabonnement/réabonnement et de s'assurer qu'ils sont appelés au bon moment, éliminant des classes entières de code de propagation de données qui autrement bloqueraient votre application avec une logique sujette aux erreurs. "

2
mb.

Je ne peux pas croire que personne n'ait mentionné Meteor . C'est un nouveau cadre immature à coup sûr (et ne prend officiellement en charge qu'une seule base de données), mais il prend tout le travail de grognement et la réflexion d'un multi-utilisateur application comme l'affiche décrit. En fait, vous ne pouvez PAS créer une application de mise à jour en direct multi-utilisateurs. Voici un bref résumé:

  • Tout est dans node.js (JavaScript ou CoffeeScript), vous pouvez donc partager des choses comme des validations entre le client et le serveur.
  • Il utilise des websockets, mais peut se replier sur les anciens navigateurs
  • Il se concentre sur les mises à jour immédiates de l'objet local (c'est-à-dire que l'interface utilisateur est rapide), avec les modifications envoyées au serveur en arrière-plan. Seules les mises à jour atomiques sont autorisées pour simplifier le mélange des mises à jour. Les mises à jour rejetées sur le serveur sont annulées.
  • En prime, il gère les rechargements de code en direct pour vous et préserve l'état de l'utilisateur même lorsque l'application change radicalement.

Meteor est assez simple pour que je vous suggère au moins d'y jeter un œil pour des idées à voler.

1
BraveNewCurrency

Ces pages Wikipédia peuvent aider à ajouter une perspective à l'apprentissage de concurrence et calcul simultané pour la conception d'un ajaxapplication Web qui soit tire ou est poussé état événement ( EDA ) messages dans un modèle de messagerie . Fondamentalement, les messages sont répliqués vers les abonnés du canal qui répondent aux événements de modification et aux demandes de synchronisation.

Il existe de nombreuses formes de Web simultanées logiciel collaboratif .

Il existe un certain nombre de bibliothèques clientes de l'API HTTP pour etherpad-lite , un éditeur collaboratif en temps réel .

Django-realtime-Playground implémente une application de chat en temps réel dans Django avec diverses technologies en temps réel comme Socket.io .

AppEngine et AppScale implémentent tous deux AppEngine Channel API ; qui est distinct de Google Realtime API , ce qui est démontré par googledrive/realtime-Playground .

1
Wes Turner

Push côté serveur les techniques sont la voie à suivre ici. Comet est (ou était?) Un mot à la mode.

La direction particulière que vous prenez dépend fortement de votre pile de serveurs et de votre flexibilité. Si vous le pouvez, je voudrais jeter un œil à socket.io , qui fournit une implémentation multi-navigateur de websockets, qui fournit un moyen très rationalisé d'avoir une communication bidirectionnelle avec le serveur, permettant au serveur de pousser mises à jour des clients.

En particulier, voir la démonstration this de l'auteur de la bibliothèque, qui illustre presque exactement la situation que vous décrivez.

0
davin