Contents

架构基础学习

Contents

从0开始学架构

0. 开篇

架构设计的相关特性:

  • 架构设计思维和程序设计思维差异较大。架构设计的关键思维是判断和取舍,程序设计的关键思维是逻辑和底层实现
  • 缺少体系化的培训和训练机制
  • 程序员对架构设计理解存在很多误区

内容:

  • 架构基础
  • 高性能架构模式
  • 高可用架构模式
  • 可扩展架构模式

目标:

  • 清楚地理解架构设计相关的概念、本质、目的,避免架构师在实践过程中把握不住重点、分不清主次,眉毛胡子一把抓,导致架构设计变形或者“四不像” 。
  • 掌握通用的架构设计原则,无论是何种业务或技术,架构师在判断和选择的时候有一套方法论可以参考,避免架构设计举棋不定,或者拍脑袋式设计。
  • 掌握标准的架构设计流程,即使是刚开始做架构设计的新手,也能够按照步骤一步一步设计出合适的架构,避免某些步骤缺失导致错误的架构设计。
  • 深入理解已有的架构模式,做到能够根据架构特点快速挑选合适的模式完成架构设计,或者在已有的模式上进行创新,或者将已有的模式组合出新的架构。
  • 掌握架构演进和开源系统使用的一些技巧。

1. 架构到底是什么

1.1 三组概念

三组概念:

  • 系统与子系统。具有关联的个体按照一定规则组织起来完成个体所不能完成的工作,成为系统。子系统是更大系统中的一部分。
  • 模块(Module)与组件(component)。从业务逻辑拆分,得到的有程序和数据结构组成的软件组织成为模块。模块的接口表达了其功能。从屋里部署的角度拆分,得到的就是组件。划分模块的主要目的是职责分离,划分组件的目的是单元服用。模块与组件都是系统的组成部分,只是拆分角度不同。
  • 框架(Framework)与架构(Architecture)。框架是组件规范,提供基础功能的产品。架构是指软件系统的基础结构,创造这些基础结构的准则和描述。也就是说框架关注的是规范,架构关注的是结构

一个系统的架构只包含顶层这一层的架构,不包含下属子系统的架构。一个系统的架构从业务逻辑、物理部署不同角度看,有着不同的结构。

1.2 4R架构

软件架构指软件系统的顶层(Rank)结构,它定义了系统由哪些角色(Rule)组成,角色之间的关系(Relation)和运作规则(Rule)。通常Rank,Relation,Rule可以通过系统架构图展示,Rule可以通过系统序列图展示。 ../../arch/4R架构.png

1.3 架构设计的历史背景

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

../../arch/软件架构历史.png

1.4 架构设计的目的

架构设计的主要目的是为了解决软件系统复杂度带来的问题 设计一个系统时需要考虑: 性能、可扩展性、高可用、安全性、成本。


2. 复杂度来源

../../arch/架构复杂度来源.png

2.1 高性能

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

单机复杂度

操作系统为软件系统提供了运行环境,前者的复杂度也决定了后者的复杂度。操作系统由最初的手工操作,到批处理系统,然后进程的提出+分时复用,使用任务看起来像是并行执行,然后发展到多线程。最后发展到多核CPU。单机的复杂度来源于多进程、多线程、多核共享、通信、互斥等。

集群复杂度

通过增加机器来提升系统的性能。常见的方法:

  1. 任务分配

每台机器都可以处理完整的业务。随着业务越来越复杂,分配器集群和业务集群变得更加庞大。主要面临:如何将用户请求分配到分配集群的节点上,连接建立、检测、中断后处理。分配节点到业务节点如何分配。机器集群的状态管理、故障处理等问题。

  1. 任务分解 任务分配的方式中,每个业务节点拥有完整的业务逻辑。但是若是业务逻辑过于复杂,单机的性能会越来月拆,通过任务分配的方式收益将越来越小。此时可以将完整的业务拆分成若干独立的相对简单的子业务。 任务分解方式的理由:
  • 简单的子系统更容易做到高性能
  • 可以针对单子子系统进行扩展 但是并非任务分解越细越好,子系统之间的协作是有损的。

../../arch/集群架构变化.png

2.2 高可用

高可用:系统无中断地执行其功能的能力。 高可用实现方案本质都是通过"冗余"来实现的。高性能增加机器的目的在于扩展处理性能;高可用增加机器的目的在于冗余处理单元。

  1. 计算高可用。
  2. 存储高可用。存储高可用的难点不在于如何备份数据,而在于如何减少或者规避数据不一致对业务造成的影响。CAP定理表明,系统不可能同时满足一致性、可用性、容错性,最多满足其中的两个。
  3. 高可用状态决策。不论是计算高可用还是存储高可用,基础都是“状态决策”,即系统需要能够判断当前的状态是正常还是异常,并在异常时采取行动保证高可用。但是通过冗余来实现的高可用系统,状态决策本质上就不可能做到完全正确。
  • 独裁式。只存在单个决策者,不会出现决策混乱的情况,但是决策者自身出现问题时系统不可用。
  • 协商式。两个独立个体通过交流信息,根据规则进行决策。为防止两者信息交流出现异常,通常增加更多的连接。但是多个连接的选择也是个复杂的问题。
  • 民主式。多个独立个体通过投票的方式进行状态决策,按照多数取胜的规则来确定最终的状态。为了应对“脑裂”问题(因为连接问题,集群被分割为两个集群,两个集群各自投票,超出大于1个决策者),民主式决策一般采用“投票节点数必须超过系统节点数的一半”的规则。但是这种策略在节点真正故障时,若可用节点少于一半则陷入不可决策的状态。

2.3 可扩展性

可扩展性指,系统为了应对将来需求变化而提供的一种扩展能力,当有新的需求出现时,系统不需要或者需要少量的修改就可以支持,无须整个系统重构或者重建。系统架构设计具体良好可扩展性,应当具体两个基本条件:正确预测变化、完美应对变化。

  1. 预测变化 复杂度在于:不能每个设计点都考虑可扩展性;不能完全不考虑可扩展性;所有预测都存在出错的可能性。 2年法则:只预测2年内的可能变化,不要试图预测5年甚至10年的后的变化。
  2. 应对变化
  • 提炼稳定层和变化层。核心是通过变化层来隔离变化。一是变化层和稳定层如何拆分,二是两层之间的接口如何设计
  • 提炼抽象层和实现层。核心是通过实现层来隔离变化。

原则:事不过三,三则重构。1写2抄3重构。

2.4 低成本

低成本本质上与高性能和高可用是冲突的。低成本给架构设计带来的主要复杂度体现在,往往只有创新才能达到低成本目标。

2.5 安全

  • 功能安全,系统功能实现上存在漏洞。
  • 架构安全,传统的架构安全主要依靠防火墙。

2.6 规模

量变引起质变。一是功能越来越多,系统内部关联越来越复杂,二是数据越来越多,数据处理、备份、拆分越来越复杂。


3. 架构设计三原则

3.1 合适原则

合适优于业界领先。注意以下问题:

  1. 没那么多人,却想干那么多活,是失败的第一个主要原因。
  2. 没有那么多积累,却想一步登天,是失败的第二个主要原因。
  3. 没有那么卓越的业务场景,却幻想灵光一闪成为天才,是失败的第三个主要原因。

3.2 简单原则

简单优于复杂。KISS:Keep It Simple, Stupid!

3.3 演化原则

演化优于一步到位。

4. 架构设计流程

4.1 识别复杂度

架构设计的本质目的是为了解决软件系统的复杂性,所以在我们设计架构时,首先就要按照架构复杂度来源分析系统的复杂性。 将主要的复杂度问题列出来,然后根据业务、技术、团队等综合情况进行排序,优先解决当前面临的最主要的复杂度问题。

4.2 设计备选方案

高可用的主备方案、集群方案,高性能的负载均衡、多路复用,可扩展的分层、插件化等技术, 绝大部分时候我们有了明确的目标后,按图索骥就能够找到可选的解决方案。注意事项:

  1. 不要一心追求最优秀的方案。要遵循合适和简单原则。
  2. 备选方案3-5个为佳,方案之间要有差异性,不要只局限于已经熟悉的技术。
  3. 方案不要归于细致,关注技术选型而不是技术细节。

