总体思路
系统可扩展性是指能够低成本、高质量地在现有系统中添加新功能和优化现有功能。
可扩展设计的核心原则是:开闭原则。对新增开放,对修改关闭。也就是说,后续有新的需求,只需要新增组件,而不需要修改现有组件或逻辑(除非实现有BUG)。
要保证一个大的业务模块的可扩展性,有效的策略是拆分和分层。
- 拆分: 将大的业务模块拆分为多个子模块,针对每个子模块进行可扩展设计;
- 分层: 自下而上进行可扩展设计 → 底层数据模型的可扩展性、存储的可扩展性、业务流程的可扩展性;代码实现层面的可扩展性。
实现系统可扩展性,可从以下方面着手:
- 选择合适的架构
- 使用微服务来分解系统
- 使用消息系统进行服务解耦
- 使用代理和负载均衡来确保服务可用性
- 使用组件和配置化来提升功能的扩展性
- 使用易用的 API 来构建功能的可集成性
- 使用柔性编程来实现代码的扩展性
- 通过CI/CD 和容器提升部署的扩展性
- 预测业务变化
实现可扩展性的基本方法:
- 模块化: 将系统分解成多个可装卸的、高内聚、低耦合的模块;
- 组件化: 将功能实现成可复用的组件,组合组件来构成模块;
- 接口化: 将功能抽象成接口,提供接口的不同实现;
- 配置化: 通过配置来定制和选择功能的实现。
选择合适的架构
如果问题大到很难解决,第一步就是进行分解,把问题拆解到能够解决的范围。
需要做两件事:
- 将完整系统分解为多个子系统,每个子系统自行选择合适的架构来保证自身可扩展性;
- 在子系统之间建立可扩展的交互方式。
微服务
目前的一种主流系统拆分是采用微服务架构。首先,将一个完整的系统拆分为几个子系统,每个子系统作为一个微服务。微服务将单体应用分解为多个具有明确领域定义的业务子域,将每个相对独立的业务子域实现成单独的微服务,微服务独立管理各自子域的问题,采用不同的架构和方案来适配自身领域的问题,最终所有微服务集成起来完成整体应用功能。实现独立自治和发展、模块化、分工协作等。
这里可以持续拆分,比如把一个微服务拆分为若干内部模块。直到这些模块在能够处理的范围内。
接下来,针对每个微服务(或者模块)的特点,采用合理的可扩展的架构设计:
- 建模存储:数据模型(元数据管理)、数据 Schema 、完整性和一致性约束定义。
- 领域驱动:DDD 设计,稳定的精炼的可持续演进的领域模型,六边形架构,聚合根,充血模型。
- 架构模式:分层、微内核+插件、PipeLine 、事件驱动、订阅-推送、Actor、AKF 等。每一种架构模式,都具备在其中添加新功能的能力和方式。
插件架构模式
要具备可扩展性,系统需要具备容纳能力:
- 容许添加相似功能的处理,比订单导出可以从API 获取业务数据,也可以从MySQL、Mongo、HBase 中获取业务数据;
- 容许添加不同功能的处理,比如 IDE 可以添加各种不同功能的插件。
要具备容纳能力,则需要设计系统的插槽。插槽的具体实现形式是插件。插件具备配置与接口。微内核和插件是广泛使用的可扩展架构模式。
消息系统
消息系统通过消息的“订阅-消费”模式,增强不同子系统(或微服务)之间的交互的可扩展性。
一个下单动作之后,有支付处理、有库存处理,有优惠券处理,或者其它业务相关的处理。如果所有流程和组件都放在一起处理,就会全部耦合在一起,任一个组件处理出问题(或者性能不佳)都可能影响整体,在进行修改时也很容易出现冲突。因此,通常会采用消息系统来解耦同一个消息的不同处理。
可阅:
PipeLine
PipeLine 是通过“输入-输出”的数据模型进行扩展。
经典例子是 web 应用中的 servlet-filter 模式。每一个 Servlet 和 Filter 都可以对 Request 和 Response 进行处理,然后传递到下一个组件。
PipeLine 的一种实现形式,就是组件编排。把系统功能所需组件进行分类、分组,然后编排起来,从而实现灵活的业务处理。通常需要建立一个 Context,所有的组件都可以对 Context 进行读写,增加或更新内容;所有的组件通过配置文件或数据库配置进行编排,最终串成完整流程。当然,Context 读写需要有一定的措施来保证安全性。
AKF
通过在三个维度提供可扩展性:
- 服务实例维度: 增加多个实例,来提供服务能力的扩展性和可用性;
- 功能维度: 通过功能和职责的划分,模块化,来提供功能上的扩展性;
- 数据维度: 通过数据的划分,分表、分库、分片、分区,提供数据容量的扩展性和性能。
代理
代理的目标是性能、路由、安全、透明、迟加载、隐藏复杂实现细节。
代理看上去似乎与扩展性无关,但实际上也起着很重要的作用。无论是正向代理还是反向代理,都增强了Web应用服务的扩展能力。
技术手段
组件和配置化
具体到技术手段,组件和配置化是实现可扩展设计的重要手段。
- 组件化。配置化的基本前提。组件需要定义良好的行为规范和接口规范。
- 流程的组件编排:将整个流程划分为若干阶段,定义每个阶段的行为和目标,将每个阶段实现为组件,然后通过编排配置将组件连接成完整的流程。
- 动态语言脚本。比如订单导出使用 Groovy 脚本配置报表字段逻辑。 脚本注意做成缓存对象,避免可能的内存泄漏。
- 选项参数。选项参数的原型是命令行参数。用户可以通过选项参数来选择策略、调节性能等。
- 规则引擎。 将业务逻辑表达为若干条规则,然后用工作流将规则集合串联起来。
组件与配置化实践可阅:
API组合的扩展性
技术可以首先体现在 API 设计与实现上。API 是技术方案与代码实现之间的桥梁。它既是设计也有代码的属性。
将系统和模块的功能通过若干清晰、易用、正交的、易组合的API公开出来,不仅能够构建灵活的外部应用,还能发掘出系统原来具备但并未提供的功能。
此外,公开的 API 能够增强系统与其它系统的集成性,避免成为一个系统孤岛。
编程实现
柔性编程
落实到编程层面,即是:
- 组件化编程:将系统功能分组、分类、模块化,提炼成可复用的组件。
- 基于接口编程:抽象对象行为,定义扩展点接口,通过接口来交互。
- 设计原则与模式:应用设计原则(SOLID, KISS)指导,使用设计模式(策略模式、组合模式、装饰器模式、迭代器、访问者、中介者、观察者模式等)实现。
- 建立代码关联:建立代码的关联关系,通过关联关系自动传递改动。
- 占位符思想:规范、识别、注册、使用。
- 持续小幅重构。
可阅:
代码技巧
实现可扩展代码的四个基本步骤:
- 识别变化;
- 抽离共性,定义接口;
- 实现子类;
- 注入子类实现,实现处理框架。
可阅: “实现可扩展代码的四步曲”
其它方面
CI/CD 和容器化部署
- 持续集成,快速迭代功能到系统中。
- 分流发布:灰度发布、蓝绿发布。小批量验证。分流系数可动态配置和生效。
- 容器化部署,提升系统部署的可伸缩性。
预测业务变化
实现扩展性的前提是能够预测业务变化。既不过度设计,也不是完全不考虑扩展。
业务变化的方向:
- 相似需求:比如来了一个检测流程 A,然后又来一个检测流程 B,B 的流程 与 A 基本相同,仅有少量差异;
- 不同场景的相似功能:画了 CPU 的利用率曲线,也要画内存的利用率曲线;
- 流程中的环节增加:比如在原有检测流程中增加一个新的检测子环节;
- 流程中的分支增加:需要针对不同条件做判断和逻辑;
- 列表扩充:功能实现中有一个列表,列表元素会不断扩充;
- 自定义需求:如果有默认设置,通常可能有自定义设置需求。
- 变化频度: 观察变化频度,现在变化频繁,将来变化通常也会频繁。