整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

从 25 分钟到 7 分钟,我们用了这些方法提升 R

从 25 分钟到 7 分钟,我们用了这些方法提升 Rails CI 的效率

近,我们在 Gusto 创下了一个新纪录:6 分 29 秒。

这是我们为 Gusto 最大的一个应用程序,一个 Rails 单体程序运行测试套件所花费的时间。6 分 29 秒是公司的持续集成(CI)流水线启用以来的最快纪录。上一次 CI 套件跑出这样的成绩,公司的规模还很小,而现在我们共有全球数百名工程师在使用这个 Rails 单体应用,为全美 1% 的小型企业提供支持。

对 Gusto 而言,高速 CI 流水线并不只是做做样子,我们把它视为一种竞争优势,代码部署越快,那么客户的业务开展也会越快。随着 CI 速度的提升,工程师的生产率也在提高,CI 时间每缩短一分钟,Gusto 每位工程师每周可增加 2%的拉取请求。

我们的目标很简单,希望让测试套件的速度成为一个参数的函数,这个参数就是:我们愿意花多少钱?将基础架构简化到这个层面后,就更容易做成本效益分析,例如如果想要将构建速度从 7 分钟提升到 5 分钟,那么需要花费 1 美元。

这篇文章介绍了我们是怎样加快测试套件速度的,其中涉及一个 Rails 单体程序和一个主要用 React 编写的 JavaScript 单页应用程序(SPA),这些经验适用于所有速度较慢的测试套件。

我的同事 Kent 说,构建软件有 3 个步骤:

让它跑起来(Make it work)

让它走上正轨(Make it right)

让它跑得更快(Make it fast)

“让它跑起来”指的是做出不会随便崩溃的软件。在这一步代码可能晦涩难懂,但足以为客户提供价值,并且通过了测试,让我们能信任它。没有测试,就很难判断“它能行吗?”

“让它走上正轨”指的是要让代码可维护,且易于更改。代码不仅能在计算机上运行,更要让人容易理解。新来的工程师可以轻松向代码添加功能,代码中的缺陷也应该很容易隔离和纠正。

“让它跑得更快”指的是要提升软件性能。为什么它会是最后一步呢?对于像 Gusto 这样的金融科技公司来说,如果只关注速度却无视质量,那么我们的客户和我们自己就离破产不远了。并非每段代码都需要优异的性能,如果一段代码每天可能只执行一次,那么就算它有 " 高性能 " 水平,却难以阅读和理解,那也是一段失败的代码。

我们把这套原则应用在 CI 套件的提速优化过程中。

让它跑起来

消除不可靠测试

首先需要做的事情是消除测试套件中的不可靠测试(test flakes)。不可靠测试(flaky test)指的是结果不确定的测试,它有时会通过,有时会失败。速度飞快但不可靠的测试套件并不能让你确信代码可以正常运行,这只是在抛硬币赌运气而已。

为了让一个规模庞大的工程团队消除不可靠测试,我们采用并执行了以下政策:

在 master 分支上所有失败的测试都将视为不可靠的。这些测试将标记为已跳过(skipped)。负责不可靠测试的团队可以在空闲时修复它们并取消跳过标记。

这个做法不仅能让测试套件一直亮绿灯,同时也让各个团队决定何时编写更多确定性测试。他们可以立刻开始编写,也可以选择等到再次处理这个功能时再行动。这种方法减少了一个团队的不确定测试给其他团队带来的损害。

当然,这种方法也存在质疑,“如果我们跳过了一项重要的测试该怎么办?”是最常见的问题。没错,这个问题很重要,但我们需要搞清楚问题的背景。一个测试之所以会被标记为已跳过,是因为它会随机失败,首先要考虑的是我们对这个测试和功能到底有多大的信心。很多时候,测试会出现不可靠情况是因为生产环境中的确存在错误!

通过这种方式,我们在主分支上的构建绿灯率从约 75%增至 98%!

让它走上正轨

回到默认状态

