软件架构设计

177 阅读39分钟

软件架构设计

架构到底指的是什么?

不同的定义

拿上面这个问题去问其他人,相信会得到很多不一样的答案。不仅仅在实际工作中,在网络上搜索“软件架构设计”也有非常多的回答,并且这些答案不尽相同。对比“设计模式”,设计模式在大家的回答中大部分都是“GOF设计模式”,即使有所变化也不会脱离GOF设计模式的一些思想,这说明了架构设计本身不是一个简单问题。

百度百科:软件架构(software architecture),是一系列相关的抽象模式,用于指导大型软件系统各个方面的设计。软件架构是一个系统的草图。软件架构描述的对象是直接构成系统的抽象组件,各个组件之间的连接则明确和相对细致地描述组件之间的通讯。在实现阶段,这些抽象组件被细化为实际的组件,比如具体某个类或者对象。在面向对象领域中,组件之间的连接通常用接口(计算机科学)来实现。

其他解释:

  • 架构设计是使用各种技术构件(语言Language、组件Component、框架Framework、中间件Middleware等),组成能够支撑业务的作用体系,通过合理的规划使用视图(View)方式表达出来的过程。
  • 架构设计是定义满足所有技术和操作要求的结构化解决方案的过程,同时优化常见的质量属性,例如性能、安全性和可管理性。它涉及基于广泛因素的一系列决策,每个决策都可能对应用程序的质量、性能、可维护性和整体成功产生相当大的影响。

容易混淆的概念

系统和子系统

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

子系统也是由一群有关联的个体所组成的系统,多半会是更大系统中的一部分。

按照这个定义,系统和子系统比较容易理解,我们以微信为例来做一个分析:微信本身是一个系统,包含聊天、登录、支付、朋友圈等子系统。

一个系统的架构,只包括顶层这一个层级的架构,而不包括下属子系统层级的架构。 所以微信架构,就是指微信系统这个层级的架构。当然,微信的子系统,比如支付系统,也有它自己的架构,同样只包括顶层。

模块和组件

软件模块(Module)是一套一致而互相有紧密关连的软件组织。它分别包含了程序和数据结构两部分。现代软件开发往往利用模块作为合成的单位。模块的接口表达了由该模块提供的功能和调用它时所需的元素。模块是可能分开被编写的单位。这使它们可再用和允许人员同时协作、编写及研究不同的模块。

软件组件定义为自包含的、可编程的、可重用的、与语言无关的软件单元,软件组件可以很容易被用于组装应用程序中。

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

从业务逻辑的角度来拆分系统后,得到的单元就是“模块”;从物理部署的角度来拆分系统后,得到的单元就是“组件”。划分模块的主要目的是职责分离;划分组件的主要目的是单元复用。

一个模块可以包含多个组件,一个组件可以应用到多个模块中。

框架和架构

软件框架(Software framework)通常指的是为了实现某个业界标准或完成特定基本任务的软件组件规范,也指为了实现某个软件组件规范时,提供规范所要求之基础功能的软件产品。

软件架构指软件系统的“基础结构”,创造这些基础结构的准则,以及对这些结构的描述。

  • 框架是组件规范(框),比如:MVC就是一种常见的开发规范,类似的有MVP、MVVM、J2EE等框架。
  • 框架提供基础功能的产品(架)。比如:Spring MVC是MVC的开发框架,除了满足MVC的规范,Spring提供了很多基础功能来帮助我们实现功能,包括注解@Controller,Spring Security,Spring JPA等很多功能。

单纯从定义的角度来看,框架和架构的区别还是比较明显的:框架关注的是“规范”,架构关注的是“结构”。

框架是一整套开发规范,架构是某一套开发规范下的具体落地方案,包括各个模块之间的组合关系以及它们协同起来完成功能的运作规则。

架构设计的历史背景

编程语言的发展

机器语言(1940 年之前)

最早的软件开发使用的是“机器语言”,直接使用二进制码 0 和 1 来表示机器可以识别的指令和数据。

机器语言的主要问题是三难:太难写、太难读、太难改!

汇编语言(20 世纪 40 年代)

为了解决机器语言编写、阅读、修改复杂的问题,汇编语言应运而生。汇编语言又叫“符号语言”

相比机器语言来说,汇编语言就清晰得多了。

但是不同 CPU 的汇编指令和结构是不同的,同一个程序,在不同的CPU中需要编写多次。

