Skip to content

分布式系统架构:提升可修改性

About 4059 wordsAbout 14 min

架构新书

2026-07-08

软件系统总是被要求有很好的的可修改性,以适应业务需求变化或则和其他架构质量的提升。同时降低修改而付出的人力成本和软硬件成本。 这些成本分摊到软件开发过程中,包括需求分析,设计,开发,测试以及上线中。 当我们说到系统有较高可修改性,意味着是系统容易修改,且上线风险低,人力成本付出少。

架构对可修改性的影响:系统的修改成本与其修改影响范围相关,对软件工程早期的设计进行修改,影响范围广,有很高的成本,比如对架构的修改会有非常高的修改成本。

  • 数据架构调整:随着流量增大,把数据存储系统从传统的数据库改成NOSQL作为查询缓存,数据库作为事务库结合方案
  • 软件架构调整:随着流量增大,软件架构从单体系统编程微服务。
  • 开发架构调整:更换开发框架,或者更换开发语言。
  • 部署架构调整:替换数据库等基础设施,数据库即使支持标准SQL以及使用JDBC,这种替换也不是没有成本的。需要开发和测试人员进行全量测试。

系统的早期设计对可修改性影响:设计调整时候较高的修改成本的需求

  • 替换系统依赖的中间件,即使中间件准寻共同的标准,比如应用服务的JavaEE。无缝替换也是不存在的。有的中间件支持较多的功能、松散的规范校验。 就很难无缝替换到较少功能的中间件上或者严格校验规范的应用服务器上。
  • 一个无状态的系统,为了性能,修改成有状态的系统。 或者相反,为了高可用,一个有状态的系统修改成无状态系统。

业务迭代对可修改性影响:最常见的业务功能性开发,其修改成本也可能超出预期,这源于对影响范围的估计不足"

  • 对修改范围影响评估不足,需要找到影响所有相关方。比如基于消息平台的系统,需要找到所有的订阅方的系统。这些系统属于不同地区的不同团队,需要找出这些订阅系统以及团队,沟通其影响范围并制定各自的修改后的上线计划。这种沟通占用的成本远超过修改系统自身的成本
  • 对于老系统不熟悉:老系统存在十几年,经历过几个团队的维护。对老系统的简单修改,比如理论上几行的代码修改,需要较长的评估时间和较长的测试时间

软件工程执行对可修改性影响:软件工程执行的不顺利也可能导致修改成本增加,超出预期

  • 修改的功能,无法得到测试人员及时测试。 比如物联网网关调整完毕,但设备还未寄送到测试人员那里。导致程序员和测试人员在空转,项目上线延期。
  • 修改一个简单功,没有有良好的单元测试,只能部署到验证环境下开发验证。但验证环境是多个团队同时使用,在快速迭代的情况下,你修改代码依赖的其他服务,可能正在被别的团队使用,且使用的新版本服务。你不得不空转一天,待他们使用完毕,然后期望服务拥有者尽快部署为原来的服务。下图展示了具备单元测试最快1天即可提交测试,不具备最慢需要3-6天提测。

系统的生存周期有几十年,每个月都要为客户调整系统功能。软件系统从业者最早就在研究如何提升系统的可修改性以实现高效效果和降低修改带来的系统风险。上世纪60年代就提出了分层架构,70年代,高内聚和低耦合设计原则就被提出来,随后还有更多的设计原则、设计模式、架构模式等提出来,提高软件的可修改性。 现代软件系统使用的各种中间件,如代理服务器,消息平台等, 这些都是降低系统修改影响范围,增强可修改性。软件工程也一直在增强系统的可修改性,如CI/CD. 容器化部署等。下面思维导图总结了提升系统可修改的办法。

思维导图每一个节点都意味着有提升系统可修改性的最佳实践,下表总结了每个节点的含义,本节会详细描述职责分配模式和架构的可修改性,不会详细描述所有实践,其他可修改性技术实现在第三篇描述。

