本文整理自极客时间的《从 0 开始学架构》专题的前七个章节。通过这部分内容,大概了解下什么是架构,架构解决什么问题,这些问题又是怎么来的。并不涉及该专题中架构设计的原则、流程、方案等内容。

1. 什么是架构

为了准确回答什么是架构,首先要明白这样几个概念:

  • 系统,一群有关联的个体组成,根据某种规则运作,能完成个别元件不能单独完成的工作的群体;
  • 子系统,与系统的定义一样,只是观察角度有差异,一个系统可能是另一个大系统的子系统;
  • 模块,从逻辑角度拆分系统,得到的单元是“模块”;
  • 组件,从物理角度拆分系统,得到的单元是“组件”;
  • 框架,Framework,组件规范,关注的是“规范”;
  • 架构,Architecture,软件系统的“基础结构”,关注的是“结构”。

由于“基础结构”的概念并没有明确说是从什么角度来分解,所以有可能按业务逻辑(模块)划分,也有可能按物理部署(组件)划分,或者从开发规范的角度来划分。

总结:软件架构指软件系统的顶层结构

  • 架构需要明确系统包含哪些“个体”;
  • 架构需要明确个体运作和协作的规则;
  • 架构是顶层结构。

2. 架构设计的历史背景

从机器语言,到汇编语言,再到高级语言,然后出现结构化程序设计,有了“模块”的概念,接着是面向对象,有了“对象”的概念。20 世纪 90 年代开始,一些大公司开发规模比较大的软件,软件架构的概念开始流行起来。

随着软件系统规模的增加,计算相关的算法和数据结构不再构成主要的设计问题;当系统由许多部分组成时,整个系统的组织,也就是所说的“软件架构“,导致了一系列新的设计问题。

软件架构流行之后,有了“组件”的概念。软件系统越来越大,越来越复杂,对软件进行拆分的粒度越来越粗,层次越来越高:从“模块”,到“对象”,到“组件”。

3. 架构设计的目的

  • 不要为了设计而设计,要紧贴业务需求
  • 架构设计与开发效率没有必然联系

架构设计的主要目的是为了解决软件系统复杂度带来的问题。要针对系统复杂点进行架构设计,而不一定要追求高性能、高并发,或者照搬巨头公司的架构方案,更不必追求流行,要看自己的系统到底有什么问题。时刻记着”为了解决软件系统复杂度带来的问题”这个准则。

有时候是为了实现系统的高性能、高并发等需求,给系统带来了复杂度。

4.复杂度来源:高性能

高性能带来的复杂度主要体现在单机复杂度和集群复杂度两方面。

单机复杂度

计算机开始只有输入、计算和输出功能,然后有了批处理操作系统,又发明了“多进程”,之后又有了“多线程”。编写代码和软件系统的复杂度依次升高。为了做到真正的并行,又出现了 SMP(对称多处理器结构), NUMA(非一致存储访问结构) 和 MPP(海量并行处理结构) 等解决方案。

集群的复杂度

业务发展速度远超硬件发展速度,单机性能不可能支撑双 11 或春节微信红包这样的业务,必须采用集群的方式,实现高性能的目的。

集群有这样几种方式:

  • 任务分配
    • 每个业务服务器做同样的工作,在业务服务器之前加上任务分配器。根据业务需求不同,任务分配器和业务服务器之间可能是一对多,也可能是多对多的关系。
    • 要综合考虑多方面因素来选择使用什么任务分配器,以及选择任务分配器和业务服务器之间的连接方式,和任务分配器的分配算法。
  • 任务分解
    • 把一个任务分成若干个小任务,一个复杂的系统拆成多个更小的子系统。简单的系统更容易做到高性能,可以针对单个任务扩展。
    • 拆分出来的子系统之间需要通信,如果分得太细,子系统之间的调用次数太多,通信延迟会造成整体性能下降。所以任务分解的粒度控制非常关键。

5. 复杂度来源:高可用

高可用的定义:

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

各种高可用方案本质上都是通过“冗余”来实现目的。下面分析计算高可用和存储高可用两种场景。

计算高可用

为了高可用而做的双机架构,跟为了高性能做的双机架构基本类似,复杂度的来源以基本一致。更多机器的计算高可用方案,要根据业务需求确定好各个机器之间的主备关系,分配算法更比双机结构复杂。

