最近有很多同事问我如何构建可扩展架构,什么是可扩展架构?基于这个想法,我在职业生涯的头几年一直在审查我的代码和框架。当我们追求可扩展的功能时,您是否合并了业务?考虑商业?基于这些问题,今天我们将讨论如何构建一个灵活的系统;
在实践中,业务需求一直在变化,因此您经常会面临以下问题:
对于需求的新变化,我们一方面要快速完成,另一方面要稳扎稳打。但问题是,虽然软件被称为“软”,但不能随心所欲地改变。如果没有提前设计和调整好,往往会影响到全身,导致系统处处出现问题。
那么如何设计一个可以快速支持业务变更实施的具有良好扩展性的系统呢?
接下来,我们关注系统的可扩展性,首先要了解什么是系统,什么样的系统才能有好的扩展性。然后通过一个实际的例子,展示了如何通过架构的手段来构建一个可扩展的系统。
系统的组成:模块+关系
我们每天都在处理系统,但你有没有想过系统是什么?在我看来,系统内部有一个清晰的结构,可以简化为:系统=模块+关系。
我了解系统
在这里,模块是系统的基础部分,一般是指子系统、应用程序、服务或功能模块。关系是指模块之间的依赖关系。简单来说就是模块之间有调用。我们知道调用区分发起者和服务器。因此,依赖关系是有方向的。
这个模型很简单,但它为我们提供了深入分析系统的工具。接下来,我们将从业务扩展性出发,讨论什么样的模块容易修改,什么样的依赖容易调整。
模块
我们先来看一下模块,它们定义了系统中的基本“参与者”以及他们承担的职责。从业务角度来看,每个模块都代表一个业务概念或业务领域。
模块内部由数据和业务逻辑组成,其中以数据为核心,业务逻辑围绕数据展开,对数据进行进一步处理,方便外部使用。
从可扩展性的角度来看,首先我们对模块的要求是:定位清晰,概念完整。
每个模块都要有明确的定位,模块有定位,说明我们弄清楚了它的核心职责是什么,这样大家对它的期望和理解才会一致。在实践中,我们经常争论一个函数应该放在 A 模块还是 B 模块中。表面上,各有各的道理,没有人信服。但是,如果我们对比一下模块的定位,回归到模块设计的初衷,我们往往很快就能得到答案。
定位比较抽象。在具体划分模块职责时,必须保证模块业务概念的完整性。在数据方面,模块需要覆盖相应业务领域的所有数据。比如订单模块需要覆盖所有渠道的订单,包括第三方平台的订单、自有商城的订单、线下门店的订单。数据模型和实际数据都由订单模块处理。
在功能上,模块应该包含业务领域的所有功能。比如订单模块包含了所有订单相关的功能,包括订单数据的增删改查、订单业务规则校验、订单状态和生命周期管理等。
其次,模块也应该是独立的,粒度适中。
模块的业务逻辑应该尽可能的围绕自己的内部数据进行处理。外部依赖越小,模块的封装性越好,稳定性越强,不会随着外部模块的调整而调整。
模块的粒度要保持适中,不能为了追求清晰的定位而把模块的粒度分成小块,这样会导致系统的碎片化。例如,在系统早期,我们一般将集成功能放在用户模块中,而不是单独构建集成模块。如果后续集成的概念越来越突出,所承载的业务越来越复杂,那么那个时候集成功能就可以分离出来。单独的模块。
在这里,为了帮助大家更好的理解,我举一个模块划分的反面例子。在实际工作中,很多老系统都有很大的模块,我们称之为“肿瘤”,其特点是定位模糊、职责广泛、功能包罗万象。这样,模块的可维护性就很差了。,没有人敢轻易在上面动刀。
好了,说完模块,我们继续看模块的依赖关系。
依赖关系
依赖关系定义了模块如何协同工作以完成业务流程。依赖关系本质上反映了模块的组织结构。
如果没有专门设计模块依赖关系,则依赖关系是多对多的网络结构。在具有 N 个模块的系统中,理论上存在 N×N 个依赖项。如果依赖是有方向的,这个数字就翻倍了。
因此,要简化模块的依赖,就必须简化依赖的方向,同时减少依赖的数量。
首先,我们希望模块之间的依赖是单向的,尽量避免相互调用。为什么单向更好?我们知道业务流程是有序的。如果模块依赖更直观地反映业务流程的顺序,将有助于人们理解。否则,我们会被双向依赖箭头所迷惑,模块之间的依赖关系难以传递。还原实际业务流程。
接下来,我们来看看模块的组织结构。我们知道,网络结构是一个松散的结构,节点之间存在复杂的依赖关系,一般用来表示非正式的关系,比如群体的社会关系;而层次结构是一种比较有序的结构,一般用来表示正式的关系,比如公司内部的人事关系。
模块组织结构的设计也是如此。我们应该尽力将网络结构转换为层次结构。模块的层次结构是简化模块依赖关系的有力手段。
具体做法是我们根据模块的不同定位将模块划分为不同的层次,比如上面是应用层,下面是资源层。以这种方式,一个层通过将多个模块组合在一起形成概念上更细粒度的模块。有了一层之后,我们在理解业务的时候,因为模块的定位是一样的,所以我们往往会关注这个粒度比较大的层,依赖只需要指向这一层,而不是层中的模块。这样,从人类对业务的理解来看,依赖的数量大大减少了。
另外,我们知道层间的依赖是层间自顶向下的依赖。与多对多的网络依赖相比,层依赖的方向更加清晰,尤其符合人类的理解习惯。.
举一个具体的例子,作为开发,我们都知道MVC架构。系统模块按定位分为表示层、应用层、聚合服务层和基础服务层。
我在这里发布了 MVC 层次结构图,如您所见,模块通常是一个非常清晰的层次结构。
层次清晰
既然了解了可扩展系统对模块和依赖的需求,那么我们再回到系统可扩展性的目标,做一个深入的总结。
可扩展性的本质
文章开头我们说过,因为业务一直在变化,所以需要架构设计为系统提供良好的扩展性。
这只是表象。根本原因在于,当有新需求进来时,系统并不像添加一个新功能那么简单。系统的调整会引起一系列连锁反应,大面积影响系统现有功能。在设计架构的时候,如果模块没有很好的划分,N个模块的系统复杂度是N×N(这在上一讲介绍的支付宝生成架构中有明显的体现)。如果增加一个新的模块,复杂度变为(N+1)×(N+1),系统复杂度随着函数的数量呈指数增长。当规模达到一定程度时,复杂度会失控,导致系统完全混乱。因此,为了支持系统的扩展,架构设计必须能够控制系统的复杂性。面对新的需求,必须增加系统复杂度而不是乘法,从而保证系统的调整是局部化和最小化的,
因此,业务架构可扩展性的本质是:通过构建合理的模块体系,可以有效控制系统复杂度,将业务变化带来的系统调整降到最低。
那么如何打造合理的模块体系呢?具体的架构手段是根据业务对系统进行拆分和集成:通过拆分,实现模块划分;通过集成,优化模块依赖。
接下来,我们以一家在线旅游公司为例。它拥有三个业务线:出租车、特快列车和拼车。让我们看看如何为它创建一个合理的模块系统。
构建可扩展的模块系统:模块拆分
我们首先将系统模块化。拆分有两种方式:水平拆分和垂直拆分。
水平和垂直分割
水平分割
横向拆分是指将系统从上到下划分为多个层次,将业务按照系统处理的顺序划分为若干步骤。
例如,整个网约车流程可以分为UI展示、地图搜索、运力调度、订单支付等几个环节,按照系统的处理流程来划分。
这样,我们把一个复杂的流程分解成几个相对独立的环节,分别处理,带来很多好处。
首先,UI呈现部分独立成为一个模块,实现前后端分离。我们知道前端的用户体验和界面风格会经常变化,而后端的数据和业务逻辑相对稳定。通过水平拆分,我们可以将稳定部分和不稳定部分分开,避免相互影响。
这里的后端由三个模块组成,其中地图搜索负责路线规划,容量调度负责车辆匹配,订单支付负责交易管理。
可以看出,通过横向拆分,可以更加清晰地定义各个块的职责,功能可以连贯,各个模块可以管理自己的内部复杂度。同时,模块之间是松耦合的,一个模块的修改不会影响另一个模块。例如,地图搜索模块中优先路线推荐的变化不会影响容量调度模块中的人车匹配算法。
横向分层可以很好地满足现有业务的深度拓展。当业务发生变化时,系统会在特定层进行调整,对其他层的影响有限,从而将变化限制在很小的范围内。
垂直分割
纵向拆分是指根据不同的业务线进行拆分。例如,整个旅游业务分为出租车业务、快车业务和网约车业务。业务自成一体,形成自己的业务闭环。
通过纵向拆分,将一个复杂的出行场景拆分成几个具体的场景,我们可以根据各个业务线的特点来设计系统,从而降低整个系统的复杂度。
纵向拆分可以很好地满足业务广度的扩大。比如增加一条新的业务线,就可以按照这个思路来实现系统。
一般在做业务架构的时候,我们首先考虑纵向拆分,从大方向区分不同业务,然后针对具体业务根据业务处理流程进行横向拆分。
如果同时进行纵向拆分和横向拆分,将一个大系统拆分成一个二维的模块矩阵,每个模块既属于某个业务线,也属于业务流程的某个环节。这样,各个模块的职责就很明确了。当业务发生变化时,我们可以清楚地知道变化涉及到哪些模块,然后对这些模块进行相应的调整。
为了帮助您更好地了解这两种拆分方法的好处,这里是一个构建块的示例。拆分后楼宇自控扩展模块,每个业务模块成为一个积木,然后我们通过积木来构建系统。当业务发生变化时,我们调整相应的积木。如果系统拆分合理,拆分后的模块会有很好的封装性,也就是说我们主要是调整积木的内部,而它们的外观基本不变。. 这样,相邻的区块不受影响,整个系统也不需要大的调整。因此,对系统的更改是本地的和可控的,从而确保了灵活响应更改的能力。
构建可扩展的模块系统:模块集成
系统拆解后,下一步就是模块集成工作。有两种很好的集成方法:泛化和平台化。
广义整合
泛化是指通过抽象设计,一个模块具有通用的能力,可以替换多个功能相似的模块。
回到刚才的出行平台,我们发现三个业务线都有地图搜索、运力调度、订单支付等模块。在不同的业务线之间,这些同名的模块在逻辑上高度相似,但在细节上存在差异。
那么,我们能不能把这些相似的模块抽象出来,整合成一个通用的模块呢?答案是肯定的。我们可以通过在模块界面中输入参数来识别呼叫来自的业务,是出租车、快递还是搭便车,然后在模块内部针对不同业务的差异化部分进行针对性处理线。结果可能是这个通用模块增加了5%的逻辑,却避免了95%的重复逻辑,这样在泛化集成之后,新模块以极低的成本为多条业务线提供复用。而且,当一个新的业务线进来时,这个通用模块很可能已经提供了现成的支持。
通过模块泛化,模块数量减少,模块定位更清晰,概念更完整,职责更集中。在实践中,当不同的业务线对一个功能有相似的需求时,我们经常会使用这种方法。
平台整合
平台化就是将定位相同的模块组织在一起,以群体的方式对外提供服务。对于外部系统,我们可以将这些模块视为一个整体,共同为业务场景提供全面的支持。
如下图所示,我们可以看到地图搜索、产能调度、订单支付等都是各条业务线需要的基础通用业务能力。当我们添加新的业务线时,这些基础能力仍然是密不可分的。.
抽象的叫车业务模块的分层
因此,我们可以将这些基础模块放在同一层,形成一个基础业务平台。以前是独立输出能力的离散服务,现在已经成为可以提供整体能力输出的大型业务平台。
通过搭建业务平台,一方面我们将多个业务模块打包,形成更大粒度的抽象,相当于减少了模块的数量;另一方面,作为一个平台,其定位更清晰,系统依赖更清晰;而且,如果有新的业务线进来,它可以基于业务平台快速落地。
业务平台化是模块依赖层次的特例,但偏向于基础能力。在实践中,当业务线较多、业务规则复杂时,我们往往会抽取底层业务能力进行平台化处理。
总结
好了,总结一下今天的内容。
首先,我们对系统进行建模,系统=模块+关系,这将简化您对系统的理解。在此模型的基础上,我们对模块划分和关系定义提出了具体要求,您可以在实际设计中参考。
此外,我们深入分析了可扩展性的本质。系统的可扩展性来自于内部有序的模块体系,可以低成本应对业务变化。认识到这一点将帮助您从根本上理解和关注架构的可扩展性设计。
然后,我提供了一个旅游平台的例子,帮助大家理解如何通过模块拆分和集成的方式设计一个可扩展的架构,希望大家在工作中可以灵活使用。