在 2018 年纽约 QCon 大会上,Michael Bryzek 讨论了如何“以正确的方式”设计微服务架构。关键要点包括:应该先为所有 API 和事件设计模式,这样就可以自动生成样板代码代码;优先使用事件流订阅,而不是直接调用 API;应该在自动化方面进行投入,例如自动化部署和依赖管理;工程团队必须专注于编写简单有效的测试,因为这样可以提高质量、简化维护任务并实现持续交付。
Flow.io 的联合创始人、董事长兼首席技术官 Bryzek(之前是 Gilt 的联合创始人)在演讲开始时分享了自己经历过的一个故事。当时他们要将一个系统内的 URL 从“foo.com/latest/bar.js”改为“foo.com/1.5.3/bar.js”,却收到反馈说“需要几周的时间”,而且团队中已经没有足够的资源可以完成这项任务。为什么如此简单的一个 URL 格式变更需要这么大的工作量?带着这个疑惑,他发现,URL 是在一个库中实现的,这个变更涉及了几百个依赖服务,其中有一些已经几年没有更新过,为了重新构建和部署这些服务,需要其他的依赖项也做出更新。
这个故事很明显可以作为“不那么伟大的架构”的一个例子,为了眼前的开发速度,却换来了未来的瘫痪。相比之下,一个伟大的架构应该有助于扩展开发团队,带来更高的质量和更高的性能,并降低成本,同时能够很自然地为未来的特性提供支持。微服务架构是一种非常流行的现代系统实现方式,但它需要正确的设计方式;工程师应该努力不要把它设计成“意大利面”系统,而是寻求“分层”的方法。
接下来,Bryzek 提出了一系列对微服务的常见误解。第一个误解是“微服务能够让团队选择符合他们任务的编程语言和框架”。而实际情况是,这可能需要付出很高的代价,为了在技术栈中支持额外的开发语言,团队规模和投入成了关键的输入。
如果我们将 Google 视为一家优秀的工程公司,他们拥有大约 20,000-30,000 名工程师,使用了至少 8 种编程语言。那么可以说,每 4000 名工程师采用了一种编程语言。
第二个误解是“代码生成是邪恶的”。而现实情况是,“定义 100%受信任的模式”对于资源和事件来说非常重要,因为这个时候生成代码对于扩展开发和维护工作来说非常有用。第三个误解,“事件日志一定是事实来源”,而现实情况是,事件是接口的关键部分,但“将服务作为资源的记录系统是没有问题的”。最后一个误解是“每个开发人员维护的服务不应该超过三个”,在实际当中,这个数字可能是错误的。Bryzek 表示,这是“自动化发挥作用”的地方,Flow.io 的开发人员每人维护大约五个服务,每周用于维护的时间不到 5%。
接下来,Bryzek 介绍了 Flow.io 的架构。他们的架构使用了基于 JSON 的面向资源的 API 模式,可以对实体和相应属性进行定义,并带有适当的元数据。模式定义文件保存在 Git DVCS 中,并启用了持续集成。他们通过一系列测试保证整套 API 的一致性,并将这些测试作为高级的 linter。这些测试的目标是让工程师相信“整套 API 是由一个人写出来的”。
工程团队使用了开源的 apibuilder.io 工具,并结合他们的测试来防止出现错误,并在 API 设计阶段验证可能发生的重大变更。apibuilder 的 CLI 工具可用于生成和更新与资源 API 和路由相关的代码。API 客户端的代码也可以自动生成,其中包括用于高保真和快速测试的模拟客户端。Bryzek 表示,在 Flow.io 的架构中,记录系统是 API 规范,代码生成可以确保团队“遵循规范”。
接下来讨论了数据库架构。有人建议每个微服务应该要有自己的数据库,并且不允许其他服务直接连接到此数据库,其他服务应该使用服务接口来访问不属于自己的数据库,服务接口由 API 和事件流组成。 Flow.io 工程团队花了很大精力创建 CLI “dev” 工具,用来管理所有的基础设施和常见的开发任务。创建数据库只需要一个命令就可以完成。所有的存储需求通过 JSON 模式来定义,虽然 JSON 模式与相关的 API 模式是相互独立的,但使用的是相同的工具链。数据库 DDL 操作和配置(例如创建表)是自动生成的,相关的应用程序服务客户端数据访问对象(DAO)代码也是如此。这样不仅标准化了对数据库的访问,而且确保了从一开始就有适当的索引。
后续的演示侧重于代码部署。创建相关的 Git 标签就会触发部署,而 master 分支上发生变更(例如拉取请求的合并)时就会自动创建这些标签,并且是“100%自动化,100%可靠”的。Bryzek 演示了 Flow.io 的持续交付管理系统“ delta ”(代码可在 GitHub 上获得),并强调了这种做法的重要性。
持续交付是微服务架构的先决条件。
微服务基础设施的定义要尽量保持简单,并使用单个 YAML 文件来定义元数据,其中包含计算实例类型、端口和操作系统的版本。系统中所有服务都会开放标准的“健康检测”端点,让所有部署、可观察性和警报工具都能够检测它们的健康状况。
在接下来的演讲中,Bryzek 讨论了事件和事件流的重要性。对于人们提出的要访问 Flow.io API 的请求,他回应说,“我们提供了一组出色的 API,但还是尽量订阅我们的事件流吧”。事件接口的关键原则包括:为所有事件定义模式;生产者可以保证至少一次交付;消费者需要实现幂等性。在 Flow.io 的架构中,端到端的单个事件延迟约为 500 毫秒,基于 PostgreSQL 实现,每个服务每天最大规模约为 10 亿个事件。
生产者将所有的操作日志记录到相应的数据表中,记录插入、更新、删除等操作。在创建事件时,相应的日志记录被放入队列,等待发布。这是实时发生的,而且是异步的,每个日志记录对应一个事件。要在服务中重播事件,只需要把日志记录重新放入队列。消费者将新事件存储在本地数据库中,为了加快删除事件,数据库被分区。在事件到达时,记录进入队列,等待处理。事件通过微批次的方式进行处理,每次消费之间的间隔默认为 250 毫秒。出现任何故障都会在本地进行记录。有关这个方案的更多信息和代码示例,请参阅 Gilt Technology 的 db-journalling 代码库。有兴趣的读者也可以看看变更数据捕获(CDC)的概念, Debezium 是这方面的一个非常好的例子。
演讲的最后一部分侧重于依赖管理和“如何保持最新状态”。保持最新状态的目标就是要定期并自动更新所有服务,以便使用最新版本的依赖项;这对于核心库能够及时用上安全补丁和错误修复程序来说至关重要。Bryzek 回顾了开场时所讲的那个故事,他说,“这应该只要几个小时,而不是数周或数月”,并且内部开发的库应该与外部开源库一样使用相同的流程。Flow.io 的工程团队每周都会将所有服务的依赖项升级到最新版本,他们使用“ flowcommerce/dependency ”工具(已开源)来跟踪依赖项。这个工具基于服务代码库开启自动升级,并创建相关的拉取请求。一旦构建通过,就会部署每个服务。
Bryzek 总结说,工程师应该首先为所有 API 和事件设计模式,并且应该优先使用事件而不是直接调用 API。工程师应该在适当且有效的自动化上做一些投入,重点关注部署、代码生成和依赖管理等任务。还应该鼓励团队编写“简单却有效”的测试,因为这样可以提高质量、简化维护工作并实现持续交付。
Michael Bryzek 的演讲幻灯片可以在 SlideShare 上找到,演示视频以及 QCon 纽约的所有演讲将在未来几个月内在 InfoQ.com 上发布。
查看英文原文: Designing Microservice Architectures the Right Way: Michael Bryzek's Lessons Learned at QCon NY
评论 1 条评论