经验贴 | 如何从业务实际需求出发,参与 5.9K star 的 Node.js 开源项目

语言: CN / TW / HK

theme: channing-cyan

软件正在吞噬世界,而开源正在吞噬软件

前言

开源对于软件生态的意义已经人尽皆知。如何参与开源,也成为很多“开源”新手最关注的问题。本文旨在记录作者从使用autocannon到为autocannonPull Request并被Merge的过程。从一个真实案例出发,向大家介绍:如何从业务实际需求,反哺开源生态?

本文相关的PR地址:https://github.com/mcollina/autocannon/pull/443

背景

最近团队在做服务端 SSR 框架的升级。对于升级工作来说,如何量化升级前后的性能提升数据是非常重要的部分,也是衡量我们工作成果的最有效的手段。

为此,我对市面上流行的压测方案进行了一些考察比较,autocannon从使用方式、可定制化、细粒度的结果指标等多个维度都很满足我们的需要。最重要的是还处于活跃维护状态。于是我们基于autocannon封装了一个团队内部的压测工具。

image.png

同时可以看到,autocannonREADME.md的第一句就清晰的写着:written in node。这对于前端工程师来说意味着它不是黑盒,我们可以去深入它、了解它,从而改变它。

所以接下来,我们就从发现问题、确认问题、解决问题这几个部分来拆解如何深入了解并改变这个written in node的小东西。

发现问题

用内部基于autocannon封装的工具进行几次压测之后,我发现了第一个问题:autocannon默认情况下把由于压力过大而返回的兜底页面也当做正确返回来统计了,因为此时的状态码依然是200

而从实际需求角度出发,这部分应该算作异常返回。对于我们的场景来说,正确返回的判断条件应该是 正确的状态码 + 正确的页面内容

于是我在文档中仔细查看,找到了一个对返回内容校验的参数expectBody,通过参数解释我们可以看到这个参数可以对返回内容做一次equal判断,不匹配则统计到错误数据中。这正是我们需要的!

image.png

正准备开始试验这个参数时,我发现了第二个问题:返回体是很大的一段HTML,加到配置文件中可读性很差,同时每次请求都会有一些随机的内容。也就是说每次压测请求的返回体都不是完全一致的,都会有一些差别,但它们都是正确的。

从上面这个问题来看,在我们的业务场景下,这个参数是不能用的,而文档中并没有提供其他类似的参数。

我们的压测工作陷入了短暂的僵局。

确认问题

既然发现了开源项目的问题,我们此时就需要确定一下几点: 1. 这个问题是不是只有我们的业务场景才出现,对于普遍用户来说是伪需求?如果是,请到方案一;如果不是,请看 2 2. 既然是普遍需求,那么之前有没有人提过?项目官方是否已经在安排解决?如果是,请到方案二;如果不是,请到方案三

下面三个解决方案:

  • 方案一:极度个性的需求请使用patch-package方案,在自己的项目进行修改(详情请点击链接进一步了解)
  • 方案二:普遍需求,并且官方已经安排解决。那么就去相关issue下积极回复,描述自己遇到的问题,催促官方尽快解决并发布新版
  • 方案三:普遍需求,并之前没有人提过issue,那么我来提issue去跟官方沟通

再回到我们的实际场景,对于压测返回内容的正确性验证,每个业务可能都有自己的逻辑,单纯的完全匹配肯定是不满足需要的。所以,这是一个普遍需求

接下来我们要看官方是否有安排修改,这就需要我们issue中去查找搜索关键词

image.png

通过对body关键词的过滤,我们发现相关的issue或讨论的不是我们的问题,或者在一年以前提过类似的问题但没有进一步跟进。

所以我们走到了方案三的分支:自己提issue,自己改

解决问题

maintainer沟通

来到了解决问题的步骤,我们就需要先通过提issue的方式,与项目的maintainer明确问题,确定方案。

下面附上我对于这个问题提的issue:https://github.com/mcollina/autocannon/issues/442

image.png

