架构到底是什么?

系统和子系统:
关联:系统由一群有关联的个体组成,没有关联的个体堆在一起不能成为一个系统
规则:系统的个性按照指定规则运作,相互协作
能力:系统能力和个体能力有区别,个性能力组合产生新的能力

一个系统可能是另一个更大系统的子系统
逻辑架构:
从0开始学架构笔记 - 图1
部署架构:
从0开始学架构笔记 - 图2
规范架构
从0开始学架构笔记 - 图3

模块与组件

模块:从逻辑角度拆分:注册、个人信息、角色管理…
组件:从物理角度拆分:Nginx,redis,Mysql

框架与架构

框架:组件规范,MVC,J2ee
框架提供的功能产品: spring(mvc)

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

开发架构历史

机器语言(1940 年之前):最早的软件开发使用的是“机器语言”,直接使用二进制码 0 和 1 来表示机器可以识别的指令和数据,机器语言的主要问题是三难:太难写、太难读、太难改
汇编语言(20 世纪 40 年代):为了解决机器语言编写、阅读、修改复杂的问题,汇编语言应运而生。汇编语言又叫“符号语言”,用助记符代替机器指令的操作码,用地址符号(Symbol)或标号(Label)代替指令或操作数的地址
高级语言(20 世纪 50 年代):Fortran
第一次软件危机与结构化程序设计(20 世纪 60 年代~20 世纪 70 年代):危机的根源在于软件的“逻辑”变得非常复杂
第二次软件危机与面向对象(20 世纪 80 年代):主要体现在软件的“扩展”变得非常复杂。结构化程序设计虽然能够解决(也许用“缓解”更合适)软件逻辑的复杂性,但是对于业务变化带来的软件扩展却无能为力
软件架构:

架构设计的真正目的
架构是为了应对软件系统复杂度而提出的一个解决方案。个人感悟是:架构即(重要)决策,是在一个有约束的盒子里去求解或接近最合适的解。这个有约束的盒子是团队经验、成本、资源、进度、业务所处阶段等所编织、掺杂在一起的综合体(人,财,物,时间,事情等)。架构无优劣,但是存在恰当的架构用在合适的软件系统中,而这些就是决策的结果。

需求驱动架构。在分析设计阶段,需要考虑一定的人力与时间去”跳出代码,总揽全局”,为业务和IT技术之间搭建一座”桥梁”。

架构设计处于软件研制的前期,一方面,越是前期,如有问题,就能够越早发现,修改的代价也就越低;另外一方面,也意味着,软件实施后期若有架构上的修改,也需要付出更多的代价。

1 架构是为了应对软件系统复杂度而提出的一个解决方案。
2 架构即(重要)决策
3 需求驱动架构,架起分析与设计实现的桥梁
4 架构与开发成本的关系

复杂度来源

高性能

软件系统中高性能带来的复杂度主要体现在两方面,一方面是单台计算机内部为了高性能带来的复杂度;另一方面是多台计算机集群为了高性能带来的复杂度。

线程是任务调度的最小单元,共用进程内的资源。
进程是资源分配的最小单元,与其他进程资源互相独立。

我们要完成一个高性能的软件系统,需要考虑如多进程、多线程、进程间通信、多线程并发等技术点,而且这些技术并不是最新的就是最好的,也不是非此即彼的选择。在做架构设计的时候,需要花费很大的精力来结合业务进行分析、判断、选择、组合,这个过程同样很复杂

1、单机:多进程多线程 2、集群:任务分配(lvs、Nginx),任务分解(拆分模块,微服务)

没有设计导致性能瓶颈,过度设计导致系统复杂

任务分配:
从0开始学架构笔记 - 图4

任务拆解
从0开始学架构笔记 - 图5
简单的系统更加容易做到高性能,可以针对单个任务进行扩展,但是过分的拆解又会带来问题

从0开始学架构笔记 - 图6

高可用