概念描述
职责分配职责正确分配是一个设计软件系统的最基础原则,职责分配模式包含一系列模式,如创建者模式,专家模式,中介,虚构等模式。职责分配贯穿软件工程:需求分析,架构和设计,以及系统实现。
可观测性分布式系统可观测性,通过日志,指标和链路跟踪,确保系统的修改上线后,运行状态符合预期。比如电商系统性能优化运费查询服务后,响应时间的的TP95在线上确实降低
业务架构业务架构定义了业务流程,业务对象,如果流程调整或者业务对象职责变化,影响范围巨大,带来修改成本很高。业务架构目标是业务独享。业务架构的主要目标是定义业务对象和其职责。
应用架构应用架构划分了功能模块,这些功能表现为微服务或者Java对象,应用架构最首要的任务是分配正确的职责给架构的各个模块,或者虚构出模块实现以实现业务架构
数据模型数据模型来源于业务架构,表现为系统的概念模型,物理模型。数据模型调整,影响范围较大,且具备一定风险。数据模型最重要的部分就是设计出数据库实体,赋予属性和关联关系。职责分配创建者,专家,中介模式有助于设计数据库实体
软件架构软件架构风格的主要一个目标就是降低修改范围,提升系统可修改性,如分层架构,仅需要改动需要调整的那一层。
OOD面向对象设计主要目标就是高内聚和低耦合,以降低修改范围和风险。 GOF设计模式主要实现了高内聚和低耦合,职责分配模式是设计模式的基础模式。
DDD领域模型驱动设计,本质上还是对业务对象的职责分配。
参数化系统通过参数配置系统,当系统功需要调整的时候,修改参数即可。
服务查找无需直接持有其他服务接口,而是在调用时候通过服务查找获得最新的服务实现。广泛用于分布式服务调用
Java技术Java提供了较多的技术提升可修改性,包括SPI,反射调用,AOP等,前者类似服务中查找,通过名字(或者类名,方法名)查找调用类,AOP则类似中介者模式
DSLDSL,领域特定语言 (Domain-Specific Language),不需要编译部署,嵌入到Java中,能在线修改和生效。用于工作流配置,规则脚本等,在Java系统中嵌入DSL,以实现经常变动的业务功能。
单元测试单元测试能早期发现错误以降低修改带来的风险,单元测试和Cucumber结合起来使得单元测试也能充当一个项目可运行的,好理解的功能文档,这对于打算运维数十年的项目来说是非常有益,它提升可系统可修改性。单元测试在分布式系统中,需要结合Mock框架和TestContainer工具来实现
易读代码代码运行一次,阅读多次。编写易读代码有利于项目维护。实现易读代码有多条建议,包括“发现对象”,“不要使用String代表对象”,“精简注释”,“用日志和单元测试解释项目”等等策略
热部署当部署一个拥有上千个节点的大型项目时候,重启系统时候,系统对依赖的外部资源初始化,如数据库,Zookeeper,下游服务等,会造成系统本生以及其他系统瞬间的大量访问。通常应对这种流量激增采用滚动发布,或者使用热部署机制。有状态节点会尽量避免重启而丢失状态,热部署机制有ClassLoader,Class Redefine,以及DecVM技术
可扩展协议分布式系统交互协议,还是物联网的设备协议,都有可能随着需求变化而发生变化。需要设计一个支持扩展,并能原有系统保持兼容的协议。比如HTTP 协议的的设计的Header就适合扩展,而MQTT不支持HEADER,扩展较为困难。TLV格式的分布式协议比LV协议更适合扩展。像Dubbo这样分布式框架,应该选择一个支持向后兼容的序列化协议
底层平台Java系统运行在JVM上,Java系统部署在应用服务器上,这些底层平台都具备支持对已经部署的系统进行热修改。比如JVM 支持Class Redefine能 动态修改方法的实现字节码。商业应用服务器通常支持在不影响用户使用情况下部署新的系统。
部署除了修改的影响范围外,部署导致的成本也能影响可修改性。当部署一个系统需要滚动俩周时候,这种修改成本就很高。容器化,CI/CD, 灰度都有利于降低部署成本。当使用灰度部署的时候,需要系统以及各个基础设施,如Kafka,数据库,Redis等支持灰度发布功能。
无服务架构在无服务架构中,业务功能被拆分成一组函数。函数可以独立部署到云端。无服务架构缩短了开发和上线周期。无服务架构还具备高可用,高伸缩等特性

