服务 IDL 实践:海外某民宿平台如何扩展 SOA 服务开发能力(第一部分)

本文是“扩展服务开发”系列文章的第一篇,重点介绍支撑海外某民宿平台新一代 SOA 架构的核心基础:服务 IDL。文章将围绕服务接口定义、RPC 客户端生成、Thrift-over-HTTP、强类型 API 和后端服务治理等关键内容展开。

服务 IDL 实践:海外某民宿平台如何扩展 SOA 服务开发能力(第一部分)

该公司正在加速将基础设施迁移到 SOA(面向服务架构)。然而,在持续开发新产品和新功能的同时,将系统从单体 Rails 应用迁移到 SOA,并不是一件容易的事。

本文将介绍我们为扩展后端服务开发能力而设计和构建的一套方案。我们的目标是:在投入相同工程时间的前提下,构建更多更稳定、性能更好、也更易维护的后端服务。这是本系列文章的第一篇,将概述整体方案和架构;后续文章会进一步深入介绍其中的具体组件。

我们此前的后端服务技术栈

在该公司,后端服务大多使用 Java 编写,并基于 Dropwizard Web 服务框架构建。我们还围绕 Dropwizard 开发了许多定制过滤器和模块,用于规范服务端最佳实践。此外,我们也提供了一个“创建服务”工具,帮助工程师快速搭建可直接部署的 Dropwizard 应用骨架,并在此基础上添加基于 RESTful 和 JSON-over-HTTP 的服务资源端点。

不过,这套组合仍然存在不少问题。服务开发者反馈最多的痛点主要包括以下几类。

首先,服务缺少定义清晰且强类型的接口和数据模式。很多时候,如果不直接阅读源代码,就很难理解某个服务到底提供什么能力、应该如何发送请求,以及会收到怎样的响应。由于缺少服务端与客户端之间的契约校验,过去也曾发生过因破坏兼容性的变更而引发的生产事故。

其次,RESTful 服务框架本身并不提供服务 RPC 能力。开发者必须花费大量时间编写 Ruby 和 Java 客户端来调用服务。而优秀的 RPC 代码远不只是对 HTTP 客户端做一层简单封装;它还应稳健地实现一系列标准基础设施能力和平台最佳实践,例如传递请求上下文、采集请求和性能指标、传播服务异常及其原因、启用双向 TLS,以及对服务端点进行监控和告警。所有这些工作都会额外消耗数小时甚至数天的工程时间。

第三,与紧凑且具备模式约束的二进制格式相比,JSON 请求和响应负载体积更大、效率更低。例如,JSON 中包含大量字符串字段名,这不利于高效的服务间通信;当负载大小达到 P95 或 P99 水位时,这一问题尤其明显。

为什么需要扩展服务开发能力

现成方案

服务 API 通常包括服务接口以及请求、响应数据模式。在 REST 服务环境中,可以使用 Swagger 等工具自动化一些样板工作,例如编写 REST API 文档、生成基础客户端代码等。

然而,与 Thrift、gRPC 这类功能完整的服务 RPC 框架相比,大多数 REST 工具仍然缺少一些关键能力,例如强类型支持、请求和响应校验、高效的负载编码以及高效的传输协议等。而这些能力对于构建稳定且高性能的后端服务至关重要。

Thrift 和 gRPC 是业界广泛使用的两种 RPC 系统。不过,如果直接用其中任何一种替换该公司现有的 HTTP 服务栈,就意味着需要对基础设施进行大规模改造。这不仅需要投入大量工程资源,也会严重影响团队的开发效率。

我们的服务 IDL 方案

在权衡各种技术方案的利弊之后,我们决定采用一种渐进式方案:既不打断当前 Java 服务开发流程,又为未来持续改进和演进服务架构留下空间。

具体来说,我们保留 Dropwizard 服务框架,在其基础上引入定制化的 Thrift 服务 IDL(接口定义语言),将传输协议从 JSON-over-HTTP 切换为 Thrift-over-HTTP,并构建用于生成多语言 RPC 客户端的工具。