高级语言(20 世纪 50 年代)

为了解决汇编语言的问题,计算机前辈们从 20 世纪 50 年代开始又设计了多个高级语言。

为什么称为“高级语言”呢?原因在于这些语言让程序员不需要关注机器底层的低级结构和逻辑,而只要关注具体的问题和业务即可。

除此以外,通过编译程序的处理,高级语言可以被编译为适合不同 CPU 指令的机器语言。程序员只要写一次程序,就可以在多个不同的机器上编译运行,无须根据不同的机器指令重写整个程序。

第一次软件危机与结构化程序设计(20 世纪 60 年代~20 世纪 70 年代)

高级语言的出现,解放了程序员,但好景不长,随着软件的规模和复杂度的大大增加,20 世纪 60 年代中期开始爆发了第一次软件危机,典型表现有软件质量低下、项目无法如期完成、项目严重超支等,因为软件而导致的重大事故时有发生。 例如,1963 年美国的水手一号火箭发射失败事故,就是因为一行 FORTRAN 代码错误导致的。

软件危机最典型的例子莫过于 IBM 的 System/360 的操作系统开发。佛瑞德·布鲁克斯(Frederick P. Brooks, Jr.)作为项目主管,率领 2000 多个程序员夜以继日地工作,共计花费了 5000 人一年的工作量,写出将近 100 万行的源码,总共投入 5 亿美元,是美国的“曼哈顿”原子弹计划投入的 1/4。尽管投入如此巨大,但项目进度却一再延迟,软件质量也得不到保障。布鲁克斯后来基于这个项目经验而总结的《人月神话》一书,成了畅销的软件工程书籍。

为了解决问题,在 1968、1969 年连续召开两次著名的 NATO 会议,会议正式创造了“软件危机”一词,并提出了针对性的解决方法“软件工程” 。虽然“软件工程”提出之后也曾被视为软件领域的银弹,但后来事实证明,软件工程同样无法根除软件危机,只能在一定程度上缓解软件危机。差不多同一时间, “结构化程序设计”作为另外一种解决软件危机的方案被提了出来。艾兹赫尔·戴克斯特拉(Edsger Dijkstra)于 1968 年发表了著名的《GOTO 有害论》论文,引起了长达数年的论战,并由此产生了结构化程序设计方法。同时,第一个结构化的程序语言 Pascal 也在此时诞生,并迅速流行起来。结构化程序设计的主要特点是抛弃 goto 语句,采取“自顶向下、逐步细化、模块化”的指导思想。结构化程序设计本质上还是一种面向过程的设计思想,但通过“自顶向下、逐步细化、模块化”的方法,将软件的复杂度控制在一定范围内,从而从整体上降低了软件开发的复杂度。

结构化程序方法成为了 20 世纪 70 年代软件开发的潮流。

第二次软件危机与面向对象(20 世纪 80 年代)

结构化编程的风靡在一定程度上缓解了软件危机,然而随着硬件的快速发展,业务需求越来越复杂,以及编程应用领域越来越广泛,第二次软件危机很快就到来了。 第二次软件危机的根本原因还是在于软件生产力远远跟不上硬件和业务的发展。第一次软件危机的根源在于软件的“逻辑”变得非常复杂,而第二次软件危机主要体现在软件的“扩展”变得非常复杂。结构化程序设计虽然能够解决(也许用“缓解”更合适)软件逻辑的复杂性,但是对于业务变化带来的软件扩展却无能为力,软件领域迫切希望找到新的银弹来解决软件危机,在这种背景下,面向对象的思想开始流行起来。面向对象的思想并不是在第二次软件危机后才出现的,早在 1967 年的 Simula 语言中就开始提出来了,但第二次软件危机促进了面向对象的发展。面向对象真正开始流行是在 20 世纪 80 年代,主要得益于 C++ 的功劳,后来的 Java、C# 把面向对象推向了新的高峰。到现在为止,面向对象已经成为了主流的开发思想

虽然面向对象开始也被当作解决软件危机的银弹,但事实证明,和软件工程一样,面向对象也不是银弹,而只是一种新的软件方法而已。

软件架构的历史背景

虽然早在 20 世纪 60 年代,戴克斯特拉这位上古大神就已经涉及软件架构这个概念了,但软件架构真正流行却是从 20 世纪 90 年代开始的,由于在 Rational 和 Microsoft 内部的相关活动,软件架构的概念开始越来越流行了。

