Appearance
了解架构设计
架构设计的思维和程序设计的思维差异很大。架构设计的关键是判断和取舍,程序设计的关键是逻辑和实现。
架构指什么
“架构”这个词大家都很常见,那么:
- 准确来说架构指的是什么呢?
- 架构和框架有什么关系和不同?
为了回答这些问题,关键在于理解这几个概念:
- 系统与子系统
- 模块与组件
- 框架与架构
系统与子系统
系统
系统指由一群有关联的个体组成,根据某种规则运作,能完成个别元件不能单独完成的工作的群体。
- 关联:系统中的个体是有关联的,没有关联的个体不能成为一个系统。可以把发动机、地盘、轮胎和车架组合成一台汽车,但是发动机和手机放在一起不能称为一个系统。
- 规则:系统内个体需要按照指定的规则运作,规则规定了系统内个体的分工和协作方式。例如发动机提供动力,由传动轴把动力传到轮胎,从而驱动汽车前进。
- 单独个体不具备的能力:系统能力和个体能力有本质的区别,系统能力不是个体能力之和,而是产生了新的能力。汽车能载重前行,而发动机、轮胎本身不具备这种能力。
子系统
子系统是由一群由关联的个体所组成的系统,多半会是更大系统中的一部分
可以看出子系统和系统的定义是一样的,只是观察的角度不同,一个系统可能是另外一个更大系统的子系统。
例如,以微信来说
- 微信本身是一个系统,包含聊天、登录、支付、朋友圈等子系统
- 朋友圈这个系统,又包含动态、评论、点赞等子系统
- 评论这个系统可能又包含审核子系统、发布子系统、存储子系统
- 评论审核系统不再包含业务意义上的子系统,而是包括了各个模块或者组件,这些功能模块或者组件本身也是另一个维度上的系统。例如,MySQL、Redis 等是存储系统,但不是业务系统。
模块与组件
模块和组件在实际的工作中,很容易混淆,主要是因为它们的定义不好理解,也不好区分。
软件模块(Module)是一套一致而互相有紧密关连的软件组织。它分别包含了程序和数据结构两部分。现代软件开发往往利用模块作为合成的单位。模块的接口表达了由该模块提供的功能和调用它时所需的元素。模块是可能分开被编写的单位。这使它们可再用和允许人员同时协作、编写及研究不同的模块。
软件组件定义为自包含的、可编程的、可重用的、与语言无关的软件单元,软件组件可以很容易被用于组装应用程序中。
就算看到这里也比较难以区分 😂,根本原因是模块和组件都是系统的组成部分,只是从不同角度拆分系统而已。
- 从逻辑角度来拆分,得到的单元就是“模块”
- 从物理角度拆分,得到的单元就是“组件”
划分模块的主要目的是职责分离,划分组件的主要目的是单元复用,它“独立且可替换”。
以一个简单学生管理系统为例:
- 从逻辑上拆分,可以分为“登录注册模块”、“个人信息模块”、“个人成绩模块”
- 从物理角度拆分,可以分为 Nginx、Web 服务器、MySQL
框架与架构
这两个概念也比较相似,工作中有时候也分不清楚。
软件框架(Software framework)通常指的是为了实现某个业界标准或完成特定基本任务的软件组件规范,也指为了实现某个软件组件规范时,提供规范所要求之基础功能的软件产品。
软件架构指软件系统的“基础结构”,创造这些基础结构的准则,以及对这些结构的描述。
从上面的定义中提取一些关键的部分:
- 框架是组件规范。例如,MVC 就是一种常见的开发规范,类似的还有 MVP、MVVM、J2EE 等框架。
- 框架是提供基础功能的产品。例如,Spring MVC 是 MVC 的开发框架,除了满足 MVC 的规范,Spring 提供了很多的基础功能帮助我们实现功能,包括 Spring Security、Spring JPA 等很多基础功能
- 和架构对比起来,框架关注的是“规范”,架构关注的是“结构”。
就算是看起来它们是不同的,平台的工作工作中还是会有些似是而非的说法。例如,我们的系统是基于 Spring MVC 框架开发,标准的 MVC 架构 ...
这里的主要原因就是,架构中的“基础结构”并没有明确以什么角度来分解,采用不同角度,可以将系统分解成不同的结构。
还是看前面的学生管理系统,从不同角度区划分,我们接可以得到不同的“架构”。
重新定义架构
参考前面维基百科的定义,将架构重新定义为:软件架构指软件系统的顶层结构。
- 第一,“系统是一群有关联的个体组层”,架构需要明确系统包含哪些“个体”
- 第二,“系统中的个体是根据某种规则运作的”,架构需要明确个体运作和协作的规则。
- 第三,这里把“基础结构”改为“顶层建筑”,更好的区分系统和子系统,避免混淆。
总结
架构是顶层设计,框架是面向编程或者配置的半成品,组件是从物理角度的拆分,模块是从业务逻辑上的划分,系统是个体之间相互协作的实体。
架构设计的历史背景
深入了解事物的本质,最好的方式就是寻找这个事物出现的历史背景和发展目的。接下来去探索软件架构的历史背景。
最早的“机器语言”
1940 年之前
最早的开发语言使用的是机器语言,直接使用二进制码 0 和 1 来表示机器可以识别的指令和数据。例如,在 8086 机器上完成“s=768+12288-1280”的数学运算,机器码如下:
101100000000000000000011
000001010000000000110000
001011010000000000000101
- 看不懂 😢
- 又难写 🖊️
- 出问题也难找到 ❓
稍微好点的“汇编语言”
20 世纪 40 年代
为了解决前面的问题,汇编语言出现了。用助记符代替机器指令的操作码,用地址符号(Symbol)或标号(Label)代替指令或操作数的地址。
// 将寄存器 BX 的内容送到 AX 中
机器语言:1000100111011000
汇编语言:mov ax,bx
- 相比机器语言更加清晰了,可以了解大概意思 😅
- 本质还是面对机器的,需要清楚地了解计算机底层知识 😿
- 写起代码,还是很复杂的 😳
例如,完成一个简单的 4 + 6 = ?
.section .data
a: .int 10
b: .int 20
format: .asciz "%d\n"
.section .text
.global _start
_start:
movl a, %edx
addl b, %edx
pushl %edx
pushl $format
call printf
movl $0, (%esp)
call exit
高级语言
20 世纪 50 年代
为了解决汇编的问题,高级语言出现了。不需要关注机器底层的低级结构和逻辑,而只要关注具体的问题和业务即可。此外,通过编译程序的处理,高级语言可以被编译为适合不同 CPU 指令的机器语言。我们开发的代码,只需要写一次,就可以在不同的机器上编译运行。
软件危机
第一次软件危机和结构化程序设计
20 世纪 60 年代~20 世纪 70 年代
高级语言出现后,随着软件规模的增长后复杂度的增加,爆发了第一次软件危机。典型的表现为,项目无法如期完成、严重超支等。
最经典的例子就是,IBM 的 System/360 操作系统,佛瑞德·布鲁克斯作为项目主管
- 率领 2000 多个程序员
- 花费超过 5000 人一年的工作量
- 写出将近 100W 行代码
- 总投入 5 亿美元,占曼哈顿原子弹计划的 1/4
尽管投入如此巨大,但是项目却一再延迟,质量也没有保障,布鲁格斯最后将这个项目经验总结为《人月神话》一书
为了化解问题,1968、1969 年NATO 会议提出了解决方法“软件工程”,但是最后证明软件工程也无法根除程序危机,只是在一定程度上缓解这个问题。
差不多同一时间,艾兹赫尔·戴克斯特拉提出了“结构化程序设计方法”,主要特点就是抛弃 goto 语句,采取“自顶向下,逐步细化,模块化”的指导思想,将复杂度控制在了一定范围。
第二次软件危机与面向对象在
20 世纪 80 年代
随着硬件的发展,和编程领域越来越广泛,第二次程序危机到来了。第一次危机主要根源在于软件的“逻辑”变得复杂,而第二次软件危机则是“扩展”变得非常复杂。在这种背景下,面向对象 的思想开始流行起来,得益于 C++ 以及后来的 Java,面向对象已经成为一种主流思想。
但是面向对象也不是银弹,而只是一种新的软件方法而已。
软件架构的历史背景
软件架构这个概念最早在 20 世纪 60 年代就提出了,90 年代由于 Rational 和 Microsoft 的内部活动,这个概念开始流行起来。
卡内基·梅隆大学的玛丽·肖(Mary Shaw)和戴维·加兰(David Garlan)在 199 年的一篇文章《软件架构介绍》中写到:
“When systems are constructed from many components, the organization of the overall system-the software architecture-presents a new set of design problems.”
也就是当系统由许多部分组成时,整个系统的组织,也就是所说的“软件架构”,导致了一系列新的设计问题。
例如:
- 系统规模庞大,内部耦合严重,开发效率低;
- 系统耦合严重,牵一发动全身,后续修改和扩展困难;
- 系统逻辑复杂,容易出问题,出问题后很难排查和修复。
软件架构的出现是又其历史必然性。
架构设计的目的
为什么做架构设计❓
架构设计的误区
架构设计常见的误区有:
1. 架构很重要,所以要做架构设计
这是一句废话,架构很重要,但为什么重要呢?是因为
- 不做架构设计系统就跑不起来❓ 那也不是这样,创业公司初期的产品可能就没有做架构设计,大家简单讨论一下就开始编码了,开发速度更快,上线的效果也不错
- 做了架构设计就能提升开发效率❓ 也不一定,有时候最简单的设计反而效率是最高的,毕竟架构设计也需要投入时间和人力的
- 设计良好的架构能促进业务发展么❓ 是有一定道理,但是反过来想,如果我们照搬微信的架构,业务就能达到微信的量级么?
2. 不是每个系统都需要做架构设计吗
这其实是知其然不知其所以然,这让很容易走入生搬硬套业界其他公司的已有架构的错误道路,很容易导致“水土不服”。
3. 因为要高性能、高可用、可扩展,所以要做架构设计
这确实是架构设计的目标...,但是简单的持有这种观点可能给项目带来巨大的灾难。如果不管什么系统,一上来就“高性能、高可用、可扩展”,结果就是把架构设计得异常复杂,落地遥遥无期,上线之后,又不稳定经常出问题,加个功能要 1 个月...
真正目的
整个软件技术的发展史,其实就是一部与“复杂度”斗争的历史。前面回顾了架构产生的背景和原因,可以找到架构设计的真正目的:架构设计的主要目的是为了解决软件系统复杂度带来的问题。
这是架构设计需要铭记的一条准则。
1. 遵循这条准则能够让“新手”架构师心中有数,而不是一头雾水。
新手架构师开始做架构设计的时候,恨不得设计出一个世界上最牛逼的系统,从此走上人生巅峰,但面对具体的需求时,又往往陷入一头雾水的状态:
- 这么多需求,从哪里开始下手做架构设计?
- 要考虑“高性能、高可用...”这么多高xx,全部设计完感觉需要 1 个月,老板只给 1 周时间
- 阿里是 A 架构,腾讯是 B 架构,要参考哪个呢?
这些问题,如果明确“架构是为了解决软件复杂度”原则后,就很好回答了。
- 这么多需求,从哪里开始下手做架构设计:通过熟悉和理解需求,识别系统复杂性所在的地方,然后针对这些复杂点进行架构设计。
- 要考虑“高性能、高可用...”这么多高xx,全部设计完感觉需要 1 个月,老板只给 1 周时间:架构设计并不是要面面俱到,不是每个系统都需要具备高性能、高可用、搞扩展等特点,而是识别出复杂点然后针对性的解决问题
- 阿里是 A 架构,腾讯是 B 架构,要参考哪个呢:理解每个架构方案背后解决的复杂点,然后对比自己业务的复杂点,参考复杂点相似的方案
2. 遵循这聊准则让“老鸟”架构师有的放矢,而不是贪大求全。
技术人员往往想做出最牛逼的东西,老鸟架构师也不例外:
- 我们的系统要做到每秒 TPS 10w
- 淘宝的架构怎么做我们就怎么做
- Docker 现在很流行,我们的系统应该将 Docker 应用进来
同样的,用“架构设计是为了解决软件复杂度”这个准则来衡量,可以容易的判断
- 我们的系统要做到每秒 TPS 10w:如果系统的复杂度不在性能这部分,TPS 做到 10w 并没有什么卵用
- 淘宝的架构怎么做我们就怎么做:淘宝的结构是为了解决淘宝的业务复杂度设计的,它的复杂度并不一定就是我们的复杂度
- Docker 现在很流行,我们的系统应该将 Docker 应用进来:Docker 不是万能的,只是为了解决资源重用和动态分配而设计的,如果我们系统复杂度不在这方面,引入 Docker 没有意义。
简单的复杂度分析
看一个简单的案例,将“架构设计是为了解决系统复杂度带来的问题”的原则应用到实践中
“假设我们需要设计一个大学的学生管理系统,其基本功能包括登录、注册、成绩管理、课程管理“,面对这样一个系统,应该如何做设计呢?
- 首先,识别系统的复杂度在哪里?
- 性能:1 个学校大约 1~2 万个学校,系统的访问频率并不高,平均每天每个学生访问可能都不到 1 次。所以性能这部分并不复杂,存储用 MySQL 就可以,缓存都可以不用,Web 服务器用 Nginx 绰绰有余。
- 可扩展性:学生管理系统比较稳定,因此可扩展性也并不复杂
- 高可用:学生管理系统即使宕机 2 小时,对学生管理工作也不会又大影响,因此可以不做复杂均衡,更不用考虑异地多活这类复杂的方案了。但是,如果学生的数据丢失,修复是非常麻烦的,只能人工逐条修复,这个是很难接受的,因此需要考虑存储高可靠。这里就有点复杂了,需要考虑多种情况:机器故障、机房故障
- 针对机器故障,我们需要设计 MySQL 同机房主备方案
- 针对机房故障,我们需要设计 MySQL 跨机房同步方案
- 安全性:学生信息是有一定隐私性,例如家庭情况,但是又和金融不同,也没有强隐私(玉照、情感等)信息,所以安全性只需要考虑 3 件事:Nginx 提供 ACL 控制、用户账号密码管理、数据库访问权限控制
- 成本:由于系统简单,基本几台服务器就可以搞定,对于大学来说不是问题,可以不用考虑
👋 Nginx acl 使用关键字 access 和 deny 进行配置,从上往下匹配,完成匹配则执行对应的操作。access 和 deny 可以放在 http、server、location、limit_except 段中。可以限制网段,也可以是单个IP。例子如下
location / {
deny 192.168.1.1;
allow 192.168.1.0/24;
allow 10.1.1.0/16;
allow 2001:0db8::/32;
deny all;
}
通过前面的分析,可以知道系统的复杂性主要是在存储的可靠性上,需要保证异常情况下数据不丢失,对应的架构如下:
架构是为了解决系统复杂度问题,需求驱动架构设计。