1.3.2.1.1. 架构是什么

系统与子系统、模块与组件、框架与架构

系统泛指由一群有关联的个体组成,根据某种规则运作,能完成个别元件不能单独完成的工作的群体。它的意思是“总体”“整体”或“联盟”。

1、关联:系统是由一群有关联的个体组成的,没有关联的个体堆在一起不能成为一个系统。例如,把一个发动机和一台 PC 放在一起不能称之为一个系统,把发动机、底盘、轮胎、车架组合起来才能成为一台汽车。

2、规则:系统内的个体需要按照指定的规则运作,而不是单个个体各自为政。规则规定了系统内个体分工和协作的方式。例如,汽车发动机负责产生动力,然后通过变速器和传动轴,将动力输出到车轮上,从而驱动汽车前进。

3、能力:系统能力与个体能力有本质的差别,系统能力不是个体能力之和,而是产生了新的能力。例如,汽车能够载重前进,而发动机、变速器、传动轴、车轮本身都不具备这样的能力。

模块和组件都是系统的组成部分,只是从不同的角度拆分系统而已。

从业务逻辑的角度来拆分系统后,得到的单元就是“模块”;

从物理部署的角度来拆分系统后,得到的单元就是“组件”。

划分模块的主要目的是职责分离;划分组件的主要目的是单元复用。

框架关注的是“规范”,架构关注的是“结构”。

1.3.2.1.1.1. 4R 架构

软件架构指软件系统的顶层(Rank)结构,它定义了系统由哪些角色(Role)组成,角色之间的关系(Relation)和运作规则(Rule)。

image-20221121144908395

架构设计的主要目的是为了解决软件系统复杂度带来的问题。

1.3.2.1.1.2. 单机复杂度

计算机内部复杂度最关键的地方就是操作系统。计算机性能的发展本质上是由硬件发展驱动的,尤其是 CPU 的性能发展。著名的“摩尔定律”表明了 CPU 的处理能力每隔 18 个月就翻一番;而将硬件性能充分发挥出来的关键就是操作系统,所以操作系统本身其实也是跟随硬件的发展而发展的,操作系统是软件系统的运行环境,操作系统的复杂度直接决定了软件系统的复杂度。

多进程多线程虽然让多任务并行处理的性能大大提升,但本质上还是分时系统,并不能做到时间上真正的并行。解决这个问题的方式显而易见,就是让多个 CPU 能够同时执行计算任务,从而实现真正意义上的多任务并行。目前这样的解决方案有 3 种:SMP(Symmetric Multi-Processor,对称多处理器结构)、NUMA(Non-Uniform Memory Access,非一致存储访问结构)、MPP(Massive Parallel Processing,海量并行处理结构)。其中 SMP 是我们最常见的,目前流行的多核处理器就是 SMP 方案

1.3.2.1.1.3. 集群的复杂度

1、任务分配的意思是指每台机器都可以处理完整的业务任务,不同的任务分配到不同的机器上执行。

image-20221121150044602

2、任务分解

  • 简单的系统更加容易做到高性能

  • 可以针对单个任务进行扩展

而系统间的调用通道目前都是通过网络传输的方式,性能远比系统内的函数调用要低得多。

系统拆分某种程度上能提升业务处理性能,但提升性能也是有限的,不可能系统不拆分的时候业务处理耗时为 50ms,系统拆分后业务处理耗时只要 1ms,因为最终决定业务处理性能的还是业务逻辑本身,业务逻辑本身没有发生大的变化下,理论上的性能是有一个上限的,系统拆分能够让性能逼近这个极限,但无法突破这个极限。因此,任务分解带来的性能收益是有一个度的,并不是任务分解越细越好,而对于架构设计来说,如何把握这个粒度就非常关键了。

高性能增加机器目的在于“扩展”处理性能;高可用增加机器目的在于“冗余”处理单元。

存储高可用的难点不在于如何备份数据,而在于如何减少或者规避数据不一致对业务造成的影响。

通过冗余来实现的高可用系统,状态决策本质上就不可能做到完全正确

将不变的部分封装在一个独立的“稳定层”,将“变化”封装在一个“变化层”(也叫“适配层”)。这种方案的核心思想是通过变化层来隔离变化。

提炼出一个“抽象层”和一个“实现层”。如果说方案一的核心思想是通过变化层来隔离变化,那么方案二的核心思想就是通过实现层来封装变化

image-20221121151820436

1.3.2.1.2. 架构设计三原则

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

1.3.2.1.2.2. 简单原则宣言:“简单优于复杂”。

“复杂”在制造领域代表先进,在建筑领域代表领先,但在软件领域,却恰恰相反,代表的是“问题”。

1.3.2.1.2.3. 演化原则宣言:“演化优于一步到位”。

对于建筑来说,永恒是主题;而对于软件来说,变化才是主题。