与之前的各种新方法或者新理念不同的是,“软件架构”出现的背景并不是整个行业都面临类似相同的问题,“软件架构”也不是为了解决新的软件危机而产生的。

1994 年的一篇文章《软件架构介绍》(An Introduction to Software Architecture)中写到:

“When systems are constructed from many components, the organization of the overall system-the software architecture-presents a new set of design problems.

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

这段话很好地解释了“软件架构”为何先在 Rational 或者 Microsoft 这样的大公司开始逐步流行起来。因为只有大公司开发的软件系统才具备较大规模,而只有规模较大的软件系统才会面临软件架构相关的问题,例如:

  • 系统规模庞大,内部耦合严重,开发效率低;
  • 系统耦合严重,牵一发动全身,后续修改和扩展困难;
  • 系统逻辑复杂,容易出问题,出问题后很难排查和修复。

软件架构的出现有其历史必然性。

20 世纪 60 年代第一次软件危机引出了“结构化编程”,创造了“模块”概念;

20 世纪 80 年代第二次软件危机引出了“面向对象编程”,创造了“对象”概念;

20 世纪 90 年代“软件架构”开始流行,创造了“组件”概念。

我们可以看到,随着软件的复杂度不断增加,“模块”“对象”“组件”本质上都是对达到一定规模的软件进行拆分。

架构设计的目的

架构设计的误区

关于架构设计的目的,常见的误区有:

  • 因为架构很重要,所以要做架构设计
  • 不是每个系统都要做架构设计吗
  • 公司流程要求系统开发过程中必须有架构设计
  • 为了高性能、高可用、可扩展,所以要做架构设计

架构设计的真正目的

通过架构设计的历史背景,可以看到,整个软件技术发展的历史,其实就是一部与“复杂度”斗争的历史,架构的出现也不例外。简而言之,架构也是为了应对软件系统复杂度而提出的一个解决方案,通过回顾架构产生的历史背景和原因。

我们可以基本推导出答案:架构设计的主要目的是为了解决软件系统复杂度带来的问题。

架构的分类

4 + 1视图模型

“4+1”视图是对逻辑架构进行描述,最早由 Philippe Kruchten 提出,他在1995年的《IEEE Software》上发表了题为 《The 4+1 View Model of Architecture》 的论文,引起了业界的极大关注,并最终被 RUP 采纳,现在已经成为*架构设计*的结构标准。

+1 场景视图

场景视图在某种意义上讲是对最重要需求的一种抽象,将上述4视图连接在一起的纽带。因此场景视图看似多余(因此归为+1)。但是它至少有两个重要功能:

1、驱动我们去发现架构元素(领域对象等),用于架构设计过程中的其它视图。

2、用于架构设计完成后,对架构的的使用说明以及测试验证的参考。

原型需求,测试用例?

逻辑视图

架构视图描述系统的顶层设计,系统为了完成功能(要解决的问题),需要那些对象以及他们之间的关系是什么(即实现域),同时要完成一些支撑功能实现的通用机制的设计。此时还处于一个比较高的层次,不需要引入太多的细节,只考虑具有架构意义的元素。(领域模型属于这个层次?)

业务架构?

运行视图

运行视图需要考虑,如何让逻辑视图中的对象运行起来。此时需要考虑一些非功能性需求(例如性能,可靠性等),同时需要考虑并发、部署、系统集成、容错性等。

应用架构,数据架构?

物理视图

展现软件到硬件的映射,以及软件的分布式部署。聚焦于软件的非功能性需求,例如可用性、可靠性(容错)、性能(吞吐量)和可伸缩性。物理视图展示软件网络结点、进程、任务、对象到硬件结点的部署关系。软件的部署应尽可能灵活,减少对原代码的影响/依赖。

技术(部署)架构?

开发视图

描述软件在开发环境中的静态组织方式。聚焦于软件模块的组织和软件开发环境(语言、框架等)。软件需要分层,分块,然后定义良好的接口用于多团队并行开发。

技术架构?

4+1视图对软件开发过程中的不同利益相关人分离了关注点,例如,系统工程师首先接触物理视图,然后转向运行视图;最终用户、顾客、数据分析专家从逻辑视图入手;项目经理、软件配置人员则从开发视图来看待"4+1"视图。