4.3 评估和选择备选方案


5. 高性能架构模式-高性能数据库集群

5.1 读写分离

../../arch/读写分离.png

概念: 将对数据库的读写操作分散到不同的节点上。主从节点都包含所有的业务数据。复杂度来源:主从复制延迟、分配机制。

复杂度来源——主从复制延迟

主从复制需要时间,尤其在大量数据更新时所需时间更长,这回带来一个问题,在主机写入后访问从机,此时读不到数据。 解决方案:

  • 在写入之后的读操作也访问主机。这种方式与具体业务强绑定,耦合比较严重。
  • 二次读取。在从机读取失败时读取主机,这种方式与业务无绑定,但是在某些场景下会对主机造成很大压力,例如黑客破解。
  • 关键业务读写指向主机,非关键业务采用读写分离。例如注册+登录访问主机,查看粉丝数、发文采用读写分离。

复杂度来源——分配机制

当读写操作区分开,按照规则分配到不同的节点上。两种方式:程序代码封装、中间件封装。

  • 程序代码封装是指在业务代码中实现一个管理连接、读写分离的中间层,用于读写数据库。该方法实现简单, 可以根据业务定制。但是每种语言都要重新实现一遍,不同业务之间也可能需要重新发开,复用程度有限。例如淘宝DDDL。
  • 中间件封装是指独立一套系统出来实现连接管理与读写分离。中间件需要能够探测数据库节点状态、支持标准查询语句、 高性能、支持多语言等。例如MySQL Proxy,MySQL Router,Atlas

可见中间件与程序代码封装复杂的得多,更推荐使用成熟的中间件或者程序代码封装。

5.2 分库分表

读写分离实现了对数据库访问的分散,但是没有实现存储分散。单个数据库存储数据量太大时,单节点读写、复制性能降低,数据安全性降低。 分库和分表是实现分散存储的两种方式。

不同的业务分库

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

  • join操作问题,分库后不同的数据不能像同一个库中使用join查询,需要在不同的库中查询多次。
  • 事务问题,同一个数据库中的不同的表可以在同一个事务中修改,业务分库后则无法通过数据库事务统一修改了。一些分布式事务的复杂度较高、性能较差。往往需要根据业务场景模拟实现事务功能。
  • 成本问题,分库之后往往需要在不同服务器上运行,加上主备,机器数上升。

同一业务分表

根据不同业务进行分库之后,同一个业务在数据规模较大时,单台数据库会存在性能瓶颈,需要进行分表。通常有垂直分表(不同的属性被切分开)、水平分表(不同的记录被切分开)两种方式。分表之后不同的子表,即便是不再分库也会有性能提升。

  1. 垂直分表。垂直分表之后,一条记录的完整信息被分到多个表中,需要进行多次查询才能获取完整结果。
  2. 水平分表。单表的记录超过千万时需要结合性能表现考虑是否进行分表。水平分表将所有记录拆分到多个表中,存在以下复杂度问题:
  • 路由,用于确定某条记录存在哪个表中.路由方法:
  1. 范围路由,选取有序的数据列作为路由条件。优点是随着数据增加可以平滑的扩充新表,缺点是数据分布不均匀,因为无法保证数据列能代表数据均匀程度。
  2. hash路由,选取一个或多个数据列计算hash,然后对总表数求余。优点是数据分布均匀,缺点是初始总表数不好选取,扩充困难。
  3. 配置路由,专门一个表记录着数据和库表之间的映射关系。优点是设计简单、扩充灵活,缺点是必须先查询到数据对应的库表才能去访问数据,多了一次查询。
  • join操作

    数据分散在多个表中,如果需要与其他表join,则实际上需要join多次,然后合并结果。

  • count操作

    一个是对多个表分别进行count,这种方式执行效率低。二是新建一张表,记录数据条数,但是每次插入删除时都需要更新这个记录表,增加了数据库的写压力。对于实时性要求不高的场景,可以采用定时更新记录表的方式。

  • order by操作

    水平拆表后,排序无法在一个表中完成,需要多次排序后汇总。

实现方法

代码封装、中间件。实现更为复杂,需要识别SQL的读写操作,还需要针对order by、count、group by风操作进行不同的处理。

5.3 高性能NoSQL(Not Only SQL)

关系型数据库的缺点:

  • 无法存储数据结构,例如无法存储列表,只能存储多行记录。
  • schema扩展不方便,新增一列时需要重新执行DDL语句。
  • 大数据场景下IO较高,读取的时候是读取一整行,即便只需要其中一列数据。
  • 全文搜索能力较弱。

NoSQL牺牲了关系型数据库的ACID中的某些特性,解决了上述问题。NoSQL方案:

  1. K-V存储(解决无法存储数据结构的问题) 例如redis。存储的结构多样,支持多种数据操作,redis事务只能保证一致性、隔离性,无法保证原子性、持久性。
  2. 文档数据库(解决关系型数据库schema约束问题) 例如MongoDB。文档数据库最大特点是No-schema,可以存储和读取任一的数据。No-schema的优点:新增字段简单、历史数据不会出错、很容易存储复杂数据(例如电商场景下,每个商品的数据都不太一样)。缺点是不支持事务,无法实现关系型数据库的join操作。
  3. 列式数据库(解决大数据场景下的I/O问题) 例如HBase。优点:同时读取多个列时效率高(列数据是按照行存储的)、一次性完成对多个列的写操作、更高的压缩率(列数据相似程度更高)。可见,列式存储很依赖具体的业务场景(离线海量数据统计)。如场景经常发生多个列的更新,此时列式存储会成为劣势,因为列数据存储在磁盘不连续的空间上。
  4. 全文搜索引擎(解决关系型数据库全文搜索性能问题) 例如ES。正排适用于根据文档查询文档的具体内容,倒排适用于根据关键词和查询文档内容。

5.4 高性能缓存架构

上述我们总结了如何提高存储性能,但是某些场景下单纯依赖存储系统的性能提升是不够的:

  1. 重计算型的任务,例如每次都要执行大量count
  2. 读多写少的数据,大量select语句对数据库依然存在较大压力

缓存是为了弥补存储系统的不足,将可重复使用的数据放入到内存中,一次生成多次使用。 缓存架构的设计要点:

  1. 缓存穿透,去查缓存但是缓存无结果。通常是以下两种情况导致的:
  • 存储数据不存在,可以缓存空结果或默认值解决
  • 缓存数据的生成消耗大量的时间或资源,缓存失效时压力给到存储系统。例如电商的分页数据,正常业务请求基本没有问题,但是爬虫遍历会对缓存造成影响,性能下降。通常识别恶意访问后封禁可以缓解。
  1. 缓存雪崩,缓存失效时,生成缓存期间大量请求直接访问数据库,另外不同的请求在查缓存无结果时都会重新计算缓存,需要访问数据库,系统性能下降。
  • 更新锁

对缓存更新操作加锁,只是用一个线程进行计算更新,降低对数据库的压力,通常需要使用分布式锁。

  • 后台更新

业务线程不进行缓存更新,缓存时间设置永久,后台线程定期更新缓存。但是当缓存满时需要淘汰数据,淘汰数据到重新更新该数据期间,业务线程查询到的都是空数据,可能会出现问题。解决方案:

  • 后台线程除了定期更新缓存,还需要定期访问缓存,发现数据被淘汰后更新缓存。
  • 业务线程发现缓存失效后,通过消息队列发送消息通知后台线程更新缓存。
  1. 缓存热点

大量请求都打到同一台缓存服务器,对缓存服务器造成很大压力。解决方案可以是多分缓存副本,将请求分散到多个缓存服务器。需要注意不同的缓存副本不要设置相同的过期时间。


6. 高性能架构模式-单服务器高性能模式

6.1 PPC与TPC

单服务器高性能的关键之一是服务器采用的并发模型:服务器如何管理连接、服务器如何处理请求。