高性能增加机器目的在于“扩展”处理性能;高可用增加机器目的在于“冗余”处理单元。
计算高可用:无论在哪台机器上进行计算,同样的算法和输入数据,产出的结果都是一样的
存储高可用:存储高可用的难点不在于如何备份数据,而在于如何减少或者规避数据不一致对业务造成的影响。

高可用状态决策:

  • 独裁式:独裁式的决策方式不会出现决策混乱的问题,因为只有一个决策者,当决策者本身故障时,整个系统就无法实现准确的状态决策
  • 协商式:最常用的协商式决策就是主备决策。两者的信息交换出现问题(比如主备连接中断),此时状态决策应该怎么做,在某些场景一定会出问题
  • 民主式:多个独立的个体通过投票的方式来进行状态决策;民主式决策还有一个固有的缺陷:脑裂;为了解决脑裂问题,民主式决策的系统一般都采用“投票节点数必须超过系统总节点数一半”规则来处理

可扩展性

有两个基本条件:正确预测变化、完美封装变化

  • 不能每个设计点都考虑可扩展性
  • 不能完全不考虑可扩展性
  • 所有的预测都存在出错的可能性
  1. 系统需要拆分出变化层和稳定层
  2. 需要设计变化层和稳定层之间的接口

设计模式的核心就是,封装变化,隔离可变性

“上中下”策的设计方案,下策一般考虑的变化少,短视,但迅速,修改小,立竿见影。上策一般看重远期,但成本高很高,也很可能预测不中。
最后还要分析,如果决定采用下中策,如果预测的变化发生了,系统修改为中上策的代价有多大,有些代价几乎是无穷大的,比如必须中断服务进行升级。如果代价小,那可以放心采用下策或中策。如果答案是否,可上策当前的代价又真的不可接受,那又要返回头重新分析了
实践发现这个方法挺好用

其他来源

低成本:往往只有“创新”才能达到低成本目标,一般直接引入新技术
安全:功能安全其实就是“防小偷”,架构安全就是“防强盗”
规模:规模带来复杂度的主要原因就是“量变引起质变”

架构设计中最为重要的是考虑各种非功能性需求,同样的功能但不同的非功能性需求设计方案会有很大的不同,比如登陆系统,功能都是相同,但一个要求是100/s,另一个是10w/s,,这两个架构是完全不一样

架构设计原则

合适优于先进>演化优于一步到位>简单优于复杂

合适原则

合适原则宣言:“合适优于业界领先”

  1. 将军难打无兵之仗:没那么多人,却想干那么多活,是失败的第一个主要原因
  2. 罗马不是一天建成的:没有那么多积累,却想一步登天,是失败的第二个主要原因
  3. 冰山下面才是关键:业界领先的方案其实都是“逼”出来的

    简单原则

    简单原则宣言:“简单优于复杂”
    软件领域的复杂性:

  4. 结构的复杂性:组件越多,就越有可能其中某个组件出现故障;某个组件改动,会影响关联的所有组件;定位一个复杂系统中的问题总是比简单系统更加困难

  5. 逻辑的复杂性:单个组件承担了太多的功能;采用了复杂的算法。复杂算法导致的问题主要是难以理解,进而导致难以实现、难以修改,并且出了问题难以快速解决

    演化原则

    演化原则宣言:“演化优于一步到位”
    本质:软件架构需要根据业务发展不断变化

  6. 设计出来的架构要满足当时的业务需要

  7. 架构要不断地在实际应用过程中迭代,保留优秀的设计,修复有缺陷的设计,改正错误的设计,去掉无用的设计,使得架构逐渐完善
  8. 当业务发生变化时,架构要扩展、重构,甚至重写;代码也许会重写,但有价值的经验、教训、逻辑、设计等(类似生物体内的基因)却可以在新架构中延续

架构设计步骤

架构设计第 1 步:识别复杂度