企业架构

关于企业架构的主要组成部分,业内目前有多种理解,有的认为企业架构应以“业务架构、应用架构、技术架构”为主要框架,“数据”相关内容融入这3个框架中;有的认为企业架构主要是4A,分别是业务架构(BA)、数据架构(DA)、应用架构(AA)、技术架构(TA) ;有的认为企业架构需要考虑5A,在4A的基础上还要考虑解决方案架构;有的认为还需要把安全架构结合起来,考虑6A。

目前来说,4A是国内相对比较主流的提法。

企业架构的位置

企业架构是贯通企业战略和具体落地项目的桥梁。利用企业架构能更好地理解和承接业务战略的相关诉求,并指导具体举措及项目落地。与此同时,在具体项目执行过程中,也需要复核相关项目是否遵循企业架构的原则和要求,以确保项目落地时不走偏,高效准确地支撑业务战略实现。

企业架构中主要架构的基本协作关系

围绕业务对象(Business Object)

典型的业务对象有“产品”“客户”“合作伙伴”“合同”“订单”等,企业的实际业务都是围绕这些业务对象展开的,相应的业务架构、数据架构、应用架构也应该围绕“业务对象”来设计,这也会有利于企业架构各组成部分的整体协同。

业务架构(BA)整体牵头

总的来说,数据、应用、技术等都是为业务服务的,要想让其他要素服务好业务,那么首先需要先说清楚业务。在这四者中,业务架构起到整体牵头的作用;否则,各干各的,无法真正实现基于业务的整体协同,实际效果会很差。

业务架构包括业务规划、业务模块、业务流程,对业务进行拆分,对领域模型进行设计,最后把现实的业务抽象出来。

因此设计一个良好的业务架构不但能够减少业务人员和技术人员之间的沟通成本,还能够让业务人员大致了解到未来进行大的业务变更时需要付出的成本,在需求侧进行更加合理的规划。

示例

下图是一个虚构的电商平台业务架构示例,包含的主要模块有“运维基础设施”、“三方服务依赖”、“基础服务”、“核心业务”、“运营支持”和“销售平台”,其中“核心业务”是重点,需要对业务进行拆分、划分出业务边界。

示例(职教赛事平台)

数据架构(DA)全局拉通

数据已经成为一个重要的生产要素,各个企业需要沉淀企业级数据资产并挖掘数据价值、赋能业务。数据,尤其是“主数据”,会贯穿多个业务单元、多个业务环节,起到全局拉通的作用。

在大数据时代,数据的地位越来越重要,数据成了很多企业的核心资产,数据架构也应运而生。数据架构是对存储数据(资源)的架构,在设计时需要考虑系统的业务场景,需要根据不同的业务场景对数据进行异构设计、数据库读写分离、分布式数据存储策略等。数据架构包含的元素有:数据源、数据集成、数据存储、计算和调度等。

示例

应用架构(AA)合理呈现

应用架构的主要作用是呈现。把业务对象所涉及的相关业务活动,通过线上化的方式呈现给业务用户,以便更高效地执行业务活动。

应用架构描述了设计和构建应用的模式与技术,体现了IT系统功能和技术实现的内容。应用架构包含前端和后端服务,前端开发事关应用的用户体验,而后端开发则侧重于提供对数据、服务及其他现有系统的访问,以确保应用正常工作。应用架构细分下来也有很多种,这里介绍四种比较常用的架构:单体架构、微服务架构、事件驱动架构和SOA架构。

示例

下图是一个虚构的电商平台应用架构示例,通过“基础设施”、“数据存储”、“中间件”、“基础服务层”、“业务服务层”和“UI层”等不同的层次,描绘了IT系统功能和技术实现的内容。

技术架构(TA)有效支撑

在业务架构牵头之下,形成与业务架构协同的数据架构、应用架构之后,需要技术架构进行统一支撑。

技术架构简单来说就是从技术的视角来来描述整个系统,列举出实现整个系统所需要使用到的主要技术框架,包含开发语言、平台、组件和依赖库、开源或商用软件等。

示例

下图是一个虚构的电商平台技术架构图示例,展现了“基础设施”、“数据存储”、“基础服务”、“业务服务”、“网关”和“UI”层等使用到的具体技术和服务,也包含了从“开发平台”到“运维平台”使用到的各种技术和工具。

