Feathers: Canaux/salles d'événement

Créé le 12 août 2016  ·  24Commentaires  ·  Source: feathersjs/feathers

Dans de nombreux cas, les événements de service doivent être filtrés pour restreindre leur émission à un ensemble limité de clients (1 à 1 ou 1 à quelques messages d'utilisateurs dans une application de chat par exemple). Les filtres permettent de le faire mais au prix d'une itération sur toutes les connexions, pour chaque événement... Cela peut entraîner une charge inutile du serveur lorsque le nombre d'événements et/ou de clients connectés est important.
Une fonctionnalité intéressante qui pourrait être envisagée pour améliorer cela serait d'ajouter une possibilité de fournir une liste de connexions à considérer par le répartiteur avec l'événement de service. Ainsi, la boucle foreach (io.sockets.clients().forEach(function(socket) {...} ) ne pourra être effectuée que sur cette liste restreinte de connexions (et sur toutes les connexions par défaut, comme déjà le cas) Alternativement, nous pourrions également transmettre une liste d'identifiants d'utilisateurs et conserver automatiquement un mappage de _id vers une connexion quelque part dans les plumes.
Avec cette amélioration, il serait possible, par exemple, de maintenir une liste de connexion user._id <-> (si elle n'est pas gérée par des plumes) et d'appeler simplement le répartiteur pour émettre un événement (comme un message 1 à 1) sur une connexion spécifique (récupérée directement depuis le user._id -> table de hachage de connexion, ce qui est beaucoup plus rapide que d'avoir à parcourir toutes les connexions (avec filtres) pour trouver celle ciblée)

Pour vous donner une idée, j'ai fait un benchmark vraiment simple pour tester la différence entre faire à chaque fois un test vraiment simple (comme connection.user._id === message.to_user_id) par rapport à la recherche d'une entrée donnée dans une table de hachage de connexion :

Voici les résultats:

avec 10k connexions :
méthode foreach : 3,608 ms
méthode de hachage : 0,073 ms

avec 100k connexions :
méthode foreach : 27,761 ms
méthode de hachage : 0,126 ms

avec 1M de connexions :
méthode foreach : 284,016 ms
méthode de hachage : 0,288 ms

Ainsi, jusqu'à 10k connexions, la différence est toujours "raisonnable". Mais passé cela, la différence commence à être significative (à peu près 1/3 de seconde juste pour parcourir les connexions dans le répartiteur, pour un seul événement, lorsque 1 million d'utilisateurs. Cela signifie que vous ne pourriez pas traiter plus de 3 événements/s (au mieux) quand 1M d'utilisateurs simultanés alors que vous pourriez traiter 1000x plus si vous saviez la connexion où dispatcher a priori).
Et vous devez garder à l'esprit que le nombre d'événements par seconde augmente généralement avec le nombre d'utilisateurs...
Ainsi, la boucle foreach dans le répartiteur peut devenir un véritable goulot d'étranglement si vous souhaitez utiliser des plumes pour de grosses applications... (Si vous avez 1M d'utilisateurs simultanés, vous ne pourrez pas traiter plus de 3 événements/seconde sur chaque nœud car de cela)

Voici le code utilisé pour mon benchmark très simple :

var nbconnections = 1000000;

var array = new Array(nbconnections);
console.time("foreach method");
array.forEach(function(socket) {
    if (socket === 0){

    }
});
console.timeEnd("foreach method");


var hash = {};
for (i = 0; i < nbconnections; i++) {
    hash[i+'']=i;
}
console.time("hash method");
var conn = hash['1000'];
console.timeEnd("hash method");
Breaking Change Proposal

Commentaire le plus utile

Je suppose que nous ne saurons vraiment ce qui est possible qu'avec un benchmark reproductible d'une application un peu réelle 😄

Passons maintenant à ma proposition d'API actuelle pour tout cela :

1) Événements et filtrage d'événements en tant que crochets

Refactoriser l'émission et le filtrage d'événements dans un hook. @ekryski et moi en avons déjà parlé. Fondamentalement, les crochets de plumes deviendront une dépendance du noyau et l'émission d'événements deviendra un crochet qui s'exécute toujours en dernier.

2) app.canal

Ajoutez une méthode app.channel qui vous permet de coller une connexion (l'émetteur d'événement) dans un "canal" codé par une chaîne :

const updateChannels = (connection, user) => {
  // A channel just for this user
  app.channel(user._id, connection);

  // For each user room add the socket to the room channel
  user.rooms.forEach(roomId => app.channel(`rooms/${roomId}`, connection));

  // Register the socket in the `company/<company_id>` channel
  app.channel(`company/${user.company_id}`, connection);
};
app.on('connection', updateChannels);
app.on('login', updateChannels);

La partie délicate ici est de trouver un moyen de garder ces canaux synchronisés lorsque l'utilisateur est mis à jour, par exemple en quittant une pièce tout en étant connecté.

3) Filtrage des événements mis à jour