PPC-Process Per Connection

每次有新连接时,就从父进程创建一个子进程用于处理这个连接上的请求。、 优点: 实现简单。 缺点:

  • fork成本高
  • 父子进程通信成本高
  • 支持的并发连接数有限。若每个连接存活时间较长,则存在多个进程,OS调度和切换频繁,系统压力会越来越大。

prefork

相比于PPC是提前创建多个子进程,共同监听同一个socket。linux2.6之前,存在"惊群"现象,即虽然只有一个子进程accept成功,但是多个子进程都会被唤醒,导致了不必要的进程调度和上下文切换。linux2.6之后,OS层面解决了这个问题。

TPC-Thread Per Connection

新连接建立时有专门的线程处理这个连接上的请求。相比于PPC更加轻量,但是也引入了新的问题。

  • 线程创建也是有成本的
  • 线程间共享和互斥会引入复杂度,可能引发死锁
  • 多线程存在互相影响,单个线程挂了,所在进程退出

prethread

预先创建线程,然后才开始接受请求。主进程可以accept,然后将连接交给某个线程处理;子进程也可以accept,最终只有一个线程accept成功。

6.2 Reactor与Proactor

Reactor

无论是PPC还是TPC,连接结束后进程/线程就被销毁,其实是很大的浪费。自然而然的想法是资源复用,引入进程/线程池,将连接分配给进程/线程,一个进程/线程可以处理多个连接的作业。为了高效的处理多个连接,进程/线程需要将read方法改为非阻塞,然后轮询多个连接,这种方式实现不太优雅且轮询消耗CPU,如果处理的连接数很多,性能较低。如果有通知机制,则很好的解决这个问题,连接上有数据时再去处理,这就是I/O多路复用技术。两个关键点:

  • 多个连接共用一个阻塞对象时,进程/线程只需要在这个阻塞对象上等待,无须轮询,例如select、epoll、kqueue
  • 当某个连接有数据可以处理时,OS会通知进程/线程,进程从阻塞状态返回,进行业务处理。

Reactor=I/O多路复用+线程池,来了一个事件就有对应的反应。Reactor模式,IO多路复用统一建通事件,收到事件后分配给资源池,根据Reactor、资源池的情况,有如下经典方案:

  • 单Reactor单进程/线程

该模式没有进程通信、进程竞争等问题,实现简单。但是性能存在瓶颈,一是无法利用多核CPU,二是单进程在处理业务逻辑时无法处理其他连接事件。该模式适用于业务处理非常快的场景,例如Redis

  • 单Reactor多线程

单Reactor负责监听和读写socket,多线程处理业务逻辑。

  • 多Reactor多进程/线程

主reactor负责连接建立,子reactor负责监听和处理主Reactor传过来的连接事件。

../../arch/reactor模式.png

Proactor

Reactor是非阻塞同步网络模式,因为socket的read和write是在用户进程中同步操作的。接下来自然而然想到的是read,write的异步操作。Proactor即是这种有事件(新连接、有数据可读、可写)了OS内核先进行处理,处理完毕之后通知用户进程/线程。

../../arch/proactor.png

Initiator创建Proactor和Handler,并注册到内核;Asynchronous Operation Processor负责处理注册请求并完成IO操作,然后通知Proactor;Proactor根据事件的类型回调不同的Handler进行业务处理。


7. 高性能架构模式-负载均衡

通过扩展机器的方式增加集群性能的方法,复杂度来源:需要增加一个任务分配器、选择合适的分配算法。

7.1 负载均衡分类和架构

分类

  • DNS负载均衡,一般用于地域级别的均衡。优点是简单、成本低、就近访问。缺点是更新不及时、扩展性差、分配策略简单。
  • 硬件负载均衡,通过单独的硬件设备实现负载均衡功能,例如A10、F5。优点是功能、性能强大(100W以上并发)、支持安全防护,缺点是价格昂贵、扩展能力差。
  • 软件负载均衡,通过负载均衡软件来实现负载均衡功能,例如Nginx、LVS,性能可以达到W-几十W/S。优点是简单、便宜、灵活,缺点是性能一般,步军被防火墙和防DDoS攻击等安全功能。

典型架构

  • DNS服务器用于地域级别
  • 硬件负载均衡用于同一地域内的不同集群
  • 软件负载均衡用于统一集群内的不同服务器

7.2 负载均衡算法

  • 轮询
  • 加权轮询,解决了不同服务器处理能力差异的问题。
  • “负载"最低优先,在服务器角度衡量服务器负载,优先分配给负载最低的服务器。服务器负载可能包括连接数、CPU负载、平均负载等指标。该方式要求负载均衡系统能够感知到负载节点的状态,理论上能够解决轮询方式的问题。
  • “性能"最低优先,在客户端角度衡量服务器状态,优先分配给处理速度最快的节点。
  • Hash类,源地址hash,ID hash。

8. 高可用架构模式-CAP

8.1 CAP理论

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

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

一致性: 对于某个指定的客户端来说,读操作保证能够返回最新的写操作结果。 可用性: 非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应) 分区容灾性: 当出现网络分区(丢包、网络故障、连接中断等原因)后,系统能够继续“履行职责”

在分布式系统中,基本上P选项是必须的,因此通常由CP,AP系统。

  • CP系统,当出现网络分区时,节点直接数据不一致,对于未更新的节点,为了保证强一致性,此时客户端会返回error。
  • AP系统,当出现网络分区时,节点直接数据不一致,对于未更新的节点,为了保证强可用性,此时客户端会返回未更新数据。

8.2 CAP、ACID、BASE

  1. CAP细节
  • CAP关注的粒度是数据,而不是整个系统。
  • CAP是忽略网络延迟的,意味着完美的CP场景是不存在的(数据复制延迟导致不满足一致性)。
  • 在系统正常运行的情况下,是能够同时满足CA的,CP AP是考虑系统发生网络分区。
  • 被放弃的特性不代表什么都不做,需要能够在分区结束后恢复。例如分析系统日志、最后修改优先等策略。
  1. ACID 数据库为了保证事务的正确性提出的理论。
  • Atomicity(原子性), 一个事务中的操作要么全部完成要么全部不完成,不存在中间状态。
  • Consistency(一致性),事务开始和结束后,数据库的完整性没有被破坏。
  • Isolation(隔离性),多个事务并发执行时,一个事务所做的修改对其他事务是不可见的。隔离级别分为:读未提交、读提交、可重复读、串行化。
  • Durability(持久性),事务提交后对数据的修改是永久的,即便系统故障也不会丢失。
  1. BASE AP方案的延申。

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

  • 基本可用,分布式系统出现故障时,允许损失部分可用性,保证核心可用。
  • 软状态,允许系统存在一定时间的软状态。
  • 最终一致性,系统中所有副本经过一定时间后,最终能够打到一致的状态。

9. 高可用架构模式-FMEA分析方法

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

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

FMEA分析内容:

  1. 功能点,从用户角度来看的功能点,而不是模块功能点,例如登录、注册功能点,而不是缓存、存储这些模块功能点。
  2. 故障模式。故障描述要尽可能精准,量化描述。例如mysql响应时间达到5s。
  3. 故障影响。描述功能点具体受到的影响,常见的影响有:功能点偶尔不可用、功能点完全不可用、部分用户功能点不可用、功能点响应缓慢、功能点出错等。
  4. 严重程度。业务角度的影响程度。严重程度 = 功能点重要程度 × 故障影响范围 × 功能点受损程度
  5. 故障原因。不同的故障原因发生概率不相同;不同的故障原因检测手段不一样;不同的故障原因的处理措施不一样
  6. 故障概率。指的是引发故障的因素发生的概率,例如磁盘损坏的概率。
  7. 风险程度。风险程度 = 严重程度 × 故障概率
  8. 已有措施。针对具体的故障原因,系统现在是否提供了某些措施来应对,包括:检测告警、容错、自恢复等
  9. 规避措施。降低故障发生概率的技术或者管理手段
  10. 解决措施。为了解决问题的技术手段。优先解决措施,但通常只能规避。
  11. 后续规划。综合前面的分析,就可以看出哪些故障我们目前还缺乏对应的措施,哪些已有措施还不够,针对这些不足的地方,再结合风险程度进行排序,给出后续的改进规划

