作者 | Joyce Lin
策划 | Tina
来自 Postman 的服务基础(Server Foundation)团队关于 Bifrost websocket 网关的真实故事的分享。
在漫威电影宇宙中,Bifrost 是彩虹桥的名字,它可以在神界和人界之间进行瞬间旅行。类似地,我们的 Bifrost websocket 网关也同样神奇地让 Postman 客户端即时连接到 Postman 服务。
照片由 Toni Reed 拍摄
正如我之前在 Postman 如何实现微服务中所分享的那样,所有的软件架构都是一个持续进行的工作。在实际执行中意味着需要偶尔重新评估旧的思维方式以适应新的情形,这是软件设计的自然演化。
下面是一个关于 Postman 的工程师们如何通过精简一个增长过大的服务来开发 Bifrost websocket 网关的故事。
1Postman 公司的开发团队
Postman 公司的大多数开发团队都在跨职能部门工作,专注于单一的核心领域,比如文档或版本控制。每个小队都遵循领域驱动设计的原则,为 Postman 用户开发内部微服务和产品功能。
虽然大多数工程师都是以小组形式工作,但有些工程师以功能团队形式工作,这些功能团队构建用于整个工程组织共享的组件。服务基础团队就是一个 Postman 功能团队的例子。这些工程师创造了工具,其他小组用这些工具来构建、交付和观察自己的特性。这个团队也是 AWS 专家和基础设施专家们的集中地。
服务基础团队是一个 Postman 功能团队的例子,他们创建和管理供整个工程组织使用的东西
Postman 的大多数微服务都是松耦合的,因此它们可以独立于其他团队进行发展。不幸的是,服务有时候可能会变得太大,提供一些看似毫不相关的服务。这些服务让团队能够快速迭代,但可能开始表现得更像一个臃肿的庞然大物、一个大泥团或是某种笨重的生物。
当这种情况在 Postman 出现时,来自不同团队的很多工程师们都会贡献代码,针对每个修改都需要非常仔细的跨团队协作。
2单体 Sync 服务
其中一个 Postman 服务变得过于庞大而难以有效管理,这个服务叫做 Sync。它具有令人生畏的任务,与 Postman 服务器同步您本地计算机上的 Postman 客户端的所有活动。Postman 中的每个用户操作,都会通过 websocket 连接来触发一系列的 API 调用,并遵循发布 - 订阅模式,使得这些信息可以在用户及团队之间实时流动。
例如,当你登录到 Postman 并更新一个集合时会发生以下情况:
你向 Postman 集合添加一个参数
Postman 将这条更新的记录和 Profile 信息保存在版本控制中
实时地在集合视图展示最新信息
Sync 服务最初希望用于处理数据库事务,比如更新集合。然而,去年的这个时候 Sync 还管理了一些额外的活动,比如给所有集合订阅者通知和显示最新版本。
3承压的 Sync
当你在制造汽车时,车架是所有其他模块依附的主要支撑结构。车架上的一个小裂缝可能看起来不是什么大问题。在低速行驶时,它很可能不被注意到。然而在速度比较高时,会产生连锁反应导致不协调升级。这个看似不起眼的裂缝将使得振动在车辆的其它部分放大,直到把它烧成残骸。
“较小系统中被忽视的东西到了复杂系统将变得难以承受。”—— Postman 公司的工程经理 Kunal Nagpal
Sync 是 Postman 公司最早的服务之一,它的单体架构使得团队可以快速发布 Postman 功能。随着时间的推移,它开始承担越来越多的责任。时至今日,Sync 服务仍然对整个工程团队有着广泛的影响,许多工程师在 Sync 出现非预期行为或者定期性地宕机时都会感到异常痛苦。
2019 年,Sync 同时处理 websocket 连接和数据库事务。随着我们的 1100 万用户之间产生的协作越来越多,Postman 在高峰期有接近一百万并发连接。
作为 Postman 实际上所有微型服务的基础,Sync 的压力越来越大。
反向压力带来的级联故障:Sync 的每次部署都会导致通过 websockets 连接的 Postman 客户端断开。当百万个套接字重新连接时,服务器资源就会被消耗从而导致更多的连接断开,造成可预见但无法避免的浪涌,需要 6 到 8 个小时才能恢复。
影响用户体验:虽然这种情况不经常发生,断开连接意味着在查看团队工作区最新更新和活动时会有偶尔的延迟。
更高的维护成本:由于每个小组都依赖于 Sync,实际上每个 Postman 的工程师都必须学习如何处理连接掉线,启动新连接,然后解决数据中的所有冲突。
基础服务团队知道他们需要提高 websocket 连接的效率,并且还需要将它们与 Sync 服务分离。虽然目标明确,但达成目标的路径却不清晰。
“这是软件设计的自然演化过程。微服务开始很灵活,但会逐渐长大,它们需要被打破。为了能够引入更多功能,我们希望将套接字处理与 Sync 分离开来。” —— Yashish Dua,Postman 公司的软件工程师
有些内部服务过于庞大,需要团队之间仔细协作
4事情经过
第一步: 我们得到组织的支持
第一个需要解决的挑战不是技术问题。这在 Postman 不是首次。工程师们已经从过去的尝试中学会了如何从人开始。从 2019 年 10 月开始,服务基础团队的工程师们进行了一系列评审,旨在向更广泛的组织传达这一目标,并解释对所有依赖的服务带来的好处。
如果这个新系统成功了,处理掉线和后续影响就不再是经常的事情了。这是其他工程团队支持和迁移到新系统的真正动机。这种公开的沟通和协调将在整个项目期间持续下去。
第二步:我们识别未知的盲区
尽管工程部门知道他们的前进方向,他们还是花了一些时间来梳理所有的场景并更好的理解基本概念。工程师们安排了与其他利益相关方的研究会议以确定未知的盲区,这些不可预见的情况可能暴露更大的风险。
即便 Postman 团队擅于进行研究和规划,但由于这些改变的关键性,这个过程花费的时间比正常情况要久得多。他们研究了不同的选择,考虑了辅助需求,在两个月的时间里作出了一个计划。
第三步:我们构建了 Bifrost websocket 网关
Bifrost 由两部分组成:
公共网关:网关使用 Fastify web 框架和 Amazon AWS ElastiCache for Redis 作为中央消息代理来管理所有的 websocket 连接。
私有 API:API 还使用 Fastify 作为一个低开销的 web 框架来代理其他内部 Postman 服务。
Bifrost 由两部分组成: 一个公共网关和一个私有 API
第四步: 我们测试了新网关
当 Postman 工程师准备交付一个特性时,他们需要测试它以及所有相关特性。由于几乎每个 Postman 特性都依赖于 websocket,这意味着每个特性都必须为这个发布进行测试。此外,由于 Postman 尚未设立 websockets 的自动化测试框架,因此所有测试工作在 Bifrost 投入生产前都是由人工完成的。
这是一段艰苦的旅程,但到 2020 年 1 月底,工程部已经有了一个可行的概念验证。
第五步: 我们迁移到新网关
所有 Postman 客户端,比如 Electron 应用或 Web,都依赖于对另一个名为 Godserver 的核心服务进行初始化引导调用。这个服务负责确定客户端的访问和配置,以及如何控制进行增量产品展示。因为所有这些都是由 Godserver 预先决定和控制的,所以迁移到 Bifrost 网关不需要 Postman 客户端代码单独更新。
基础服务团队总结了所有小组的迁移步骤、所需的代码更改和应用配置。在短短几个星期的过程中,依赖服务开始从依赖 Sync 转变为基于 Bifrost 的 websocket 连接进行处理。Godserver 将越来越多的流量转移到新的 websocket 网关,以便观察 Bifrost 如何处理负载和响应边界情况。
“这就像在空中更换飞机的引擎。”—— Postman 公司的工程总监 NumaanAshraf
第六步: 我们对服务进行了扩展
Bifrost 正在工作!
但在新网关规划和开发过程中,Postman 又获得了大约一百万用户。其他的工程团队在这段时间并没有停止他们自己的工作,新添加的协作特性,如版本控制和基于角色的存取控制 (RBAC),都严重依赖 websockets 来实时更新信息。大量即将发布的产品将真正考验这个新网关。
Bifrost 已经准备好支持不断增长的需求和扩展的 websocket 处理。
水平扩展: 大多数时候,Postman 服务通过扩展到更大容量的实例或者向集群添加更多计算实例,来应对使用量的增加。因此,Postman 公司的工程师通常升级 AWS EC2 实例的规格和计算能力来垂直扩展服务,例如使用 AWS Elastic Beanstalk。但是对于 Bifrost 来说,websocket 通过使用更多的机器来进行水平扩展。当大量使用较小规模的实例时,整体可以达到最佳效率。这种类型的超级水平扩展适用于 Bifrost,因为客户端不需要高网络吞吐并可以限制每台机器的连接数量,从而控制了故障的影响范围。
CPU 和内存的新负载因子: 大多数 Postman 服务可以有效地使用单一维度的缩放指标进行扩展,比如 CPU、内存或延迟。然而,对于 Bifrost 来说,情况会稍微有些微妙,因为内存和 CPU 的使用对不同吞吐量级的操作有不同的影响。为了解决这个问题,Bifrost 使用了一个基于负载因子的自定义比例指标。这个负载因子是一种多维计算结果,它提供了自定义的非线性的缩放概述。
让我们深入研究一下 Postman 工程团队所做的架构和技术决策。
5Bifrost 的架构和技术栈
Bifrost 系统有两个主要组成部分——网关和 API。这两部分架构是系统稳定性和可扩展性的秘密武器。
网关服务充当所有 websocket 连接的末端节点。尽管可以购买商业网关,但保留多年来优化积累下来的已有业务逻辑非常重要。Postman 工程师也想完全控制 websockets 的处理方式,例如他们想深入干预握手协议。对于 Bifrost 网关,他们使用了 Amazon ElastiCache For Redis,可以同时通过读节点和写节点来查询 Redis 缓存。由于将流量分割为两个节点进行读写操作,团队可以进一步优化性能。
“Bifrost 是我们所有 websocket 连接的入口。它是所有 Postman 客户端的代理,并负责处理 Postman 内部服务的底层套接字操作。”—— Mudit Mehta,Postman 公司的软件工程师
Postman 的大多数其他服务都使用 Sails 作为 Node.js 的实时 MVC 框架。然而对于 Bifrost 网关,工程师们需要一个更高性能的后端框架,能够快速处理大容量并能优化内存使用率。另外,他们还希望深入到套接字层,而不是停留在 Sails 上层的抽象和封装。因此,他们转向 Fastify,并基于 socketio-adapter 中间件,针对自己的场景进行了优化。
Bifrost 使用 AWS、Redis 和 Fastify 来处理 websockets
除了网关服务,Bifrost 的另一个组件是用来代理上行流量到其他 Postman 服务的私有 API。它基于灵活的业务规则,因此可以不断调整流量转发规则。
“简单的组件。复杂的逻辑。”—— Kunal Nagpal,Postman 公司的工程经理
对于这两个组件,工程团队决定使用他们自己的组件。尽管 Bifrost 的网关部分并不经常更新,但是团队可以完全控制 websocket 的底层处理过程。Bifrost 的 API 部分是操作的核心,它将传入的实时消息转换为标准的 HTTP 调用。它也可以作为 Sync 和 Bifrost 网关之外的独立组件,以便进行更快的更新迭代。
还记得那个秘密武器吗?通过将 Bifrost 解耦成两个分离的系统,使得两部分都能按自己的目标进行优化。
6旅程远未结束
就像所有有趣的工程学故事一样,这远远还不是终点。关于 Postman 工程团队接下来会做什么,这里我给你们留下一些悬念。
构建附加冗余:Redis 缓存是一个中央消息代理。Websocket 处理仍然依赖于有故障的单点,因此如果缓存服务器挂了,会发生什么情况呢?
增加带宽和吞吐:网关目前能够处理额外 10 倍的并发量,但 Postman 社区正在快速增长,工程师正在构建更多的协作特性,随之而来的是处理更多 websocket 流量的工程需求。
继续打破单体服务:Sync 服务的代码库里杂糅了一堆其他服务。从 Sync 服务解耦套接字处理过程,解除了和其他服务的耦合,所以现在剥离这些服务变得更加容易。
这是 Postman 工程团队如何运作的幕后观察。请关注我们以获取更多来自实战的故事。
原文链接:
https://medium.com/better-practices/how-postman-engineering-handles-a-million-concurrent-connections-15c8807f6393
推荐阅读:
最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。
明天见(。・ω・。)ノ♡