架构设计的模式

大型网站架构模式

分层

分层是企业应用系统中最常见的一种架构模式,将系统在横向维度上切分成几个部分,每个部分负责一部分相对比较单一的职责,然后通过上层对下层的依赖和调用组成一个完整的系统

分层结构在计算机世界中无处不在,网络的7层通信协议是一种分层结构;

分割

分割就是在纵向方面对软件进行切分。网站越大,功能越复杂,服务和数据处理的种类也越多,将这些不同的功能和服务分割开来,包装成高内聚低耦合的模块单元,一方面有助于软件的开发和维护;另一方面,便于不同模块的分布式部署,提高网站的并发处理能力和功能扩展能力。

分布式

对于大型网站,分层和分割的一个主要目的是为了切分后的模块便于分布式部署,即将不同模块部署在不同的服务器上,通过远程调用协同工作。分布式意味着可以使用更多的计算机完成同样的功能,计算机越多,CPU、内存、存储资源也就越多,能够处理的并发访问和数据量就越大,进而能够为更多的用户提供服务。

分布式方案有以下几种:分布式应用和服务,分布式静态资源,分布式数据和存储,分布式计算,分布式配置,分布式锁,分布式文件系统等

集群

使用分布式虽然已经将分层和分割后的模块独立部署,但是对于用户访问集中的模块(比如网站的首页),还需要将独立部署的服务器集群化,即多台服务器部署相同应用构成一个集群,通过负载均衡设备共同对外提供服务。

在网站应用中,即使是访问量很小的分布式应用和服务,也至少要部署两台服务器构成一个小的集群,目的就是提高系统的可用性。

缓存

缓存就是将数据存放在距离计算最近的位置以加快处理速度。缓存是改善软件性能的第一手段,现代CPU越来越快的一个重要因素就是使用了更多的缓存,在复杂的软件设计中,缓存几乎无处不在。大型网站架构设计在很多方面都使用了缓存设计。

常见的缓存方案:CDN,反向代理,本地缓存,分布式缓存

异步

计算机软件发展的一个重要目标和驱动力是降低软件耦合性。事物之间直接关系越少,就越少被彼此影响,越可以独立发展。大型网站架构中,系统解耦合的手段除了前面提到的分层、分割、分布等,还有一个重要手段是异步,业务之间的消息传递不是同步调用,而是将一个业务操作分成多个阶段,每个阶段之间通过共享数据的方式异步执行进行协作。

在分布式系统中,多个服务器集群通过分布式消息队列实现异步,分布式消息队列可以看作内存队列的分布式部署。

分布式消息队列有以下特性:提高系统可用性,加快网站响应速度,消除并发访问高峰。

但需要注意的是,使用异步方式处理业务可能会对用户体验、业务流程造成影响,需要网站产品设计方面的支持。

冗余

网站需要7×24小时连续运行,但是服务器随时可能出现故障,特别是服务器规模比较大时,出现某台服务器宕机是必然事件。要想保证在服务器宕机的情况下网站依然可以继续服务,不丢失数据,就需要一定程度的服务器冗余运行,数据冗余备份,这样当某台服务器宕机时,可以将其上的服务和数据访问转移到其他机器上。

访问和负载很小的服务也必须部署至少两台服务器构成一个集群,其目的就是通过冗余实现服务高可用。数据库除了定期备份,存档保存,实现冷备份外,为了保证在线业务高可用,还需要对数据库进行主从分离,实时同步实现热备份。

为了抵御地震、海啸等不可抗力导致的网站完全瘫痪,某些大型网站会对整个数据中心进行备份,全球范围内部署灾备数据中心。网站程序和数据实时同步到多个灾备数据中心。

自动化

在无人值守的情况下网站可以正常运行,一切都可以自动化是网站的理想状态。目前大型网站的自动化架构设计主要集中在发布运维方面。

自动化包括:自动化代码管理,自动化测试,自动化安全检测,自动化部署,自动化监控,自动化报警,自动化失效转移,自动化失效恢复,自动化降级,自动化分配资源等。

安全

互联网的开放特性使得其从诞生起就面对巨大的安全挑战,网站在安全架构方面也积累了许多模式:通过密码和手机校验码进行身份认证;登录、交易等操作需要对网络通信进行加密,网站服务器上存储的敏感数据如用户信息等也进行加密处理;为了防止机器人程序滥用网络资源攻击网站,网站使用验证码进行识别;对于常见的用于攻击网站的XSS攻击、SQL注入、进行编码转换等相应处理;对于垃圾信息、敏感信息进行过滤;对交易转账等重要操作根据交易模式和交易信息进行风险控制。