软件架构设计其实更加类似于大自然“设计”一个生物,通过演化让生物适应环境,逐步变得更加强大:

1.3.2.1.3. 识别复杂度

将主要的复杂度问题列出来,然后根据业务、技术、团队等综合情况进行排序,优先解决当前面临的最主要的复杂度问题

1.3.2.1.4. 高性能架构模式

高性能数据库集群的第一种方式是“读写分离”,其本质是将访问压力分散到集群中的多个节点,但是没有分散存储压力;第二种方式是“分库分表”,既可以分散访问压力,又可以分散存储压力

1.3.2.1.4.1. 解决主从复制延迟有几种常见的方法:

  1. 写操作后的读操作指定发给数据库主服务器
  2. 读从机失败后再读一次主机
  3. 关键业务读写操作全部指向主机,非关键业务采用读写分离

业务分库指的是按照业务模块将数据分散到不同的数据库服务器

业务分库能够分散存储和访问压力,但存在的问题:无法join 操作问题、事务问题、成本问题

分表能够有效地分散存储压力和带来性能提升,但和分库一样,也会引入各种复杂性。

无法join、count()、order by

  • 垂直分表适合将表中某些不常用且占了大量空间的列拆分出去

  • 水平分表适合表行数特别大的表,有的公司要求单表行数超过 5000 万就必须进行分表,这个数字可以作为参考,但并不是绝对标准,关键还是要看表的访问性能

  • 路由

    水平分表后,某条数据具体属于哪个切分后的子表,需要增加路由算法进行计算,这个算法会引入一定的复杂性

范围路由:选取有序的数据列(例如,整形、时间戳等)作为路由的条件,不同分段分散到不同的数据库表中

Hash 路由:选取某个列(或者某几个列组合也可以)的值进行 Hash 运算,然后根据 Hash 结果分散到不同的数据库表中

配置路由:配置路由就是路由表,用一张独立的表来记录路由信息

1.3.2.1.4.2. NO SQL

常见的 NoSQL 方案分为 4 类。

K-V 存储:解决关系数据库无法存储数据结构的问题,以 Redis 为代表。

文档数据库:解决关系数据库强 schema 约束的问题,以 MongoDB 为代表。

列式数据库:解决关系数据库大数据场景下的 I/O 问题,以 HBase 为代表。

全文搜索引擎:解决关系数据库的全文搜索性能问题,以 Elasticsearch 为代表。

1.3.2.1.4.3. 缓存架构

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

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

缓存雪崩是指当缓存失效(过期)后引起系统性能急剧下降的情况。当缓存过期被清除后,业务系统需要重新生成缓存,因此需要再次访问存储系统,再次进行运算,这个处理步骤耗时几十毫秒甚至上百毫秒。

缓存雪崩的常见解决方法有两种:更新锁机制和后台更新机制。

未能获取更新锁的线程要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。

由后台线程来更新缓存,而不是由业务线程来更新缓存,缓存本身的有效期设置为永久,后台线程定时更新缓存。

缓存热点的解决方案就是复制多份缓存副本,将请求分散到多个缓存服务器上,减轻缓存热点导致的单台缓存服务器压力

1.3.2.1.5. 单服务器:PRC与TPC

PPC 是 Process Per Connection 的缩写,其含义是指每次有新的连接就新建一个进程去专门处理这个连接的请求,这是传统的 UNIX 网络服务器所采用的模型。

TPC 是 Thread Per Connection 的缩写,其含义是指每次有新的连接就新建一个线程去专门处理这个连接的请求

单服务器高性能的关键之一就是服务器采取的并发模型,并发模型有如下两个关键设计点:

  • 服务器如何管理连接

  • 服务器如何处理请求

I/O 模型:阻塞、非阻塞、同步、异步。 进程模型:单进程、多进程、多线程。

I/O 多路复用技术归纳起来有两个关键实现点:

  • 当多条连接共用一个阻塞对象后,进程只需要在一个阻塞对象上等待,而无须再轮询所有连接,常见的实现方式有 select、epoll、kqueue 等。
  • 当某条连接有新的数据可以处理时,操作系统会通知进程,进程从阻塞状态返回,开始进行业务处理。

Reactor 是非阻塞同步网络模型,因为真正的 read 和 send 操作都需要用户进程同步操作。这里的“同步”指用户进程在执行 read 和 send 这类 I/O 操作的时候是同步的,如果把 I/O 操作改为异步就能够进一步提升性能,这就是异步网络模型 Proactor。

image-20221121160549333

Proactor 可以理解为“来了事件我来处理,处理完了我通知你”。这里的“我”就是操作系统内核,“事件”就是有新连接、有数据可读、有数据可写的这些 I/O 事件,“你”就是我们的程序代码。

image-20221121160521801

CAP

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

BASE

