作者 | Eran Stiller
译者 | 刘雅梦
策划 | 丁晓昀
Agoda 最近描述了它们从单体 GraphQL API 转向微服务架构的非常规方法。与侧重于优先拆解服务器端组件的传统方法不同,Agoda 采用了客户端优先的策略,使用一个内部智能编排器库来使其客户端应用程序准备好并行处理单体和微服务。
在服务器迁移之前,Agoda 优先考虑让客户端准备好处理单体和微服务,从而降低风险和所需的协调工作。Agoda 的助理开发经理 Numan Hanif 告诉 InfoQ:“这种方法最大限度地减少了中断,使我们的团队能够更好地控制整个堆栈,并使架构更好地与敏捷和现代开发原则保持一致。”
打破单体架构,拆分 GraphQL 单体架构 (来源)
内部智能编排器(Smart Orchestrator)是 Agoda 客户端优先迁移策略的关键组成部分,旨在在迁移期间将客户端应用程序与后端连接起来。它充当动态路由层,根据配置将请求定向到单体 GraphQL API 或新部署的 微服务。Agoda 的工程师更喜欢使用这种方法,而不是使用 Apollo Federation,后者与奈飞(Netflix)处理这种情况的方式相似。
发布与单体架构相同的接口允许客户端应用程序无需任何修改即可运行,从而减少了迁移过程中进行大量更改的需要。这种设置确保了客户端应用程序可以处理混合后端环境,为并行微服务开发奠定了基础。
智能编排器(Smart Orchestrator)根据配置路由客户端查询(来源)
除了路由之外,智能编排器(Smart Orchestrator)还自动管理模式更新和映射,支持迁移的增量特性。最初,所有模式都指向单体,但随着域的拆分,编排器更新了映射,将查询路由到正确的微服务上。
Agoda 以增量方式执行向微服务的迁移。每个新创建的微服务都经过了严格的单体测试,以验证数据的正确性,确保在转换客户端流量之前,两个系统的响应相匹配。此外,Agoda 还实施了一个准确性测试系统,在增量部署期间不断比较单体和微服务的输出,以进一步验证迁移。
Agoda 采用了一种自动化的数据驱动方法,在其 100 个应用程序 Git 存储库中分析和准备 GraphQL 查询,以确保在迁移过程中客户端准备就绪。使用生产数据,Agoda 根据域之间相互依赖的程度将查询分为简单、中等和复杂类型。
“拆解”跨域查询是 Agoda 迁移的关键一步。简单的查询不需要更改,而 Agoda 工程师将中等查询拆分为每个域单独的独立请求,并在客户端上合并结果。涉及紧密耦合嵌套域的复杂查询,当一个查询依赖于另一个查询的结果时,通常需要顺序执行,因此需要更广泛的重组。
“拆解”复杂查询(来源)
在拆分单体架构的同时,Agoda 专注于将现有代码原封不动地迁移到新的微服务中,从而导致了旧的技术问题转移到了新的微服务中。例如,Hanif 告诉 InfoQ,“Agoda 遇到了诸如缺少客户端速率限制等问题,这经常影响我们的运营。”他说,事后看来,“为了更好地实现这种平衡,我们应在迁移过程中实施重构实践,确保迁移后的架构更清洁、更高效。”
InfoQ 在采访 Hanif 的过程中,谈到了 Agoda 的微服务迁移、使用的方法以及吸取到的教训。
InfoQ:你能详细介绍一下你们决定从单体架构迁移到微服务架构的原因吗?哪些特定的增长指标或痛点是启动这一迁移的转折点?
Numan Hanif:供应商管理系统由一个由 7 人组成的专业工程团队管理,监督 70 多个通过 GraphQL、gRPC 和 REST 等多个接口交互的客户端服务。这个单体系统在处理重要流程方面至关重要,包括通过该单体系统进行预订确认。
在这个单体系统中,我们的平均变更交付周期(Agoda 使用的开发人员体验指标)环比增长了 12%,平均每月创建了 40 个合并请求(MR)。我们广泛的测试环境包括 9200 个单元和集成测试,这些测试大约需要一个小时才能运行完,并在计划运行中实现约 73% 的稳定性。
单体架构的设置还严重依赖于各种基础设施组件,并且难以管理不相关的域,从而导致了架构的复杂性。由于每季度积压 210 张操作票据和高昂的运维开销,以及知识孤岛和庞大的代码库,学习曲线很陡峭。
这些问题凸显了可扩展性解决方案的必要性,使得微服务成为增强该系统开发过程中的可管理性、可扩展性和敏捷性的理想选择。
InfoQ:你们是否遵循了任何特定的方法或框架来定义每个微服务的边界,比如领域驱动设计(DDD)或类似的实践?
Hanif:我们没有采用领域驱动设计(Domain-Driven Design,DDD)作为我们微服务边界的默认框架,而是根据我们的具体需求和 GraphQL API 的独特方面开发了一种定制方法。我们从自低而上的方法开始,分析数据库模式,并通过标记将表分类到特定的域中。这提供了一个与我们的数据管理模式相一致的基础结构,确保了识别服务边界的一致基础。
标记了表之后,我们将每个 GraphQL 端点与相应的表域对齐,以保持跨数据和服务边界的一致性。虽然 75% 的 GraphQL 端点域标记起来很简单,但剩下的 25% 却带来了挑战,因为它们涉及存储过程从多个域访问表的复杂模式。这种复杂性需要进一步的改进,以确保内聚的服务结构与我们的架构目标相一致。
我们的流程与 DDD 共享元素,例如将服务与业务功能对齐,但不同之处在于它是基于数据驱动、自底而上的方法。与自顶而下、领域专家主导的 DDD 方法不同,我们专注于现有的数据结构,并使用领域标记来解决 GraphQL 的独特挑战。这使我们能够创建连贯且功能一致的微服务,同时解决基础设施的操作复杂性。
InfoQ:是什么促使你们采用“客户端优先”的迁移方法?这一策略是否是基于以往的经验、理论优势或从以往项目中吸取的教训?Agoda 现有的架构或团队结构中是否存在任何特定的挑战,使这种方法特别适用?
Hanif:采用“客户端优先”的方法迁移到微服务的决定受到了理论和实践经验的影响。最初,我们考虑使用单体作为客户端通信的路由器,但这与我们实现真正的垂直(客户端数据库)所有权的目标相冲突。路由器最终将成为一个软单体,使长期架构变更变得复杂,并保持集中控制。这一见解强调了需要采取更分布式的策略。
过去采用服务器优先迁移方法的经验凸显了几个挑战,包括由于客户端迁移延迟而导致的迁移时间过长。这些瓶颈阻碍了我们完全停用单体架构的能力,并暂时推迟了微服务架构带来的好处。这些经验教训强调了首先让客户端做好准备的重要性,确保它们能够处理后端更改并促进无缝迁移的重要性。
客户端优先策略旨在通过允许后端开发与客户端准备活动同时进行来减少与外部团队的协调工作。这种方法最大限度地减少了中断,使我们的团队能够更好地控制整个堆栈,并使架构更好地与敏捷和现代开发原则保持一致。它还解决了诸如主动拼接查询、实现并行开发和减少依赖性之类的挑战,考虑到现有体系结构的复杂性,这一点尤其有益。
InfoQ:智能编排器(Smart Orchestrator)客户端库是内部构建的,还是你们根据需要调整了现有的工具?在创建和维护它的过程中,有哪些主要的挑战?对于处理 GraphQL 模式和路由的复杂性,你们是否发现了一些必要的特定特性?
Hanif:智能编排器(Smart Orchestrator)客户端库是内部开发的,用于解决现有解决方案无法满足的特定需求。这一决定允许使用定制工具,在不改变客户端接口的情况下,有效地管理跨微服务的模式更新和请求路由。自动模式映射更新等关键功能对于处理 GraphQL 的复杂性至关重要,可确保与现有客户端应用程序的无缝集成,同时可以避免嵌入式业务逻辑。
开发智能编排器(Smart Orchestrator)带来了挑战,每当迁移过程中出现问题时,都要用新的库版本更新多个存储库,这需要仔细的协调和测试。此外,在生产环境中启用 GraphQL 自检查询是至关重要的,但通常会由于安全和性能问题而受到限制。通过应对这些挑战,我们确保库始终是轻量级的,同时有效地支持跨域查询并适应我们的架构需求。
作者介绍
Eran Stiller 是澳大利亚墨尔本的首席软件架构师。作为一名经验丰富的软件架构师和首席技术官,Eran 设计、实施和审查了跨多个业务领域的各种软件解决方案。Eran 在软件开发领域拥有多年的经验,并有着丰富的公开演讲和社区贡献记录。
4000520066 欢迎批评指正
All Rights Reserved 新浪公司 版权所有