A look at how Tencent Games built service architecture based on CQRS and event sourcing patterns with Pulsar and ScyllaDB. Como parte do Tencent Interactive Entertainment Group Global (IEG Global), a Proxima Beta está empenhada em apoiar nossas equipes e estúdios para trazer jogos únicos e excitantes para milhões de jogadores em todo o mundo. Nossa equipe da Level Infinite (a marca de publicação global) é responsável por gerenciar uma ampla gama de riscos para o nosso negócio – por exemplo, atividades fraudulentas e conteúdo prejudicial. Neste blog, compartilhamos nossa experiência de construir este sistema de análise orientado a eventos em tempo real. Primeiro, vamos explorar por que construímos nossa arquitetura de serviços com base na segregação de responsabilidade de comando e consulta ( ) e event sourcing padrões com e ScyllaDB. Em seguida, veremos como usamos o ScyllaDB para resolver o problema de enviar eventos para inúmeras sessões de jogo. Finalmente, cobriremos como usamos os espaços-chave e a replicação de dados do ScyllaDB para simplificar nossa gestão global de dados. CQRs O Apache Pulsar Um olhar sobre o caso de uso: abordando os riscos nos jogos da Tencent Vamos começar com um exemplo do mundo real do que estamos trabalhando e os desafios que enfrentamos. Esta é uma captura de tela de Tower of Fantasy, um role-playing game de ação 3D. Os jogadores podem usar este diálogo para enviar um relatório contra outro jogador por várias razões. Se você fosse usar um sistema CRUD típico para isso, como você manteria esses registros para seguimento? Existem diferentes razões para fazer um relatório (incluindo uma opção chamada “Outros”), de modo que um caso pode ser tratado por equipes funcionais diferentes. É por isso que é uma escolha natural para nós capturar este caso como um evento, como “relatar um caso”.Toda a informação é capturada neste evento como é.Todas as equipes funcionais só precisam se inscrever neste evento e fazer sua própria filtragem.Se eles acham que o caso cai em seu domínio, eles podem simplesmente capturá-lo e desencadear outras ações. CQRS e Sourcing de Eventos A arquitetura de serviços por trás deste exemplo é baseada nos padrões de CQRS e de aquisição de eventos. Se estes termos são novos para você, não se preocupe! No final desta visão geral, você deve ter uma sólida compreensão desses conceitos. . Blog dedicado ao tema O primeiro conceito a ser compreendido aqui é a aquisição de eventos. A ideia central por trás da aquisição de eventos é que cada mudança no estado de um sistema é capturada em um objeto de evento e esses objetos de eventos são armazenados na ordem em que foram aplicados ao estado do sistema.Em outras palavras, em vez de apenas armazenar o estado atual, usamos um armazenamento apenas de apêndice para registrar toda a série de ações tomadas nesse estado. O próximo conceito é CQRS, que significa Comand Query Responsibility Segregation. O CQRS foi inventado por Greg Young há mais de uma década e originou-se do Princípio de Separação de Comando e Query. A ideia fundamental é criar modelos de dados separados para leituras e escrituras, em vez de usar o mesmo modelo para ambos os fins. Seguindo o padrão CQRS, cada API deve ser um comando que executa uma ação, ou uma consulta que retorna dados para o chamador – mas não ambos. Esta separação oferece vários benefícios.Por exemplo, podemos escalar a capacidade de escrita e leitura de forma independente para otimizar a eficiência de custos.Do ponto de vista do trabalho em equipe, diferentes equipes podem criar vistas diferentes dos mesmos dados com menos conflitos. O fluxo de trabalho de alto nível do lado de escrita pode ser resumido da seguinte forma: eventos que ocorrem em inúmeras sessões de jogo são alimentados em um número limitado de processadores de eventos. A implementação também é simples, geralmente envolvendo um bus de mensagens como Pulsar, Kafka, ou um sistema de fila mais simples que atua como uma loja de eventos. Eventos de clientes são persistentes na loja de eventos por tópico e processadores de eventos consomem eventos por assinatura de tópicos. Se você está interessado em por que escolhemos o Apache Pulsar sobre outros sistemas, você pode encontrar mais informações no . Blog comentado anteriormente Embora os sistemas de fila geralmente sejam eficientes no gerenciamento do tráfego que flui em uma direção (por exemplo, fan-in), eles podem não ser tão eficazes no gerenciamento do tráfego que flui na direção oposta (por exemplo, fan-out). Em nosso cenário, o número de sessões de jogo será grande, e um sistema de fila típico não se encaixa bem porque não podemos dar ao luxo de criar uma fila dedicada para cada sessão de jogo. Precisamos encontrar uma maneira prática de distribuir resultados e métricas para sessões de jogo individuais através de APIs de consulta. Antes de avançar, aqui está um resumo da nossa arquitetura de serviços. A partir do lado de escrita, os servidores de jogos continuam enviando eventos para o nosso sistema através de endpoints de comando e cada evento representa um certo tipo de atividade que ocorreu em uma sessão de jogo. Processadores de eventos produzem resultados ou métricas contra os fluxos de eventos de cada sessão de jogo e atuam como uma ponte entre dois lados. No lado de leitura, temos servidores de jogos ou outros clientes que mantêm as métricas e as descobertas de pesquisa através de endpoints de consulta e tomam ações adicionais se atividades anormais tiverem sido observadas. Loja de Eventos Distribuída para Séries do Tempo Agora vamos olhar como usamos o ScyllaDB para resolver o problema de enviar eventos para inúmeras sessões de jogo. By the way, se você Google “Cassandra” e “queue”, você pode encontrar um artigo de mais de uma década atrás afirmando que usar o Cassandra como uma fila é um anti-pattern. Embora isso possa ter sido verdade na época, eu argumentaria que é apenas parcialmente verdade hoje. Nós fizemos isso funcionar com o ScyllaDB (que é compatível com Cassandra). Para suportar o envio de eventos para cada sessão de gameplay, usamos o id de sessão como a chave de partição para que cada sessão de gameplay tenha sua própria partição e os eventos pertencentes a uma sessão de gameplay específica possam ser localizados pelo id de sessão de forma eficiente. Cada evento também tem um id de evento único, que é um UUID de tempo, como a chave de agrupamento. Como os registros dentro da mesma partição são classificados pela chave de agrupamento, o id de evento pode ser usado como o id de posição em uma fila. Finalmente, os clientes do ScyllaDB podem recuperar eficientemente eventos recém-chegados rastreando o id de evento do evento mais recente que foi recebido. Há uma advertência a ter em mente ao usar esta abordagem: o problema de consistência. Receber novos eventos rastreando o ID de evento mais recente baseia-se na suposição de que nenhum evento com um ID menor será cometido no futuro. No entanto, esta suposição pode não ser sempre verdadeira. Por exemplo, se dois nós gerarem dois identificadores de evento ao mesmo tempo, um evento com um ID menor pode ser inserido mais tarde do que um evento com um ID maior. Este problema, que eu refiro como uma "leitura fantasma", é semelhante ao fenômeno no mundo SQL onde repetir a mesma consulta pode produzir resultados diferentes devido a alterações não comprometidas feitas por outra transação. Existem várias maneiras de resolver este problema.Uma solução é manter um status em todo o cluster, que eu chamo de um "pseudo agora", com base no menor valor dos timestamps móveis entre todos os processadores de eventos.Cada processador de eventos deve também garantir que todos os eventos futuros tenham um ID de evento maior que o seu timestamp atual. Outra consideração importante é permitir a TimeWindowCompactionStrategy, que elimina o impacto negativo no desempenho causado por pedras sepulcrales.A acumulação de pedras sepulcrales foi um problema importante que impediu o uso de Cassandra como uma fila antes que a TimeWindowCompactionStrategy se tornasse disponível. Agora vamos passar para discutir outros benefícios além de usar o ScyllaDB como uma fila de envio. Simplificar os desafios complexos da distribuição global de dados Uma vez que estamos construindo um sistema multi-tenancy para atender clientes em todo o mundo, é essencial garantir que as configurações dos clientes sejam consistentes entre aglomerados em diferentes regiões. Resolvemos este problema simplesmente permitindo a replicação de dados em um espaço-chave em todos os centros de dados. Isso significa que qualquer alteração feita em um centro de dados eventualmente se espalhará para outros.Obrigado ScyllaDB, bem como DynamoDB e Cassandra, pelo levantamento pesado que faz com que este problema desafiador pareça trivial. Você pode estar pensando que usar qualquer RDBMS típico poderia alcançar o mesmo resultado, uma vez que a maioria dos bancos de dados também suporta a replicação de dados. Isto é verdade se houver apenas uma instância do painel de controle em execução em uma determinada região. Em uma arquitetura primária/replica típica, apenas o nó primário suporta leitura/escrita, enquanto os nós de replicação são apenas de leitura. No entanto, quando você precisa executar várias instâncias do painel de controle em diferentes regiões – por exemplo, cada inquilino tem um painel de controle em execução em sua região de origem, ou mesmo cada região tem um painel de controle em execução para equipes locais – torna-se muito mais difícil implementar isso usando uma arquitetura primária/replica típica. Se você usou o AWS DynamoDB, você pode estar familiarizado com um recurso chamado Global Table, que permite que os aplicativos leiam e escrevam localmente e acessem os dados globalmente. Keyspaces como contêineres de dados Em seguida, vamos olhar para como usamos espaços-chave como contêineres de dados para melhorar a transparência da distribuição global de dados. Vamos dar uma olhada no diagrama abaixo. Ele mostra uma solução para um problema típico de distribuição de dados imposto pelas leis de proteção de dados. Por exemplo, suponha que a região A permita que certos tipos de dados sejam processados fora de suas fronteiras, desde que uma cópia original seja mantida em sua região. *Não *Não Uma solução potencial é realizar testes end-to-end (E2E) para garantir que os aplicativos enviem corretamente os dados corretos para a região correta como esperado.Esta abordagem exige que os desenvolvedores de aplicativos assumam a responsabilidade total pela implementação correta da distribuição de dados. No entanto, à medida que o número de aplicativos aumenta, torna-se impraticável para cada aplicativo lidar com este problema individualmente e os testes E2E também se tornam cada vez mais caros em termos de tempo e dinheiro. Ao permitir a replicação de dados em espaços-chave, podemos dividir a responsabilidade pela distribuição correta de dados em duas tarefas: 1) identificar tipos de dados e declarar seus destinos, e 2) copiar ou mover dados para os locais esperados. Ao separar essas duas tarefas, podemos abstrair configurações e regulamentos complexos das aplicações.Isso ocorre porque o processo de transferência de dados para outra região é muitas vezes a parte mais complicada para lidar, como atravessar fronteiras de rede, criptografar corretamente o tráfego e lidar com interrupções. Depois de separar essas duas tarefas, os aplicativos só são obrigados a executar corretamente o primeiro passo, o que é muito mais fácil de verificar através de testes em estágios anteriores do ciclo de desenvolvimento.Além disso, a correção das configurações para a distribuição de dados torna-se muito mais fácil de verificar e auditar. Dicas para outros que seguem um caminho semelhante Para concluir, vamos deixá-lo com lições importantes que aprendemos, e que recomendamos que você aplique se você acabar tomando um caminho semelhante ao nosso: Ao usar o ScyllaDB para lidar com dados de série de tempo, como usá-lo como uma fila de envio de eventos, lembre-se de usar a Estratégia de Compactação de Janela de Tempo. Considere o uso de espaços-chave como contêineres de dados para separar a responsabilidade da distribuição de dados. Assista ao Tech Talks On-Demand Este artigo é baseado em uma palestra de tecnologia apresentada na ScyllaDB Summit 2023.Você assiste a esta palestra – bem como palestras de engenheiros da Discord, Epic Games, Disney, Strava, ShareChat e muito mais – on-demand. Assista às conversas de tecnologia sob demanda