系统可扩展性的设计与实现

发布时间 2023-07-30 08:12:01作者: 琴水玉

总体思路

系统可扩展性是指能够低成本、高质量地在现有系统中添加新功能和优化现有功能。

可扩展设计的核心原则是:开闭原则。对新增开放,对修改关闭。也就是说,后续有新的需求,只需要新增组件,而不需要修改现有组件或逻辑(除非实现有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 的利用率曲线,也要画内存的利用率曲线;
  • 流程中的环节增加:比如在原有检测流程中增加一个新的检测子环节;
  • 流程中的分支增加:需要针对不同条件做判断和逻辑;
  • 列表扩充:功能实现中有一个列表,列表元素会不断扩充;
  • 自定义需求:如果有默认设置,通常可能有自定义设置需求。
  • 变化频度: 观察变化频度,现在变化频繁,将来变化通常也会频繁。

参考文献