架构的复杂度主要来源于“高性能”“高可用”“可扩展”等几个方面
实际上大部分场景下,复杂度只是其中的某一个,少数情况下包含其中两个,如果真的出现同时需要解决三个或者三个以上的复杂度,要么说明这个系统之前设计的有问题
将主要的复杂度问题列出来,然后根据业务、技术、团队等综合情况进行排序,优先解决当前面临的最主要的复杂度问题

一个普遍的担忧:如果按照优先级来解决复杂度,可能会出现解决了优先级排在前面的复杂度后,解决后续复杂度的方案需要将已经落地的方案推倒重来。这个担忧理论上是可能的,但现实中几乎是不可能出现的,原因在于软件系统的可塑性和易变性。对于同一个复杂度问题,软件系统的方案可以有多个,总是可以挑出综合来看性价比最高的方案。

架构设计第 2 步:设计备选方案

第一种常见的错误:设计最优秀的方案。根据架构设计原则中“合适原则”和“简单原则“的要求,挑选合适自己业务、团队、技术能力的方案才是好方案
第二种常见的错误:只做一个方案;备选方案的数量以 3 ~ 5 个为最佳;备选方案的差异要比较明显;备选方案的技术不要只局限于已经熟悉的技术
第三种常见的错误:备选方案过于详细。备选阶段关注的是技术选型,而不是技术细节,技术选型的差异要比较明显

架构设计第 3 步:评估和选择备选方案

列出我们需要关注的质量属性点,然后分别从这些质量属性的维度去评估每个方案,再综合挑选适合当时情况的最优方案
性能、可用性、硬件成本、项目投入、复杂度、安全性、可扩展性 遵循架构设计原则 1“合适原则”和原则 2“简单原则”

如果每次做方案都考虑这种小概率事件,我们的方案会出现过度设计,导致投入浪费。考虑这个问题的时候,需要遵循架构设计原则 3“演化原则”,避免过度设计、一步到位的想法

按优先级选择,即架构师综合当前的业务发展情况、团队人员规模和技能、业务发展预测等因素,将质量属性按照优先级排序,首先挑选满足第一优先级的,如果方案都满足,那就再看第二优先级……以此类推

架构设计第 4 步:详细方案设计

详细方案设计就是将方案涉及的关键技术细节给确定下来

详细设计方案阶段可能遇到的一种极端情况就是在详细设计阶段发现备选方案不可行,一般情况下主要的原因是备选方案设计时遗漏了某个关键技术点或者关键的质量属性. 如何规避

  • 架构师不但要进行备选方案设计和选型,还需要对备选方案的关键细节有较深入的理解
  • 通过分步骤、分阶段、分系统等方式,尽量降低方案复杂度,方案本身的复杂度越高,某个细节推翻整个方案的可能性就越高,适当降低复杂性,可以减少这种风险
  • 采取设计团队的方式来进行设计,博采众长,汇集大家的智慧和经验

高性能数据库集群-读写分离、分库分表

NOSQL

我们不能盲目地迷信 NoSQL 是银弹,而应该将 NoSQL 作为 SQL 的一个有力补充,NoSQL != No SQL,而是 NoSQL = Not Only SQL。

常见的 NoSQL 方案分为 4 类。

  • K-V 存储:解决关系数据库无法存储数据结构的问题,以 Redis 为代表。
  • 文档数据库:解决关系数据库强 schema 约束的问题,以 MongoDB 为代表。
  • 列式数据库:解决关系数据库大数据场景下的 I/O 问题,以 HBase 为代表。
  • 全文搜索引擎:解决关系数据库的全文搜索性能问题,以 Elasticsearch 为代表。

高性能缓存架构

缓存穿透是指缓存没有发挥作用,业务系统虽然去缓存查询数据,但缓存中没有数据,业务系统需要再次去存储系统查询数据。

  1. 存储数据不存在
  2. 缓存数据生成耗费大量时间或者资源

缓存雪崩是指当缓存失效(过期)后引起系统性能急剧下降的情况

  • 更新锁机制:保证只有一个线程能够进行缓存更新,分布式锁
  • 后台更新:发现缓存失效后,通过消息队列发送一条消息通知后台线程更新缓存,定时任务(不推荐)