存储高可用

存储高可用是个难点,CAP 定理从理论上论证了存储高可用的复杂度。为了满足存储高可用,使用分布式存储方案,对这个系统做架构设计时,必须在“一致性、可用性、分区容错性”三者间进行取舍。

高可用状态决策

不论是哪种场景的高可用,基础都是“状态决策”:判断系统当前状态是正常还是异常,出现异常时做出什么行动来保证高可用。没有完美的决策方案。

独裁式

只有一个决策者,所有冗余个体向它上报状态,问题在于还需要保证决策者本身的高可用。

协商式

两个冗余个体之间通信,协商做出决策。协商式决策的常见例子是主备决策。问题是一旦两者间的通信中断,无法协商,这种策略就失效了。

民主式

各个冗余个体之间通信,进行投票选举,做出状态决策。这种策略的选举算法一般比较复杂。而且这种策略有“脑裂”缺陷:一部分子集群和另一部分的子集群之间的通信中断,不同的子集群选出了各自的主节点,导致整个集群有多个主节点,进而状态混乱。

为了解决“脑裂”带来的问题,一般会有“投票节点数必须超过系统总节点数一半”的规则,节点数少于总数一半的子集群不参与投票。但是这样就降低了系统的可用性。

如果系统不是因为脑裂问题导致投票节点数过少,而真的是因为节点故障,此时系统也不会选出主节点,整个系统就相当于宕机了,尽管此时还有节点是正常的。

高可用、高性能(集群)方案在形式上都要增加机器,通常实现高可用比实现高性能更为复杂、困难。没有完美的、适用于任何场景的高可用方案。

6. 复杂度来源:可扩展性

可扩展性指系统为了应对将来需求变化而提供的一种扩展能力,当有新的需求出现时,系统不需要或者仅需要少量修改就可以支持,无须整个系统重构或者重建。

设计具备良好可扩展性的系统,有两个基本条件:正确预测变化、完美封装变化。

预测变化

  • 不能每个设计点都考虑可扩展性(架构设计会异常庞大,最终无法落地)
  • 不能完全不考虑可扩展性
  • 所有的预测都存在出错的可能性

应对变化

一种应对变化的常见方案时将“变化”封装在“变化层”,不变的部分封装在独立的“稳定层”。由此带来复杂性:

  1. 系统需要拆分出变化层和稳定层
  2. 需要设计变化层和稳定层之间的接口

另外一种应对变化的常见方案是提炼出一个“抽象层”和一个”实现层”。抽象层是稳定的,一般不做修改,实现层根据业务来定制。这种方案的典型实践是设计模式规则引擎,具体到代码实现中,都不是很容易就能做好的。

7. 复杂度来源:低成本、安全、规模

低成本

当系统要用成千上万台机器时,成本就会成为架构设计中的一个非常重要的点。用的机器越少,成本越低,但是这与“高性能”、“高可用”的需求相悖。通常需要技术上创新来达到低成本的目的,比如本来 10 台机器能做的事情,用新技术后,5 台机器就能完成。

小公司一般通过引入新技术来创新,大公司可能会开创新技术,两种方式都回让系统复杂度提升。

安全

功能安全

功能上的安全牵扯到的是系统的各种漏洞,更多地跟具体的编码相关,与架构关系不大。功能安全是个逐步完善的过程。

架构安全

传统的架构安全主要依靠防火墙,性能一般,不适合有海量用户访问、高并发特点的互联网业务场景。针对大规模的 DDoS 攻击部署大量的防火墙,成本很高,而且在机房出口总带宽被 DDoS 打满后,不管防火墙能力多强,对用户来说,业务还是不可用的。

互联网系统的架构安全目前并没有太好的设计手段来实现,更多地是依靠运营商或者云服务商强大的带宽和流量清洗的能力,较少自己来设计和实现。

规模

系统功能越来越多,导致系统复杂度指数级上升。 3 大功能增加到 8 大功能,相差不是 5,而是 30,因为功能与功能间的连接是指数级增长的。

数据量越来越大导致复杂度上升。数据库在数据量变大后,性能变差,需要用上各种优化手段,数据更多后,要考虑拆表,拆表又有各种需要考虑的问题点。数据量大到一定规模后,又要引入“大数据”技术,复杂度进一步升高。