01 | 架构到底是指什么?
系统与子系统
系统泛指由一群有关联的个体组成,根据某种规则运作,能完成个别元件不能单独完成的工作的群体。它的意思是“总体”“整体”或”联盟“。
关键词: 关联、规则、能力。
子系统也是由一群有关联的个体所组成的系统,多半会是更大系统中的一部分。
模块与组件
模块和组件都是系统的组成部分,只是从不同的角度拆分系统而已。
从逻辑的角度来拆分系统后,得到的单元就是“模块”;从物理的角度来拆分系统后,得到的单元就是“组件”。
框架与架构
框架关注的是“规范”,架构关注的是“结构”。框架的英文是 Framework,架构的英文是 Archiecture。
02 | 架构设计的历史背景
软件开发进化史
机器语言 -> 汇编语言 -> 高级语言
第一次软件危机与结构化程序设计(20 世纪 60 年代~20世纪70年代)
结构化程序设计的主要特点是抛弃 goto 语句,采取“自顶向下、逐步细化、模块化”的指导思想。
虽然“软件工程”提出之后也曾被视为软件领域的银弹,但后来事实证明,软件工程同样无法根除软件危机,只能在一定程度上缓解软件危机。
第二次软件危机与面向对象(20 世纪 80 年代)
根本原因还是在于软件生产力远远跟不上硬件和业务的发展。
第一次软件危机的根源在于软件的“逻辑”变得非常复杂,而第二次主要体现在软件的“扩展”变得非常复杂。
虽然面向对象开始也被当作解决软件危机的银弹,但事实证明,和软件工程一样,面向对象也不是银弹,而只是一种新的软件方法而已。
03 | 架构设计的目的
架构设计的主要目的是为了解决软件系统复杂度带来的问题
遵循这条准则能够让“老鸟”架构师有的放矢,而不是贪大求全。
04 | 复杂度来源:高性能
软件系统中高性能带来的复杂度主要体现在两方面,一方面是单台计算机内部为了高性能带来的复杂度;另一方面是多台计算机集群为了高性能带来的复杂度。
单机复杂度
计算机内部复杂度最关键的地方就是操作系统,操作系统是软件系统的运行环境,操作系统的复杂度直接决定了软件系统的复杂度。
操作系统和性能最相关的就是进程和线程。
人工录入 -> 批处理指令 -> 进程 -> 线程
操作系统调度的最小单位就变成了线程,而进程变成了操作系统分配资源的最小单位。
集群复杂度
通过大量机器来提升性能,并不仅仅是增加机器这么简单,让多台机器配合起来达到高性能的目的,是一个复杂的任务。
1. 分配任务 (水平拆分)
随着性能的增加,任务分配器本身又会成为性能瓶颈,当业务请求达到每秒 10 万次的时候,单台任务分配器也不够,任务分配器本身也需要扩展为多台机器,这时的架构又会演变成这样。
2.任务分解(垂直拆分)
业务越来越复杂,单台机器处理的性能会越来越低,单纯只通过任务分配的方式来扩展性能,收益会越来越低。如果系统的子业务足够内聚(有边界),可以进行垂直拆分。
既然将一个大一统的系统分解为多个子系统能够提升性能,那是不是划分得越细越好呢?
这样做性能不仅不会提升,反而还会下降,最主要的原因是如果系统拆分得太细,为了完成某个业务,系统间的调用次数会呈指数级别上升,而系统间的调用通道目前都是通过网络传输的方式,性能远比系统内的函数调用要低得多。
虽然系统拆分可能在某种程度上能提升业务处理性能,但提升性能也是有限的,最终决定业务处理性能的还是业务逻辑本身,业务逻辑本身没有发生大的变化下,理论上的性能是有一个上限的,系统拆分能够让性能逼近这个极限,但无法突破这个极限。因此,任务分解带来的性能收益是有一个度的,并不是任务分解越细越好。
05 | 复杂度来源:高可用
高可用定义:
系统无中断地执行其功能的能力,代表系统的可用性程度,是进行系统设计时的准则之一。
"无中断"的难点
- 硬件故障
- 软件出bug
- 不可抗力(自然灾害、断电、挖断光纤)
系统的高可用方案五花八门,但万变不离其宗,本质上都是通过“冗余”来实现高可用。
高性能增加机器目的在于“扩展”处理性能;高可用增加机器目的在于“冗余”处理单元
通过冗余增强了可用性,但同时也带来了复杂性。
计算高可用
存储高可用
高可用状态决策
协商式
民主式
脑裂 (解决方法:过半数同意才选举成功,缺点:可用性降低
Paxos算法
06 | 复杂度来源:可扩展性
预测变化
软件系统与硬件或者建筑相比,有一个很大的差异:软件系统在发布后还可以不断地修改和演进,这就意味着不断有新的需求需要实现。
应对变化
无论采取哪种形式,通过剥离变化层和稳定层的方式应对变化,都会带来两个主要的复杂性相关的问题。
- 系统需要拆分出变化层和稳定层
- 需要设计变化层和稳定层之间的接口
常见的应对变化的方案是提炼出一个“抽象层”和一个“实现层”。