来源:《持续交付2.0》,作者 乔梁
信息的快速变化是我们这个时代的特点,谁能更快地捕捉和利用信息带来的价值谁就能在竞争中占得先机,软件发布和部署的频率也因此被要求能够跟上外部业务变化的速度,这种频率已经从以往的若干个月、若干个周进化到天、小时、分甚至是秒,这就好比一辆在高速公路上行驶的汽车,我们要一边行驶还得一边不停地更新车上的零部件。由此可见,为了能够更好地应对业务发展,持续交付是必然趋势,这一目标达成需要软件架构按照“大系统小做”的原则进行设计和演进。
“大系统小做”原则
所谓“大系统小做”原则是指架构设计应该要考虑:
- 为测试而设计(design for test),在我接触过的诸多项目里,测试过程(研发自测、集成测试、方案测试)绝对是一个时间消耗大户,如果解决不了这个问题,那么快速发布和部署就是空谈。回想我们日常的场景大致是这样的:先花一些时间打包部署一个应用,然后开始测试,测试过程中发现了一个bug,分析了半天发现是一个简单的错误。这还只是一个简单的场景,如果涉及到多人、跨团队、跨职能部门,那情况复杂的程度还会翻倍。那么为何不在写代码时就把这样的错误给杜绝呢?很大原因就在于前期设计时埋下了隐患,没有考虑“可测试性”。可测试性为什么如此重要?因为我们做设计,其实就是把一个软件拆分成一个一个的小模块。如果不尽可能地保证每个小模块的正确性,而只是从最外围的系统角度去验证系统的正确性,这将会是一个非常困难的过程。
- 为部署而设计(design for deployment),部署耗时甚至需要停机已经是不可接受的缺陷,部署的耗时是客户能够实实在在体会到的,先人一步才存在调整的可能性,落后意味着挨打已经不是什么商业秘密,同样客户也不会给你停机的窗口,停机将引发海量的用户投诉和巨大的经济损失。
-
为监控而设计(design for monitor),持续交付是一个不停滚动的过程,运维过程无时无刻不在发生,需要摒弃过往那种当问题发生之后才进行处理的做法,而是采取监控的方式提前发现和消灭问题。InfoQ在“Software Architecture and Design InfoQ Trends Report—April 2021”
一文中也提到了可观测设计是一种开始得到业界认可的设计思想。
- 为扩展而设计(design for scale),可扩展性不是一个新鲜的概念,这里扩展性包含两个方面,一是支持团队成员规模的扩展;二是支持系统自身的扩展。在云时代,扩展的概念有了更多的内涵,架构上有了新的关注点:弹性边界(Elasticity Boundary)。弹性边界是云原生(Cloud Native)架构的核心概念,决定了我们是否能够充分发挥云平台的全部能力。
- 为失效而设计(design for failure),这是一种基于风险控制的架构设计,它思考的问题是“一旦部署或发布失败,如何优雅且快速地处理”,在很多产品设计中都可以看到降级处理的身影,这是一种非常典型的为失效而设计。
上述架构要求带来的变化使人意识到原本一个单体应用包打天下的方法已经有些不适用了,系统需要被拆分成更小的颗粒度,使其提升系统灵活性、可理解性并缩短开发时间,如何拆分才更合理呢?
- 组件或服务职责清晰,也是我们经常说的SRP(单一职责原则);
- 高内聚、低耦合,减少组件或服务获知不相关信息的可能,其实也是我们在设计时会提到的ISP(接口隔离原则);
- 易于构建和测试,这两点是实际生产活动最耗时的,不克服这两点难题,无法做到“持续交付”;
- 使团队成员之间的沟通协作更加顺畅,换句话说就是组织架构必须适配软件架构,这一点很关键也是常常被忽略的。康威定律指出“设计系统的组织其产生的设计等价于组织间的沟通结构”,如果我们希望持续地高效、高质量地交付有价值的软件产品,那么最终还是会把视线转向软件生产者,保证他们能获得充足的资源,信息及其流通性是其中一种重要的资源。
尽管拆分有不少好处,但也意味着构建、测试、部署与监测都需要随之建立。每一个小系统都是一个自治系统,都有完整的构建、测试、部署与监测,这样才能在获得系统拆分的好处的同时又能把控住系统拆分而带来的复杂性。
常见架构模式
基于上述的架构要求,我们有一些可以参考使用的架构模式。微核架构、微服务架构和巨石架构是三种比较常见的架构模式,它各有各的优缺点,在不同的场景下均有应用,也都能支撑持续交付。
微核架构又叫插件架构,顾名思义,主要业务功能和业务逻辑都通过插件实现,核心框架部分只包含系统启动运行的基础功能,显然这种架构具备了:
- 功能扩展通过插件搞定
- 独立地发布插件,还能独立安装与卸载
- 功能相互隔离,易于测试
- 可定制
- 可逐步地增加功能
插件化架构比较适合用于客户端软件,因此我们常用的Eclipse、IntelliJ IDEA、OSGi、Spring Plugin、SPI等都可以看作是插件化架构产品。
微服务架构是当下非常主流的一种架构模式,也是目前公司不少产品在使用的架构模式,它的主旨就是将单一应用划分为一组小的服务,服务之间采用轻量级的通信机制,服务围绕业务进行构建可以独立地部署,不难看出微服务具有:
- 扩展性好,服务与服务低耦合,可以对单一服务进行扩容;
- 易部署
- 易开发
- 易测试
很多人把微服务架构看成是万灵药,认为所有的应用都应该考虑采用这种架构,这样的想法是有问题的,微服务在提供上述的便利性的时候也会有问题,比如原子操作较困难,调用链长等。
Service Mesh(服务网格)被认为是下一代微服务架构,Service Mesh并没有给我们带来新的功能,它是用于解决其他工具已经解决过的服务网络调用、限流、熔断和监控等问题,只不过这次是在Cloud Native 的 kubernetes 环境下的实现。
巨石应用也叫单体式应用,巨石架构,也就是由单一结构体组成的软件应用,这也是我们很常见的架构模式。当它被良好地设计、实现与守护时,就利于开发和调试,部署操作也很简单,但它也通常是混乱代码、学习困难、架构僵硬的代表。
巨石架构也能做到持续交付,关键点在于是否针对代码的整个生命周期(开发、测试、部署)进行了良好的设计,否则即使是微服务这种对于持续交付天然合适的架构也会遭遇到持续交付困难的问题。
架构改造实施模式
当我们遇到了不好的架构时自然而然就需要进行改造,改造的过程有三种很形象的比喻:
- 拆迁者,就是对软件架构重新设计,然后一次性替换原有系统;
- 绞杀者,保持原有遗留系统不变,开发新功能时,逐步使遗留系统失效,最终实现替换;
- 修缮者,将遗留系统的部分功能与其余部分隔离,以新架构进行单独改善,改善同时,需保证与其他部分仍能协同工作,这种方式与绞杀者类似,但改造发生在系统内部。就像我们在住的家中进行装修那样会做很多隔离、铺垫,然后再去进行装修。
这三种模式,最难是修缮者,需要对系统有很深的理解和抽象能力;最常用的是拆迁者,简单易用,我自己就干过拆迁者的活;比较主流的是绞杀者,非常适合作为从单体系统向微服务迁移的策略。
绞杀者主张通过用新服务替换特定功能来将单体系统逐步转换为微服务,一旦新服务已经能够代替原有旧有功能,就将原有功能组件绞杀(即彻底停用)。基于绞杀者模式将单体系统拆分为微服务的时候,任何新的开发都应该作为新服务的一部分而不是单体系统,这保证了新的开发领域的代码具有较高的质量。
借助绞杀者模式从单体系统向微服务系统迁移可以大致简化为三个步骤,即转化、共存和消亡。简而言之,就是开发一个新组件(即微服务),让新组建和旧组件共存一段时间,最终消除旧组件。大致步骤如下:
- 最初,所有访问应用的流量都路由到旧版系统;
- 构建新组件后,可以对单体系统代码与新功能一起进行测试;
- 单体系统和新组件需要共同运行一段时间,这段过渡时间可能会较⻓,对新组件进行增量开发和测试后,可以完全停用旧的单体系统。
改造的过程还可以结合当下主流的架构设计方法DDD展开,我们可以先通过系统痛点分析,如代码坏味道、系统或组件的耦合,发现我们要修订的问题;再利用DDD提供的方法(统一语言、事件风暴等)对遗留系统建模;当DDD模型代码合入的时采用绞杀者模式,最后在建立架构度量进行持续地守护。这样的一套流程是许多互联网企业改造他们架构的标准做法,也是值得我们学习和借鉴的。
总结
第5章“持续交付的软件系统架构”探讨了持续交付对于软件架构的要求,即考虑可测试、易部署、可监测、可扩展以及失效处理,并且把一些系统架构拆分的原则“大系统小做”进行了强调,包括了职责清晰、高内聚低耦合、易构建与测试以及架构相匹配的组织架构,这些都是我们在设计阶段需要考虑的关键点,也是影响软件持续交付的关键点。
网友评论