10. 高可用存储架构

10.1 双机架构

存储高可用的本质是通过将数据复制到多个存储设备,通过数据冗余的方式实现高可用,复杂度来源主要是复制延迟和中断导致的数据不一致问题。

  1. 主备复制

内部系统使用较多,如人员管理系统.

优点:简单。客户端无需知道备机的存在;主备之间只是进行数据复制 缺点:备机不能提供读写操作;故障后需要人工切换,切换效率低

  1. 主从复制 写少读多的业务使用较多

优点:相比于主备,从机可以提供读操作,在主机故障时,部分读相关的业务可以正常运行 缺点:客户端需要感知从机的存在;从机提供读操作,由于复制延时或中断,从机数据可能和主机不一致;故障需要人工干预

../../arch/主备主从架构.png

  1. 双机切换 双机切换是在主备/主从的基础上增加了自动切换的能力.复杂度来源:双机之间的状态判断;切换决策(切换时机,切换策略,自动程度);数据冲突解决,故障发生时,新主机的数据可能与旧主机数据冲突(例如重复)

常见架构:互联式,中介式(中介可以使用开源组件: 如Zookeeper,Keepalived),模拟式

../../arch/双机切换.png

  1. 主主复制

两台都是主机,不需要切换,主机之间相互复制数据,客户端可以连接任一机器进行读写. 采用该架构需要保证数据能够进行双向复制,一般是临时性,可覆盖,可丢失的数据场景.

10.2 数据集群与数据分区

双机架构中假设式主机能够完全存储所有数据切处理能力正常,在数据较多时往往是不成立的.

数据集群

  1. 数据集中集群

全部数据仍然存储在主机上,存在多个备机(从机).复杂度来源:主机如何将数据复制给多个备(从)机,如何处理复制数据不一致的问题;备机如何检测主机状态;如何进行主机升级

  1. 数据分散集群

多台服务器存储数据,每台服务器有自己的备机.分散集群的复杂度需要考虑:均衡性,数据分布均衡;容错性,存储节点故障时如何将故障节点的数据分配给其他节点;可伸缩性,节点数量变化时如何进行数据迁移

数据分区

按照地理位置进行数据分区,应对极端情况下地域级别的数据损坏. 数据分区的几个考虑因素:

  • 数据规模,数据量越大,分区规则会越复杂
  • 分区规则,与业务相关,洲际分区,城市分区等
  • 复制规则.集中式,不同地域公用同一个数据备份分区;互备式,存储分区之间相互备份其他分区的数据;独立式,每个分区拥有自己独立的备份分区.

../../arch/数据分区.png


11. 计算高可用

计算高可用架构的设计复杂度主要体现在任务管理方面,即当任务在某台服务器上执行失败后,如何将任务重新分配到新的服务器进行执行

  1. 主备架构

与存储高可用的主备架构类似,但是主备之间不需要进行数据复制,更为简单.根据备机的状态又分为:

  • 冷备,备机上的服务式启动的,但是没有接入业务系统.故障发生时需要手动启动业务系统,并将任务分配器的请求切换到备机.
  • 热备,备机上的业务系统已经启动,但是没有对外提供服务,故障发生时只需将任务分配器的请求切换到备机.

缺点是状态判断和切换需要人工介入.

  1. 主从

从节点也可以执行计算任务.架构与存储高可用的主从类似.

  1. 集群

同样是为了解决主从(主备)架构中需要人工介入的问题, 集群方案目的是自动完成切换.

  • 对称集群,每个节点都是相同的,都可以执行计算任务.又称为负载均衡集群. 正常情况下,任务分配器按照某种策略将请求分发给各个节点,当故障发生时,任务分配器熔断故障节点,直到节点恢复.关键点在于任务分配策略;节点状态检测

  • 非对称集群,不同服务器节点角色不同,承担不同的任务。此时集群需要能够区分不同节点的角色;任务分配器需要能够根据任务类型分发到正确节点;角色故障时需要进行角色转移。此时的任务分配和角色分配更加复杂。


12. 业务高可用-异地多活

极端情况下,某地区的所有服务器可能发生故障,而从备份系统恢复需要消耗较长的时间,且备份系统平时不提供服务,可能存在意想不到的问题。为了解决这类问题,需要进行异地多活的设计。 异地多活的系统需要满足:

  • 正常情况下,用户无论访问哪一个地点的业务系统,都能够得到正确的业务服务。
  • 某个地方业务异常的时候,用户访问其他地方正常的业务系统,能够得到正确的业务服务。

12.1 架构模式

  • 同城异区,同城异区指的是将业务部署在同一个城市不同区的多个机房。应对机房级别的故障。
  • 跨城异地,跨城异地指的是业务部署在不同城市的多个机房,而且距离最好要远一些。应对极端的城市级别故障。但是距离上会带来传输延迟的问题,引起数据不一致。常用于一致性要求不高的业务,如登录、新闻网站等。
  • 跨国异地,跨国异地指的是业务部署在不同国家的多个机房.不同地域之间数据传输时间更长,已经不能满足异地多活系统要求的第一条。通常是应用:为不同地区的用户提供服务;为只读不写的一致性要求不高的业务服务。

12.2 设计技巧

核心思想: 采用多种手段,保证绝大部分用户的核心业务异地多活。 同城异区,通过高速网络连接,基本等同于一个机房。跨国异地,多用于服务不同区域用户以及只读业务。而跨城异地,本身存在传输延时,同时又要满足异地多活的要求,架构设计上较复杂,下面以跨城异地介绍异地多活的设计技巧。

  1. 保证核心业务的异地多活 并非所有的业务逻辑都要实现异地多活。分析业务逻辑对整体业务的影响,优先保证核心业务的异地多活。
  2. 保证核心数据的最终一致性 并非所有的数据都要实时同步,同时为了加快数据同步的速度,建议搭建高速网络、只同步核心业务数据、保证最终一致性不保证实时一致性。
  3. 多种手段同步数据 存储系统本身自带的同步功能,在某些场景下是无法满足业务需要的,例如MySQL的单线程复制,redis的重启dump,有可能导致复制时间较长。
  • 消息队列方式
  • 二次读取,首次读取本地区机房内数据,读取不到时访问其他机房数据。
  • 存储系统同步方式,利用存储系统自身的数据复制逻辑。
  • 回溯读取方式,取数据时计算数据所处的位置,直接向数据源发起请求。
  • 重新生成数据
  1. 只保证大部分用户的异地多活

12.3 设计步骤

  1. 业务分级 分级标准:访问量大的业务、核心业务、产生大量收入的业务,通过业务分级确定核心业务
  2. 数据分类 识别出核心业务之后,需要分析核心业务相关的数据特征。数据特征包含:
  • 数据量,包含总的数据量和数据的增删改的量,数据量越大,异地同步的成本越大,同步方案选择时需要考虑数据量。
  • 唯一性,异地机房产生的同类数据是否要保证唯一
  • 实时性,实时性要求越高,同步方案越复杂
  • 可丢失性,数据是否可丢失
  • 可恢复性,可恢复性指数据丢失后,是否可以通过某种手段进行恢复
  1. 数据同步 根据数据特性,选择同步方案。
  • 存储系统同步,最简单,缺点是这类同步方案都是通用的,无法针对业务数据特点做定制化的控制
  • 消息队列,消息队列同步适合无事务性或者无时序性要求的数据
  • 重复生成,适用于例如缓存数据、session等数据
  1. 异常处理 异常处理主要有以下几个目的:问题发生时,避免少量数据异常导致整体业务不可用;问题恢复后,将异常的数据进行修正;对用户进行安抚,弥补用户损失。

多通道同步