随着时间的流逝,我们逐渐偏离了运行 RSpec 测试的默认路径。遵守默认值是很难的。下面是 RSpec 测试的一些默认值:

  • 在各个测试用例之间重置状态。这样可以确保测试是可重复的、确定性的,并且不会相互依赖。
  • 测试执行是随机的。这样可以确保测试之间不存在相互依赖,帮助避免测试污染。
  • 测试文件使用 Rails 自动加载器。这意味着我们仅加载应用程序所需的部分,而不是程序整体,可以帮助避免不完整的测试设置。

重新采用这些默认值的过程并不轻松。确保每个测试用例都重置其状态(数据库、Redis 值、缓存等),都会带来新的不可靠测试。根据其性质,我们可以修复更改或将之前正常的测试标记为不可靠。

我们慢慢重新引入了 RSpec 默认值,这为测试提速奠定了基础。

让它跑得更快

引入测试时间上限

我们的测试是不平衡的。有些测试文件只需几毫秒就能执行完毕,还有些则需要花费数十分钟时间。耗时几分钟的测试是集成测试,涉及我们应用程序中最重要的一些流程。我们希望这些测试的速度能更快,但并不想移除它们。

因为测试套件是分布在多个节点上并行执行的,所以很快就遇到了测试提速的瓶颈。

我们的测试套件速度取决于最慢的测试文件,因此实施了一项新政策:

任何测试文件的执行时间都不能超过 2 分钟。

这个门槛是凭空拉出来的,但似乎很实用。我们只有 40 多个耗时超过 2 分钟的文件。

确定界限之后,我们开始处理速度缓慢的测试,试图让它们通过新的门槛,之前 40 个文件的时间都降到了阈值以下。之后,每个团队都有责任确保其测试文件的执行时间不超过 2 分钟,而执行时间超过 2 分钟的测试文件会被标记为已跳过。

根据最坏情况来平衡测试

现在我们有了一个可靠的测试套件,只是速度很慢,它可以按任何顺序执行测试,但是将测试分配给节点的方法是随机的。有些节点只需几秒钟就完成了,而另一些节点则需要数十分钟。我们怎样才能让它们平衡呢?

我们面临的最后一个问题是测试平衡。我们在这一步评估了两种解决方案:

  1. 开发一个队列,以在节点准备就绪后为其输入测试用例。虽然这种方案原理上没问题,但 RSpec 需要对框架做大幅更新才能兼容这种方案。此外,它在所有各不相同的并行作业之间引入了共享状态。
  2. 在一次 CI 流程开始时在一个数据库中记录测试时间,将测试分为不同的桶,让所有分组都有相同的长度。

我们采用了记录与分桶的方法将测试分配到各个节点上,因为它非常适合 knapsack 。测试运行期间,这种方法也不会在许多不同的并行作业之间共享状态。这是很重要的,因为一个共享队列可能有数百个节点,每个节点每秒为一个构建可以请求数千次工作。

我们建立了一个 MySQL 实例来记录所有文件的测试时间。在每次 CI 流程开始时,它会根据每个测试文件的第 99 个百分位时间生成一个 knapsack 文件。在每次 CI 流程结束时,它将上传新的结果。

为什么是第 99 个百分位?由于我们在共享硬件(AWS)上运行 CI,因此无法控制基础架构,各个测试文件的测试时间会大相径庭。我们无法将这些波动与使用的 EC2 实例类型,或者其他任何可以衡量的参数关联起来。

我们没有进一步完善构建基础架构,而是让系统具备了弹性。我们使用第 99 个百分位来组织测试,从而保证了测试的性能表现有一个下限,而不是在获得较好的平均性能时却存在明显偏低的个例。即便底层硬件发生变化或基础架构层出现故障,CI 管道依旧能保障可预期的性能水平。

这套策略实施之后,我们就有了一个自平衡的系统。测试越多,系统也就越平衡。如果某些测试随着时间的推移变慢,则测试桶也会随之调整平衡状态。

提升并行度

现在到了有意思的地方:让测试速度真的变快。