GRASP(General Responsibility Assignment Software Patterns,通用职责分配软件模式)是面向对象设计基本原则,用于指导如何分配对象的职责和设计类之间的关系。此原则也可以用于需求分析,系统架构。这些原则由 Craig Larman 在其著作《Applying UML and Patterns》中提出。主要包含如下

模式说明
信息专家分析和设计任何系统,都需要识别系统的信息专家,以及它拥有什么样的信息。比如以现实学校为例子,教导主任,总务主任各司其职,分别是主管教学,主管后勤,因此在校庆活动餐饮(分配职责),是总务处主任负责而不教导主任。 以IT物联网为例子,所有系统都需要查询设备是否在线,以及在哪个网关在线。此时哪个专家提供这个信息? 最直观的就是设备连接的网关。在编写易读代码一节中,也提到过“发现对象”,这就是信息专家模式设计过程中的体现
创建者模式创建者模式解决了对象由谁创建,对象的生命周期交给谁来管理的问题。比如电商中,支付对象(服务)是通过订单对象(服务)创建,这是因为订单包含了支付所需要的信息,且初始化了支付必要的信息。
纯虚构当分析或者设计系统时候,找不到信息专家时候,意味着需要虚构出一个专家。现实社会中,经常为了解决某种事情而成立的联合调查组就是虚构的一种表现。信息专家中介绍的设备是否在线,设备连接网关并不适合成为信息专家,这是因为网络不稳定,设备可能会连接多个网关。有的设备以最后连接为主。而有的设备(如蓝牙设备)以他连接所带的子设备数最多的网关为准。因此需要虚构一个服务,记录设备所有连接以及设备类型。当其他系统查询设备是否在线时候,需要查询这个虚构对象(服务)。
中介低耦合常用的实现方式,通过引入中介对象来解耦两个服务,或类之间的直接依赖。各种架构模式和设计模式都会采用中介,比如架构中的引用消息平台的事件驱动架构,设计模式中的MVC,工厂模式等
受变化的保护通过增加接口、抽象类,或者微服务来封装变化点,隔离系统其他部分免受变化影响。比如JavaEE标准就是定义了各种企业应用所需接口,以JDBC为例子,定义了一套标准接口来访问各种数据库,一些NOSQL数据库也支持JDBC接口。

注意,上述表格中GRASP的模式能覆盖业务分析,架构和设计过程,GRASP的模式还包含了高内聚,低耦合,多太(Polymorphism),控制器等设计原则未在表格里列出。这是因为考虑到高内聚,低耦合一个设计目标,而多太和控制则在设计模式中经常提到。

衡量各个架构风格的质量属性,就包括可修改的属性。

架构风格可修改性描述
分层架构仅需要修改分层架构中的一层,不影响其他层。比如企业应用4层架构,采用Spring Data作为DAO层,如果数据库切换,或者切换成其他NOSQL。则其他层不需要改动。
管道和过滤器通过新增或者删除管道上的过滤器实现可修改性,或者重组管道。比如使用Apache Flume 收集和整理日志,通过添加过滤器,对日志添加IP地址等额外信息。
微内核微内核架构,只需要修改和重部署插件而不需要修改整个系统。
代理架构同GRASP的中介,用于同步解耦多个系统。如API网关解耦用户和服务,Nginx服务器解耦用户和Web服务器
消息驱动架构同GRASP的中介,用于异步解耦多个系统。如使用消息平台实现消息发布和订阅
SOA通过ESB以及协议适配,可以提升SAO架构各个系统的可修改性。比如XML报文格式可以通过ESB的协议适配器,转成JSON,发布给订阅者。
微服务架构微服务架构以高内聚低耦合为标准拆分系统服务。以物联网为例子,设备是否在线(以及所属网关)由专门的服务维护。当新的设备,如蓝牙设备和子设备连接物联网导致在线逻辑变化,只需修改此服务即可。

知行合一