架构的演进

单机架构

以淘宝作为例子。在网站最初时,应用数量与用户数都较少,可以把Tomcat和数据库部署在同一台服务器上。浏览器往www.taobao.com发起请求时,首先经过DNS服务器(域名系统)把域名转换为实际IP地址10.102.4.1,浏览器转而访问该IP对应的Tomcat。

第一次演进:Tomcat与数据库分开部署

Tomcat和数据库分别独占服务器资源,显著提高两者各自性能。

随着用户数的增长,并发读写数据库成为瓶颈

第二次演进:引入本地缓存和分布式缓存

在Tomcat同服务器上或同JVM中增加本地缓存,并在外部增加分布式缓存,缓存热门商品信息或热门商品的html页面等。通过缓存能把绝大多数请求在读写数据库前拦截掉,大大降低数据库压力。其中涉及的技术包括:使用memcached作为本地缓存,使用Redis作为分布式缓存,还会涉及缓存一致性、缓存穿透/击穿、缓存雪崩、热点数据集中失效等问题。

缓存抗住了大部分的访问请求,随着用户数的增长,并发压力主要落在单机的Tomcat上,响应逐渐变慢

第三次演进:引入反向代理实现负载均衡

在多台服务器上分别部署Tomcat,使用反向代理软件(Nginx)把请求均匀分发到每个Tomcat中。此处假设Tomcat最多支持100个并发,Nginx最多支持50000个并发,那么理论上Nginx把请求分发到500个Tomcat上,就能抗住50000个并发。其中涉及的技术包括:Nginx、HAProxy,两者都是工作在网络第七层的反向代理软件,主要支持http协议,还会涉及session共享、文件上传下载的问题。

反向代理使应用服务器可支持的并发量大大增加,但并发量的增长也意味着更多请求穿透到数据库,单机的数据库最终成为瓶颈

第四次演进:数据库读写分离

把数据库划分为读库和写库,读库可以有多个,通过同步机制把写库的数据同步到读库,对于需要查询最新写入数据场景,可通过在缓存中多写一份,通过缓存获得最新数据。其中涉及的技术包括:Mycat,它是数据库中间件,可通过它来组织数据库的分离读写和分库分表,客户端通过它来访问下层数据库,还会涉及数据同步,数据一致性的问题。

业务逐渐变多,不同业务之间的访问量差距较大,不同业务直接竞争数据库,相互影响性能

第五次演进:数据库按业务分库

把不同业务的数据保存到不同的数据库中,使业务之间的资源竞争降低,对于访问量大的业务,可以部署更多的服务器来支撑。这样同时导致跨业务的表无法直接做关联分析,需要通过其他途径来解决,但这不是本文讨论的重点,有兴趣的可以自行搜索解决方案。

随着用户数的增长,单机的写库会逐渐会达到性能瓶颈

第六次演进:把大表拆分为小表

比如针对评论数据,可按照商品ID进行hash,路由到对应的表中存储;针对支付记录,可按照小时创建表,每个小时表继续拆分为小表,使用用户ID或记录编号来路由数据。只要实时操作的表数据量足够小,请求能够足够均匀的分发到多台服务器上的小表,那数据库就能通过水平扩展的方式来提高性能。

数据库和Tomcat都能够水平扩展,可支撑的并发大幅提高,随着用户数的增长,最终单机的Nginx会成为瓶颈

第七次演进:使用LVS或F5来使多个Nginx负载均衡

由于瓶颈在Nginx,因此无法通过两层的Nginx来实现多个Nginx的负载均衡。图中的LVS和F5是工作在网络第四层的负载均衡解决方案,其中LVS是软件,运行在操作系统内核态,可对TCP请求或更高层级的网络协议进行转发,因此支持的协议更丰富,并且性能也远高于Nginx,可假设单机的LVS可支持几十万个并发的请求转发;F5是一种负载均衡硬件,与LVS提供的能力类似,性能比LVS更高,但价格昂贵。由于LVS是单机版的软件,若LVS所在服务器宕机则会导致整个后端系统都无法访问,因此需要有备用节点。可使用keepalived软件模拟出虚拟IP,然后把虚拟IP绑定到多台LVS服务器上,浏览器访问虚拟IP时,会被路由器重定向到真实的LVS服务器,当主LVS服务器宕机时,keepalived软件会自动更新路由器中的路由表,把虚拟IP重定向到另外一台正常的LVS服务器,从而达到LVS服务器高可用的效果。