下面将进一步说明,这一服务 IDL 方案如何在尽量降低迁移干扰的同时,提升服务开发的规模和效率。

使用 Thrift 作为服务 IDL

我们使用 Thrift IDL 定义服务 API,包括服务接口以及请求、响应数据模式。在基于 IDL 的 Java 服务开发流程中,开发者先在 .thrift 文件中定义服务 API,系统随后会根据这些文件生成服务端代码和 RPC 客户端,并集成该公司服务平台的标准观测能力,以帮助团队落实基础设施最佳实践。

这一流程显著简化了服务开发。工程师可以把精力集中在业务逻辑上,而不必反复处理底层样板工作和监控能力,例如服务间传输、指标采集、告警,以及向后兼容的 API 管理。

启用基于 HTTP 的 Thrift

服务开发者已经熟悉 Dropwizard 服务框架,我们也围绕它构建了许多常用库。Dropwizard 默认基于 JSON-over-HTTP,并使用 Jersey 和 Jackson 等库;而我们希望在保留现有开发体验的前提下,将 Thrift 服务 IDL 引入服务开发流程。

由于不破坏现有服务框架和服务间通信协议至关重要,我们扩展了 Dropwizard,使其支持 Thrift 负载类型,从而实现服务间通信协议从 JSON-over-HTTP 到 Thrift-over-HTTP 的平滑过渡。

强类型 API:提升服务接口的稳定性

使用 Thrift 作为服务接口定义语言,本身就为服务 API 提供了清晰的定义和文档。此外,数据模式中的每个字段都有明确类型,并会在生成的 Java 和 Ruby 数据类中得到强制约束。

内置的模式校验机制,再加上服务端和客户端代码在设计时对 API 变更与向后兼容性的充分考虑,使 API 兼容性管理变得更加简单、可靠。

我们还在标准 Thrift IDL 之外扩展了类型系统,新增了 DateDateTime 等类型。这一扩展来自我们过去的经验:如果只向服务开发者提供过小的类型集合,他们最终往往会借助现有类型“模拟”所需类型,例如把日期转成字符串。这样做会绕过自动类型检查和校验,最终违背强类型数据模式的初衷。

服务 IDL 的进一步扩展

编写服务远不只是实现服务端业务逻辑。用不同语言实现服务 RPC 客户端、配置并执行服务间通信、添加服务端和客户端监控指标,以及遵循标准服务平台实践,这些工作繁琐、容易出错,也会带来显著的工程成本。

在引入服务 IDL 之前,创建和管理服务的成本很高,以至于在时间压力之下,工程师和管理者往往会对 SOA 望而却步。

我们的目标是让基于服务 IDL 的服务开发既简单又经济高效,并能随着时间推移持续增强能力。为此,仅使用标准 Thrift 还不够。我们对现有 Thrift IDL 编译器进行了大量定制,使其能够生成带有多种附加能力的服务样板代码和 RPC 客户端。我们认为,这对于加速公司技术栈向 SOA 架构转型至关重要。

下面以一个名为 Banana 的 Java 服务为例:它使用服务 IDL 定义 API;与此同时,一个 Ruby 服务和另一个 Java 服务则使用自动生成的客户端对其发起 RPC 调用。图中红色部分表示根据 Banana 的服务 IDL 自动生成或自动添加的组件。

服务 IDL 依赖与编译器集成

除了编写本服务的 IDL,服务开发者还可以通过引用其他服务的 IDL 文件来声明服务依赖。本服务的 IDL 文件及其依赖服务的 IDL 文件,都会无缝集成到现有构建流程和构建工具中。

服务开发者只需像过去一样开发,所有必要的 IDL 相关代码都会自动生成,并出现在 IDE 中,无需额外操作。

服务框架样板代码

我们扩展了 Thrift 代码生成器,使其支持生成 Dropwizard 资源代码。生成的服务资源类和方法会带有必要的 Jersey 与 Jackson 注解,以支持 HTTP 负载中的 JSON 和 Thrift 媒体类型。也就是说,同一个服务既可以接收和返回 JSON 请求、响应数据,也可以接收和返回 Thrift 请求、响应数据。