多通道同步的含义是采取多种方式来进行数据同步,其中某条通道故障的情况下,系统可以通过其他方式来进行同步,这种方式可以应对同步通道处故障的情况。多通道同步设计的方案关键点有:

  • 一般情况下,采取两通道即可,采取更多通道理论上能够降低风险,但付出的成本也会增加很多;
  • 数据库同步通道和消息队列同步通道不能采用相同的网络连接,否则一旦网络故障,两个通道都同时故障;可以一个走公网连接,一个走内网连接;
  • 需要数据是可以重复覆盖的,即无论哪个通道先到哪个通道后到,最终结果是一样的。例如,新建账号数据就符合这个标准,而密码数据则不符合这个标准。

同步和访问结合

异地之间进行数据同步的同时,也要求异地能够访问其他地域的数据。设计关键点:

  • 接口访问通道和数据库同步通道不能采用相同的网络连接,不能让数据库同步和接口访问都走同一条网络通道,可以采用接口访问走公网连接,数据库同步走内网连接这种方式。
  • 数据有路由规则,可以根据数据来推断应该访问哪个机房的接口来读取数据。例如,有 3 个机房 A、B、C,B 机房拿到一个不属于 B 机房的数据后,需要根据路由规则判断是访问 A 机房接口,还是访问 C 机房接口。
  • 由于有同步通道,优先读取本地数据,本地数据无法读取到再通过接口去访问,这样可以大大降低跨机房的异地接口访问数量,适合于实时性要求非常高的数据。

日志记录

日志记录主要用于用户故障恢复后对数据进行恢复.日志可以保存在数据库服务器上、独立的日志系统、异地存储,用于应对不同的故障级别。

用户补偿

无论采用什么样的异常处理措施,都只能最大限度地降低受到影响的范围和程度,无法完全做到没有任何影响。例如,双同步通道有可能同时出现故障、日志记录方案本身日志也可能丢失。我们可以采用人工的方式对用户进行补偿,弥补用户损失,培养用户的忠诚度


13. 应对接口级故障

接口级故障的典型表现就是,系统并没有宕机、网络也没有中断,但业务却出现问题了,例如业务响应缓慢、大量访问超时和大量访问出现异常。 解决接口级故障的核心思想和异地多活基本类似,都是优先保证核心业务和优先保证绝大部分用户。常见的应对方法有四种,降级、熔断、限流和排队。

13.1 降级

降级指系统将某些业务或者接口的功能降低,可以是只提供部分功能,也可以是完全停掉所有功能。实现上分为:系统后门降低、独立的降级系统

13.2 熔断

熔断是指按照规则停掉外部接口的访问,防止某些外部接口故障导致自己的系统处理能力急剧下降或者出故障。降级的目的是应对系统自身的故障,而熔断的目的是应对依赖的外部系统故障的情况。熔断设计需要考虑:统一的API调用层统计和采样;选取熔断阈值。

13.3 限流

限流是从用户访问压力的角度来考虑如何应对故障。

  • 基于请求限流,如限制请求总量、限制单位时间内的访问量。选择阈值时通常需要对系统进行压测,根据压测结果选择阈值。
  • 基于资源限流, 基于资源限流是从系统内部考虑的,也就是找到系统内部影响性能的关键资源,对其使用上限进行限制。例如连接数、句柄数、线程数、请求队列等。关键在于如何确定关键资源和阈值。

限流算法

  1. 固定时间窗 ../../arch/窗口限流.png

统计固定周期内的请求量,超出阈值后进行限制。但是在周期临界点存在问题,在跨临界点时有可能超过阈值,但是分布在两个窗口内,统计到每个窗口中不会超过阈值。

  1. 滑动时间窗

两个统计周期部分重叠,从而避免短时间内的两个统计点分属不同的时间窗的情况。

  1. 漏桶

利用模拟的桶来临时存储一些东西,根据存储的东西可以分为漏桶和令牌桶。漏桶中放的是请求,业务处理单元从桶中拿请求,桶满时则丢弃新请求。

../../arch/漏桶限流.png

特点:流入速度不固定;匀速流出,业务处理单元的处理能力有限,流出速度为最大处理速度;桶满丢弃请求(桶的大小与业务处理速度没有绝对关系,例如桶可以缓存100w请求)

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

  • 突发大量流量时丢弃的请求较少,因为漏桶本身有缓存请求的作用。
  • 桶大小动态调整比较困难(例如 Java BlockingQueue),需要不断的尝试才能找到符合业务需求的最佳桶大小。
  • 无法精确控制流出速度,也就是业务的处理速度。

适用场景:漏桶算法主要适用于瞬时高并发流量的场景(例如刚才提到的 0 点签到、整点秒杀等)。在短短几分钟内涌入大量请求时,为了更好的业务效果和用户体验,即使处理慢一些,也要做到尽量不丢弃用户请求。

  1. 令牌桶

令牌桶中存放的是令牌,业务系统首先要从桶中拿到令牌才能处理请求,拿不到则不处理。令牌桶算法的技术本质是速率控制,令牌产生的速率是设计关键,具体的优缺点如下:

  • 可以动态调整处理速率,实现更加灵活。
  • 突发大量流量的时候可能丢弃很多请求,因为令牌桶不能累积太多令牌。
  • 实现相对复杂。

适用场景:一种是需要控制访问第三方服务的速度,防止把下游压垮,例如支付宝需要控制访问银行接口的速率;另一种是需要控制自己的处理速度,防止过载,例如压测结果显示系统最大处理 TPS 是 100,那么就可以用令牌桶来限制最大的处理速度。

../../arch/令牌桶.png

对于秒杀这种高并发的场景,使用漏桶更加合适,应为漏桶会尽可能的缓存请求到桶中,而不是直接丢弃。令牌桶在高并发时,放入速度不可能远超处理速度,会出现大量请求直接丢弃。

13.4. 排队

排队实际上是限流的一个变种,限流是直接拒绝用户,排队是让用户等待一段时间。首先缓存用户请求,然后按照一定规则发给业务系统处理。


14. 可扩展架构

软件系统与硬件和建筑系统最大的差异在于软件是可扩展的。难点体现在如何以最小的代价去扩展系统。如何避免扩展时改动范围太大,是软件架构可扩展性设计的主要思考点。 可扩展的基本思想:拆,就是将原本大一统的系统拆分成多个规模小的部分,扩展时只修改其中一部分即可,无须整个系统到处都改,通过这种方式来减少改动范围,降低改动风险。 常见的拆分思路有如下三种(流程>服务>功能),不同的拆分方式,本质上决定了系统的扩展方式:

  • 面向流程拆分:将整个业务流程拆分为几个阶段,每个阶段作为一部分。(分层架构)
  • 面向服务拆分:将系统提供的服务拆分,每个服务作为一部分。(SOA、微服务)
  • 面向功能拆分:将系统提供的功能拆分,每个功能作为一部分。(微内核架构)

14.1 分层架构

分层架构又称为N层架构,通常N至少是2层,例如C/S、B/S架构,常见的是3层架构,如MVC、MVP架构。 无论采取何种分层维度,分层架构设计最核心的一点就是需要保证各层之间的差异足够清晰,边界足够明显,让人看到架构图后就能看懂整个架构。分层架构的本质在于隔离关注点(Separation of concerns,SOC),每个层中的组件只处理本层的逻辑。分层架构的缺点:

  • 分层结构的代价就是冗余,也就是说,不管这个业务有多么简单,每层都必须要参与处理,甚至可能每层都写了一个简单的包装函数
  • 分层架构另外一个典型的缺点就是性能,因为每一次业务请求都需要穿越所有的架构分层,有一些事情是多余的,多少都会有一些性能的浪费

注:封层是需要保证层与层之间的依赖是稳定的,才能真正支撑快速扩展。

C/S、B/S架构

划分的对象是整个业务系统,划分的维度是用户交互,即将和用户交互的部分独立为一层,支撑用户交互的后台作为另外一层。

MVC架构

划分的对象是单个业务子系统,划分的维度是职责,将不同的职责划分到独立层,但各层的依赖关系比较灵活。MVC架构的核心是分离关注点。