Modifiez le filtrage des événements pour qu'il ne s'exécute qu'une seule fois par événement. Cela signifie qu'il passera toutes les connexions au lieu de s'exécuter pour chaque connexion individuelle. Le filtre renvoie ensuite les connexions auxquelles l'événement doit être distribué. Vous pouvez rapidement ( O(1) ) récupérer les connexions en fonction de leur identifiant de chaîne :

app.service('messages').filter('eventname', (message, connections, hook) => {
  // Just dispatch to one user
  if(message.isPrivate) {
    return connections.channel(message.receiver_id);
  }

  // The message room channel
  return connections.channel(`rooms/${message.room_id}`);

  // EVERYBODY!
  return connections;

  // Filter connections manually, e.g. if the connection user and message user are friends
  return connections.filter(connection => connection.user.friends.indexOf(message.user) !== -1);
});

Ce sera un changement décisif. Les filtres d'événements ne pourront plus modifier les données qui sont distribuées. La modification des données d'événement peut être effectuée dans les hooks en définissant soit hook.result soit hook.dispatch (#376 est lié). Le chaînage peut toujours être possible pour affiner les connexions, mais le renvoi d'un canal renvoie toujours toutes les connexions de ce canal. Pendant que nous y sommes, nous pourrions aussi bien rendre les filtres d'événements obligatoires.

Tous les 24 commentaires

Cela a déjà été soulevé et je pense que cela vaudrait la peine d'enquêter un peu plus. C'est très similaire à la question de savoir comment faire des chambres. Voici ma première fissure de ce à quoi je pense que cela pourrait ressembler. Laissez-moi savoir ce que vous pensez:

// The `channel` service method returns a string which is the key for a
// registered channel
app.service('messages').channel(message => `rooms/${message.room_id}`);
app.service('todos').channel(todo => `company/${todo.company_id}`);

app.on('connection', socket => {
  socket.on('authenticated', data => {
    // data.token
    // data.user
    const { token, user } = data;

    // For each user room add the socket to a channel
    user.rooms.forEach(roomId => app.channel(`rooms/${roomId}`, socket));
    // Register the socket in the `company/<company_id>` channel
    app.channel(`company/${company_id}`, socket);
  });
});

Je serais également intéressé par un benchmark des performances du filtrage des événements en cours. Jusqu'à présent, cela n'est pas apparu comme un goulot d'étranglement, mais avoir un moyen plus simple de créer des canaux/salles et d'augmenter potentiellement les performances en même temps serait formidable.

Un système de salle similaire à celui de socket.io est, je pense, une bonne idée. Pour plus de commodité, chaque socket doit également avoir sa propre salle à laquelle il s'abonne automatiquement (comme la salle par défaut de socket.io http://socket.io/docs/rooms-and-namespaces/), ce qui facilite l'émission un événement à un seul client (en diffusant vers la salle par défaut du client cible).
La question est de savoir comment distribuer des événements à tous les abonnés d'une salle ? S'il s'agit d'une sorte de filtre qui parcourt toutes les connexions socket du serveur pour filtrer celle appartenant à la salle souhaitée, vous vous retrouverez avec le même problème d'efficacité que le répartiteur actuel. Si vous ne bouclez que des sockets dans le canal (=avoir rejoint la salle dans laquelle l'événement doit être diffusé), ce serait OK.

