A look at how Tencent Games built service architecture based on CQRS and event sourcing patterns with Pulsar and ScyllaDB. В составе Tencent Interactive Entertainment Group Global (IEG Global) Proxima Beta стремится поддержать наши команды и студии, чтобы предоставить уникальные и захватывающие игры миллионам игроков по всему миру. Наша команда в Level Infinite (бренд для глобального издательства) отвечает за управление широким спектром рисков для нашего бизнеса – например, мошеннической деятельности и вредоносного контента. В этом блоге мы поделимся своим опытом построения этой системы анализа событий в режиме реального времени.Во-первых, мы исследуем, почему мы построили нашу архитектуру услуг на основе разделения ответственности команд и запросов ( ) и события sourcing паттернов с Далее мы рассмотрим, как мы используем ScyllaDB для решения проблемы рассылки событий на многочисленные сессии игры. CQRS Apache Пульсар Взгляд на случай использования: устранение рисков в играх Tencent Начнем с реального примера того, с чем мы работаем и с какими вызовами сталкиваемся. Это скриншот из Tower of Fantasy, 3D-акционной ролевой игры.Игроки могут использовать этот диалог, чтобы подать отчет против другого игрока по разным причинам.Если бы вы использовали для этого типичную систему CRUD, как бы вы сохранили эти записи для последующих действий?И каковы потенциальные проблемы? Первая задача заключается в том, чтобы определить, какая команда будет владеть базой данных для хранения этой формы.Есть различные причины для составления отчета (включая вариант под названием «Другие»), так что дело может быть обработано различными функциональными командами. Вот почему естественным выбором для нас является захват этого случая как события, например, «ответить о случае».Вся информация захватывается в этом случае так, как это есть.Все функциональные команды просто должны подписаться на это событие и сделать собственное фильтрование.Если они думают, что случай попадает в их домен, они могут просто захватить его и запустить дальнейшие действия. CQRS и Event Sourcing Архитектура сервисов, лежащая в основе этого примера, основана на моделях CQRS и поглощения событий. Если эти термины новые для вас, не волнуйтесь! К концу этого обзора вы должны иметь прочное понимание этих концепций. . Блог, посвященный этой теме Первая концепция, которую следует понимать здесь, это источник событий. Основная идея, лежащая в основе источника событий, заключается в том, что каждое изменение состояния системы фиксируется в объекте событий и эти объекты событий хранятся в том порядке, в котором они были применены к состоянию системы. Другими словами, вместо простого хранения текущего состояния, мы используем хранилище только приложений для записи всей серии действий, предпринятых на этом состоянии. Эта концепция простая, но мощная, поскольку события, представляющие каждое действие, записываются так, что любая возможная модель, описывающая систему, может быть построена из событий. Следующая концепция — CQRS, что означает Command Query Responsibility Segregation. CQRS был придуман Грегом Яном более десяти лет назад и возник из Принципа разделения команд и запросов. Основная идея заключается в создании отдельных моделей данных для чтения и письма, а не использования одной и той же модели для обоих целей. Следуя шаблону CQRS, каждый API должен быть либо командой, которая выполняет действие, либо запросом, который возвращает данные вызывающему — но не обоим. Такое разделение дает несколько преимуществ. Например, мы можем масштабировать запись и чтение независимо, чтобы оптимизировать эффективность затрат. С точки зрения совместной работы разные команды могут создавать разные представления о тех же данных с меньшим количеством конфликтов. Высокоуровневый рабочий процесс страницы написания можно обобщить следующим образом: события, происходящие в многочисленных сессиях игры, подаются в ограниченное количество процессоров событий. Реализация также проста, обычно включая автобус сообщений, такой как Pulsar, Kafka, или более простая система очередей, которая действует как магазин событий. События от клиентов сохраняются в магазине событий по темам и процессоры событий потребляют события, подписываясь на темы. . Блог, упомянутый ранее Несмотря на то, что системы, похожие на очереди, обычно эффективны в управлении трафиком, поступающим в одном направлении (например, фан-ин), они могут быть не такими эффективными в управлении трафиком, поступающим в противоположном направлении (например, фан-аут). В нашем сценарии количество сеансов игры будет большим, а типичная система очереди не подходит хорошо, так как мы не можем позволить себе создать специальную очереди для каждой сессии игры. Прежде чем мы перейдем дальше, вот краткое описание нашей архитектуры услуг. Начиная с страницы записи, игровые серверы продолжают отправлять события в нашу систему через конечные точки команды, и каждое событие представляет собой определенный вид деятельности, который произошел в сессии игры. Процессоры событий производят выводы или показатели против потоков событий каждой сессии игры и действуют как мост между двумя сторонами. Распределенный Queue-Like Event Store для событий Time Series Теперь давайте рассмотрим, как мы используем ScyllaDB, чтобы решить проблему рассылки событий на многочисленные сеансы игры. Кстати, если вы Google «Cassandra» и «queue», вы можете столкнуться со статьей более десяти лет назад, в которой говорится, что использование Cassandra в качестве очереди является анти-образцом. Хотя это могло быть правдой в то время, я бы утверждал, что это только частично верно сегодня. Чтобы поддерживать рассылку событий в каждую сессию игры, мы используем идентификатор сессии в качестве ключа раздела, так что каждая сессия игры имеет свой собственный раздел, а события, принадлежащие к определенной сессии игры, могут быть эффективно локализованы идентификатором сессии. Каждое событие также имеет уникальный идентификатор события, который является временем UUID, в качестве кластерного ключа. Поскольку записи в пределах одного и того же раздела сортируются кластерным ключом, идентификатор события можно использовать в качестве идентификатора позиции в очереди. Есть одна предостережение, чтобы иметь в виду при использовании этого подхода: проблема согласованности. Поиск новых событий, отслеживая последний идентификатор событий, основывается на предположении, что ни одно событие с меньшим идентификатором не будет совершено в будущем. однако, это предположение может не всегда соответствовать действительности. Например, если два узла генерируют два идентификатора событий одновременно, событие с меньшим идентификатором может быть введено позже, чем событие с большим идентификатором. Эта проблема, которую я называю «фантомным чтением», похожа на феномен в мире SQL, где повторение одного и того же запроса может дать разные результаты из-за необязательных изменений, сделанных другой транзакцией. Существует несколько способов решения этой проблемы. Одним из решений является поддержание кластерного статуса, который я называю «псевдо-сейчас», основанного на наименьшем значении движущихся временных штампов среди всех процессоров событий. Еще одним важным соображением является возможность TimeWindowCompactionStrategy, которая устраняет негативное влияние производительности, вызванное гробницами. накопление гробниц было основной проблемой, которая препятствовала использованию Cassandra в качестве очереди до того, как TimeWindowCompactionStrategy стала доступной. Теперь перейдем к обсуждению других преимуществ, помимо использования ScyllaDB в качестве очереди. Упрощение сложных проблем глобального распространения данных Поскольку мы строим систему для обслуживания клиентов по всему миру, важно убедиться, что конфигурации клиентов согласуются между кластерами в разных регионах.Доверие – сохранение согласованности распределенной системы не является тривиальной задачей, если вы планируете сделать это все самостоятельно. Мы решили эту проблему, просто разрешив репликацию данных на ключевом пространстве во всех центрах обработки данных. Это означает, что любые изменения, сделанные в одном центре обработки данных, в конечном итоге будут распространяться на другие.Спасибо ScyllaDB, а также DynamoDB и Cassandra, за тяжелое поднятие, которое делает эту сложную проблему тривиальной. Вы можете думать, что использование любого типичного RDBMS может достичь того же результата, так как большинство баз данных также поддерживают репликацию данных. Это верно, если в определенном регионе работает только один пример панели управления. В типичной архитектуре первичной/реплики только первичный узел поддерживает чтение/написание, а узелки реплики являются только для чтения. Однако, когда вам нужно запускать несколько экземпляров панели управления в разных регионах — например, у каждого арендатора есть панель управления, работающая в его домашнем регионе, или даже у каждого региона есть панель управления, работающая для местных команд — это становится намного сложнее реализовать с помощью типичной архитектуры первичной/реплики. Если вы использовали AWS DynamoDB, вы можете быть знакомы с функцией под названием Global Table, которая позволяет приложениям читать и записывать локально и получать доступ к данным глобально. Ключевые пространства как контейнеры данных Далее рассмотрим, как мы используем ключевые пространства в качестве контейнеров данных для повышения прозрачности глобального распространения данных. Давайте рассмотрим диаграмму ниже. Она показывает решение типичной проблемы распространения данных, навязанной законами о защите данных. Например, предположим, что регион А позволяет обрабатывать определенные типы данных за пределами его границ, если оригинальная копия хранится в его регионе. * « * « Одним из возможных решений является проведение тестов от конца до конца (E2E), чтобы убедиться, что приложения правильно отправляют правильные данные в правильный регион, как и ожидалось. Этот подход требует, чтобы разработчики приложений несли полную ответственность за правильное внедрение распределения данных. Позволяя репликации данных на ключевых пространствах, мы можем разделить ответственность за правильное распределение данных на две задачи: 1) выявление типов данных и декларирование их назначений, и 2) копирование или перемещение данных в ожидаемые места. Отделив эти две задачи, мы можем абстрагировать сложные конфигурации и правила от приложений.Это потому, что процесс передачи данных в другой регион часто является самой сложной частью для решения, такой как переход через границы сети, правильное шифрование трафика и управление прерываниями. После разделения этих двух задач приложениям требуется только правильно выполнить первый шаг, который намного проще проверить с помощью тестирования на более ранних этапах цикла разработки.Кроме того, корректность конфигураций для распределения данных становится намного проще проверить и проверить. Советы для других, следующих аналогичным путем В заключение, мы оставим вам важные уроки, которые мы узнали, и которые мы рекомендуем вам применить, если вы в конечном итоге поймете путь, похожий на наш: При использовании ScyllaDB для обработки данных временной серии, например, используя его в качестве очереди для рассылки событий, не забудьте использовать Стратегию сжатия временного окна. Рассмотрим использование ключевых пространств в качестве контейнеров данных для разделения ответственности за распространение данных. Смотреть Tech Talks On-Demand Эта статья основана на технической беседе, представленной на саммите ScyllaDB в 2023 году.Вы смотрите эту беседу - а также беседы инженеров из Discord, Epic Games, Disney, Strava, ShareChat и других - по запросу. Смотреть Технические переговоры по запросу