本文要点
- BuzzFeed 近日从一个单体 Perl 应用程序迁移到了大约 500 个使用 Python 和 Go 编写的微服务。
- 起初,他们的路由逻辑是在 CDN 中实现的,但事实证明,这很难测试和维护。他们后来改成使用 NGINX 路由。
- 网站路由服务被设计成在 BuzzFeed 自己的 Rig 平台上运行,这便于他们部署到他们运行的每一个 Amazon 弹性容器服务环境(ECS)(测试、过渡、生产)中的每个服务上。
- BuzzFeed 构建了一个抽象层,让他们可以使用 YAML 配置路由服务。
2016 年,BuzzFeed 启动了一个重构项目,把使用 Perl 编写的一个单体应用程序转换成一组微服务。他们之所以这样做,主要是因为 Perl 应用程序被证明很难扩展,使 buzzfeed.com 独自服务于每月大约 70 亿次页面访问,而且,组织还要向大约 30 个不同的社交平台发布内容,包括不同的语言版本。
除了扩展问题外,BuzzFeed 发现,寻找懂 Perl 并且希望使用它开发的工程师越来越难。最后,他们希望自己能够在有了新产品想法时可以更快速地迭代。
从一个很高的层面上来说,新架构包含一个 CDN,指向一个路由服务,该服务位于 AWS 上,使用了 ECS 容器化服务:
新的微服务是以Python 作为主要语言开发的,对于性能比较敏感的组件则使用了Go。BuzzFeed 的工程团队发现,这两种语言可以很好地互补,对于开发人员而言,根据需要从一种切换到另一种也相对简单。
在本文写作时,他们在AWS 上的过渡环境和生产环境中已经有大约500 个微服务。他们在分解服务时使用了听上去和 SCS 类似的东西;buzzfeed.com 的首页是一个服务,新页面是单独的服务,作者页面也是,诸如此类。
团队面临的一项挑战是把请求路由到正确的后台应用程序。他们的 CDN 提供商 Fastly 具有在边缘通过编程定义行为逻辑的能力,使用的是一种基于 C 的编程语言 VCL,工程团队最初就是直接使用 VCL 编写他们所有的路由逻辑。
不过,他们发现,随着配置越来越复杂,修改变得越来越困难,充分测试这些配置的能力愈发重要。BuzzFeed 资深软件工程师 Mark McDonnell 告诉 InfoQ:
我们在 CDN 中有大量的逻辑,只有少数几名工程师真正了解它是如何工作的。另外,CDN 还被有意锁定,因为缓存层对我们而言特别重要,我们需要防止工程师意外修改,那可能会对我们的缓存策略产生不利影响,或者会导致进入的请求被路由到错误的源服务器。
此外,Fastly 还有自己的 Varnish HTTP 加速器实现。BuzzFeed 发现,这让工程师很难启用一个本地开发环境,并通过它运行他们的 VCL 逻辑,因此,VCL 代码测试就成了问题。为此,工程团队决定实现一个新的路由服务来代替 VCL 路由,把大部分路由逻辑从 CDN 提取到一个单独的路由器微服务中。
网站路由服务被设计成在 BuzzFeed 自己的 Rig 平台上运行,这便于他们部署到他们运行的每一个 Amazon 弹性容器服务环境(ECS)(测试、过渡、生产)中的每个服务上。
对于反向代理,他们考虑了包括 HAProxy 在内的多个选项,但最终决定使用 NGINX。McDonnell 说,“我们选择 NGINX ,因为我们平台基础设施团队中的大部分人都有使用经验,所以,我们得到了 SRE 的支持,我们其中的一个卖点是,它很容易提升速度”。如你所想,向系统添加 NGINX 确实会给单个请求带来额外的延迟,但按照 McDonnell 的说法,这可以“忽略不计”。
虽然把简单易用和熟悉度作为选择 NGINX 的其中两个原因,但 BuzzFeed 还是构建了一个配置该服务的抽象层。该服务是用 Python 编写的,它会从 YAML 动态生成所需的 nginx.conf 文件。它广泛使用了 YAML 特有的特性,如“锚和别名(Anchors and Aliases)”和“合并键(Merge Key)”,据 BuzzFeed 技术博客介绍,“这让我们可以跨环境块重用配置,而且很容覆盖特定的键,不需要重复大量的配置工作。我们主要把这种方法用于‘上游’和‘超时’区域,因为我们通常会希望覆盖那些特定于环境的值。”
起初,路由服务是使用 NGINX 的开源版本构建的,但后来转到了商业版本 NGINX Plus 。按照 McDonnell 的说法,可以获得付费支持是一个原因,但团队也需要只有付费版本中才有的某些特性,尤其是 DNS 监控;BuzzFeed 的服务器区域相当复杂,那样,当 AWS 负载均衡改变 IP 地址时,NGINX Plus 能够保证 DNS 条目也更新。 BuzzFeed 技术博客上有一篇博文是这样解释的:
在 NGINX 启动的时候,它会为我们在配置文件中定义的下游解析 DNS。也就是说,它会把主机名转换成 IP(或者一组 IP,和使用 AWS Elastic 负载均衡器一样)。然后,NGINX 会缓存这些 IP,我们的问题在这里开始出现了。
你可能不熟悉负载均衡器,它们会检查多个服务器,看看它们是否健康,如果不健康,服务器会被关闭,并有一个新实例启动。我们发现的问题是,每个服务器实例的 IP 都在不断变化。如前所述,NGINX 在启动时会缓存 IP,而因为 ELB[Elastic 负载均衡器] 的原因,新进来的请求会被代理到不再有效的缓存 IP。
一种解决方案是热重载 NGINX 配置。那是个手动过程,我们不知道什么时候该那样做,因为我们无法知道服务器实例何时会停止服务。
NGINX 文档中有几个设法变通解决这个问题的选项;不过,除了一个选项外,其他所有选项都意味着我们需要重新设计我们的整个架构,因为它们不支持 NGINX 的 upstream 指令(作为配置抽象的一部分,我们对它的依赖度很高)。
允许我们利用接口设计的那个解决方案只有 NGINX Plus 提供。采用商业版本的成本是合理的:我们不需要从头重写我们的整个服务,后续我们也可以利用额外的特性(其中有一部分我们已经考虑过)。一旦切换,该解决方案只需要增加一行 resolver 指令,为缓存的 DNS 解析结果定义一个 TTL。
VCL 逻辑用于帮助 BuzzFeed 在他们推出新服务时根据用户的地理位置分类用户,那样,他们就可以从一个类似新西兰这样的小国家开始。McDonnell 解释说:
对于任何 geoip.country_code 是 NZ 的用户,他们会设置一个 X-BF-SiteRouter 头。然后,在 VCL 代码中,如果 X-BF-SiteRouter 头已经设置(取决于请求哪个路由),我们会把后端切换到 Site Router。
一旦我们满意,我们就会把服务区域扩展到 AU、GB、OC、EU、SA(南美),最终,我们会把所有东西都指向 Site Router(实际上,北美是最后一个区域)。
我们还使用了 Vary 头保证来自不同区域、还没准备好访问 Site Router 的用户不会意外从中获得缓存的响应。
迁移到新架构已经在团队生产率方面为我们带来了益处,McDonnell 告诉我们:
工程师可以根据自己的意愿自由启动服务,我们每天部署很多次。周遭有各种流行的术语,如持续集成、持续交付和持续部署,它们只有稍微的不同。最终,我们有了一个非常棒的交付管道,但是,我们特有的平台实现无法自动把变更推送到生产环境,目前我们还得手动选择,那对于我们的应用场景而言是合理的(例如,并不是我们构建的所有东西都必须自动存在于生产环境中)。
BuzzFeed 有一个富 A/B 系统,用于测试单个的“特性”,规模较大的基于 A/B 测试的上线在他们的方法中反而变得比较简单(综合运用 VCL 逻辑和 Site Router 特有的“override”配置来处理请求路由)。
新基础设施监控(以及日志聚合和分析)是使用DataDog 实现的。他们还在评估 Honeycomb.io 。
关于作者
Charles Humble 于 2014 年 3 月成为 InfoQ.com 编辑团队负责人,指导我们的内容创作,包括新闻、文章、著作、视频演讲和采访。在承担 InfoQ 的这个全职角色之前,Charles 负责我们的 Java 报道,并且是薪酬研究公司 PRPi Consulting 的 CTO,该公司 2012 年 7 月被 PwC 收购。他已经从事企业级软件工作将近 20 年,做过开发,当过架构师和开发经理。在业余时间,他从事音乐创作,是伦敦氛围科技舞曲团 Twofish 的三名成员之一。
查看英文原文: How BuzzFeed Migrated from a Perl Monolith to Go and Python Microservices
评论