Je pense que si, jusqu'à présent, la boucle sur les connexions dans le répartiteur n'a pas eu de goulot d'étranglement, c'est simplement parce que personne n'a encore utilisé de plumes dans une grande application (avec plus de 5 000 utilisateurs simultanés). Mais cela viendra tôt ou tard (les plumes sont encore vraiment nouvelles) et, d'après ce que j'ai vu avec mon benchmark très simple, nul doute que cette boucle foreach serait un problème lorsque vous essayez de redimensionner avec des plumes...
Faire de "vrais" tests de résistance de plumes avec un nœud de serveur unique de plumes et certaines machines clientes émulant des connexions client de 1k à 100k serait une bonne idée pour voir les limites et les goulots d'étranglement actuels de la plume. Si vous avez besoin de volontaires pour exécuter un script qui effectue des milliers de connexions simultanées au serveur pour ce test de résistance, je peux l'exécuter sur deux de mes ordinateurs (qui ont deux connexions Internet distinctes) avec plaisir.

Oui. Les canaux doivent réduire le nombre de clients connectés. Si vous créez par exemple un canal utilisateur spécifique, il n'y aura qu'une seule entrée dans la liste de connexion à parcourir. Cela doit être un peu plus générique car Primus ne prend pas en charge les salles (enfin seulement dans un plugin qui, je pense, n'a pas besoin d'être ajouté).

Pour faire avancer les choses, je pense que nous devons :

  • Déterminez (et éventuellement implémentez) l'API de canal qui permet de coller des connexions dans des canaux spécifiques (ou peut-être devraient-ils être appelés salles) et ajoutez la possibilité pour les services de réduire les canaux de données à diffuser
  • Créez une référence d'application réelle pour Socket.io, les filtres d'événements Feathers + et une version pour les canaux Feathers + pour voir quelles sont les améliorations

Je voudrais mentionner que dans mon expérience, plus de 5 000 clients (avec de vraies connexions Websocket) par serveur dans plus qu'une application de base est également un défi avec Socket.io simple - et physiquement impossible avec 64k+ car le serveur s'épuisera simplement de ports (note latérale: Meteor s'étouffe sur quelques centaines). Plus de 100 000 connexions simultanées sur une seule instance semblent très improbables, donc ce n'est probablement pas un cas dont nous devons nous inquiéter (j'ai trouvé des références Socket.io ici ).

Cela dit, j'attends avec impatience les fonctionnalités de canal/salle car c'est une question fréquemment posée que cela rendrait beaucoup plus facile tout en améliorant les performances en même temps.

La limite de 64k est un malentendu courant. Les gens ont tendance à penser qu'un serveur ne peut pas accepter plus de 65 536 (216) sockets TCP car les ports TCP sont des nombres entiers de 16 bits.
Mais en fait ce n'est pas le cas car votre serveur n'utiliserait en fait qu'un seul port d'écoute pour toutes les sockets et distinguerait les sockets en utilisant l'adresse IP et le port de chaque client (il n'y a donc pas de limite théorique sur le nombre de connexions simultanées de sockets un seul serveur peut gérer).
J'ai vu des rapports de personnes qui prétendaient réaliser plus de 1 million de connexions Websocket simultanées sur un serveur node.js et plus de 100K sur un serveur socket.io pur. Ainsi, avec quelques ajustements, il est probablement possible d'avoir environ 50 000 connexions Websocket simultanées sur un serveur soket.io à nœud unique (sur un plumes.js ;) ) si vous disposez de suffisamment de puissance CPU et de RAM.
Cela étant dit, avoir un système de canaux dans plumes.js qui fonctionnerait à la fois pour socket.io et Primus serait un réel avantage je pense (hormis l'amélioration des performances, cela faciliterait probablement aussi de nombreux cas d'utilisation qui nécessitent actuellement de définir des filtres )

Je suppose que nous ne saurons vraiment ce qui est possible qu'avec un benchmark reproductible d'une application un peu réelle 😄

Passons maintenant à ma proposition d'API actuelle pour tout cela :

1) Événements et filtrage d'événements en tant que crochets

Refactoriser l'émission et le filtrage d'événements dans un hook. @ekryski et moi en avons déjà parlé. Fondamentalement, les crochets de plumes deviendront une dépendance du noyau et l'émission d'événements deviendra un crochet qui s'exécute toujours en dernier.