这里的主要做法是增加并行度。项目开始以来,我们已经从 40 个并行作业增加到了 130 个。这稍稍增加了成本,但大幅提升了 CI 的运行速度。在 Gusto,我们使用 Buildkite 作为 CI 基础架构,但这种并行化的理念适用于所有主流 CI 产品。

虽然我们将并行度提高到了 3 倍以上,但 CI 费用却没有随之线性增长。为什么?因为我们更好地利用了已有的 CPU 时间,通过在各个节点之间平衡作业,总 CPU 时间并没有变化,但是实际运行时间大幅缩短了。

总结

在过去几个月中,我们在一点点让 Gusto 主要应用程序的 CI 管道变得更坚实可靠,而且速度更快。

这种改进依旧是一项日常工作。在出现不可靠测试时我们还是会跳过它们,或者寻找新的优化策略来加快构建速度。无论你们现在使用的是什么技术,我们都希望这篇文章可以为你们的团队提供一个路线图参考,帮助改进你们的 CI 管道和软件发布架构。

关注我并转发此篇文章,私信我“领取资料”,即可免费获得InfoQ价值4999元迷你书!

日分享最新,最流行的软件开发知识与最新行业趋势,希望大家能够一键三连,多多支持,跪求关注,点赞,留言。

如果您正在使用 Ruby on Rails 开发现代单页应用程序,您很可能会使用一些花哨的 JS 框架来很好地更新您的 UI,而无需重新加载页面。如果不使用它们,您将无能为力;这些天来这是一种标准......直到Rails得到了Hotwire。

使用 Hotwire,您无需编写大量 Javascript 即可获得快速且响应迅速的 Web 应用程序。嗯,听起来不错,但 Hotwire 是什么?在本文中,我们将介绍 Hotwire 的基础知识并使用它构建一个示例应用程序。

响应式 Rails 应用程序有哪些选项?
2013 年 6 月 25 日,Rails 4 发布,引入了 Turbolinks。Turbolinks 为 Rails 的“响应能力”做了什么?Turbolinks 拦截所有链接点击,而不是发送常规GET请求,而是发送异步 Javascript 请求 (AJAX) 以获取 HTML。然后它合并head获取页面的标签并替换页面的整个body标签,因此不会重新加载整个页面。无需重新加载样式表或脚本,这意味着更快的页面导航。但它仍然在替换整个body页面,而不仅仅是更改页面的某些部分。

但是,如果您只想重新加载已更改的部分怎么办?当您将某些元素标记为 时,您可以使用 Rails AJAX 助手data-remote='true',这使得这些元素发送 AJAXGET/POST请求而不是常规GET/POST请求。Rails 以生成的 JS 代码进行响应,然后由浏览器执行以动态更新页面的这些部分。

然后我们可以在前端使用一些 JS 组件(例如,使用 React),以使应用程序感觉更加响应。所以,JS 组件发送一个 AJAX 请求,Rails 服务器用 JSON 数据响应它。然后前端框架将接收到的 JSON 转换为 DOM 元素并更新 DOM 以反映这些变化。而且效果很好;唯一的缺点是它在页面上混合了服务器端渲染和客户端渲染。

如今,另一种更传统的方式是全力以赴并在前端使用 React、Vue 或其他 JS 框架来仅使用客户端渲染,即所谓的单页应用程序 (SPA)。使用这种方法,前端是一个单独的应用程序,它将 AJAX 请求发送到 Rails 服务器,而 Rails 只是一个 JSON API。您可能知道,构建、维护和部署两个具有可互换数据的独立应用程序非常复杂。

但是,如果您可以构建一个 SPA,而不需要构建两个单独的应用程序和编写大量 Javascript 代码的复杂性呢?以下是 Hotwire 可以为您提供的帮助。

什么是Hotwire?
Hotwire 是构建类似 SPA 的 Web 应用程序的另一种方法,它使用 Rails 模板呈现所有 HTML 服务器端,同时保持应用程序的快速响应。在服务器端保持渲染使您的开发体验更简单、更高效。Hotwire 名称基本上是“HTML Over the Wire”的缩写,意思是从服务器向客户端发送生成的 HTML 而不是 JSON。它也不需要您编写太多自定义 javascript 代码。Hotwire 由 Turbo 和 Stimulus 组成。

