架构基础(二):为什么要有架构?架构解决了一些什么问题?

2023-03-29 00:00:00 架构 系统 设计 可用 复杂度

 架构设计貌似是一个高大上的名词,但是如果深入思考一下,“为何要做架构设计?”或者“架构设计目的是什么?”,还有可信的答案吗?

一.目的
1.误区
 谈到为什么需要架构这个话题,不同的人有着不同的理解,如:

因为架构很重要,所以要做架构设计
不是每个系统都要做架构设计吗?
公司流程要求系统开发过程中必须有架构设计
为了高性能、高可用、可扩展,所以要做架构设计
 这些说法都有一定的理由,但本质上都是为了架构而架构,没有结合具体的业务场景,而是将其当做一种所谓的银弹。

2.架构设计的目的
 通过上一篇文章,我们知道,架构的提出,是为了应对是为了应对软件系统复杂度(逻辑、扩展等)而提出的一个解决方案,其目主要是为了:解决软件系统复杂度带来的问题。
 那么我们在做系统设计的时候,首先就是要分析系统复杂度的来源,做到心中有数,而不是一片雾水;分析清楚复杂度后,接下来就是设计,来解决系统中的复杂度,在这个过程中,切勿贪大求全,而要有的放矢,毕竟,罗马不是一日建成的。
 进行系统复杂度分析的一些维度:性能、可扩展性、高可用、安全性、成本等。


二.解决的问题
 既然架构设计的主要问题就是分析系统得到复杂度,那么,这些复杂度主要包括哪些呢?

1.高性能
 性能的提升,一定会带来复杂度的提升吗?并不一定,如:硬件存储从纸带→磁带→磁盘→SSD,并没有显著带来系统复杂度的增加。因为新技术会逐步淘汰旧技术,这种情况下我们直接用新技术即可,不用担心系统复杂度会随之提升。只有那些并不是用来取代旧技术,而是开辟了一个全新领域的技术,才会给软件系统带来复杂度,因为软件系统在设计的时候就需要在这些技术之间进行判断选择或者组合。
 单台计算机内部为了高性能带来的复杂度

单机性能的提升:手工操作 》批处理操作系统》进程》线程
带来的复杂度:进程/线程间的通信方式(管道、消息队列、信号量、共享存储)、多核处理器架构
示例:Redis 采用的是单进程、Memcache 采用的是多线程、Nginx 可以用多进程也可以用多线程

 多台计算机集群为了高性能带来的复杂度

集群性能的提升:任务分配(使用多台机器来分担运行)、任务分解(对任务的拆分,如:多机器协调或者服务拆分)
任务分配的复杂度:需要任务分配器(Nginx、F5)、连接管理、分配算法(轮询)
任务分解的复杂度:不同任务间的通信、任务的拆分

 集群性能计算:

假设单台业务服务器每秒能够处理 5000 次业务请求,那么两台业务服务器理论上能够支撑 10000 次请求,实际上的性能一般按照 8 折计算,大约是 8000 次左右。


2.高可用
 维基百科对高可用的定义为:

系统无中断地执行其功能的能力,代表系统的可用性程度,是进行系统设计时的准则之一。

 但是,无论是单个硬件还是单个软件,都不可能做到无中断,硬件会出故障,软件会有 bug;硬件会逐渐老化,软件会越来越复杂和庞大……,所以,硬件和软件本质上无法做到“无中断”。
 各种高可用的方案,其实本质上都是一种冗余(单个机器保证不了),也就是通过添加机器的方式来实现,这和高性能的方式一样,但是,两者的目的有所不同:高性能增加机器目的在于“扩展”处理性能;高可用增加机器目的在于“冗余”处理单元。
 在高可用体系中,根据其特性,可以简单的将其分为计算高可用,和存储高可用。

 计算高可用:

特点:无论在哪台机器上进行计算,同样的算法和输入数据,产出的结果都是一样的。
复杂度:需要任务分配器(Nginx、F5)、连接管理、分配算法(双机算法:主备<冷备、温备、热备>、主主)
示例:ZooKeeper 采用的就是 1 主多备、Memcached 采用的就是全主 0 备

 存储高可用:

特点:将数据从一台机器搬到到另一台机器,需要经过线路进行传输。
复杂度:如何进行数据的备份、如何减少或者规避数据不一致

 数据在备份的过程中,有物理上的传输速度限制(光纤、带宽)、也有传输线路本身的问题(中断、拥塞、异常<错包、丢包>)。但是无论是正常情况下的传输延迟,还是异常情况下的传输中断,都会导致系统的数据在某个时间点或者时间段是不一致的,而数据的不一致又会导致业务问题;但如果完全不做冗余,系统的整体高可用又无法保证,所以存储高可用的难点不在于如何备份数据,而在于如何减少或者规避数据不一致对业务造成的影响。

 状态的决策:
 无论是计算高可用还是存储高可用,其基础都是“状态决策”,即系统需要能够判断当前的状态是正常还是异常,如果出现了异常就要采取行动来保证高可用。但是由于高可用的本质是“冗余”,而数据的冗余又依赖于数据的备份(数据的传输),所以,高可用体系中的状态决策不可能做到完全正确。创建的状态决策方式有以下几种:
 (1)独裁式