2) app.canal

Ajoutez une méthode app.channel qui vous permet de coller une connexion (l'émetteur d'événement) dans un "canal" codé par une chaîne :

const updateChannels = (connection, user) => {
  // A channel just for this user
  app.channel(user._id, connection);

  // For each user room add the socket to the room channel
  user.rooms.forEach(roomId => app.channel(`rooms/${roomId}`, connection));

  // Register the socket in the `company/<company_id>` channel
  app.channel(`company/${user.company_id}`, connection);
};
app.on('connection', updateChannels);
app.on('login', updateChannels);

La partie délicate ici est de trouver un moyen de garder ces canaux synchronisés lorsque l'utilisateur est mis à jour, par exemple en quittant une pièce tout en étant connecté.

3) Filtrage des événements mis à jour

Modifiez le filtrage des événements pour qu'il ne s'exécute qu'une seule fois par événement. Cela signifie qu'il passera toutes les connexions au lieu de s'exécuter pour chaque connexion individuelle. Le filtre renvoie ensuite les connexions auxquelles l'événement doit être distribué. Vous pouvez rapidement ( O(1) ) récupérer les connexions en fonction de leur identifiant de chaîne :

app.service('messages').filter('eventname', (message, connections, hook) => {
  // Just dispatch to one user
  if(message.isPrivate) {
    return connections.channel(message.receiver_id);
  }

  // The message room channel
  return connections.channel(`rooms/${message.room_id}`);

  // EVERYBODY!
  return connections;

  // Filter connections manually, e.g. if the connection user and message user are friends
  return connections.filter(connection => connection.user.friends.indexOf(message.user) !== -1);
});

Ce sera un changement décisif. Les filtres d'événements ne pourront plus modifier les données qui sont distribuées. La modification des données d'événement peut être effectuée dans les hooks en définissant soit hook.result soit hook.dispatch (#376 est lié). Le chaînage peut toujours être possible pour affiner les connexions, mais le renvoi d'un canal renvoie toujours toutes les connexions de ce canal. Pendant que nous y sommes, nous pourrions aussi bien rendre les filtres d'événements obligatoires.

Cela me semble bon.
Concernant la façon de garder les chaînes synchronisées. Je pense que c'est la responsabilité du développeur de s'en charger. Elle ne doit pas être automatique (sauf si l'utilisateur s'est déconnecté, auquel cas, bien entendu, sa connexion doit être automatiquement supprimée de chaque canal dont il faisait partie). Donc, si vous avez une sorte de fonction app.unchannel() , vous devriez pouvoir l'appeler dans un hook de service. Ainsi, si un utilisateur a quitté une salle, depuis votre service utilisateur de patch ou de mise à jour par exemple, vous devez appeler app.unchannel( rooms/${roomId} , connection) pour supprimer sa connexion de la salle( s) il est parti (ou mieux, vous pouvez directement appeler cette fonction dans un service permettant de souscrire/désinscrire une chambre si vous en avez une plutôt que de la gérer via le service 'utilisateur').

Super discussion les gars et merci pour le coup d'envoi @ramsestom ! @daffl Je suis d'accord avec tout ce que vous avez proposé là-bas et @ramsestom, nous aimerions aider à évaluer les choses à mesure que cela progresse à coup sûr. J'ai quelques exemples de repos à conclure avec des services autonomes individuels et ce serait génial d'obtenir des références sur eux une fois qu'ils sont terminés. ??

La partie délicate ici est de trouver un moyen de garder ces canaux synchronisés lorsque l'utilisateur est mis à jour

Je suis d'accord. En pensant à haute voix ici, comment cette gestion des canaux va-t-elle fonctionner sur un cluster de services/d'applications ? Est-ce que ça va changer quelque chose ?

La synchronisation de plumes devra-t-elle également être adaptée pour prendre en charge les canaux ? Mon intuition est oui.

Je suppose que la synchronisation de plumes devrait être adaptée pour prendre en charge les canaux, mais ce ne serait probablement pas trop difficile. En fait, au lieu de propager un seul événement à toutes les instances d'application, il vous suffirait de propager (un événement + un chanel_ID) à toutes les instances d'application (il s'agit donc simplement d'adapter plumes-sync pour ajouter un paramètre chanel_ID dans les données transmises entre les nœuds du cluster).
En effet, un canal peut être divisé entre les nœuds d'un cluster sans problème. Il vous suffit de garder un étiquetage de canal cohérent entre les clusters de nœuds et de les remplir uniquement avec les connexions attachées à ce nœud (donc si un événement finit par demander d'ajouter la connexion 'c1' (=du client 1) au canal 'A' par exemple, seul le nœud de cluster gérant la connexion c1 répondrait à la demande, d'autres l'ignoreraient simplement en silence car ils n'ont pas de connexion c1 attachée).
Ainsi, un canal 'A' contenant les connexions c1,c2,c3,c4 dans une application à un seul nœud deviendra le canal 'A' contenant les connexions c1,c2 sur le nœud n1 et le canal 'A' contenant les connexions c3,c4 sur le nœud n2 par exemple. Ensuite, tant que vous savez qu'un événement doit être propagé au canal A, il n'y a aucune différence si toutes les connexions de ce canal sont attachées au même nœud ou divisées entre plusieurs.

J'ai peut-être raté quelque chose, mais le fait-il? Le filtre s'exécute pour chaque client _connecté_ sur ce serveur. Si vous ne faites que mettre à l'échelle la même application, toutes les connexions seront toujours placées dans le bon canal et les événements envoyés uniquement au client connecté correspondant.

Je ne vois pas la nécessité de synchroniser autre chose que les événements de service - ce que fait déjà plumes-sync.

Cela dépend de la façon dont fethers-sync fonctionne actuellement. Synchronise-t-il les événements du service de demande ou les événements du service de réponse ? Je veux dire, si j'ai une requête create() avec (données, paramètres), est-ce cette requête qui serait propagée à toutes les instances d'application (et traitée sur chacune) ? ou est-ce le résultat de cette demande (les promesses qui devraient être retournées aux clients une fois la demande traitée) qui serait propagée à tous les répartiteurs d'applications ?
J'aurais pensé que ce serait la deuxième option (sinon si une demande de service pour créer ou mettre à jour une entrée dans une base de données, par exemple, cela poserait des problèmes car chaque instance d'application essaierait de l'exécuter si elles traitent toutes l'événement de demande .. .)