看得出来在我描述完问题,并提出了自己的解决方案之后,这位maintainer非常积极的及时回应并同意了我的方案。

这也是我想分享的参与开源项目的一个心得:选择还在频繁迭代,maintainer积极响应的项目进行参与,不仅提高解决问题的效率,也能增强参与开源的信心

接下来我们就需要深入代码进行修改。

修改项目代码

由于我们的目的非常明确:支持传入自定义的验证函数对responseBody进行验证

所以我们可以通过从expectBody逻辑入手的方式,在代码中增加我们的逻辑。

image.png

expectBody的验证逻辑在lib/httpClient.jsClient类的constructorparser[HTTPParser.kOnMessageComplete]函数中实现。

可以看到逻辑非常直接,存在这个参数且responseBody与其不是全等,就emit一个mismatch消息,加入错误统计中。同时我们如果想增加自定义校验逻辑,肯定也是在这个位置。

找到了逻辑实现的位置,我们还面临接下来的诸多问题:参数定义逻辑在哪里?参数校验逻辑在哪里?worker模式如何传参?...

带着这些问题,我们可以以点及面,逐步了解项目的全貌。这也是我想分享的阅读开源代码的一个心得:带着问题读代码,在解决问题的过程中,逐步拼上一个个碎片,最后了解完整的项目

通过这个思路,我们完成了这个需求的逻辑开发,过程不再赘述。

功能逻辑开发完毕。不过在提PR之前,有几个问题我们还需要注意。

单元测试

为自己的代码增加对应的单测是每个开发者都应该遵守的美德,也是开源社区的公约。完善的单测并通过也是为我们的代码增加公信力的最好方式。

代码风格

一千个人有一千个哈姆雷特。每个开源项目也都会有自己的代码风格,可能与我们平时的代码风格不太一致。

这个时候我们应该尽量遵守项目维护者选择的风格,大部分开源项目也会提供的checklint命令让维护者自查。(不符合规定风格很有可能不允许提交)

顺手优化

在本地调试的过程中发现,当自定义验证逻辑执行耗时比较长时(比如responseBody很大,并且需要类似indexOf的逻辑进行验证),开启正确性验证会影响压测结果

这是一个很明显的问题,无论如何正确性验证行为是不应该影响压测本身的。

通过排查代码我们可以得到原因,在之前的实现中发起下一次请求的逻辑阻塞在正确性验证之后,也就是说只有本次正确性验证完毕,才会发起下一个请求。所以当正确性验证逻辑执行时间长时,会导致整体压测的吞吐量的下降,导致影响压测数据。

image.png

改造的逻辑其实很简单:将发起下次请求逻辑调整到正确性验证之前,也就是onresponse的顶部。也就是说不管后面正确性验证消耗多少时间,我都会在接到上一次请求的返回的第一时间马上发起下一次请求。这样才是真实的压测结果。

具体代码改动如图:

image.png

提交 Pull Request

前面的工作完毕之后,我们就可以正式的提交PR,等待被Merge到主干上了。

下面附上我为本次功能提交的PR:https://github.com/mcollina/autocannon/pull/443

image.png

maintainer对代码进行review并通过之后,本次PR就成功收尾了,也帮助我成为了autocannoncontributor。笔芯。

总结

最后有几点经验想和大家分享一下:

  1. 勇敢的迈出第一步,从实际出发:自己业务中经常使用的开源项目可能更容易发现问题,提出改进。
  2. 选择合适的开源项目:一个积极响应的maintainer会极大的提高参与开源的积极性与热情,再次感谢autocannon的作者的鼓励与帮助。
  3. 带着问题读代码:对于复杂项目来说,由点及面,由浅入深的阅读能有效的降低阅读门槛。

总结一下,本次参与开源是从一个真实的业务场景入手,先从实际使用中发现开源项目存在的优化点,再带着问题去读代码,由点及面的了解整个项目的代码结构,从而在原有的基础上增加自己的功能,成功为开源添砖加瓦。