A look at how Tencent Games built service architecture based on CQRS and event sourcing patterns with Pulsar and ScyllaDB. En tant que partie de Tencent Interactive Entertainment Group Global (IEG Global), Proxima Beta s’engage à soutenir nos équipes et nos studios pour apporter des jeux uniques et passionnants à des millions de joueurs du monde entier. Notre équipe de Level Infinite (la marque pour l’édition mondiale) est responsable de la gestion d’un large éventail de risques pour notre entreprise – par exemple, des activités frauduleuses et du contenu nuisible. Dans ce blog, nous partageons notre expérience de la construction de ce système d'analyse axé sur les événements en temps réel. Tout d'abord, nous explorerons pourquoi nous avons construit notre architecture de service basée sur la ségrégation de la responsabilité des commandes et des requêtes ( ) et événement sourcing des modèles avec et ScyllaDB. Ensuite, nous allons examiner comment nous utilisons ScyllaDB pour résoudre le problème de l'expédition d'événements à de nombreuses sessions de jeu. Enfin, nous allons couvrir comment nous utilisons les espaces clés ScyllaDB et la réplication des données pour simplifier notre gestion globale des données. CQRs Apache Pulsaire Un regard sur le cas d'utilisation: faire face aux risques dans les jeux Tencent Commençons par un exemple du monde réel de ce avec quoi nous travaillons et des défis auxquels nous sommes confrontés. Ceci est une capture d'écran de Tower of Fantasy, un jeu de rôle d'action en 3D. Les joueurs peuvent utiliser ce dialogue pour déposer un rapport contre un autre joueur pour diverses raisons. Le premier défi serait de déterminer quelle équipe sera propriétaire de la base de données pour stocker ce formulaire. Il existe différentes raisons de créer un rapport (y compris une option appelée « Autres »), de sorte qu'un cas peut être traité par différentes équipes fonctionnelles. C’est pourquoi c’est un choix naturel pour nous de capturer ce cas comme un événement, comme « signaler un cas ». Toutes les informations sont capturées dans cet événement tel qu’il est. Toutes les équipes fonctionnelles n’ont qu’à s’abonner à cet événement et à faire leur propre filtration. CQRS et Sourcing d’événements L'architecture de service derrière cet exemple est basée sur les modèles de CQRS et de sourcing d'événements. Si ces termes sont nouveaux pour vous, ne vous inquiétez pas! À la fin de cette vue d'ensemble, vous devriez avoir une solide compréhension de ces concepts. Et si vous voulez plus de détails à ce stade, jetez un coup d'œil à notre . Un blog dédié à ce sujet Le premier concept à comprendre ici est le sourcing d'événements. L'idée fondamentale derrière le sourcing d'événements est que chaque changement dans l'état d'un système est capturé dans un objet d'événement et que ces objets d'événement sont stockés dans l'ordre dans lequel ils ont été appliqués à l'état du système. En d'autres termes, au lieu de simplement stocker l'état actuel, nous utilisons un magasin append-only pour enregistrer toute la série d'actions prises sur cet état. Ce concept est simple mais puissant car les événements qui représentent chaque action sont enregistrés de sorte que tout modèle possible décrivant le système puisse être construit à partir des événements. Le prochain concept est CQRS, qui signifie Ségrégation de la responsabilité de la requête de commande. CQRS a été conçu par Greg Young il y a plus d'une décennie et est issu du principe de séparation de la commande et de la requête. L'idée fondamentale est de créer des modèles de données séparés pour lire et écrire, plutôt que d'utiliser le même modèle pour les deux fins. En suivant le modèle CQRS, chaque API devrait soit être une commande qui exécute une action, soit une requête qui renvoie des données à l'appelant - mais pas les deux. Cela divise naturellement le système en deux parties: le côté écrire et le côté lire. Cette séparation offre plusieurs avantages.Par exemple, nous pouvons évoluer indépendamment la capacité d’écriture et de lecture pour optimiser l’efficacité des coûts.D’un point de vue de travail d’équipe, différentes équipes peuvent créer des vues différentes des mêmes données avec moins de conflits. Le flux de travail de haut niveau du côté de l'écriture peut être résumé comme suit: les événements qui se produisent dans de nombreuses sessions de jeu sont alimentés dans un nombre limité de processeurs d'événements. La mise en œuvre est également simple, impliquant généralement un bus de messages comme Pulsar, Kafka, ou un système de file d'attente plus simple qui agit comme un magasin d'événements. Les événements des clients persistent dans l'événement par thème et les processeurs d'événements consomment les événements en souscrivant à des sujets. Si vous êtes intéressé par la raison pour laquelle nous avons choisi Apache Pulsar par rapport à d'autres systèmes, vous pouvez trouver plus d'informations dans le . Blog mentionné plus tôt Bien que les systèmes de file d'attente soient généralement efficaces pour gérer le trafic qui circule dans une seule direction (par exemple, fan-in), ils peuvent ne pas être aussi efficaces pour gérer le trafic qui circule dans la direction opposée (par exemple, fan-out). Dans notre scénario, le nombre de sessions de jeu sera élevé et un système de file d'attente typique ne convient pas bien puisque nous ne pouvons pas nous permettre de créer une file d'attente dédiée pour chaque session de jeu. Nous devons trouver un moyen pratique de distribuer les résultats et les mesures aux sessions de jeu individuelles via les API de requête. Avant de continuer, voici un résumé de notre architecture de service. À partir du côté de l'écriture, les serveurs de jeux continuent d'envoyer des événements à notre système via les endpoints Command et chaque événement représente un certain type d'activité qui s'est produite lors d'une session de jeu. Les processeurs d'événements produisent des résultats ou des mesures contre les flux d'événements de chaque session de jeu et agissent comme un pont entre les deux côtés. Boutique d'événements distribuée de type queue pour les événements de la série Time Maintenant, voyons comment nous utilisons ScyllaDB pour résoudre le problème de l'expédition d'événements à de nombreuses sessions de jeu. Par ailleurs, si vous googlez "Cassandra" et "queue", vous pouvez rencontrer un article d'il y a plus d'une décennie indiquant que l'utilisation de Cassandra en tant que file d'attente est un anti-pattern. Bien que cela ait pu être vrai à l'époque, je dirais que c'est seulement partiellement vrai aujourd'hui. Pour soutenir l'envoi d'événements à chaque session de jeu, nous utilisons l'id de session comme clé de partition afin que chaque session de jeu ait sa propre partition et que les événements appartenant à une session de jeu particulière puissent être localisés par l'id de session de manière efficace. Chaque événement a également un identifiant d'événement unique, qui est un temps UUID, comme clé de regroupement. Parce que les enregistrements dans la même partition sont triés par la clé de regroupement, l'identifiant d'événement peut être utilisé comme l'identifiant de position dans une file d'attente. Enfin, les clients ScyllaDB peuvent récupérer efficacement les événements nouvellement arrivés en suivant l'identifiant d'événement de l'événement le plus récent reçu. Il y a une précaution à garder à l'esprit lors de l'utilisation de cette approche: le problème de cohérence. Récupérer de nouveaux événements en suivant l'identifiant d'événement le plus récent repose sur l'hypothèse qu'aucun événement avec un id plus petit ne sera commis à l'avenir. Cependant, cette hypothèse peut ne pas toujours être vraie. Par exemple, si deux nœuds génèrent deux identifiants d'événement en même temps, un événement avec un id plus petit pourrait être inséré plus tard qu'un événement avec un id plus grand. Ce problème, que j’appelle une « lecture fantôme », est similaire au phénomène dans le monde SQL où la répétition de la même requête peut donner des résultats différents en raison de changements non engagés effectués par une autre transaction. Il existe plusieurs façons de résoudre ce problème. Une solution consiste à maintenir un état de cluster, que j'appelle un «pseudo maintenant», basé sur la plus petite valeur des timestamps en mouvement parmi tous les processeurs d'événements. Une autre considération importante est d'activer TimeWindowCompactionStrategy, qui élimine l'impact négatif sur les performances causé par les pierres tombales. L'accumulation de pierres tombales a été un problème majeur qui a empêché l'utilisation de Cassandra en tant que file d'attente avant que TimeWindowCompactionStrategy ne soit disponible. Maintenant, passons à discuter d'autres avantages au-delà de l'utilisation de ScyllaDB en tant que file d'attente. Simplifier les défis complexes de la distribution mondiale des données Étant donné que nous construisons un système multi-tenance pour servir les clients du monde entier, il est essentiel de veiller à ce que les configurations des clients soient cohérentes entre les groupes dans les différentes régions. Nous avons résolu ce problème en facilitant simplement la réplication des données sur un espace clé dans tous les centres de données. Cela signifie que tout changement effectué dans un centre de données se propage finalement à d'autres. Merci à ScyllaDB, ainsi qu'à DynamoDB et Cassandra, pour le lourd soulèvement qui rend ce problème difficile trivial. Vous pensez peut-être que l'utilisation de n'importe quel RDBMS typique pourrait obtenir le même résultat puisque la plupart des bases de données prennent également en charge la réplication des données. Ceci est vrai s'il n'y a qu'une seule instance du panneau de contrôle en cours d'exécution dans une région donnée. Dans une architecture primaire/réplica typique, seul le nœud primaire prend en charge la lecture/écriture tandis que les nœuds de réplication sont uniquement en lecture. Cependant, lorsque vous avez besoin d'exécuter plusieurs instances du panneau de contrôle dans différentes régions – par exemple, chaque locataire a un panneau de contrôle en cours d'exécution dans sa région d'origine, ou même chaque région a un panneau de contrôle en cours Si vous avez utilisé AWS DynamoDB, vous connaissez peut-être une fonctionnalité appelée Global Table, qui permet aux applications de lire et d'écrire localement et d'accéder aux données à l'échelle mondiale. Les espaces clés comme conteneurs de données Ensuite, regardons comment nous utilisons les espaces clés comme conteneurs de données pour améliorer la transparence de la distribution mondiale des données. Prenons le diagramme ci-dessous. Il montre une solution à un problème typique de distribution de données imposé par les lois sur la protection des données. Par exemple, supposons que la région A permet de traiter certains types de données en dehors de ses frontières tant qu'une copie originale est conservée dans sa région. * à * à Une solution potentielle consiste à effectuer des tests end-to-end (E2E) pour s'assurer que les applications envoient correctement les données correctes dans la bonne région comme prévu. Cette approche exige que les développeurs d'applications prennent la pleine responsabilité de la mise en œuvre de la distribution de données correctement. Cependant, à mesure que le nombre d'applications augmente, il devient impraticable pour chaque application de gérer ce problème individuellement et les tests E2E deviennent également de plus en plus coûteux en termes de temps et d'argent. En permettant la réplication des données sur les espaces clés, nous pouvons diviser la responsabilité de la répartition correcte des données en deux tâches: 1) identifier les types de données et déclarer leurs destinations, et 2) copier ou déplacer les données aux endroits escomptés. En séparant ces deux tâches, nous pouvons abstraire des configurations et des réglementations complexes des applications.Cela est dû au fait que le processus de transfert de données vers une autre région est souvent la partie la plus compliquée à gérer, comme le passage des frontières du réseau, le cryptage correct du trafic et la gestion des interruptions. Après avoir séparé ces deux tâches, les applications ne sont obligées d’effectuer correctement que la première étape, ce qui est beaucoup plus facile à vérifier par le biais de tests à des stades antérieurs du cycle de développement.En outre, la correction des configurations pour la distribution des données devient beaucoup plus facile à vérifier et à auditer. Conseils pour les autres qui suivent un chemin similaire Pour conclure, nous vous laissons avec des leçons importantes que nous avons apprises, et que nous vous recommandons d’appliquer si vous finissez par prendre un chemin similaire au nôtre: Lorsque vous utilisez ScyllaDB pour traiter les données de la série temporelle, par exemple en l’utilisant comme file d’attente pour l’expédition d’événements, n’oubliez pas d’utiliser la stratégie de compactage de la fenêtre du temps. Pensez à utiliser les espaces clés comme conteneurs de données pour séparer les responsabilités de la distribution des données. Télécharger Tech Talks on Demand Cet article est basé sur un discours technologique présenté au ScyllaDB Summit 2023. Vous regardez ce discours – ainsi que des discours d’ingénieurs de Discord, Epic Games, Disney, Strava, ShareChat et plus – sur demande. Télécharger Tech Talks on Demand