什么是Turbo gem?
Turbo gem 是 Hotwire 的核心。它是一组动态更新页面的技术,通过将页面划分为可以利用 WebSockets 作为传输进行部分更新的组件来加速导航和表单提交。如果您曾经在 Rails 中使用过 WebSockets,那么您很可能知道 Rails 使用 ActionCable 来处理 WebSockets 连接,并且它默认包含在 Rails 中。Turbo gem 由 Turbo Drive、Turbo Frames 和 Turbo Streams 组成。

Turbo Drive
Turbo Drive 用于拦截链接点击(就像 Turbolinks 之前所做的那样)并拦截表单提交。Turbo Drive 然后合并页面的 head 标签并替换页面的 body 标签。在与 Turbolinks 相同的情况下,不会重新加载整个页面,这对于某些页面可能会很快,但响应速度不如 2022 应用程序预期的那样,因此您可能会考虑仅更新页面的某些部分,而不是整个页面. 这就是 Turboframes 派上用场的地方。

Turbo Frame
您可以通过简单地将页面的一部分包装在turbo-frame具有唯一 id 的标签中来简单地使页面的一部分成为 Turbo Frame。

<turbo-frame id="13">


在众多WEB框架中,国外大型科技公司广泛使用的两个主要框架是 Ruby on Rails 和 Django。 Django 是一种 Python 编程语言框架,许多企业在其应用程序和网站项目开发中使用它,例如 Dropbox、YouTube、Instagram 和 Spotify等,Ruby on Rails,它是一种 Ruby 编程语言框架,已经在包括 Shopify、Bloomberg、Airbnb 和其他领先公司在内的研发产品中使用。本文将对 Django 与 Ruby on Rails 将进行对比。

Django

介绍