Model: 负责存储中心数据和对数据进行处理 Controler: 负责接受用户输入,传递给Model进行处理,输出处理结果给用户 View: 负责向用户展示数据

MVP架构是MVC架构的演变形式,Presenter层负责与Model层交互,View不直接访问Model层。

逻辑分层架构

划分的对象可以是单个业务子系统,也可以是整个业务系统,划分的维度也是职责。虽然都是基于职责划分,但逻辑分层架构和 MVC 架构、MVP 架构的不同点在于,逻辑分层架构中的层是自顶向下依赖的

14.2 Service Oriented Architecture,SOA

SOA中3个关键概念:

  • 服务,所有业务功能都是一项服务,对外提供开发的能力
  • Enterprise Service Bus, ESB,企业服务总线。将不同的服务连接起来,由于各个独立的服务是异构的,没有统一的标准,SOA使用ESB来屏蔽异构系统的不同接口。
  • 松耦合, 各个服务独立运行,减少各个服务之间的依赖和影响。

SOA 解决了传统 IT 系统重复建设和扩展效率低的问题,但其本身也引入了更多的复杂性。SOA 最广为人诟病的就是 ESB,ESB 需要实现与各种系统间的协议转换、数据转换、透明的动态路由等功能。

14.3 微服务

微服务与SOA的关系

长存在以下几个观点:

  • 微服务是SOA的实现方式
  • 微服务是去掉ESB之后的SOA,ESB改为轻量级的HTTP
  • 微服务是一种和SOA相似但本质上不同的架构理念,本质上不同的地方在于几个核心理念的差异:是否有 ESB、服务的粒度、架构设计的目标等

服务粒度: SOA服务粒度相比于微服务更粗一些 服务通信: SOA采用的ESB作为服务间通信的关键组件,ESB负责服务定义、路由、消息转换、消息传递。微服务采用了更轻量级的协议和格式,如HTTP、RPC,仅作消息传递。 服务交付: SOA更多的是兼容已有的系统,对服务交付没有特殊要求;微服务的架构理念是快速交付,响应的要采用自动化测试、持续集成、自动化部署等敏捷开关相关的实践。 应用场景: SOA 更加适合于庞大、复杂、异构的企业级系统,这也是SOA诞生的背景。这类系统的典型特征就是很多系统已经发展多年,采用不同的企业级技术,有的是内部开发的,有的是外部购买的,无法完全推倒重来或者进行大规模的优化和重构。因为成本和影响太大,只能采用兼容的方式进行处理,而承担兼容任务的就是 ESB。微服务更加适合于快速、轻量级、基于Web的互联网系统,这类系统业务变化快,需要快速尝试、快速交付;同时基本都是基于Web,虽然开发技术可能差异很大(例如,Java、C++、.NET等),但对外接口基本都是提供HTTP RESTful 风格的接口,无须考虑在接口层进行类似SOA的ESB那样的处理。

微服务陷阱

  1. 服务划分过细,服务间关系复杂 服务划分过细,单个服务的复杂度确实下降了,但整个系统的复杂度却上升了,因为微服务将系统内的复杂度转移为系统间的复杂度了,从理论的角度来计算,n 个服务的复杂度是 n×(n-1)/2。

  2. 服务数量太多,团队效率急剧下降

  3. 调用链太长,性能下降,问题定位困难

  4. 没有自动化支撑(自动化测试、部署、监控),无法快速交付

  5. 没有服务治理,微服务数量多了后管理混乱。包括服务路由、服务故障隔离、服务注册与发现

14.4 微服务最佳实践-方法篇

服务粒度

在微服务设计和开发阶段,一个微服务拆分粒度可以考虑"三个火枪手"原则,即一个服务三个人负责开发。

拆分方法

  1. 基于业务逻辑拆分 将系统中的业务模块按照职责范围识别出来,每个单独的业务模块拆分为一个独立的服务。在拆分时可以考虑"三个火枪手"原则。

  2. 基于可扩展拆分 将系统中的业务模块按照稳定性排序,将已经成熟和改动不大的服务拆分为稳定服务,将经常变化和迭代的服务拆分为变动服务。 稳定服务的拆分粒度可以大一些,变动服务则要细一些。这种拆分可以提升项目的迭代效率。

  3. 基于可靠性拆分 将系统中的业务模块按照优先级排序,将可靠性要求高的核心服务和可靠性要求低的非核心服务拆分开来,然后重点保证核心服务的高可用。这种拆分方式:避免非核心服务故障影响核心服务、核心服务的高可用方案可以更简单、降低高可用成本。

  4. 基于性能拆分 将性能要求高或者性能压力大的模块拆分出来,避免性能压力大的服务影响其他服务。

基础设施

../../arch/微服务基础设施.png

  • 服务发现、服务路由、服务容错:这是最基本的微服务基础设施。
  • 接口框架、API 网关:主要是为了提升开发效率,接口框架是提升内部服务的开发效率,API 网关是为了提升与外部服务对接的效率。
  • 自动化部署、自动化测试、配置中心:主要是为了提升测试和运维效率。
  • 服务监控、服务跟踪、服务安全:主要是为了进一步提升运维效率。

14.5 微服务最佳实践-微服务基础设施

自动化测试

微服务架构下服务数量上升,同时要求快速交付,若依赖人工回归,工作量大、效率低,必须通过自动化测试来完成绝大部分测试回归。包括: 代码级别的单元测试、单个系统集成测试、系统间的接口测试。

自动化部署

服务数量增加吗,同时部署频率提升,若是依赖人工部署工作量大且容易出错。自动化部署系统包括: 版本管理、资源管理、部署操作、回退

配置中心

手动操作修改配置在节点较多时工作量大且容易出错,需要一个统一的配置中心来管理所有微服务节点的配置。包括:版本管理、增删改查配置、节点管理、配置同步、配置推送等。

接口框架

出了统一接口协议外,也需要约定接口数据格式规范。一般以包的形式提供给微服务使用,进行协议的解析。

API网关

API网关,负责外部系统的访问操作。API网关是外部系统访问的接口,所有的外部系统接⼊系统都需要通过API网关,主要包括接入鉴权(是否允许接入)、权限控制(可以访问哪些功能)、传输加密、请求路由、流量控制等功能。

服务发现

微服务节点众多且节点很可能经常发生扩展和隔离,人工维护这项信息是变焦繁琐和低效的。服务发现完成节点的自动发现、自动隔离。实现方式: 自理式、代理式 自理式: 自理式结构就是指每个微服务自己完成服务发现。通常以程序包的形式提供给各个微服务调用,每个服务承担了服务发现的功能,访问压力分散到各个节点,性能和可用性上不存在明显压力。 代理式: 微服务节点将请求转发给Load Balancer 节点,由该节点完成请求转发。中间节点承担了很大压力,本身性能要求很高,导致系统复杂度就很高。

不管是自理式还是代理式,服务发现的核心功能就是服务注册表,注册表记录了所有的服务节点的配置和状态,每个微服务启动后都需要将自己的信息注册到服务注册表,然后由微服务或者 LOAD BALANCER 系统到服务注册表查询可用服务。

服务路由

具体进行某次调用请求时,我们还需要从所有符合条件的可用微服务节点中挑选出一个具体的节点发起请求,这就是服务路由需要完成的功能。通常是在服务发现一起实现,常见的路由算法有:随机路由、轮询路由、最小压力路由、最小连接数路由等。

服务容错

节点出现故障时,服务容错包括请求重试、流控、服务隔离,通常集成到服务发现和服务路由服务中。

服务监控

通常情况下,服务监控需要搜集并分析大量的数据,因此建议做成独立的系统。服务监控的主要作用有: 实时搜集信息并进行分析,避免故障后再来分析,减少了处理时间。 服务监控可以在实时分析的基础上进行预警,在问题萌芽的阶段发觉并预警,降低了问题影响的范围和时间。

服务跟踪

服务监控是微服务节点级别的监控,无法细致到某一次具体的请求。服务跟踪是跟踪具体请求在整个链路上的完整路径和具体处理过程。

服务安全

