什么是架构?架构的发展历程 一.背景
在我们的工作中,时常伴随的架构一词,如:MVC架构、微信架构、淘宝架构…,虽然常见,但具体的指的是什么呢?
-
架构和框架是什么关系?有什么区别?
-
Linux 有架构,MySQL 有架构,JVM 也有架构,使用 Java 开发、MySQL 存储、跑在 Linux 上的业务系统也有架构,应该关注哪个架构呢?
-
微信有架构,微信的登录系统也有架构,微信的支付系统也有架构,当我们谈微信架构时,到底是在谈什么架构?
要想准确地回答这些问题,关键在于一些似是而非的概念,如:系统与子系统、模块与组件、框架与架构。
1.系统与子系统
维基百科对系统的定义为:
系统泛指由一群有关联的个体组成,根据某种规则运作,能完成个别元件不能单独完成的工作的群体。它的意思是“总体”“整体”或“联盟”。
在这段描述中可知,系统的主要关键点可以提炼为关联(个体之间是有一定的关联关系的),规则(个体之间需要按照某种骨规则运行),能力(系统能力和个体能力会有本质的区别,不是简单的 1+1 的关系)。
维基百科对子系统的定义为:
子系统也是由一群有关联的个体所组成的系统,多半会是更大系统中的一部分。
其实子系统和系统的定义没有什么差别,只是观察的角度不一样,一个系统可能会是另一个更大系统的子系统。以微信系统做分析:
-
微信本身是一个系统,包含聊天、登录、支付、朋友圈等子系统。
-
朋友圈这个系统又包括动态、评论、点赞等子系统。
-
评论这个系统可能又包括防刷子系统、审核子系统、发布子系统、存储子系统。
-
评论审核子系统不再包含业务意义上的子系统,而是包括各个模块或者组件,这些模块或者组件本身也是另外一个维度上的系统。例如,MySQL、Redis 等是存储系统,但不是业务子系统。
2.模块与组件
模块和组件是很容易让人混淆的两个名词,如:Mysql 模块只要负责数据的存储;XX项目有安全加密组件、审核组件,某某 App 的下载模块使用了第三方的组件等描述。模块和组件的定义如下:
维基百科对模块的定义为:
软件模块(Module)是一套一致而互相有紧密关连的软件组织。它分别包含了程序和数据结构两部分。现代软件开发往往利用模块作为合成的单位。模块的接口表达了由该模块提供的功能和调用它时所需的元素。模块是可能分开被编写的单位。这使它们可再用和允许人员同时协作、编写及研究不同的模块。
维基百科对组件的定义为:
软件组件定义为自包含的、可编程的、可重用的、与语言无关的软件单元,软件组件可以很容易被用于组装应用程序中。
从两者的定义来看,其实没有太大的差异,主要原因是:因为模块和组件都是一个系统的组成部分,只不过是从不同的角度进行拆分。
如果我们从逻辑的角度对系统进行拆分,得到的就是“模块”;如果我们从物理的角度对系统进行拆分,得到的就是“组件”。而划分模块的主要目的是职责分离;划分组件的主要目的是单元复用。其实组件的定义,更类似于我们生活中的“零件”一词。
3.框架与架构
框架和架构也是一个比较相似的概念,并且这两者在工作上有较强的关联关系,其定义如下:
维基百科对框架的定义为:
软件框架(Software framework)通常指的是为了实现某个业界标准或完成特定基本任务的软件组件规范,也指为了实现某个软件组件规范时,提供规范所要求之基础功能的软件产品。
从定义中可看出,对于框架一词,主要的描述为框架是组件规范(如 MVC 开发规范)、框架是提供基础功能的产品(如 Spring MVC框架、Spring 框架等,提供了一下基础功能,简易了开发)。
维基百科对架构的定义为:
软件架构指软件系统的“基础结构”,创造这些基础结构的准则,以及对这些结构的描述。
从两者定义的角度看,框架和架构的关注点是不一样的,框架关注的是规范;而架构关注的是结构,但是对于这个基础结构,并没有明确的说明是从哪个角度进行分解的,所以也就有了 IBM 的 RUP 将软件架构视图分为著名的“4+1 视图”。
4.4 + 1 视图:学生管理系统分析
在实际工作中我们却经常碰到一些似是而非的说法。例如,“我们的系统是 MVC 架构”“我们需要将 android app 重构为 MVP 架构”“我们的系统基于 SSH 框架开发”“我们是 SSH 的架构”“XX 系统是基于 Spring MVC 框架开发,标准的 MVC 架构”……
究竟什么说法是对的,什么说法是错的呢?
其实这些说法都是对的,造成这种现象的根本原因隐藏于架构的定义中,关键就是“基础结构”这个概念并没有明确说是从什么角度来分解的。
假设我们要做一个学生信息管理系统,这个系统从逻辑的角度来拆分,可以分为“登录注册模块”“个人信息模块”“个人成绩模块”;从物理的角度来拆分,可以拆分为 Nginx、Web 服务器、MySQL。
从业务逻辑的角度分解,“学生管理系统”的架构是:
从物理部署的角度分解,“学生管理系统”的架构是:
从开发规范的角度分解,“学生管理系统”可以采用标准的 MVC 框架来开发,因此架构又变成了 MVC 架构:
“4+1”视图模型
“4+1”视图模型是从5个不同的视角去描述软件的体系结构,这些视图包括:逻辑视图、进程视图、物理视图、开发视图和场景视图。
每一个视图只关心系统的一个侧面,5个视图结合在一起才能反映系统的软件体系结构的全部内容(架构)。
5.重新定义架构
参考维基百科的定义,可以将架构重新定义为:软件架构指软件系统的顶层结构。
这个定义看似比较简单,但包含的信息却很丰富,基本上把系统、子系统、模块、组件、架构等概念都串起来了。
-
首先,“系统是一群关联个体组成”,这些“个体”可以是“子系统”“模块”“组件”等;架构需要明确系统包含哪些“个体”。
-
其次,系统中的个体需要“根据某种规则”运作,架构需要明确个体运作和协作的规则。
-
最后,维基百科定义的架构用到了“基础结构”这个说法,这里改为“顶层结构”,可以更好地区分系统和子系统,避免将系统架构和子系统架构混淆在一起导致架构层次混乱。
6.架构的发展史
要想深入理解一个事物的本质,最好的方式就是去追寻这个事物出现的历史背景和推动因素。
(1)机器语言(1940 年之前)
使用二进制的形式,其主要的问题是:太难写、太难读、太难改!
(2)汇编语言(20 世纪 40 年代)
汇编语言又叫“符号语言”,用助记符代替机器指令的操作码,用地址符号(Symbol)或标号(Label)代替指令或操作数的地址。
解决了机器语言读写复杂的问题,但是本质上还是面向机器的,因此,需要去了解计算机很多底层知识(CPU、寄存器等);同时,不同 CPU 的汇编指令和结构还不一样。
(3)高级语言(20 世纪 50 年代)
称为高级语言是因为不需要程序员去关注计算机底层的知识,只需要关注自己的业务即可;同时,通过编译器的处理,能将其编译成适合不同 CPU 指令的机器语言。
(4)第一次软件危机与结构化程序设计(20 世纪 60 年代~20 世纪 70 年代)
“1963 年美国(http://en.wikipedia.org/wiki/Mariner_1)的水手一号火箭发射失败事故”,“IBM 的 System/360 的操作系统开发事故”等事故,创造了“软件危机”一词,为了解决软件危机问题,分别提出了解决方案:
-
1968、1969 提出“软件工程”方案
-
1968 年“结构化程序设计”方案
结构化程序设计的主要特点是抛弃 goto 语句,采取“自顶向下、逐步细化、模块化”的指导思想。结构化程序设计本质上还是一种面向过程的设计思想,但通过“自顶向下、逐步细化、模块化”的方法,将软件的复杂度控制在一定范围内,从而从整体上降低了软件开发的复杂度。结构化程序方法成为了 20 世纪 70 年代软件开发的潮流。
(5)第二次软件危机与面向对象(20 世纪 80 年代)
第二次软件危机的根本原因还是在于软件生产力远远跟不上硬件和业务的发展。第一次软件危机的根源在于软件的“逻辑”变得非常复杂,而第二次软件危机主要体现在软件的“扩展”变得非常复杂。结构化程序设计虽然能够解决(也许用“缓解”更合适)软件逻辑的复杂性,但是对于业务变化带来的软件扩展却无能为力,软件领域迫切希望找到新的银弹来解决软件危机,在这种背景下,面向对象的思想开始流行起来。
虽然面向对象开始也被当作解决软件危机的银弹,但事实证明,和软件工程一样,面向对象也不是银弹,而只是一种新的软件方法而已。
(6)软件架构
软件架构的出现,并不是整个行业面临了类似的问题,也不是为了解决新的软件危机。而是当时某些企业发展到一定程度后,为了解决面临的软件问题,而被提出。如:Rational 或者 Microsoft 这样的大公司。
软件架构的出现有其历史必然性。20 世纪 60 年代第一次软件危机引出了“结构化编程”,创造了“模块”概念;20 世纪 80 年代第二次软件危机引出了“面向对象编程”,创造了“对象”概念;到了 20 世纪 90 年代“软件架构”开始流行,创造了“组件”概念。我们可以看到,“模块”“对象”“组件”本质上都是对达到一定规模的软件进行拆分,差别只是在于随着软件的复杂度不断增加,拆分的粒度越来越粗,拆分的层次越来越高。
架构设计貌似是一个高大上的名词,但是如果深入思考一下,“为何要做架构设计?”或者“架构设计目的是什么?”,还有可信的答案吗?
一.目的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 亿等。
架构设计我我们平时写代码不一样,两者的差异主要体现在“不确定性”上。对于编程来说,本质上是确定的,对于同样一段代码,不管是谁写的,不管什么时候执行,执行的结果应该都是确定的;而对于架构设计来说,本质上是不确定,并没有像编程语言那样的语法来进行约束,更多的时候是面对多种可能性时进行选择。
示例:
-
是要选择业界最先进的技术,还是选择团队目前最熟悉的技术?
-
是要选 MySQL 还是 MongoDB?团队对 MySQL 很熟悉,但是 MongoDB 更加适合业务场景?
-
淘宝的电商网站架构很完善,我们新做一个电商网站,是否简单地照搬淘宝就可以了?
1.合适原则
合适优于业界领先。
在进行架构设计的同时,需要考虑自身业务,而不是一味的去参照业界顶尖的规模,如:QQ、微信、淘宝架构。真正优秀的架构都是在企业当前人力、条件、业务等各种约束下设计出来的,能够合理地将资源整合在一起并发挥出最大功效,并且能够快速落地。
2.简单原则
简单优于复杂。
软件架构设计是一门技术活,当我们进行架构设计时,会自然而然地想把架构做精美、做复杂,这样才能体现我们的技术实力,也才能够将架构做成一件艺术品。然而,“复杂”在制造领域代表先进,在建筑领域代表领先,但在软件领域,却恰恰相反,代表的是“问题”。
软件复杂度的体现,主要有以下两个方面:
-
结构的复杂性
– 组成复杂系统的组件数量更多;
– 组件之间的关系也更加复杂。
其问题主要有:
(1)组件越多,就越有可能其中某个组件出现故障,从而导致系统故障。
(2)某个组件改动,会影响关联的所有组件。
(3)定位一个复杂系统中的问题总是比简单系统更加困难。
-
逻辑的复杂性
逻辑的复杂性来源于一个组件集中了太多的功能,修改协作困难;并且,其中某些业务还可能使用了一些复杂的算法,导致难以理解、修改困难。
一个组件集中了太多功能,就会表现出一些逻辑复杂性的特征,为了解决这个问题,一般的手段是进行组件的拆分,但随着组件的细化,又会引入结构复杂性的一些特征,所以,在做结构设计的时候,需要权衡这两者。
3.演化原则
演化优于一步到位。
维基百科对“软件架构”的定义如下:
从和目的、主题、材料和结构的联系上来说,软件架构可以和建筑物的架构相比拟。
这个定义中,将建筑和软件架构做了一个比较,但是,两者之间是有一个本质区别的:对于建筑来说,永恒是主题;而对于软件来说,变化才是主题。
也就是说,软件架构的本质是:软件架构需要根据业务发展不断变化,所以,我们在做软件架构设计的时候,不要试图一步到位设计一个软件架构,期望不管业务如何变化,架构都稳如磐石。
架构设计的过程基本上可以总结为下面三个历程:
-
首先,设计出来的架构要满足当时的业务需要。
-
其次,架构要不断地在实际应用过程中迭代,保留优秀的设计,修复有缺陷的设计,改正错误的设计,去掉无用的设计,使得架构逐渐完善。-- 小重构
-
最后,当业务发生变化时,架构要扩展、重构,甚至重写;代码也许会重写,但有价值的经验、教训、逻辑、设计等(类似生物体内的基因)却可以在新架构中延续。-- 大重构
我们在做架构设计的时候,切勿贪大求全,或者盲目的照搬大公司的做法,而是要牢记软件架构的本质(软件架构需要根据业务发展不断变化)。认真分析当前业务的特点,明确业务面临的主要问题,设计合理的架构,快速落地以满足业务需要,然后在运行过程中不断完善架构,不断随着业务演化架构。