Django是一个开放源代码的Web应用框架,由Python写成。采用了MVT的软件设计模式,即模型Model,视图View和模板Template。它最初是被开发来用于管理劳伦斯出版集团旗下的一些以新闻内容为主的网站的。并于2005年7月在BSD许可证下发布。这套框架是以比利时的吉普赛爵士吉他手Django Reinhardt来命名的。Django的主要目标是使得开发复杂的、数据库驱动的网站变得简单。Django注重组件的重用性和“可插拔性”,敏捷开发和DRY法则(Don't Repeat Yourself)。在Django中Python被普遍使用,甚至包括配置文件和数据模型。

特点

Django的优点

  • 功能完善、要素齐全:自带大量常用工具和框架(比如分页,auth,权限管理), 适合快速开发企业级网站。
  • 完善的文档:经过十多年的发展和完善,Django有广泛的实践案例和完善的在线文档。开发者遇到问题时可以搜索在线文档寻求解决方案。
  • 强大的数据库访问组件:Django的Model层自带数据库ORM组件,使得开发者无须学习SQL语言即可对数据库进行操作。
  • Django先进的App设计理念: App是可插拔的,是不可多得的思想。不需要了,可以直接删除,对系统整体影响不大。
  • 自带后台管理系统admin:只需要通过简单的几行配置和代码就可以实现一个完整的后台数据管理控制平台。
  • Django debug信息详尽: 很容易找出代码的错误所在。

Django的缺点

  • 大包大揽: 对于一些轻量级应用不需要的功能模块Django也包括了,不如Flask轻便。
  • 过度封装: 很多类和方法都封装了,直接使用比较简单,但改动起来就比较困难。
  • 性能劣势: 与C, C++性能上相比,Django性能偏低,当然这是python的锅,其它python框架在流量上来后会有同样问题。
  • 模板问题: django的模板实现了代码和样式完全分离,不允许模板里出现python代码,灵活度对某些程序员来说可能不够

架构

Django已经成为web开发者的首选框架,是一个遵循 MVC 设计模式的框架。MVC是Model、View、Controller三个单词的简写,分别代表模型、视图、控制器。Django其实也是一个MTV 的设计模式。MTV是Model、Template、View三个单词的简写,分别代表模型、模版、视图 。但是在Django中,控制器接受用户输入的部分由框架自行处理,所以 Django 里更关注的是模型(Model)、模板(Template)和视图(Views),称为 MTV模式。它们各自的职责如下:

层次

职责

模型(Model),即数据存取层

处理与数据相关的所有事务: 如何存取、如何验证有效性、包含哪些行为以及数据之间的关系等。

模板(Template),即表现层

处理与表现相关的决定: 如何在页面或其他类型文档中进行显示。

视图(View),即业务逻辑层

存取模型及调取恰当模板的相关逻辑。模型与模板的桥梁。



Rails

介绍

Ruby on Rails (RoR) 是一个用 Ruby 写就的web开发框架,并且Ruby“famous”也经常被认为是归功于它调了约定大于配置和测试这两个方面。Rails 的约定大于配置(CoC)意味着几乎没有配置文件, 只有实现约定好的目录结构和命名规则。它的每一处都藏着很多小魔法: 自动引入, 自动向视图层传递控制器实体,一大堆诸如模板名称这样的东西都是框架能自动推断出来的. 这也就意味着开发者只需要去指定应用程序中没有约定的部分, 结果就是干净简短的代码了

特点

RoR的优点

  • 易于处理小型项目
  • 有着丰富的第三方 gem(gem 是一个可以复用的打包好的 Ruby 应用程序或者类库) 或插件,Ruby on Rails 具有基于 gems 和插件的组件结构,这使得有经验的开发人员可以使用更少的代码快速开发高效的应用程序。该框架提供了易于使用且文档齐全的插件。不断有新的 gem 被添加到存储库中,例如 RubyGems 资源有很多这样的 gem
  • 社区庞大有大量的教程和指南

RoR的缺点

  • 代码可能难以阅读,由于开发人员可以通过多种方式编写相同的结果,这使得代码难以阅读
  • 难于创建API,由于其复杂性,使用 RoR 构建 API 也很困难


架构

Rails的 MVC 架构

Ruby on Rails的模型-视图-控制器架构由以下各部分组成:

  • 模型:模型包含着应用的状态,状态可能是临时的也可能是长久性保存在数据库中的。需要注意的是模型不仅包含数据,而且包含数据代表的逻辑。在 Rails 中,模型通常是由一些代表关系数据库中RDBMS表的类组成的。在RoR中,模型类是通过Active Record模式进行处理的。一般来说,程序员要做的是继承ActiveRecord 类,同时程序会自动计算出要使用哪个RDBMS表,这个表有哪些列。表与表之间的关系通过简单的命令来指明。
  • 控制器:控制器将用户界面和数据模型关联起来,并充当协调运作的角色。它接收各种用户操作,更新数据模型,并用合适的view展示结果给用户。象他的名字一样,可以说应用的主要控制中心就是各个控制器。
  • 视图:View 负责根据 Model 中的数据显示用户界面。作为 web 应用,Rails 里的View通常是生成整个或者部分网页。当然可以是XML或者甚至是JavaScript代码。表现为使用内嵌Ruby的 HTML/XML/JavaScript 模板,

Rails 的组成模块

  • 模型:Active Record。ActiveRecord实现了Rails的对象关系映射。
  • 控制器:ActionPack 。ActionController 是Rails中的控制器,提供各种方法供用户操作使用。
  • 视图:ActionView 是Rails中的视图,负责展现用户界面。
  • 常用实用工具:ActiveSupport。ActiveSupport包提供一些工具和支持代码。
  • 处理邮件:ActionMailer 。ActionMailer 用来发送和接收 email。  Web服务:ActionWebService


总结

RoR 和 Django 是很好的 Web 开发框架,框架之间的主要区别在于语言方法。它们都可以提供干净、模块化的代码,并有助于减少在编码上花费的时间。这些框架在建模域、应用程序数据的呈现、用户交互等方面遵循相同的 MVC 原则。选择 RoR 还是选择 Django 取决于业务目标和对象。 Django 具有可扩展性且更易于理解。它有许多功能可以帮助简化您的开发过程。