微服务节点之间可以互连,但从业务角度来说,敏感操作和数据只希望部分服务连接,因此需要设计安全机制保证业务和数据安全,包括:接入安全、数据安全、传输安全。通常服务安全是集成到配置中心去做,配置接入安全和数据安全策略。

14.6 微内核架构

微内核架构(Microkernel Architecture),又称插件化架构(Plug-in Architecture),是一种面向功能进行拆分的可扩展性架构,通常用于实现基于产品(原文为 product-based,指存在多个版本、需要下载安装才能使用,与 web-based 相对应)的应用.

基本组成

微内核架构包含两类组件: 核心系统(core system)、插件模块(plug-in modules).核心系统负责与业务无关的通用功能,功能相对稳定;插件实现具体的业务逻辑,根据业务需求不断扩展。微内核架构本质是将变化封装到插件中,从而实现快速灵活扩展,而又不影响整体系统的稳定。

设计关键点

  • 插件管理,核心系统需要知道有哪些插件、如何加载、什么时候加载。通常使用插件注册表机制实现,注册表包含插件名称、位置、加载时机等配置。
  • 插件连接, 插件如何连接到核心系统,需要约定连接规范。
  • 插件通信,核心系统如何连接需要通信的插件。

开放服务网关协议(Open Services Gateway initiative, OSGI)

../../arch/OSGI架构.png

规则引擎架构

../../arch/规则引擎架构.png

执行引擎解析配置好的规则,执行其中的条件和规则,通过这种方式支持业务的灵活多变。常用于计费、保险、促销等领域(这些领域通常设计很多种规则逻辑)。由于配置中的规则语言也很贴近代码,所以一般需要在封装成可视化的界面。


15. 技术演进方向

  • 潮流派: 对于新技术特别热衷,紧跟技术潮流,当有新的技术出现时,迫切想将新的技术应用到自己的产品中.可能存在问题,新技术需要时间成熟,此外新技术的学习需要花费时间。
  • 保守派: 对于新技术抱有很强的戒备心,稳定压倒一切,已经掌握了某种技术,就一直用这种技术打天下。可能存在问题,新技术往往会引发质变。
  • 跟风派: 判断技术的发展就看竞争对手,竞争对手用了咱们就用,竞争对手没用咱们就等等看。可能会因为业务不一样,实际场景并不适用

技术演进的动力: 上述的几种派系都是站在技术的角度看问题,实际上更高更广的角度是从企业的业务发展看问题。企业业务分为两大类: 产品类、服务类。对于产品类业务,技术创新推动业务发展,而对于服务类业务,业务发展推动技术发展. 导致不同的原因是用户选择产品时是基于"功能"考虑,选择服务是基于"规模"考虑。除非是开创新的技术能够推动或者创造一种新的业务,其他情况下,都是业务的发展推动了技术的发展。

技术演进模式-互联网业务为例: 互联网业务发展几个时期: 初创期、发展期、竞争期、成熟期,不同时期的差别主要体现在复杂性、用户规模。

../../arch/互联网业务的发展阶段.png

由于复杂性和用户规模的变化,系统也由量变引起质变,不同发展时期的应对手段不同,但核心都是满足业务"快"要求。


16 互联网架构模板

../../arch/互联网标准技术架构.png

16.1 存储层技术

SQL

互联网业务依赖关系型数据库,一般选择开源的MySQL,PostgreSQL. 醉着业务发展,不可避免的会面对将数据拆分到多个实例上,面临数据如何拆分和组合的复杂性问题。在这个阶段,一般是通过中间件和封装一个通用的解决方案,提供给业务使用。同时为了避免各个业务在中间商自己搭建不同的集群,一般也会统一搭建SQL存储平台,以对业务透明的方式提供资源分配、数据备份、迁移、容灾、读写分离、分库分表等一系列操作。

NoSQL

相比与SQL,NoSQL一般具有两个特点,一是存储数据结构更加灵活、二是高性能。NoSQL一般自身具有集群功能,但是随着业务发展,考虑到运维效率和资源利用率,一般会在NoSQL集群的基础上在实现统一存储平台,来实现: 资源按需动态分配、资源自动化管理、故障自动化处理

小文件存储

展示数据,例如图片、摘要、微博内容等数据成为小文件,一般单个数据小但是数量巨大,访问量高。开源方案:HBase、Hadoop、Hypertable、FastDFS, 业务按需在开源方案上进行包装。

大文件存储

业务上的大数据,例如视频,另外是海量的日志数据,这些都是大文件。单个数据较大,数量上不如小文件这么多。开源方案现在也很成熟了,所以大数据存储和处理这块反而是最简单的,因为你没有太多选择,只能用这几个流行的开源方案,例如,Hadoop、HBase、Storm、Hive 等

16.2 开发层技术

开发框架

随着业务越来越复杂,系统越来越多,开发人员也越来越多,如果大家使用不同的开发框架和技术会带来很多问题。所以,一般是使用统一的开发框架来提升团队的开发效率。选择时遵循: 优选成熟的框架,避免盲目追逐新技术。

web服务器

开发框架只是负责完成业务功能的开发,真正能够运行起来给用户提供服务,还需要服务器配合。开源方案: Tomcat, Resin, Nginx,Apache

容器

以Docker为代表的虚拟化技术,需要配套运维方式,同时在开发时就需要按照容器化的思想进行开发。

16.3 服务层技术

服务层的主要目标其实就是为了降低系统间相互关联的复杂度。

配置中心

配置中心就是集中管理各个系统的配置。

  • 集中配置多个系统,操作效率高。
  • 所有配置都在一个集中的地方,检查方便,协作效率高。
  • 配置中心可以实现程序化的规则检查,避免常见的错误。比如说检查最小值、最大值、是否 IP 地址、是否 URL 地址,都可以用正则表达式完成。
  • 配置中心相当于备份了系统的配置,当某些情况下需要搭建新的环境时,能够快速搭建环境和恢复业务

服务中心

服务中心就是为了解决跨系统依赖的“配置”和“调度”问题。服务中心的实现一般来说有两种方式:服务名字系统和服务总线系统。

../../arch/服务中心.png

消息队列

互联网业务的一个特点是“快”,这就要求很多业务处理采用异步的方式.消息队列就是为了实现跨系统异步通知的中间件系统.开源方案:RocketMQ、Kafka、ActiveMQ. 引入消息队列系统后的效果:

  • 整体结构从网状结构变为线性结构,结构清晰;
  • 消息生产和消息消费解耦,实现简单;
  • 增加新的消息消费者,消息生产者完全不需要任何改动,扩展方便;
  • 消息队列系统可以做高可用、高性能,避免各业务子系统各自独立做一套,减轻工作量;
  • 业务子系统只需要聚焦业务即可,实现简单。

16.4 网络层技术

负载均衡

负载均衡就是将请求均衡地分配到多个系统上。

  • DNS,用于地理级别的均衡。优点是全球通用、成本低,缺点是缓存时间长、无法感知后端服务状态,不够灵活。
  • Nginx, LVS, F5, 统一地点机器级别的负载均衡。其中 Nginx 是软件的 7 层负载均衡,LVS 是内核的 4 层负载均衡,F5 是硬件的 4 层负载均衡。

CDN

CDN 是为了解决用户网络访问时的“最后一公里”效应,本质上是一种“以空间换时间”的加速策略,即将内容缓存在离用户最近的地方,用户访问的是缓存的内容,而不是站点实时的内容。

大部分程序员和架构师都不太需要深入理解 CDN 的细节,因为 CDN 作为网络的基础服务,独立搭建的成本巨大,很少有公司自己设计和搭建 CDN 系统,从 CDN 服务商购买 CDN 服务即可。

多机房

常见的策略有:同城多机房、跨城多机房、跨国多机房

多中心

相比多机房来说,多中心的要求就高多了,要求每个中心都同时对外提供服务,且业务能够自动在多中心之间切换,故障后不需人工干预或者很少的人工干预就能自动恢复。多中心设计的关键就在于“数据一致性”和“数据事务性”如何保证,这两个难点都和业务紧密相关,目前没有很成熟的且通用的解决方案,需要基于业务的特性进行详细的分析和设计。