BASEBASE 是指基本可用(Basically Available)、软状态( Soft State)、最终一致性( Eventual Consistency),核心思想是即使无法做到强一致性(CAP 的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性。

FMEA

FMEA(Failure mode and effects analysis,故障模式与影响分析)又称为失效模式与后果分析、失效模式与效应分析、故障模式与后果分析等,专栏采用“故障模式与影响分析”,因为这个中文翻译更加符合可用性的语境。FMEA 是一种在各行各业都有广泛应用的可用性分析方法,通过对系统范围内潜在的故障模式加以分析,并按照严重程度进行分类,以确定失效对于系统的最终影响。

在架构设计领域,FMEA 的具体分析方法是:

  • 给出初始的架构设计图。

  • 假设架构中某个部件发生故障。

  • 分析此故障对系统功能造成的影响。

  • 根据分析结果,判断架构是否需要进行优化。

image-20221121161455815

1.3.2.1.6. 可扩展架构

降级的目的是应对系统自身的故障,而熔断的目的是应对依赖的外部系统故障的情况。

1.3.2.1.6.1. 降级

降级的核心思想就是丢车保帅,优先保证核心业务。

1.3.2.1.6.2. 熔断

熔断是指按照规则停掉外部接口的访问,防止某些外部接口故障导致自己的系统处理能力急剧下降或者出故障。

1.3.2.1.6.3. 限流

降级是从系统功能优先级的角度考虑如何应对故障,而限流则是从用户访问压力的角度来考虑如何应对故障。限流指只允许系统能够承受的访问量进来,超出系统访问能力的请求将被丢弃。

  • 时间窗 : “固定时间窗”和“滑动时间窗”。
  • 桶算法 : “漏桶”和“令牌桶”。

漏桶算法的技术本质是总量控制,桶大小是设计关键,具体的优缺点如下:

1、突发大量流量时丢弃的请求较少,因为漏桶本身有缓存请求的作用。

2、桶大小动态调整比较困难(例如 Java BlockingQueue),需要不断的尝试才能找到符合业务需求的最佳桶大小。

3、无法精确控制流出速度,也就是业务的处理速度。

令牌桶算法的技术本质是速率控制,令牌产生的速率是设计关键,具体的优缺点如下:

1、可以动态调整处理速率,实现更加灵活。

2、突发大量流量的时候可能丢弃很多请求,因为令牌桶不能累积太多令牌。

3、实现相对复杂。

其实,令牌桶的“允许突发”实际上只是“允许一定程度的突发”,比如系统处理能力是每秒 100 TPS,突发到 120 TPS 是可以的,但如果突发到 1000 TPS 的话,系统大概率就被压垮了。所以处理秒杀 时高并发流量,还是得用漏桶算法

1.3.2.1.6.4. 扩展模式

面向流程拆分:将整个业务流程拆分为几个阶段,每个阶段作为一部分。

面向服务拆分:将系统提供的服务拆分,每个服务作为一部分。

面向功能拆分:将系统提供的功能拆分,每个功能作为一部分。

image-20221121164013124

1.3.2.1.6.5. 服务粒度

微服务拆分粒度的“三个火枪手”原则,即一个微服务三个人负责开发

基于业务逻辑拆分、基于可扩展拆分、基于可靠性拆分、基于性能拆分

image-20221122111250953

1.3.2.1.7. 架构设计文档

5W 指 Who、When、What、Why、Where。

Who:需求利益干系人,包括开发者、使用者、购买者、决策者等。

When:需求使用时间,包括季节、时间、里程碑等。

What:需求的产出是什么,包括系统、数据、文件、开发库、平台等。

Where:需求的应用场景,包括国家、地点、环境等,例如测试平台只会在测试环境使用。

Why:需求需要解决的问题,通常和需求背景相关]

[8C 指的是 8 个约束和限制,即 Constraints,包括性能 Performance、成本 Cost、时间 Time、可靠性 Reliability、安全性 Security、合规性 Compliance、技术性 Technology、兼容性 Compatibility]

4R 是指 4 个关键词:Rank,Role,Relation 和 Rule。

既然可以通过 4R 来定义软件系统的架构,那么按照 4R 架构定义的思路来画架构图也是很合情合理的,具体步骤如下:

第一步,明确 Rank:也就是说,不要事无巨细地把一个大系统的方方面面都在一张架构图中展现出来,而应该明确你要阐述的系统所属的级别(L0~L4),然后只描述这个级别的架构信息。

第二步,画出 Role:从不同的角度来分解系统,看看系统包含哪些角色,角色对应架构图中的区块、图标和节点等。

第三步,画出 Relation:有了角色后,画出角色之间的关系,对应架构图中角色之间的连接线,不同的连接线可以代表不同的关系。

第四步,最后画出 Rule:挑选核心场景,画出系统角色之间如何协作来完成某项具体的业务功能,对应系统序列图。

image-20221122152114176

results matching ""

    No results matching ""