Si j'ai bien compris, le pipeline en plumes est celui-ci :

---------------------- effectué uniquement sur l'instance de nœud recevant la requête
demande d'événement
avant les crochets
méthode de service elle-même
après les crochets
événement de résultat
------------------ <- C'est à ce stade qu'il serait synchronisé entre les nœuds avec plumes-sync
------------------ effectué sur chaque instance de nœud
filtres
répartiteur

S'il s'agit de l'événement de résultat qui est synchronisé avec plumes-sync, alors oui, vous devrez probablement le modifier. Avec l'implémentation actuelle des filtres, lorsque vous parcourez toutes les connexions, vous pouvez effectuer les filtres sur chaque connexion de chaque nœud. Vous n'avez donc rien à fournir d'autre que l'événement à envoyer. Mais si vous avez des canaux, l'événement doit maintenant être distribué sur un ensemble sélectionné de connexions. Comme vous n'utilisez plus de filtres pour sélectionner cet ensemble (basé sur une fonction de test), vous devez transmettre les informations de canal avec l'événement pour savoir sur quelles connexions de votre nœud l'événement doit être émis (mais cette information de canal peut être intégré en tant que propriété de l'objet événement lui-même bien sûr, auquel cas il vous suffit de passer un objet 'événement' comme actuellement fait, et de regarder cette propriété 'canal' de votre événement dans votre répartiteur pour l'émettre sur les connexions appropriées. Cela dépend de la façon dont vous l'implémenterez).

Voici la proposition finale actuelle de ce qui sera mis en œuvre pour cela très prochainement :