16.5 用户层技术

用户管理

互联网业务的一个典型特征就是通过互联网将众多分散的用户连接起来,因此用户管理是互联网业务必不可少的一部分。

  • 单点登录,又称统一登录。多个子系统之间,用户只需要登录一次。开源单点登录方案:CAS
  • 授权登录,允许第三方应用接入。开源方案:OAuth 2.0

消息推送

消息推送根据不同的途径,分为短信、邮件、站内信、App 推送。前面几种基本就是调用外部API,App他腿上分为IOS、Android,IOS基本只能使用苹果的APNS,Android实现上则五花八门。消息推送主要包含3个功能:设备管理(唯一标识、注册、注销)、连接管理和消息管理,主要挑战:

  • 海量设备和用户管理,用户与设备关联,对用户进行分类、打标签
  • 连接保活。限制后台运行后的连接通道被中断,会导致消息无法送达。方案如应用互相拉起、手机厂商白名单等
  • 消息管理,将哪些消息发送给哪些用户

存储云、图片云

存储云和图片云通常的实现都是“CDN + 小文件存储”。

16.6 业务层技术

抛开业务上的差异,各个互联网业务发展最终面临的问题都是类似的:业务复杂度越来越高。也就是说,业务层面对的主要技术挑战是“复杂度”。 复杂度越来越高的一个主要原因就是系统越来越庞大,业务越来越多。需要对系统进行拆分,化整为零、分而治之,将整体复杂性分散到多个业务或者子系统中。但是随着子系统越来越多,业务流程越来越长。此时需要对业务按照"高内聚、低耦合"的方式进行合并,将职责关联比较强的系统合并成一个虚拟业务域,然后通过网关对外统一呈现。

16.7 平台技术

运维、测试、数据分析、管理,在业务规模比较小时,各团队自行解决,但是业务规模较大时,需要有统一的平台来支撑这些功能。

运维平台

运维平台核心的职责分为四大块:配置、部署、监控、应急。

  • 配置,负责资源管理,例如机器、IP、虚拟机管理
  • 部署,负责将系统发布到线上
  • 监控,负责收集系统相关数据并进行监控,便于发现问题
  • 应急,系统出现故障后的处理,如机器下线、切换IP等

运维平台的核心设计要素是“四化”:标准化、平台化、自动化、可视化。

  • 标准化,需要制定运维标准,规范配置管理、部署流程、监控指标、应急能力等,各系统按照运维标准来实现,避免不同的系统不同的处理方式。标准化是运维平台的基础,没有标准化就没有运维平台
  • 平台化,运维标准化的基础上,将运维的相关操作都集成到运维平台中,通过运维平台来完成运维工作
  • 自动化,将重复操作固化下来,由系统自动完成
  • 可视化,提升数据查看效率

测试平台

测试平台的核心目的是提升测试(单元测试、集成测试、接口测试、性能、稳定性测试)效率,从而提升产品质量,其设计关键就是自动化。

../../arch/测试平台.png

  • 用例管理,将用于测试的代码、脚本管理起来。
  • 资源管理,运行测试的硬件管理
  • 任务管理,将测试任务分配到具体的机器上执行
  • 数据管理,收集和分析测试运行过程中的相关数据,便于进行分析

数据平台

数据平台的核心职责主要包括三部分:数据管理、数据分析和数据应用。 ../../arch/数据平台.png

管理平台

管理平台的核心职责就是权限管理。分为身份认证、权限控制。

17. 架构重构

系统架构的演化,一部分是推倒重写,大部分则是在原有架构上进行重构。重构面临的难点:

  • 业务已经上线,既要重构又要保证业务向前迭代
  • 关联众多,架构重构涉及的业务关联方很多,不同关联方的资源投入程度、业务发展速度、对架构痛点的敏感度等有很大差异,如何尽量减少对关联方的影响,或者协调关联方统一行动,是一项很大的挑战
  • 旧架构的约束,架构重构需要在旧的架构基础上进行,这是一个很强的约束,会限制架构师的技术选择范围

关于是优化还是重构:假设我们现在需要从 0 开始设计当前系统,新架构和老架构是否类似?如果差异不大,说明采取系统优化即可;如果差异很大,那可能就要进行系统重构了

17.1 有的放矢

梳理当前架构存在的问题,首要任务是从一大堆纷繁复杂的问题中识别出真正要通过架构重构来解决的问题,集中力量快速解决,而不是想着通过架构重构来解决所有的问题。透过问题表象看到问题本质,找出真正需要通过架构重构解决的核心问题,从而做到有的放矢,既不会耗费大量的人力和时间投入,又能够解决核心问题

17.2 合纵连横

  • 合纵,系统重构时会影响业务的迭代,要向产品、项目经理、运营达成一致。沟通协调时,将技术语言转换为通俗语言,以事实说话,以数据说话,是沟通的关键!
  • 连横,重构会影响其他业务系统,要想其他开发人员达成一致。其一是解释清楚重构对对方的好处,有效的策略是“换位思考、合作双赢、关注长期”。简单来说就是站在对方的角度思考,重构对他有什么好处,能够帮他解决什么问题,带来什么收益。其次,多团队之间很难统一协调行动,要求对方提供明确的时间安排,同时自身可以进行无关联的其他重构点。

17.3 运筹帷幄

在识别出核心复杂度问题后,实际重构执行时,我们会发现为了解决这个核心问题需要很多准备事项或者需要先解决哪些问题。实际执行时,建议分段实施,将要解决的问题根据优先级、重要性、实施难度等划分为不同的阶段,每个阶段聚焦于一个整体的目标,集中精力和资源解决一类问题。指定分段实施的策略,一般需要将问题按照优先级排序,优先解决当前的核心问题、问题分类,当前阶段专注于解决一类问题、循序渐进,评估每个阶段耗时,确定里程碑。

18. 开源项目选择、使用和二次开发

Dont repeat yourself(DRY), 不要重复造轮子。面对开源项目,不要重复发明轮子,但要找到合适的轮子。 选择一个合适的开源项目:

  • 聚焦是否满足业务
  • 聚焦是否成熟,从以下几个方面考虑:版本号、使用公司、社区活跃度
  • 聚焦运维能力,从以下几个方面考虑:日志是否齐全、是否有命令行,管理控制台等运维工具,方便查看系统运行情况、是否具备故障检测和恢复能力

应用开源项目:

  • 深入研究、仔细测试,了解设计原理,关键配置,进行压力测试、性能测试、故障测试
  • 灰度发布,从非核心业务开始应用,积累经验
  • 做好应急,以防万一

二次开发:

  • 保持纯洁,加以包装,而不是直接修改原系统
  • 若是通用方案不满足业务需求,则需要重新设计开发

高效学习开源项目:

  • 首先,需要树立正确的观念:不管你是什么身份,都可以从开源项目中学到很多东西。
  • 其次,不要只盯着数据结构和算法,事实上这两点在学习开源项目的时候并没有那么重要。
  • 采取“自顶向下”的学习方法,源码不是第一步,而是最后一步。不要一上来就去看源码,而是要基本掌握了功能、原理、关键设计之后再去看源码,看源码的主要目的是为了学习其代码的写作方式,以及关键技术的实现。

自顶向下的学习过程:

  • 安装,了解项目依赖,通过这些依赖能了解项目的实现和设计基础;了解项目的目录、提供了哪些工具
  • 运行,了解项目运行提供的基本命令、配置项
  • 针对原理进行系统性研究,包括关键特性的基本实现原理、优缺点对比分析。只有清楚掌握技术方案的优缺点后才算真正的掌握这门技术,也只有掌握了技术方案的优缺点后才能在架构设计的时候做出合理的选择
  • 测试
  • 源码研究

19. 架构的设计文档

../../arch/备选方案模板.png

备选方案落地后,会针对该方案进行详细的方案设计,架构设计文档就是用来详细描述细化方案的。

../../arch/架构设计文档模板.png