从两周发布上线到一周发布上线,如何做到高效稳定?

早些年开发软件,一个版本发布上线的时间周期是以“月”甚至“年”为单位计的,但是现在随着敏捷开发的推行和普及,版本上线的周期变成了“周”为单位,甚至更短。周期缩短,并不意味着要牺牲质量,而是一样会有完善的开发流程来保障质量,比如设计、开发、自动化测试和手工测试。但是当缩短开发周期的时候,可能原本运行好好的开发流程就会出问题,软件质量下降,需要去重新调整开发流程,以重新做到高效和稳定。

在这里将向您分享一下我所在团队的经历,我们从两周一个发布周期,到每周一个发布周期,都遇到了什么问题和挑战,最终如何克服和解决的。希望能对您有所帮助启发。

在两周一个版本的迭代中,我们是进行项目开发的?

开发流程

在这里先简单介绍一下,我们两周一个 Sprint 的开发流程是什么样的。整个开发流程如图所示:

两周一个版本的开发流程
两周一个版本的开发流程

Sprint 计划和部署上一版本到生产环境

每个 Sprint 开始前,会先有一个计划会议,规划好当前 Sprint 要完成的新功能和要修复的 Bug,这些任务都在 Jira 上用一条条 Ticket 记录和跟踪。

编码和分支管理

在计划好了后,紧接着是一整周的开发编码,这期间会对计划好的 Tickets 进行编码。我们是基于分支开发的,在开发一个 Ticket 之前会创建一个新的分支,开发完成后将分支代码提交 PR,代码审核通过以及自动化测试完成后合并到 git 的 master 分支。

测试

在一周的开发编码完成后,就会基于 master 的最新代码创建 tag,并发布到测试环境。QA 人员会对发布的功能进行自动化测试和手工测试,发现的 bug 会提交 ticket 进行跟踪。开发人员会对报的 bug 进行修复,修复 bug 的代码会继续合并到 master。

代码冻结

由于我们要部署的代码都在 master 上,如果 master 持续的合并代码会不太稳定,所以在 Sprint 最后两天会对 master 的代码冻结,除非重要的 bug 修复,否则新的 PR 不合并,等到下一个 Sprint 开始再统一合并。

部署生产环境

在测试结束后,我们会根据版本创建 release tag,这样根据 tag 就可以部署对应的版本到生产环境。

两周 Sprint 的优缺点

这样两周一个 Sprint 的周期在执行的时候还算比较顺利,上线的版本由于测试较为充分,所以比较稳定。但缺点就是两周才能发布一个新的版本,响应需求的速度较慢;两周一个版本,由于更新的代码较多,在上线后出现问题也不容易定位。

其实上面的开发流程还有一个问题,只是由于两周一个迭代,并没有暴露出来。这个稍后再讨论。

在一周一个版本的迭代中,我们是进行项目开发的?

正是基于两周 Sprint 的一些问题,我们决定改成一周一个 Sprint,这样就可以更高频率的发布新版本;发布的版本变更较小,有问题也能及时定位和发现。

在最开始的时候,一周一个 Sprint 的开发流程和之前两周一个 Sprint 的开发模式类似,只是把相应的开发和测试时间缩短了。如图所示:

一周一个版本的开发流程
一周一个版本的开发流程

改成一周一个 Sprint 后遇到的问题和挑战

但在变成一个周一个 Sprint 后,我们很快发现版本质量下降了,经常在上线后发现严重的问题而需要回滚或者打补丁。

于是在一次 Retro 会议(项目回顾会议)上,我们的 QA 提出了一个建议:“Avoid last minute change”。背景是我们的服务刚刚有过一次生产环境故障,故障的原因是因为开发在上线前有过一些改动,原以为改动很小,不会有问题,Code Review 没发现问题,部署到测试环境简单测试后,也没发现问题。结果部署上生产环境后,出现严重的问题。

最后一分钟的改动
最后一分钟的改动

这是个很好的建议,然而我们能避免上线前临时修改吗?避免了上线前的临时修改服务就会稳定吗?

在接下来的时间里,我们努力避免上线前的临时修改,上线前的修改需要更严格的代码审查流程,然而想完全避免上线前的临时修改是几乎不可能的,总有一些上线前才发现的问题需要修复。

那么在减少了上线前的临时修改后,我们的服务更稳定了吗?

有一点改善,但并没有完全解决,服务还是不太稳定,而且很多服务器故障并不是由于上线前的临时修改导致的!

这就说明上线前的临时修改并不是导致服务不稳定的根本原因,那么到底是什么原因导致的服务不稳定?

于是我们又尝试了一些探索和改进,比如说增加更多的自动化测试、上线后对主要功能做一些手动的检查。这些方法都有一点效果,但都属于治标不治本的措施,服务还是不算稳定。

是什么原因让服务不稳定

对于这个问题我一直没有答案,直到几个月后,又一年的“Holiday Readiness”,也就是美国的购物季,从万圣节开始一直持续到新年,商家有各种促销打折活动,民众也是各种买买买。这期间对于我们这种线上消费类网站来说,稳定性要求特别高,当机一点点时间都可能造成巨大损失。

如何保证服务的稳定呢?根据我们过去几年的血泪教训中总结出来的经验,保证服务稳定的最简单有效的措施就是:节日期间不更新,除非必要的小更新和补丁!

所以在消费季,我们会实行“Soft/Hard Moratorium”,也就是“Holiday Readiness”期间,我们不上线新功能,只做必要的补丁更新以修复一些严重的线上故障。并且上线需要有严格的审批流程。

