要搞清楚什么是可扩展架构,我们需先弄明白可扩展是什么。
可扩展定义
可扩展是一种能力。
为了更好的理解这种能力,我们将可扩展分为两个维度:伸缩性和扩展性。
1、伸缩性,核心在于通过增加或减少资源,来提升或降低系统的处理能力。
2、扩展性,核心在于系统适应变化的能力,包含可理解和可复用两个部分。
可扩展架构
从可扩展的定义中,我们可以看出,通过对系统的伸缩性和扩展性进行设计,就可以实现可扩展架构。
那么,到底什么是可扩展架构呢?
可扩展架构,是通过采用适当的技术和策略,让系统具备了伸缩性和扩展性。
随着业务迭代,一个系统会变得越来越复杂,并且会逐渐腐化。
而复杂和腐化,会带来一系列的问题,我们一起来看看常见的几个问题。
复杂系统所面临的常见问题
1、难以理解:复杂度不断增强,认知负荷高
2、变更放大:看似简单的变更,需要修改多处代码
3、性能瓶颈:系统性能差,导致用户体验差
4、交付变慢:业务变化快,导致交付效率低
如此,可扩展架构就有了用武之地。
我们可以通过可扩展架构设计,让系统变得相对简洁和清晰,从而更容易被理解和扩展。
同时,也可以解决可能遇到的性能瓶颈和快速交付等等问题。
前文有提到,可扩展架构设计,就是对伸缩性和扩展性的设计。
接下来,让我们一起来探讨,如何对伸缩性和扩展性进行设计。
为了更全面的理解伸缩性,我们将其拆分为如下两个类别。
1、单机可伸缩
2、集群可伸缩
常用策略
伸:也就是升配,升级硬件,提升单机处理能力
缩:也就是降配,降级硬件,降低单机处理能力
伸缩对象
常见的有 CPU、内存、硬盘,等等
案例
1、数据库:单机配置从 32c256g 升级至 64c512g
2、应用服务:单机配置从 4c8g 升级至 8c16g
常用策略
伸:增加机器,分担压力,突破单机性能极限。
缩:减少机器,节省资源,避免资源闲置浪费。
确保每个机器的负载,都处于相对平衡的状态。
将流量分发到多台服务器上,防止单台服务器过载。
对水平伸缩的系统而言,最佳的情况是无状态的。
因为无状态使系统更容易维护,更容易水平伸缩,更容易做负载均衡。
伸缩对象
案例
1、数据库:一主一从 升级至 一主三从
2、应用服务:10个Pod 升级至 20个Pod
3、Redis:集群版32G8节点 升级至 集群版64G16节点
一般电商网站在大促活动期间,访问量和交易量会爆发式增长,这时我们可以向集群中增加服务器,来满足此时网站的流量访问。
活动结束后,访问量和交易量又恢复到正常水平,这时我们就需要将新加入的那些服务器下线,以此来节约成本,这才是网站真正的伸缩性。
伸缩性的关键,主要体现在伸和缩这两个字上。
为了更全面的理解扩展性,我们将其拆分为如下三个类别。
1、架构可扩展
2、应用可扩展
3、代码可扩展
架构可扩展的目标是“可理解”。怎么理解架构的“可理解”呢?
架构简单清晰有序,则容易理解,架构复杂模糊无序,则不好理解。
常用策略
扩展模式
注:微服务本质上解决的是团队分工的问题。
应用可扩展的目标也是“可理解”。怎么理解应用的“可理解”呢?
应用系统间耦合低,则容易理解,应用系统间耦合高,则不好理解。
常用策略
扩展模式
代码可扩展的目标是“可复用”。怎么理解代码的“可复用”呢?
重复的代码,意味着,低内聚,高耦合。当这个内容发生变化时,这些重复的代码统统都要修改。
因此,消除重复的过程,正是一个提高系统可复用性的过程。
常用策略
扩展模式
拆分和封装,是实现扩展性的两个关键手段,其核心目标是为了降低耦合度。
如何理解拆分呢?
拆分的目标,是降低单个问题的规模和复杂度,使拆分对象相互独立,从而分而治之。
一般我们可以从 “拆分形态” 和 “拆分粒度” 这两个维度来理解。
1、拆分形态
2、拆分粒度
拆分粒度的大小,怎样才算合适呢?
目前没有统一的标准,因为它跟团队规模,技术能力,业务体量等息息相关。
为了更好的把控拆分粒度的大小,我们将对象的复杂度分为内部复杂度和外部复杂度,以此来提高可操作性。
什么是内部复杂度呢?
也就是单体复杂度,指单个对象内的复杂度。例如传统的单体系统,所有业务在一个系统里面。
如何衡量内部复杂度呢?
我们可以用参与的开发人数来衡量单个拆分对象内部的复杂度。
例如:3个人负责一个子系统/子模块是比较合理的,20个人在同一个子系统上开发,则内部复杂度过高。
为什么是3个人,不是4个,也不是2个呢?
这里我们遵循了“三个火枪手”原则。具体原因如下:
A. 系统规模:3个人负责一个系统,系统的复杂度刚好达到每个人都能全面理解整个系统,又能进行分工的程度;
B. 团队管理:3个人可以形成一个稳定的备份,即使1个人休假或者调配到其他系统,剩余2个人还可以支撑;
什么是外部复杂度呢?
也就是群体复杂度,指拆分后的多个对象之间的关系复杂度。
如何衡量外部复杂度呢?
我们可以用业务流程涉及的对象数量来衡量多个拆分对象之间的复杂度。
例如:一次用户请求需要5个子系统是比较合适的,如果需要20个子系统参与,则外部复杂度过高。
Tips:拆分粒度和拆分形态是约束关系。
3、拆分原则
在工作中,拆分粒度往往难以准确把握,我们很难一开始就做到拆分很合适,这往往需要经验的积累,但有些思想可以作为借鉴。
内部复杂度和外部复杂度是天平的两端,一方降低,另一方必然升高,关键在于平衡。
如果你把握不准,那么就先拆少一些,后面发现有问题,再继续拆分。
这有点类似于:由简入奢易,由奢入简难。
如何理解封装呢?
封装的目标,是通过隐藏实现细节,从而屏蔽复杂度,并降低模块之间的耦合度。
如何实现封装呢?
其核心思想是:预测变化,封装变化。
通俗点讲,就是在多变中找到不变,封装不变的部分,同时允许对可变的部分进行扩展。
1、预测变化
预测最大的挑战是什么?
业务的不确定性,导致一切皆有可能。因此要准确的预测变化,就变得较为困难。
作为架构师,我们总是试图去预测所有的变化,然后设计完美的方案来应对。
但如果每个点都考虑可扩展性,我们会不堪重负,架构设计也会异常庞大且最终无法落地。
因此,我们在做预测时,可以遵循如下2个预测原则,来避免过度设计。
预测原则
只预测2年内的可能变化,不要试图预测10年后的变化。
预测没有把握就不要封装,等到需要的时候重构即可。
2、封装变化
假设所有的变化都能准确预测,是否意味着可扩展性就很容易实现了呢?
也不是。因为预测变化是一回事,采取什么方案来应对变化,又是另外一个复杂的事情。
即使预测很准确,如果方案不合适,则系统扩展一样很麻烦。
应对变化
应对变化的主要有两种方式:
将“变化的部分”封装在一个 “变化层”,将“不变的部分”封装在一个独立的 “稳定层”。
在剥离变化层和稳定层时,通常会带来2个问题。
A. 如何拆分系统的变化层和稳定层
对于哪些属于变化层,哪些属于稳定层,不同的人有不同的理解,导致架构设计评审的时候可能吵翻天。
B. 需要设计变化层和稳定层之间的接口
对于稳定层来说,接口肯定是越稳定越好;对于变化层来说,从差异中找出共同点,并且还要保证当加入新的功能时,原有的接口设计不需要太大修改,这是一件很复杂的事情。
提炼出一个“抽象层”和一个“实现层” 。
抽象层是稳定的,实现层可以根据具体业务需要定制开发,当加入新的功能时,只需要增加新的实现,无须修改抽象层。这种方案典型的实践就是设计模式和规则引擎。
封装原则
开闭原则,是面向对象设计的五大原则之一。
简单来说,就是对扩展开放,对修改关闭。
拆分是为了降低单个问题的规模和复杂度,封装是为了隐藏实现细节和屏蔽复杂度,这两者最终要实现的目标是,高内聚和低耦合,从而达到可扩展的目的。
下面是进行系统可扩展性评估的一些常见方法和工具:
通过模拟真实的工作负载和用户行为,可以测试系统在不同负载下的性能表现。
性能测试可以帮助确定系统的性能瓶颈,并提出相应的优化建议。
通过模拟系统的工作负载和用户请求,可以测试系统在不同负载下的响应时间和稳定性。负载测试可以帮助确定系统在系统负载过高时,是否会出现超时或崩溃的情况。
通过测试系统的网络带宽和传输速率,可以确定系统在处理大数据量时的性能表现。带宽测试可以帮助确定系统在网络流量过大时,是否会出现延时或丢失数据的情况。
通过分析系统的资源使用情况和趋势,可以预测系统未来的负载需求,并相应的规划系统的容量。容量规划可以帮助系统管理员合理配置资源,提高系统的可扩展性。
只有通过全面的可扩展性评估,我们才能更好的设计和构建能应对未来需求的系统。
衡量一个系统的可扩展性,主要可以从如下“2个”维度来考察。
负载可扩展性是我们对分布式系统能力最主要的衡量指标,它主要关注系统随着负载增减的可伸缩性(弹性)。
主要指标:伸缩的便捷性、伸缩的耗时、性能提升幅度,等等
扩展对象:数据、服务、中间件、带宽,等等
系统实现并提供新功能的代价与便捷性。功能可扩展性往往很难量化,通常我们可以从如下几个维度来考察。
可扩展性的本质,是系统为了应对将来的需求变化,而提供的一种扩展能力。当有新的需求出现时,系统不需要或者仅需要少量修改就可以支持,无须整个系统重构或者重建。
切记,不要过度预测,不要过度封装。