由于LVS也是单机的,随着并发数增长到几十万时,LVS服务器最终会达到瓶颈,此时用户数达到千万甚至上亿级别,用户分布在不同的地区,与服务器机房距离不同,导致了访问的延迟会明显不同

第八次演进:通过DNS轮询实现机房间的负载均衡

在DNS服务器中可配置一个域名对应多个IP地址,每个IP地址对应到不同的机房里的虚拟IP。当用户访问www.taobao.com时,DNS服务器会使用轮询策略或其他策略,来选择某个IP供用户访问。此方式能实现机房间的负载均衡,至此,系统可做到机房级别的水平扩展,千万级到亿级的并发量都可通过增加机房来解决,系统入口处的请求并发量不再是问题。

随着数据的丰富程度和业务的发展,检索、分析等需求越来越丰富,单单依靠数据库无法解决如此丰富的需求

第九次演进:引入NoSQL数据库和搜索引擎等技术

当数据库中的数据多到一定规模时,数据库就不适用于复杂的查询了,往往只能满足普通查询的场景。对于统计报表场景,在数据量大时不一定能跑出结果,而且在跑复杂查询时会导致其他查询变慢,对于全文检索、可变数据结构等场景,数据库天生不适用。因此需要针对特定的场景,引入合适的解决方案。如对于海量文件存储,可通过分布式文件系统HDFS解决,对于key value类型的数据,可通过HBase和Redis等方案解决,对于全文检索场景,可通过搜索引擎如ElasticSearch解决,对于多维分析场景,可通过Kylin或Druid等方案解决。

当然,引入更多组件同时会提高系统的复杂度,不同的组件保存的数据需要同步,需要考虑一致性的问题,需要有更多的运维手段来管理这些组件等。

引入更多组件解决了丰富的需求,业务维度能够极大扩充,随之而来的是一个应用中包含了太多的业务代码,业务的升级迭代变得困难

第十次演进:大应用拆分为小应用

按照业务板块来划分应用代码,使单个应用的职责更清晰,相互之间可以做到独立升级迭代。这时候应用之间可能会涉及到一些公共配置,可以通过分布式配置中心Zookeeper来解决。

不同应用之间存在共用的模块,由应用单独管理会导致相同代码存在多份,导致公共功能升级时全部应用代码都要跟着升级

第十一次演进:复用的功能抽离成微服务

如用户管理、订单、支付、鉴权等功能在多个应用中都存在,那么可以把这些功能的代码单独抽取出来形成一个单独的服务来管理,这样的服务就是所谓的微服务,应用和服务之间通过HTTP、TCP或RPC请求等多种方式来访问公共服务,每个单独的服务都可以由单独的团队来管理。此外,可以通过Dubbo、SpringCloud等框架实现服务治理、限流、熔断、降级等功能,提高服务的稳定性和可用性。

不同服务的接口访问方式不同,应用代码需要适配多种访问方式才能使用服务,此外,应用访问服务,服务之间也可能相互访问,调用链将会变得非常复杂,逻辑变得混乱

第十二次演进:引入企业服务总线ESB屏蔽服务接口的访问差异

通过ESB统一进行访问协议转换,应用统一通过ESB来访问后端服务,服务与服务之间也通过ESB来相互调用,以此降低系统的耦合程度。这种单个应用拆分为多个应用,公共服务单独抽取出来来管理,并使用企业消息总线来解除服务之间耦合问题的架构,就是所谓的SOA(面向服务)架构,这种架构与微服务架构容易混淆,因为表现形式十分相似。个人理解,微服务架构更多是指把系统里的公共服务抽取出来单独运维管理的思想,而SOA架构则是指一种拆分服务并使服务接口访问变得统一的架构思想,SOA架构中包含了微服务的思想。

业务不断发展,应用和服务都会不断变多,应用和服务的部署变得复杂,同一台服务器上部署多个服务还要解决运行环境冲突的问题,此外,对于如大促这类需要动态扩缩容的场景,需要水平扩展服务的性能,就需要在新增的服务上准备运行环境,部署服务等,运维将变得十分困难

