为了高性能、高可用、可扩展,所以要做架构设计, 架构设计的主要目的是为了解决软件系统复杂度带来的问题.
复杂度分析
0.简例
假设我们需要设计一个大学的学生管理系统,其基本功能包括登录、注册、成绩管理、课程 管理等。当我们对这样一个系统进行架构设计的时候,首先应识别其复杂度到底体现在哪 里。
- 性能:一个学校的学生大约 1 ~ 2 万人,学生管理系统的访问频率并不高,平均每天单个 学生的访问次数平均不到 1 次,因此性能这部分并不复杂,存储用 MySQL 完全能够胜 任,缓存都可以不用,Web 服务器用 Nginx 绰绰有余。
- 可扩展性:学生管理系统的功能比较稳定,可扩展的空间并不大,因此可扩展性也不复杂。
- 高可用:学生管理系统即使宕机 2 小时,对学生管理工作影响并不大,因此可以不做负载 均衡,更不用考虑异地多活这类复杂的方案了。但是,如果学生的数据全部丢失,修复是非 常麻烦的,只能靠人工逐条修复,这个很难接受,因此需要考虑存储高可靠,这里就有点复 杂了。我们需要考虑多种异常情况:机器故障、机房故障,针对机器故障,我们需要设计 MySQL 同机房主备方案;针对机房故障,我们需要设计 MySQL 跨机房同步方案。
- 安全性:学生管理系统存储的信息有一定的隐私性,例如学生的家庭情况,但并不是和金融 相关的,也不包含强隐私(例如玉照、情感)的信息,因此安全性方面只要做 3 个事情就 基本满足要求了:Nginx 提供 ACL 控制、用户账号密码管理、数据库访问权限控制。
成本:由于系统很简单,基本上几台服务器就能够搞定,对于一所大学来说完全不是问题, 可以无需太多关注。
1. 高性能
软件系统中高性能带来的复杂度主要体现在两方面,一方面是单台计算机内部为了高性能带 来的复杂度;另一方面是多台计算机集群为了高性能带来的复杂度。
单机复杂度
计算机内部复杂度最关键的地方就是操作系统。操作系统和性能最相关的就是进程和线程。
进程
用进程来对应一个任务,每个任务都有自己独立的内存空间,进程间互不相关,由操作系统来进行调度。分时间片处理,看起来像是并行处理的.
两 个任务之间能够在运行过程中就进行通信,会让任务设计变得更加灵活高效。 进程间通信的方式包括管道、消息队列、信号量、共享存储等。线程
很多进程内部的子任务并不要求是严格按照时间顺序来执行的,也需要并行处理。
线程是进程内部的子任务,但这些子任务都共享同一 份进程数据。为了保证数据的正确性,又发明了互斥锁机制。有了多线程后,操作系统调度 的最小单位就变成了线程,而进程变成了操作系统分配资源的最小单位。实时并行
多进程多线程虽然让多任务并行处理的性能大大提升,但本质上还是分时系统,并不能做到 时间上真正的并行。解决这个问题的方式显而易见,就是让多个 CPU 能够同时执行计算任 务,从而实现真正意义上的多任务并行。目前这样的解决方案有 3 种:SMP(Symmetric Multi-Processor,对称多处理器结构)、NUMA(Non-Uniform Memory Access,非 一致存储访问结构)、MPP(Massive Parallel Processing,海量并行处理结构)。其中 SMP 是我们最常见的,目前流行的多核处理器就是 SMP 方案。
集群复杂度
有时候,单机的性能无论如何都无法支撑业务需求,必须采用机器集群 的方式来达到高性能。
任务分配
任务分配的意思是指每台机器都可以处理完整的业务任务,不同的任务分配到不同的机器上执行。硬件上用交换机,软件上用Nginx负载均衡等.需要考虑分配算法.
性能要求继续提高 , 任务分配器本身又会成为性能瓶颈 ,可能需要扩展多台. 这个变化 带来的复杂度就是需要将不同的用户分配到不同的任务分配器上 ,常见的方法包括 DNS 轮询、智能 DNS、CDN(Content Delivery Network,内容分发网络)、GSLB 设备(Global Server Load Balance,全局负载均衡)等。任务分解
通过任务分解的方式,能够把原来大一统但复杂的业务系统,拆分成小而简单但需要多 个系统配合的业务系统。从业务的角度来看,任务分解既不会减少功能,也不会减少代码量.
简单的系统更加容易做到高性能
- 可以针对单个任务进行扩展或优化
2.高可用
看高可用的定义:系统无中断地执行其功能的能力,代表系统的可用性程度,是进行系统设计时的准则之一。
难点在“无中断”上面,因为无论是单个硬件还 是单个软件,都不可能做到无中断,硬件会出故障,软件会有 bug;硬件会逐渐老化,软 件会越来越复杂和庞大……
系统的高可用方案五花八门,但万变不离其宗,本质上都是通过“冗余”来实现高可用。也就是增加服务器.
高性能增加机器目的在 于“扩展”处理性能;高可用增加机器目的在于“冗余”处理单元。计算高可用 和 存储高可用
高可用状态决策
无论是计算高可用还是存储高可用,其基础都是“状态决策”,即系统需要能够判断当前的 状态是正常还是异常,如果出现了异常就要采取行动来保证高可用。如果状态决策本身都是 有错误或者有偏差的,那么后续的任何行动和处理无论多么完美也都没有意义和价值。但在 具体实践的过程中,恰好存在一个本质的矛盾:通过冗余来实现的高可用系统,状态决策本 质上就不可能做到完全正确。独裁式
独裁式决策指的是存在一个独立的决策主体,我们姑且称它为“决策者”,负责收集信息然 后进行决策;所有冗余的个体,我们姑且称它为“上报者”,都将状态信息发送给决策者。
独裁式的决策方式不会出现决策混乱的问题,因为只有一个决策者,但问题也正是在于只有 一个决策者。当决策者本身故障时,整个系统就无法实现准确的状态决策。如果决策者本身 又做一套状态决策,那就陷入一个递归的死循环了。协商式
协商式决策指的是两个独立的个体通过交流信息,然后根据规则进行决策,最常用的协商式 决策就是主备决策。民主式
民主式决策指的是多个独立的个体通过投票的方式来进行状态决策。例如,ZooKeeper 集 群在选举 leader 时就是采用这种方式。
民主式决策和协商式决策比较类似,其基础都是独立的个体之间交换信息,每个个体做出自 己的决策,然后按照“多数取胜”的规则来确定最终的状态。不同点在于民主式决策比协商 式决策要复杂得多 .
还可能造成脑裂, 脑裂的根本原因是,原来统一的集群因为连接中断, 造成了两个独立分隔的子集群,每个子集群单独进行选举,于是选出了 2 个主机,相当于 人体有两个大脑了
为了解决脑裂问题,民主式决策的系统一般都采用“投票节点数必须超过系统总节点数一 半”规则来处理。3.可扩展性
可扩展性指系统为了应对将来需求变化而提供的一种扩展能力,当有新的需求出现时,系统 不需要或者仅需要少量修改就可以支持,无须整个系统重构或者重建。预测变化
应对变化
第一种应对变化的常见方案是将“变化”封装在一个“变化层”,将不变的部分封装在一个 独立的“稳定层”。
第二种常见的应对变化的方案是提炼出一个“抽象层”和一个“实现层”。
架构设计的原则
- 合适原则: 合适优于业界领先.
- 简单原则: 简单优于复杂
- 演化原则 : 演化优于一步到位