缓存热点:对于一些特别热点的数据,如果大部分甚至所有的业务请求都命中同一份缓存数据,则这份数据所在的缓存服务器的压力也很大

缓存热点的解决方案就是复制多份缓存副本,将请求分散到多个缓存服务器上,减轻缓存热点导致的单台缓存服务器压力,同时设定一个过期时间范围,不同的缓存副本的过期时间是指定范围内的随机值

单服务器高性能

I/O 多路复用结合线程池

reactor:能收了你跟俺说一声。
proactor: 你给我收十个字节,收好了跟俺说一声。

Reactor:非阻塞同步网络模型,可以理解为:来了事件我通知你,你来处理
Proactor:异步网络模型,可以理解为:来了事件我来处理,处理完了我通知你
理论上:Proactor比Reactor效率要高一些。

高性能负载均衡:分类及架构

DNS 负载均衡:简单、成本低:负载均衡工作交给 DNS 服务器处理,无须自己开发或者维护负载均衡设备
硬件负载均衡:F5 和 A10功能强大:全面支持各层级的负载均衡,支持全面的负载均衡算法,支持全局负载均衡
软件负载均衡:Nginx 和 LVS

Nginx :5 万 / 秒
LVS 的性能是十万级,80 万 / 秒
F5 性能是百万级,从 200 万 / 秒到 800 万 / 秒

从0开始学架构笔记 - 图7

地理级别负载均衡:DNS
集群级别负载均衡:F5
机器级别的负载均衡:Nginx

算法均衡:轮询、加权轮询、负载最低优先(分配给当前负载最低的服务器)、性能最优类(优先将任务分配给处理速度最快的服务器,通过这种方式达到最快响应客户端的目的)、Hash 类(根据任务中的某些关键信息进行 Hash 运算,将相同 Hash 值的请求分配到同一台服务器上,这样做的目的主要是为了满足特定的业务需求)

这个Reactor Proactor好抽象,不太理解

作者回复: 对照Doug Lee讲异步io的PPT,将代码从头到尾亲自敲一遍,就比较容易理解了

CAP 定理

Consistency、Availability、Partition Tolerance

第一版:
对于一个分布式计算系统,不可能同时满足一致性(Consistence)、可用性(Availability)、分区容错性(Partition Tolerance)三个设计约束

第二版:
在一个分布式系统(指互相连接并共享数据的节点的集合)中,当涉及读写操作时,只能保证一致性(Consistence)、可用性(Availability)、分区容错性(Partition Tolerance)三者中的两个,另外一个必须被牺牲

CAP 关注的是对数据的读写操作,而不是分布式系统的所有功能

一致性(Consistency):

第一版:所有节点在同一时刻都能看到相同的数据
第二版: 对某个指定的客户端来说,读操作保证能够返回最新的写操作结果(client 读操作能够获取最新的写结果就没有问题,因为事务在执行过程中,client 是无法读取到未提交的数据的,只有等到事务提交后,client 才能读取到事务写入的数据,而如果事务失败则会进行回滚,client 也不会读取到事务中间写入的数据)

可用性(Availability):
第一版:每个请求都能得到成功或者失败的响应
第二版: 非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)(明确了不能超时、不能出错,结果是合理的,注意没有说“正确”的结果。例如,应该返回 100 但实际上返回了 90,肯定是不正确的结果,但可以是一个合理的结果。)

分区容忍性(Partition Tolerance):
第一版:出现消息丢失或者分区错误时系统能够继续运行。
第二版: 当出现网络分区后,系统能够继续“履行职责”。

CAP 理论定义是三个要素中只能取两个,分布式环境下必须选择 P(分区容忍)要素,CP 或者 AP 架构
P要求分布式和数据同步,C要求数据完全一致,A要求返回及时