指的是存在一个独立的决策主体,来收集信息,然后进行决策。其问题在于“决策者”本身怎么去解决单点问题。

 (2)协商式

指的是两个独立的个体通过交流信息,然后根据规则进行决策,如:主备决策。其难点在于信息交换的时候出现了问题(比如主备连接中断),此时状态决策应该怎么做?

 (3)民主式

指的是多个独立的个体通过投票的方式来进行状态决策。和协商式类似,其基础都依赖于独立的个体之间交换信息。但是,民主式决策更加复杂,可以看成是对协商式的一种升级版本。这种决策方式在集群中可能会出现“脑裂”现象,其解决办法是采用【节点奇数个数 + 过半原则来解决】。但是,这种处理办法,会降低了系统整体的可用性(不是脑裂,而是节点故障)。
示例:ZooKeeper 集群在选举 leader 时就是采用这种方式。

 综合上述的分析,无论采取什么样的方案,状态决策都不可能做到任何场景下都没有问题。高可用的解决方法不是解决,而是减少或者规避,而规避某个问题的时候,一般都会引发另一个问题,只是这个问题比之前的小,高可用的设计过程本质上就是一个取舍的过程。这也就是为什么系统可用性永远只是说几个九,永远缺少那个一。


3.可扩展
 可扩展性指系统为了应对将来需求变化而提供的一种扩展能力,当有新的需求出现时,系统不需要或者仅需要少量修改就可以支持,无须整个系统重构或者重建。
 设计具备良好可扩展性的系统,有两个基本条件:正确预测变化、完美封装变化。
 正确预测变化:

根据经验,去进行一些需求的预测,但是如何把握预测的程度和提升预测结果的准确性,是一件很复杂的事情,而且没有通用的标准可以简单套上去,更多是靠自己的经验、直觉。

 完美封装变化:常用的方式主要有两种

种:将“变化”封装在一个“变化层”,将不变的部分封装在一个独立的“稳定层”:
需要去分析,哪些是不变层,哪些是稳定层?对于变化层来说,还要在有差异的多个实现方式中找出共同点,并且保证在有新功能添加的时候,接口设计不会做太大的修改。

第二种:提炼出一个“抽象层”和一个“实现层”
抽象层是稳定的,实现层可以根据具体业务需要定制开发。


4.低成本
 当我们设计“高性能”“高可用”的架构时,通用的手段都是增加更多服务器来满足“高性能”和“高可用”的要求;而低成本恰恰与此相反,我们需要减少服务器的数量才能达成低成本的目标。因此,低成本本质上是与高性能和高可用冲突的,所以低成本很多时候不会是架构设计的首要目标,而是架构设计的附加约束。
 也就是说,我们首先需要设定一个成本目标,当我们根据高性能、高可用的要求设计出方案时,评估一下方案是否能满足成本目标,如果不行,就需要重新设计架构;如果无论如何都无法设计出满足成本要求的方案,那就只能找老板调整成本目标了。
 低成本给架构设计带来的主要复杂度体现在,往往只有“创新”才能达到低成本目标。这里的“创新”既包括开创一个全新的技术领域(开创新技术),也包括引入新技术,如果没有找到能够解决自己问题的新技术,那么就真的需要自己创造新技术了。


5.安全
 从技术的角度来讲,安全可以分为两类:一类是功能上的安全,一类是架构上的安全。
 功能安全:

如常见的 XSS 攻击、CSRF 攻击、SQL 注入、Windows 漏洞、密码破解等,其本质上是因为系统实现有漏洞,黑客有了可乘之机。
从实现上来看,功能安全更多地是和具体的编码相关,与架构关系不大。并且,这类安全问题,是无法完全进行预测的,更多的是在问题出现后,针对性的处理,然后不断完善系统安全的一个过程。
功能安全其实也是一个“攻”与“防”的矛盾,只能在这种攻防大战中逐步完善,不可能在系统架构设计的时候一劳永逸地解决。

 架构安全:

在互联网时代,理论上来说只要系统部署在互联网上,全球任何地方都可以发起攻击。
防火墙:
主要运用在传统的架构安全中,如:银行,其成本较高,性能一般,并且在面对一些攻击时(DDos 攻击),其效果并不是很好。

依靠运营商或者云服务商强大的带宽和流量清洗的能力:
互联网常用的手段,很少自己来设计实现。


6.规模
 规模带来复杂度的主要原因就是“量变引起质变”,当数量超过一定的阈值后,复杂度会发生质的变化。常见的规模带来的复杂度有:

功能越来越多,导致系统复杂度指数级上升
一个系统,包含了太多的功能需求,各个功能点之间相互依赖耦合。

数据越来越多,系统复杂度发生质变
系统数据越来越多时,也会由量变带来质变。如:MySQL 单表数据超过 5000 万、10 亿等。

相关文章