QCon全球软件开发大会8折优惠倒计时,购票立减¥1760!了解详情 >>> 了解详情
写点什么

另一个关于持续集成和版本分支的故事

2013 年 12 月 13 日

经典书籍《持续交付》[1] 的作者曾就分支合并和代码演化等问题详细地讨论过滥用分支对持续集成的负面影响。而我今天要说的是这样一个故事,一个只能申请到非常有限的硬件设备的团队,他们是如何在多分支策略下实践持续集成的。

一个团队接手了一个项目,需要在开发新特性的同时维护几个发布分支。团队计划实践持续集成,但手头的硬件资源严重不足,无法满足所有分支的部署流水线同时运转。

流水线分为三个阶段,分别是:

  • commit 编译、单元测试和部分集成测试并打包
  • at 部署应用程序并运行自动化验收测试
  • uat 部署应用程序并由测试工程师执行手工验收测试

这里我们略去了性能测试阶段和发布阶段,它们一般需要额外安排硬件设备,与这个故事关系不大。流水线的每一个阶段都可能依赖于某些外部服务,比如Web容器、数据库等。为防止不必要的干扰,每个阶段通常会尽可能地使用专用的外部服务,测试工程师在 uat 阶段做手工测试时可不喜欢Web容器由于at阶段被触发而被重启。

长生命周期的分支同主干一样也需要部署流水线,也就需要更多的外部服务。如果主干的 at 阶段依赖于数据库,那么某个发布分支的 at 阶段也同样需要依赖于数据库。而通常你得为它们准备不同的数据库实例以防互相干扰。外部服务的安装和运行是需要硬件资源的,在资源拮据时,分支无疑加剧了这个问题。由于一些特殊情况,在好几个项目中我们只申请到了一台破旧的 PC server 作为团队的测试设备。但我们并不打算因此放弃持续集成,所以让我们看看能不能在螺蛳壳里做道场吧。

相对其他外部服务,数据库是一个吃硬件资源的大户。这个项目的生产环境将使用 Oracle 数据库,如果流水线的每个阶段都是用独立的 Oracle 实例,那么一个主干和两个发布分支就需要 9 个 Oracle 实例。

在流水线的某些阶段,可以使用一些替代方案,比如commit阶段,可以把持久化测试运行在embeddedhsqldb上,这样可以去除此阶段对数据库实例的需求,而且这样commit阶段的构建时间应该会有所减少 [2]。

后续 at 和 uat 的功能验收测试最好运行在与生产环境相同类型的数据库上。我们曾考虑过采用增量式的数据库开发策略 [3],那么不同阶段、不同分支的数据库模式(schema)应该是兼容的,这样就只需要一个数据库实例。不过这个技术门槛比较高,而且不同分支的流水线的测试可能由于清理和准备测试数据而互相干扰。于是我们决定利用数据库用户 [4] 来隔离不同的环境,比如主干的at环境使用用户at_trunk,而分支的at环境使用at_branch_0816

这对应用程序的代码和架构有更高的要求,由于数据库用户名变为环境相关配置了,就必须去除硬编码,例如在 SQL 语句中不能有明确的用户名

复制代码
select * from billing.t_order where .....

改为

复制代码
select * from t_order where .....

其实这种限制也有其好处,减少了团队不经意地使用数据库集成 [5](比如直接操作另一个用户中的数据)的机会,从而更谨慎地设计系统边界和集成方案。不过,若要改造使用数据库集成的遗留系统,还得曲线救国,先把其置入部署流水线的反馈循环再做改动,这时对于跨数据库用户操作可以采用数据库同名来处理,比如 Oracle 支持将另外一个用户的数据库对象映射到本用户的一个同名对象:

复制代码
-- Create the synonym
create or replace synonym t_billing_order  for billing.t_order;

记得将这个脚本参数化,可以使用 Ant 和占位符替换,在执行脚本前,替换相应环境的用户名。

Web 中间件是另一个大头,不过这里目前团队也没发现有什么特别的办法。可以考虑在部署脚本中使用参数化的 contextPath,这样在同一个 Tomcat 或是 Weblogic 中可以部署多个环境的应用程序,但是这样一来加大了配置难度,二来节约的资源有限,所以基本上还是通过一个环境一个 Tomcat 或是一个 Weblogic 的一个 Domain 来实现的。