CAP 是忽略网络延迟的,比如复制时 那几毫秒就是不一致的,正常运行情况下,不存在 CP 和 AP 的选择,可以同时满足 CA:如果系统没有发生分区现象,即没有P 不存在的时候,CA是必须要满足的

AP 方案中牺牲一致性只是指分区期间,而不是永远放弃一致性

既要考虑分区发生时选择 CP 还是 AP,也要考虑分区没有发生时如何保证 CA

任何一个正常运行的分布式系统,起源于CA状态,中间(发生分区时)可能经过CP和AP状态,最后回到CA状态。
所以一个分布式系统,需要考虑实现三点:
1.正常运行时的CA状态。
2.发生分区时转变为CP或AP状态。
3.分区解决时变会CA状态。

ACID理论(针对数据库)

Atomicity(原子性),Consistency(一致性),Isolation(隔离性),Durability(持久性)

BASE理论(实践CAP)

基本可用(Basically Available):分布式系统在出现故障时,允许损失部分可用性,即保证核心可用。
软状态( Soft State):允许系统存在中间状态,而该中间状态不会影响系统整体可用性
最终一致性( Eventual Consistency):系统中的所有数据副本经过一定时间后,最终能够达到一致的状态

ACID 是数据库事务完整性的理论,CAP 是分布式系统设计理论,BASE 是 CAP 理论中 AP 方案的延伸

FMEA (Failure mode and effects analysis,故障模式与影响分析)

  • 给出初始的架构设计图。
  • 假设架构中某个部件发生故障。
  • 分析此故障对系统功能造成的影响。
  • 根据分析结果,判断架构是否需要进行优化。

FMEA 分析表:
1.功能点:以用户角度来看(例如,对于一个用户管理系统,使用 FMEA 分析时 “登录”“注册”才是功能点,而用户管理系统中的数据库存储功能、Redis 缓存功能不能作为 FMEA 分析的功能点)
2.故障模式:系统会出现什么样的故障,包括故障点和故障形式(故障模式的描述要尽量精确,多使用量化描述,避免使用泛化的描述。例如,推荐使用“MySQL 响应时间达到 3 秒”,而不是“MySQL 响应慢”)
3.故障影响:当发生故障模式中描述的故障时,功能点具体会受到什么影响。常见的影响有:功能点偶尔不可用、功能点完全不可用、部分用户功能点不可用、功能点响应缓慢、功能点出错等,需要给出大概多少影响面,比如 20% 的用户无法登录
4.严重程度:站在业务的角度故障的影响程度:致命 / 高 / 中 / 低 / 无
5.故障原因:不同的故障原因发生概率不相同,不同的故障原因检测手段不一样,不同的故障原因的处理措施不一样
6.故障概率:某个具体故障原因发生的概率,高 / 中 / 低”三档即可
7.风险程度:综合严重程度和故障概率来一起判断某个故障的最终等级
8.已有措施:针对具体的故障原因,系统现在是否提供了某些措施来应对,检测告警、容错、自恢复
9.规避措施:降低故障发生概率而做的一些事情,可以是技术手段,也可以是管理手段
10.解决措施:解决措施指为了能够解决问题而做的一些事情,一般都是技术手段
11.后续规划:前还缺乏对应的措施,哪些已有措施还不够,针对这些不足的地方,再结合风险程度进行排序,给出后续的改进规划

高可用存储架构

主备复制:备机”主要还是起到一个备份作用,并不承担实际的业务读写操作,如果要把备机改为主机,需要人工操作
主从复制:主机负责读写操作,从机只负责读操作,不负责写操作
双机切换:主备切换和主从切换
互连式:主机和备机多了一个“状态传递”的通道
中介式:主备两者之外引入第三方中介,主备机之间不直接连接
模拟式:模拟式指主备机之间并不传递任何状态数据,而是备机模拟成一个客户端,
向主机发起模拟的 读写操作,根据读写操作的响应情况来判断主机的状态
主主复制:两台机器都是主机,互相将数据复制给对方,客户端可以任意挑选其中一台机器进行读写操作