Terminologie

  • le canal est un objet qui contient n'importe quel nombre de connexions, potentiellement les données à envoyer et qui est généralement enregistré sous un certain nom
  • connection est l'objet d'information spécifique à Feathers sur une connexion bidirectionnelle et contient des informations telles que l'utilisateur connecté. Dans les implémentations actuelles pour Socket.io, ce serait socket.feathers et pour Primus socket.request.feathers
  • dispatch est une fonction qui renvoie un canal (et les données qu'il doit envoyer) en fonction de l'événement et/ou des données de hook

Canaux

app.channels , app.channel(... names)

Créera et renverra un nouveau canal pour le nom donné ou combinera plusieurs canaux en un seul

// A list of all channel names
app.channels

// A channel only for admins
app.channel('admins')

// A channel for message room with id 2
app.channel('rooms/2')

// A combined channel for admins and rooms/2
app.channel('admins', 'rooms/2');

// All channels
app.channel(app.channels)

Rejoindre/partir

.join(connection) et .leave(connection) peuvent être appelés sur un canal pour qu'une connexion rejoigne et quitte un canal :

// Join the admin channel
app.channel('admins').join(connection);

// Leave the admin channel
app.channel('admins').leave(connection);

// Leave a channel conditionally
app.channel('admins', 'rooms').leave(connection => connection.userId === user._id);

// Leave all room channels
const roomChannels = app.channels.filter(channel => channel.indexOf('room/') === 0);

app.channel(roomChannels).leave(connection);

// Leave all channels
app.channel(app.channels).leave(connection);

Envoi d'événements

Les filtres d'événements déterminent les canaux vers lesquels un événement doit être envoyé. Plusieurs filtres d'événements peuvent être enregistrés pour envoyer des données à plusieurs canaux. Si une connexion est sur plusieurs canaux, l'événement ne sera envoyé qu'une seule fois (si les données sont les mêmes, nous devrons peut-être trouver comment procéder).

// Handle a certain event for all services
app.dispatch('eventname', (message, hook) => {
  // Just dispatch to one user
  if(message.isPrivate) {
    return app.channel(message.receiver_id);
  }

  // Returning falsy or nothing will do nothing
});

// Handle a certain event for a specific service
app.dispatch('servicename', 'eventname', (message, hook) => {
  // Just dispatch to one user
  if(message.isPrivate) {
    return app.channel(message.receiver_id);
  }

  // Returning falsy or nothing will do nothing
});

// Send to a certain room
app.dispatch('messages', 'eventname', (message, hook) => {
  return app.channel(`rooms/${message.roomId}`);
});

// EVERYONE
app.dispatch('messages', 'eventname', (message, hook) => {
  return app.channel(app.channels);
});

// Filter connections manually, e.g. if the connection user and message user are friends
// This works similar to the old event filters
app.dispatch('messages', 'eventname', (message, hook) => {
  return app.channel(app.channels).filter(connection => connection.user.friends.indexOf(message.user) !== -1);
});

Remarque : L'objet hook pour les événements personnalisés contiendra { service, app, path, event } .

Il peut également déterminer avec quelles données :

app.dispatch('messages', 'eventname', (message, hook) => {
  const modifiedMessage = cloneAndModify(message);

  return app.channel(`rooms/${message.roomId}`, `rooms/general`).send(modifiedMessage);
});

Tenir les chaînes à jour

Quelques exemples de mise à jour des canaux lorsque l'état de l'utilisateur ou de la connexion change :

Utilisateurs anonymes

app.on('connection', connection => {
  app.channel('anonymous').join(connection);
});

app.on('login', (payload, meta) => {
  const connection = meta.connection;

  // Connection can be undefined e.g. when logging in via REST
  if(connection) {
    // Leave anonymous channel first
    app.channel('anonymous').leave(connection);

    // Get the user object and stick into channels
    app.service('users').get(payload.userId).then(user => {
      // A channel just for this user
      app.channel(`users/${user._id}`).join(connection);

      // Put user into the chat rooms they joined
      user.rooms.forEach(roomId => {
        app.channel(`rooms/${roomId}`).join(connection);
      });
    });
  }
});

Lorsque l'utilisateur est modifié

app.service('users').on('patched', user => {
  // Find all connections belonging to this user
  app.channel(app.channels).leave(connection => connection.user._id === user._id)

  // Re-add the user to their channels  
  // A channel just for this user
  app.channel(`users/${user._id}`).join(connection);

  // Put user into the chat rooms they joined
  user.rooms.forEach(roomId => {
    app.channel(`rooms/${roomId}`).join(connection);
  });
});

L'objet connection sera-t-il disponible dans un hook ? Nous aurons besoin d'un moyen de gérer la souscription à une chaîne dans un hook. Les connexions socket devraient-elles avoir context.connection dans l'objet hook ?

// Join the admin channel
app.channel('admins').join(connection);

// Leave the admin channel
app.channel('admins').leave(connection);

Eh bien, l'objet connection pour les sockets est ce qui est fusionné dans les appels de méthode de service params (ce n'est pas le même objet cependant). Pour quoi auriez-vous besoin de la connexion dans un crochet?