如果应用程序还依赖消息组件,那么还应该准备流水线各阶段专用的消息中间件。不像数据库,共享消息中间件而引发的问题比较隐蔽。有一次,我们正在为一个应用程序做发布前的回归测试,此前都没有发现问题,正当我们认为高枕无忧时,这个环节的测试却由于应用程序无法收到消息而失败。当时距离发布截止期的时间紧迫,我们被迫暂时放弃了异步消息的方案,改用同步处理。后来,我们发现问题的原因在于多个 Consumer 监听同一个消息队列,而一个意料之外的 Consumer“抢走”了我们的消息。

如你所见,我们只有一台 server 模式的 ActiveMQ,而手工测试环境和试生产环境都监听其中同一个消息队列,手工测试环境的应用程序消费了试生产环境的应用程序发出的消息。于是我们另外搭建了一台 server 模式的 ActiveMQ,让它们分别监听,问题就解决了。这件事让我意识到,作为一个有状态的组件,消息中间件也是环境敏感的。当然,一个好消息是消息中间件需要的硬件资源一般没有数据库那么多(多安装一个 ActiveMQ 和多安装一个 Oracle 实例相比),所以最简单的做法就是再装一个。但如果你的环境比较多(从而导致 ActiveMQ 比较多)时,也是很头疼的。一是由于消息是需要持久化的,所以每个 ActiveMQ 是需要一些自定义的配置的。如果你使用数据库作为消息存储,还得让不同的 ActiveMQ 依赖于不同的数据库实例,或至少是不同的用户,如果是文件存储,那么需要指定不同的目录,总之需要细心的配置。二是团队成员有可能被这些 broker 地址搞的晕头转向。有一段时间,我经常听到这样的对话:

John: 现在测试环境 8 月 16 号的发布分支用的是哪个 MQ?(看来这家伙被指派了一个缺陷修复 )

Jane: 我看看……(一小会之后),是 tcp:// 192.168.1.13:61617

过了一会……

Andy: 现在测试环境 8 月 28 号的发布分支用的是哪个 MQ?

Jane: 我看看……(一小会之后),是 tcp:// 192.168.1.14:61617

事实上,有时不同的开发人员会向配置管理员询问同一个发布分支的 broker 地址。由于它们不容易与对应的环境联系起来而且多少还是需要占用一些硬件资源的,于是我们决定裁剪 ActiveMQ 实例。

首先改造的是集成测试。我们原来为流水线的 commit 阶段准备了一台 Server mode 的 ActiveMQ,但其实在集成测试中,我们更关心的是集成的代码和相关的配置。这时可以使用 embedded mode 的 ActiveMQ 替代,这样它们就变为了环境不敏感了,而且由于不需要持久化,每次集成测试都可以运行在一个“干净”的环境中。

复制代码
runtime.properties:
runtime.messaging.broker.url=vm://localhost:61616?broker.persistent=false

不过 embedded mode 不能解决所有问题,当流水线晋级到自动部署后的自动化验收测试或手工验收测试时,还是 server mode 的 ActiveMQ 在排查问题时更方便。我们发现,应用程序并不是对整个消息中间件依赖,而是对消息中间件中的某个队列依赖,而不同的队列之间是不需要相互通信的,所以其实我们只需要一台 server mode 的 ActiveMQ,让不同的环境依赖于不同的队列就可以了。这就要求不同环境使用不同的队列名字,但是队列的名字一般是通过代码中硬编码 /properties 文件配置或是通过 JNDI 查找的,我们也不想因此增加配置负担。因此使用了使用环境名 + 固定队列名的队列名字拼接办法,比如:

复制代码
notificationReceivedQueue  --> commit.notificationReceivedQueue  --> uat.notificationReceivedQueue  --> pre.notificationReceivedQueue  --> pro.notificationReceivedQueue

于是我们引入了一个 Configurations 类,它有点类似于一个 Facade,提供易用的接口:

复制代码
public class Configurations {
private String jmsEnvironment = "commit";
public String getNotificationReceivedQueueName() {
return jmsEnvrironmentSpecified("notificationReceivedQueue");
}
private String jmsEnvrironmentSpecified(String destinationName) {
return jmsEnvironment + "." + destinationName;
}
}

而配置中,可以使用 spring el 来注入队列名称

复制代码
xml
queue-name="#{configurations.getNotificationReceivedQueueName() }"

这样,开发人员可以只访问一个 broker,而通过队列名称来查找他的目标,uat0816.notificationReceivedQueue 的可读性可要好多了。

实践持续交付是一个长期的渐进式的过程,往往会遇到各种个性化的问题。有的组织硬件资源不足,有的组织硬件资源充足,但还不能做到自动化环境管理。于是许多准备尝试部署流水线的团队会在流水线环境准备上遇到困难,希望这个故事能对这样的团队有帮助。有时候,外部环境或自身资源的限制也不完全是件坏事,这使我们停下细细思考,到底流水线的方案还有哪些可以改进的余地,办法总比困难多。

