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来解决将事件发送到众多游戏会话中的问题,最后,我们将讨论如何使用ScyllaDB键空间和数据复制来简化我们的全球数据管理。 CQRS的 Apache 脉冲器 使用案例:解决Tencent游戏中的风险 让我们从一个现实世界的例子开始,我们正在处理什么以及我们面临的挑战。 这是一个3D动作角色扮演游戏Tower of Fantasy的屏幕截图,玩家可以使用此对话方式对另一个玩家提交报告,因为各种原因。如果您要使用典型的CRUD系统,您将如何保留这些记录以进行追踪?以及潜在的问题是什么? 第一個挑戰是決定哪個團隊將擁有資料庫儲存此表單。 建立報告有不同的理由(包括名為「其他人」的選項),因此一個案子可能由不同的功能團隊處理。 这就是为什么我们自然选择将此案记录为事件,比如“报告一个案例”。所有信息都在此事件中被捕获,所有功能团队只需要订阅此事件并进行自己的过滤。 CQRS和事件来源 此示例背后的服务架构基于 CQRS 和事件采购模式. 如果这些条款对您来说是新的,请不要担心! 到本概览结束时,您应该对这些概念有很好的了解。 . 致力于此主题的博客 在这里要理解的第一个概念是事件源。事件源的核心想法是,系统状态的每一个变化都被记录在事件对象中,并且这些事件对象被存储在它们应用于系统状态的顺序中。 下一个概念是CQRS,即命令查询责任分离(Command Query Responsibility Segregation)。CQRS由Greg Young十多年前发明,源于命令和查询分离原则。基本理念是创建单独的数据模型来读取和写入,而不是使用相同的模型来实现这两个目的。按照CQRS模式,每个API都应该是执行一个操作的命令,或者是返回数据给呼叫者的查询,但不是两者。 这种分离提供了几个好处,例如,我们可以独立扩展写入和阅读容量,以优化成本效率。 写字侧的高层次工作流程可以概括如下:在众多游戏会话中发生的事件被送到有限数量的事件处理器中。执行过程也很简单,通常涉及Pulsar、Kafka等消息总线或更简单的排队系统,作为一个事件商店。客户端的事件在事件商店中持久,事件处理器通过订阅主题来消耗事件。 . 以前提到的博客 虽然队列类型的系统通常有效地处理向一个方向流动的流量(例如,粉丝进入),但它们可能无法有效地处理向相反方向流动的流量(例如,粉丝退出)。在我们的场景中,游戏会议的数量会很高,而典型的队列系统并不适合,因为我们无法为每个游戏会话创建专用队列。 在我们继续前,这里是我们服务架构的摘要。 从写字侧开始,游戏服务器通过命令终端继续向我们的系统发送事件,每个事件代表了在游戏会话中发生的某种类型的活动。事件处理器对每个游戏会话的事件流产生结果或指标,并作为两侧之间的桥梁。 时间系列事件的分布式队列式事件商店 现在让我们看看我们如何使用ScyllaDB来解决将事件发送到众多游戏会话的问题。顺便说一句,如果你在Google上搜索“Cassandra”和“queue”,你可能会遇到一个十多年前的一篇文章,声称使用Cassandra作为队列是一种反模式。 为了支持将事件发送到每个游戏会话中,我们使用会话ID作为分区密钥,以便每个游戏会话有自己的分区,而属于特定游戏会话的事件可以由会话ID有效地找到。 每个事件都有一个独特的事件ID,即时间 UUID,作为聚合密钥. 由于相同分区内的记录由聚合密钥进行排序,所以事件ID可以用作排列中的位置ID。 使用此方法时需要记住一个警告:一致性问题. 通过跟踪最新的事件 ID 来检索新事件取决于假设未来不会发生任何具有较小的事件 ID 的事件。 这个问题,我称之为“幽灵阅读”,类似于SQL世界的现象,在那里重复相同的查询可能会产生不同的结果,因为另一个交易所做的未承诺的更改。 有几种方法可以解决这个问题。一个解决方案是保持集群范围内的状态,我称之为“假设现在”,基于所有事件处理器中移动时刻印的最小值。 另一个重要的考虑是允许TimeWindowCompactionStrategy,这消除了墓石造成的负面性能影响。墓石积累是一个主要问题,在TimeWindowCompactionStrategy成为可用之前阻止了Cassandra作为队列的使用。 现在,让我们转向讨论除了使用ScyllaDB作为发送队列之外的其他好处。 简化复杂的全球数据分布挑战 由于我们正在构建一个多租赁系统来服务全球客户,所以确保客户配置在不同地区的集群之间一致是至关重要的。 我们通过简单地在所有数据中心的关键空间上实现数据复制来解决这个问题,这意味着在一个数据中心进行的任何更改最终都会传播到其他数据中心。 您可能会认为使用任何典型的 RDBMS 可以达到相同的结果,因为大多数数据库也支持数据复制。如果控制面板在特定区域中运行只有一个实例,这是真的。在典型的主/复制架构中,只有主节点支持读/写,而复制节点仅支持读。 如果您使用过 AWS DynamoDB,您可能熟悉一个名为“全球表”的功能,该功能允许应用程序本地阅读和写入,并在全球范围内访问数据。 关键空间作为数据容器 接下来,让我们看看我们如何使用密钥空间作为数据容器,以提高全球数据分布的透明度。 例如,假设区域A允许某些类型的数据在其境外进行处理,只要原始副本保存在该地区。 」 」 一个潜在的解决方案是执行端到端(E2E)测试,以确保应用程序按照预期正确地将正确的数据发送到正确的区域。 这种方法要求应用程序开发人员对正确地实施数据分布承担全部责任。 通过在键空间上实现数据复制,我们可以将正确分配数据的责任分为两个任务: 1)识别数据类型并声明其目的地, 2)复制或移动数据到预期的位置。 通过分离这两个任务,我们可以从应用程序中抽象化复杂的配置和法规,这是因为将数据传输到另一个区域的过程往往是最复杂的部分,例如通过网络边界,正确加密流量和处理中断。 将这两个任务分开后,应用程序只需要正确执行第一步,这在开发周期的早期阶段通过测试更容易验证。 此外,数据分布配置的正确性变得更容易验证和审计。 提示其他人采取类似的道路 最后,我们将为您留下我们学到的重要教训,我们建议您申请,如果您最终采取类似于我们的道路: 当使用 ScyllaDB 来处理时间序列数据时,例如将其用作事件发送队列时,请记住使用 Time-Window 压缩策略。 考虑使用密钥空间作为数据容器来分离数据分布的责任,这可以使复杂的数据分布问题更容易管理。 观看技术讲座按需 本文是基于在ScyllaDB Summit2023上发表的一篇技术演讲,您可以根据需求观看这篇演讲 - 以及来自Discord,Epic Games,迪士尼,Strava,ShareChat等工程师的演讲。 观看技术谈判在需求