La seule chose que j'ai pu voir, c'est qu'il serait plus facile de ne pas envoyer l'événement à l'utilisateur qui a appelé la méthode, mais nous avons toujours déconseillé de le faire.

Par exemple, lorsqu'un utilisateur crée un salon dans une application de chat, où résidera le code permettant de créer le canal et de joindre la connexion de l'utilisateur au canal de ce salon ?

Voilà une bonne question. Je dirais que par défaut, nous pouvons mettre des modèles dans le fichier de configuration des services utilisateurs qui charge et définit actuellement les filtres.

Ce sera peut-être un peu délicat de rendre possible l'appartenance à un canal dans un hook, car les données de connexion sont les mêmes que les paramètres, mais il semble qu'il serait très naturel de vouloir le faire dans un hook.

Une autre chose que nous n'avons pas abordée est la raison pour laquelle il n'est pas possible d'émettre des événements de socket dans un hook. Ce serait bien de pouvoir app.channel('admins').dispatch({...}) dans un crochet. Vous disiez pourquoi ça ne marcherait pas, mais je n'ai pas compris.

Pour l'appartenance à un canal à l'intérieur des crochets, la solution pourrait être de conserver une carte de dictionnaire synchronisée de {user_id => connexion} quelque part à la racine de l'application plumes à chaque fois qu'un utilisateur se connecte ou se déconnecte.
De cette façon, join() et leave() pourraient être appelés depuis l'intérieur d'un hook en utilisant map[params.user.id] sans avoir à passer directement l'objet de connexion aux hooks.

En ce qui concerne la possibilité d'envoyer des événements personnalisés à tout moment de l'application (pas seulement des événements de service, mais essentiellement tout événement personnalisé avec des données personnalisées), je suis d'accord qu'il serait formidable de pouvoir faire quelque chose comme app.channel('admins').dispatch({data to dispatch}) dans un crochet

Une autre chose que nous n'avons pas abordée est la raison pour laquelle il n'est pas possible d'émettre des événements de socket dans un hook. Ce serait bien de pouvoir app.channel('admins').dispatch({...}) dans un hook. Vous disiez pourquoi ça ne marcherait pas, mais je n'ai pas compris.

D'accord. C'est quelque chose sur lequel je suis un peu flou aussi @daffl. Je pense que nous aimerions pouvoir le faire.


Par exemple, lorsqu'un utilisateur crée un salon dans une application de chat, où résidera le code permettant de créer le canal et de joindre la connexion de l'utilisateur au canal de ce salon ?

Nous pourrions avoir besoin de faire quelque chose comme ce que @ramsestom a suggéré. Je pense que vous voudrez peut-être avoir une méthode pratique pour obtenir une connexion de socket par l'identifiant de socket ou l'identifiant d'entité (c'est-à-dire un utilisateur) attaché à ce socket. Bien que cela soit faisable, cela peut commencer à devenir un peu compliqué lorsqu'il s'agit de plusieurs instances d'application et d'un tas de sockets qui vont et viennent.

Cependant, maintenant que j'y réfléchis un peu plus pour couvrir le cas d'utilisation de @marshallswain , cela ne pourrait-il pas / ne devrait-il pas s'agir de 2 appels d'API ?

  • Un pour créer la chaîne
  • L'autre pour "rejoindre" la chaîne

Réfléchissez un peu plus à cela.... 2 appels API fonctionnent, mais seulement si vous connaissez le canal auquel vous pouvez vous joindre. Fondamentalement, un utilisateur ne peut s'ajouter qu'à un canal. Si vous voulez faire quelque chose comme ce que fait Slack où vous pouvez ajouter un autre utilisateur à un canal, ce n'est peut-être pas possible...