其中,Thrift 二进制传输用于提升性能;JSON 则用于兼容旧服务,并方便测试和调试。

由于业务逻辑与 Dropwizard 之间的胶水代码由系统自动生成,服务开发者可以编写与底层 Web 服务器框架无关的代码。因此,无论未来是升级 Dropwizard,还是从 Dropwizard 迁移到其他框架,迁移工作都会更容易。

自动生成 RPC 客户端

在引入服务 IDL 之前,工程师需要为其他应用程序与服务通信手动实现客户端。用多种语言编写客户端不仅耗时,也容易导致客户端过时,从而出现功能不一致的问题,例如 Ruby 客户端和 Java 客户端之间的差异。

有了服务 IDL,系统可以自动生成开箱即用的 Java 和 Ruby API 客户端。这些生成的客户端并不是原生 Thrift 自带的 Ruby 或 Java Thrift 客户端,而是真正面向公司服务平台需求的 RPC 客户端,提供了许多原生 Thrift 不具备的能力,例如 mTLS、重试、校验和请求上下文传播等。

服务平台标准实践与服务治理

随着工程师构建越来越多的服务,确保所有服务采用一致的最佳实践至关重要。

例如,服务请求会包含上下文信息,而这些请求上下文应该在服务的 RPC 调用链路中持续传播。如果不同服务以不同方式传播上下文,甚至完全不传播上下文,或者上下文的定义和获取方式各不相同,系统就会变得非常难以管理。

同样,如果每个服务都发出不同的指标和不同类型的告警,SRE 团队在监控和调试 SOA 问题时也会面临很大困难。

在服务端和客户端统一发出标准请求与响应指标,不仅有助于服务团队内外更好地监控系统,也能用于自动生成服务仪表盘和告警。服务客户端同样应采用标准化的 RPC 超时、重试和熔断逻辑。通过自动生成的 RPC 客户端,服务开发者无需自行实现这些 RPC 韧性能力。

对于希望借鉴这类服务治理经验的研发团队来说,技术框架之外的研发管理同样重要。例如,借助 PingCode 这类智能化研发管理工具,可以将团队目标、客户反馈、需求清理、评审排期、开发、测试、发布以及 Wiki 知识沉淀串联起来,并打通研发过程中使用的其他工具,让数据在研发链路中无缝流转;如果团队更关注通用项目协作,也可以通过 Worktile 统一管理任务、项目、文档、IM、目标、日历、甘特图、工时和审批等协作事项。

服务 IDL 带来的结果

在 Java 服务框架中加入 Thrift 服务 IDL,是我们构建可靠、高性能、对开发者友好的服务平台的重要一步。随着公司持续将基础设施迁移到 SOA,它将成为一项核心架构能力,并为更多可扩展的开发能力奠定基础。

自发布以来,基于服务 IDL 的全新 Java 服务开发流程已经被产品团队广泛采用,并显著提升了开发速度。几个使用服务 IDL 构建的新 Java 服务,从创建到上线只用了三周时间。我们估计,与此前的开发流程相比,每个 Java 服务节省了 2 到 3 周的工程时间。未来,我们还计划将服务 IDL 支持扩展到 Ruby 服务。

本文介绍了我们在尽量减少服务中断的前提下扩展服务开发能力的方法,并重点介绍了这一方法的核心:服务接口定义语言 IDL。我们没有展开服务 IDL 各个组件的设计和实现细节;这些内容将在本系列后续文章中进一步探讨。

文章包含AI辅助创作:服务 IDL 实践:海外某民宿平台如何扩展 SOA 服务开发能力(第一部分),发布者:su,转载请注明出处:https://worktile.com/kb/p/3981464

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
su的头像su

发表回复

登录后才能评论
注册PingCode 在线客服
站长微信
站长微信
电话联系

400-800-1024

工作日9:30-21:00在线

分享本页
返回顶部