为了应对公司的“Soft/Hard Moratorium”策略,我们组也做了一些调整:

  • 首先我们创建了一个 holiday 的 branch 分支,这个分支只修复生产环境的 Bug 或者必须的新功能更新。其他常规的开发依然放在 master 主分支。

  • 然后每次 holiday 分支的修改在部署生产环境前,都预先在测试环境测试一周左右时间,测试没问题后再部署生产环境。

这样实施下来,在“Holiday Readiness”结束的时候,我发现我们的服务异常的稳定,虽然有过数次更新,但是一次故障都没有发生。这给我很多启示,让我意识到之前服务之所以一直不稳定,有两个主要原因:

原因一:没有一个稳定的可随时发布的分支

首先,我们的版本发布是基于 master 分支发布的,新功能和 Bug 修复的 PR 都会合并到 master,这就意味着 master 分支一直是不稳定的。

在以前每两周一个 Sprint 的时候,这个问题就存在,只是当时因为测试时间更长,所以没有暴露出来。当改成每周一个 Sprint 后,这个问题就很严重了,导致了很多上线前的临时修改。

在“Holiday Readiness”期间,我们有一个稳定的 holiday 分支,这个分支只有 bug 修复,几乎没有新功能的代码,所以相对要稳定很多。

原因二:测试时间不够充分

在以前每两周一个 Sprint 的时候,我们有 3-5 天的时间测试,有大的问题问题基本上能在测试环境发现,当改成每周一个 Sprint 后,留给测试的时间只有 1-2 天,这点时间是很难充分测试的,所以很多问题要在上线后才暴露出来。

在“Holiday Readiness”期间,在每次代码修改后发布前,在测试环境都会有一周左右的测试时间,这样 Bug 就能得到充分的暴露,而不至于到生产环境才发现,而导致回滚或者补丁。

怎么改善发布流程?

既然已经发现了问题所在,怎么去改进就相对容易了。所以我针对当前流程提出了一些改进的建议。

每个 Sprint 对应一个稳定的分支

对于没有稳定分支的问题,很好解决,那就是在每次 Sprint 完成,我们都创建一个对应的 Release 分支,Release 分支创建好后,类似于我们之前在 holiday 分支做的那样,只做 Bug 修复,不增加新功能代码。

对于前面说到的上线前临时修改,如果是紧急 Bug 更新,那么放到 Release 分支,如果是其他的,则只合并到 master 分支,不会影响到 release 分支。

给测试留足时间

对于测试时间不够的问题,一个简单可行的方案就是回到两周一个 Sprint 的开发方式。但是大家已经习惯了一周一个 Sprint 的节奏,尤其是产品经理,希望新功能能尽早上线,更喜欢保持一周一个 Sprint。

那么怎样在一周一个 Sprint 的情况下,保证有充足的时间测试呢?

于是我提出了一个简单可行的方案:在“Holiday Readiness”结束后,推迟第一个 Sprint 的上线时间一周。

具体做法是:我们的第一个 Sprint 完成后,不上线生产环境,在测试环境保留一周,同时开始第二个 Sprint 的开发,等到第二个 Sprint 开发完成后,上线第一个 Sprint 的版本,第二个 Sprint 的版本发布到测试环境测试一周,同时开始第三个 Sprint 的开发。

如图所示

一周一个版本的开发流程
一周一个版本的开发流程
这就意味着我们每一个 Sprint 开发完成,有完整的一周时间进行测试和 Bug 修复,但我们还是可以每周一个版本部署生产环境。

经过这样的调整后,我们的服务马上就稳定下来了,基本上不用担心发布后会有严重的质量问题,也极少需要上线后回滚或补丁。

当然这样调整后,也带来一些小的问题:

问题一:有两个 Sprint 在并行

在当前 Sprint 开发的同时,还要对上一个 Sprint 的 Bug 进行修复。但一般 Bug 的修复都比较简单,这样并行并没有带来太多的问题,我们的开发人员对这种模式适应的很好。

问题二:多分支管理

由于每个 Sprint 都会创建一个分支,那么意味着修复一个 Bug,有可能要将修改同时合并到多个分支。

举例来说,在生产环境发现一个问题需要打补丁,那么这个 PR 要合并到生产环境版本对应的分支,还要合并到测试环境版本对应的分支,最后还要合并到 master;如果在测试环境发现的 Bug,需要同时合并到测试环境版本对应的分支和 master。

不过相对于稳定性带来的提升,这一点不便还是完全可以接受的。

问题三:开发过程不易理解

这个开发模式在我们组内部运行的很好,但是对于组外的人来说,不是很容易理解,对于他们来说,最关心的是:我的需求或者 PR 什么时候能部署到测试环境,什么时候能部署到生产环境。

于是我们开发了几个工具,可以直观的知道当前开发中的是在哪个版本,测试环境是哪个版本,生产环境是哪个版本。

版本管理工具
版本管理工具

电视投影
电视投影

配合这样的小工具,无论是组内还是组外,都可以很直观的了解当前的开发状态了。

总的来说,新的开发流程实行后,虽然存在一些小的麻烦,但是运行的很不错,服务的稳定性大幅提升。release 分支让我们有一个随时可以发布的稳定版本;一周的测试时间可以很好的保证质量;同时每周一个版本的发布频率也可以让我们及时响应需求,有问题能及时发现。

Chrome 的开发流程

无独有偶,后来我发现 Chrome 的开发流程,和我们现在的这套开发流程很类似,它是 6 周的开发流程,其中上一个版本的测试和当前版本的开发也是重叠的。

Chrome 的开发流程
Chrome 的开发流程

总结

软件工程中没有银弹,不可能有一种开发模式适用于所有的软件项目。当项目发生变化时,以前一些运行的很好的开发模式可能渐渐的不适合了,这时候就需要先找出问题产生深层次的原因,然后对症下药,探索出适合于当前项目特点的开发模式。