Je suppose que vous avez 2 options dans ce cas:

  1. Un utilisateur vous ajoute à une chaîne que vous ne connaissez pas. Plutôt que de réellement s'ajouter, vous obtenez une invitation à un canal et confirmer à « rejoindre » le canal. Par conséquent, vous vous ajoutez toujours.

  2. Un utilisateur vous ajoute en fait à la chaîne. Dans ce cas, vous auriez besoin d'un moyen de trouver une connexion par identifiant d'entité (utilisateur).

Se mettre un peu dans les mauvaises herbes...

Je pense que ce truc est faisable sans un mappage user id -> connection et avec la proposition de @daffl . C'est aussi pourquoi il sera bon de faire une pré-version et de la mettre en production/mise en scène dans quelques applications pour voir où se trouvent les cas limites.

Peut-être qu'on en fait une première coupe sans la cartographie ? Qu'est-ce que tu vas en penser ?

D'accord. C'est quelque chose sur lequel je suis un peu flou aussi @daffl. Je pense que nous aimerions pouvoir le faire.

Puisque @marshallswain et @ekryski l'ont mentionné. Je pense que c'est bien d'émettre des événements vers votre propre service (et peut-être un autre) dans un crochet, mais vous ne devriez pas pouvoir envoyer des éléments aux clients ou aux canaux connectés directement à partir d'un crochet. Je pense que cela saperait la séparation des services (qui ont des méthodes et des crochets d'exécution) du système d'événements pour l'envoi de données en temps réel.

Tout comme nous disons que les services devraient être indépendants du transport, je pense également qu'ils devraient être "non conscients de la connexion", car un service lui-même (et ses crochets) ne devrait pas avoir à se soucier de qui est connecté au serveur sur lequel il s'exécute. Je me rends compte que ce n'est pas tout à fait vrai car nous enregistrons toujours des filtres/répartiteurs via app.service('myservice').dispatch sur le service mais au moins c'est uniquement dans ce but spécifique alors qu'un crochet peut tout faire.

Par exemple, lorsqu'un utilisateur crée un salon dans une application de chat, où résidera le code permettant de créer le canal et de joindre la connexion de l'utilisateur au canal de ce salon ?

N'est-ce pas exactement ce que

app.service('users').on('patched', user => {
  // Leave all channels belonging to a user
  app.channel(app.channels).leave(connection => connection.user._id === user._id)

  // Re-add the user to their channels  
  // A channel just for this user
  app.channel(`users/${user._id}`).join(connection);

  // Put user into the chat rooms they joined
  user.rooms.forEach(roomId => {
    app.channel(`rooms/${roomId}`).join(connection);
  });
});

Est-ce que cela ferait en supposant qu'un utilisateur reçoive patched lorsqu'il rejoint une salle ?

@daffl Oui, cela fonctionnerait. ??

@daffl @ekryski @marshallswain j'ai résolu exactement ce scénario peut-être plus simple avec un service de plumes dynamique :

j'ai créé un service qui enregistre un service de messagerie personnalisé sur un espace de nom personnalisé comme /channel/name maintenant dans le client je me connecte simplement au même service /channel/name et j'ai une messagerie

À faire

  • créer un service de plumes sur le serveur qui écoute /channel/:name

    • crée un service de messagerie de canal s'il n'est pas déjà enregistré pour /channel/:name

  • create si socket.io ou primus est utilisé sur le hook d'initialisation qui filtre les messages /channel* et crée à nouveau le service /channel/name
  • sur le client, enregistrez-vous simplement et utilisez /channel/name service

J'espère que cela vous aidera, je l'utilise pour connecter différents appareils dans différentes pièces dans un déploiement plus important qui utilise le temps réel

c'est aussi le plus évolutif car vous savez que vous pouvez envoyer chaque nom de canal plus tard également à différentes boîtes comme les déploiements kafka qui gèrent facilement quelque 100 millions de messages par seconde

Ce problème a été automatiquement verrouillé car il n'y a eu aucune activité récente après sa fermeture. Veuillez ouvrir un nouveau problème avec un lien vers ce problème pour les bogues liés.

Cette page vous a été utile?
0 / 5 - 0 notes