第十三次演进:引入容器化技术实现运行环境隔离与动态服务管理

目前最流行的容器化技术是Docker,最流行的容器管理服务是Kubernetes(K8S),应用/服务可以打包为Docker镜像,通过K8S来动态分发和部署镜像。Docker镜像可理解为一个能运行你的应用/服务的最小的操作系统,里面放着应用/服务的运行代码,运行环境根据实际的需要设置好。把整个“操作系统”打包为一个镜像后,就可以分发到需要部署相关服务的机器上,直接启动Docker镜像就可以把服务起起来,使服务的部署和运维变得简单。

使用容器化技术后服务动态扩缩容问题得以解决,但是机器还是需要公司自身来管理,在非大促的时候,还是需要闲置着大量的机器资源来应对大促,机器自身成本和运维成本都极高,资源利用率低

第十四次演进:以云平台承载系统

系统可部署到公有云上,利用公有云的海量机器资源,解决动态硬件资源的问题,在大促的时间段里,在云平台中临时申请更多的资源,结合Docker和K8S来快速部署服务,在大促结束后释放资源,真正做到按需付费,资源利用率大大提高,同时大大降低了运维成本。

备注

演进的次数和顺序并非真实的演进方式,只是为了突出,架构是不断演进的

演进图中的架构仅做参考。

架构设计复杂度来源

高性能

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

高可用

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

系统的高可用方案五花八门,但万变不离其宗,本质上都是通过“冗余”来实现高可用。通俗点来讲,就是一台机器不够就两台,两台不够就四台;一个机房可能断电,那就部署两个机房;一条通道可能故障,那就用两条,两条不够那就用三条(移动、电信、联通一起上)。高可用的“冗余”解决方案,单纯从形式上来看,和之前讲的高性能是一样的,都是通过增加更多机器来达到目的,但其实本质上是有根本区别的:高性能增加机器目的在于“扩展”处理性能;高可用增加机器目的在于“冗余”处理单元。通过冗余增强了可用性,但同时也带来了复杂性。

可扩展性

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

设计具备良好可扩展性的系统,有两个基本条件:

正确预测变化

年法则:只预测 2 年内的可能变化,不要试图预测 5 年甚至 10 年后的变化

完美应对变化

方案一:提炼出“变化层”和“稳定层”

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

方案二:提炼出“抽象层”和“实现层”

核心思想就是通过实现层来封装变化。

低成本、安全、规模

感兴趣的同学,自己了解一下。

架构设计的原则

合适原则

合适优于业界领先

简单原则

简单由于复杂

演化原则

演化优于一步到位

架构设计的流程

识别复杂度

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

设计备选方案

备选方案的数量以 3 ~ 5 个为最佳

备选方案的差异要比较明显

备选方案的技术不要只局限于已经熟悉的技术

评估和选择备选方案

360 度环评

列出我们需要关注的质量属性点,然后分别从这些质量属性的维度去评估每个方案,再综合挑选适合当时情况的最优方案

详细方案设计

详细方案设计就是将方案涉及的关键技术细节给确定下来,可以指导开发人员实施的。比如:

细化设计点 1:数据库表如何设计?

细化设计点 2:业务服务器如何写入消息?

细化设计点 3:业务服务器如何读取消息?

分享总结

1、相信大家对架构设计有一个整体的初步了解,从产生到目前的演进,以及企业如何应用等。

2、企业应用建议深入了解下,根据自己的岗位角色,辨识相应的架构图,并且自行了解如何画相应的架构图。

3、建议先从工作中的项目入手,不一定局限于企业应用中涉及到的几种架构,比如项目中涉及到的业务流程图,状态流转图,服务之间调用逻辑图,异步时序图等

4、开发编写代码前,对涉及到的复杂业务进行图表的设计,对设计方案进行评审,评审通过后,便于指导进行开发

5、减少沟通成本和维护成本,项目前期对业务流程进行讨论和梳理后,产出相应的流程图和技术设计图等,根据需求,参考设计图,进行评审,形成共同的决策,达成共识。后期如果需要变更和维护,可以快速的响应需求。

参考资料

架构蓝图--软件架构 "4+1" 视图模型 The “4+1” View Model of Software Architecture

从零开始学架构

服务端架构的演进