[1] 《持续交付——发布可靠软件的系统方法》by Jez Humble & David Farley

[2] 虽然 hsqldb 提供了与 oracle 不错的兼容性,但团队需要留意一些细节上的差别。

[3] 即不修改数据库,只增加新的数据库对象或是在现有对象上新增字段等,一般情况下应用程序比较容易做到对此的兼容性

[4] 据我所知创建一个新的用户所消耗的硬件资源很少,主要是其本身数据的存储空间,如果团队对测试数据进行管理的话,这个测试数据集应该不会太大。

[5] 轻易的使用数据库集成很容易抵消你所做的解耦努力。各个模块和子系统表面上看来还不错,但当数据结构改动时牵一发而动全身。

作者简介

周宇刚( @黛丽被我抢了)是一位乐于磨练技艺的开发者。他的研究和兴趣包括 IT 架构、领域驱动设计和敏捷实践。

2013 年 12 月 13 日 00:172603

评论

发布
暂无评论
发现更多内容

智慧社区建设,平安社区解决方案服务商

135深圳3055源中瑞8032

Elasticsearch 基于脚本进行 partial update

escray

elastic 七日更 28天写作 死磕Elasticsearch 60天通过Elastic认证考试

30天消化MyBatis源码解析笔记,吊打面试官,offer接到手软

Java架构之路

Java 程序员 架构 面试 编程语言

区块链+数字版权-区块链技术如何保护版权

135深圳3055源中瑞8032

数据库恢复子系统的常见技术和方案对比(二)

星环科技

数据库 大数据

《程序员修炼之道》- 务实的哲学(3)

石云升

读书笔记 28天写作 批判性思维 程序员修炼之道 完成好过完美

拍乐云 Flutter SDK 全新发布,跨平台音视频开发更easy

拍乐云Pano

flutter 音视频 WebRTC RTC

“区块链新闻编辑部”: 从“云媒体”到“链媒体”的现实跨越

CECBC区块链专委会

区块链技术

【推荐收藏!】Gradle 与 Android 构建入门

百度Geek说

研发工具 andiod

这是阿里技术专家对 SRE 和稳定性保障的理解

阿里巴巴云原生

项目管理 运维 云原生 安全 监控

三张图解释静态NAT、动态NAT、PAT

抽奖助手利益相关方

千竹

新思科技发布《美国不良软件质量成本:2020年报告》

InfoQ_434670063458

软件质量 新思科技

数据库恢复子系统的常见技术和方案对比(一)

星环科技

数据库 大数据

10 个 JavaScript 简洁代码小技巧(文末彩蛋)

零和幺

JavaScript 前端 CleanCode

Libra演进与数字货币国际化

CECBC区块链专委会

区块链

Flink可靠性的基石-checkpoint机制详细解析

五分钟学大数据

大数据 flink

程序员入职新公司,只需8步,直接凸显出个人价值

Java架构师迁哥

区区一个SpringBoot问题就干趴下了?我却凭着这套“神级PDF文档”吊打面试官

云流

Java 编程 面试 微服务

阿里巴巴内部MySQL宝典意外流出!极致经典,堪称数据库的天花板

Crud的程序员

MySQL 数据库

读阿里P8大佬15W字的Spring文档,面试犹如开了挂,成了Offer收割机

Crud的程序员

spring

一个 3 年 Java 程序员 5 家大厂的面试总结(已拿Offer)

Java架构之路

Java 程序员 架构 面试 编程语言

5年Java经验不会源码被拒,苦学这些Spring源码笔记后,面试不再慌

Java架构之路

Java 程序员 架构 面试 编程语言

教你用Java字节码做点有趣的事

比伯

Java 编程 架构 程序人生 计算机

“区块链+电子证照”-助推数字政府建设

135深圳3055源中瑞8032

产品训练营--第二期作业

曦语

产品训练营

智能汽车为什么新势力有胜算(28天写作 Day20/28)

mtfelix

28天写作 新能源汽车 智能汽车 造车新势力

开发质量提升系列:问题登记列表(下)

罗小龙

生产事故 28天写作 解决思路

Flink 学习路线总结

大数据学习指南

大数据 flink

内存数据库解析与主流产品对比(三)

星环科技

数据库 大数据

阿里四年技术 TL 的得失总结:如何做好技术 Team Leader

阿里巴巴云原生

云计算 项目管理 程序员 微服务 云原生

移动应用开发的下一站

移动应用开发的下一站

另一个关于持续集成和版本分支的故事-InfoQ