什么是架构
一直以来,在软件行业,对于什么是架构,都有很多的争论,每个人都有自己的理解。为了准确的理解架构,我们将和架构有关系而且相似的几个概念进行梳理:
系统(system):泛指由一群有关联的个体组成,根据某种规则运作,能完成个别元件不能单独完成的工作的群体。系统分为自然系统与人为系统两大类。
模块(Module):是一套一致而互相有紧密关连的软件组织。它分别包含了程序和数据结构两部分。
组件(Component):自包含的、可编程的、可重用的、与语言无关的软件单元,软件组件可以很容易被用于组装应用程序中。
软件框架(Software Framework),通常指的是为了实现某个业界标准或完成特定基本任务的软件组件规范,也指为了实现某个软件组件规范时,提供规范所要求之基础功能的软件产品。
维基百科对于软件架构的定义:软件架构( Software Architecture)是有关软件整体结构与组件的抽象描述,用于指导大型软件系统各个方面的设计。
我比较认可的软件架构定义如下:软件架构定义了软件有哪些组件,模块,框架组成,以及他们是怎么交互了连通,体现了软件的其他非功能特性,例如扩展性,可维护性。
从这个定义上看,也不是很清晰。为了讲清楚这个问题,我们先来看看为什么会产生架构。从软件开发进化的历史,探索一下软件架构出现的历史背景。
机器语言(1940 年之前):直接使用二进制码 0 和 1 来表示机器可以识别的指令和数据,主要问题是三难:太难写、太难读、太难改!
汇编语言(20 世纪 40 年代):为了解决机器语言编写、阅读、修改复杂的问题,汇编语言应运而生。但是本身编写复杂,同时还要考虑不同 CPU 的汇编指令和结构是不同的。
高级语言(20 世纪 50 年代):这些语言让程序员不需要关注机器底层的低级结构和逻辑,而只要关注具体的问题和业务即可。
第一次软件危机与结构化程序设计(20 世纪 60 年代~20 世纪 70 年代) :高级语言的出现,解放了程序员,但好景不长,随着软件的规模和复杂度的大大增加,20 世纪 60 年代中期开始爆发了第一次软件危机,典型表现有软件质量低下、项目无法如期完成、项目严重超支等,因为软件而导致的重大事故时有发生。为了解决问题,“结构化程序设计”被提了出来。结构化程序设计本质上还是一种面向过程的设计思想,但通过“自顶向下、逐步细化、模块化”的方法,将软件的复杂度控制在一定范围内,从而从整体上降低了软件开发的复杂度。
第二次软件危机与面向对象:第二次软件危机主要体现在软件的“扩展”变得非常复杂。结构化程序设计虽然能够解决(也许用“缓解”更合适)软件逻辑的复杂性,但是对于业务变化带来的软件扩展却无能为力,软件领域迫切希望找到新的银弹来解决软件危机,在这种背景下,面向对象的思想开始流行起来。虽然面向对象开始也被当作解决软件危机的银弹,但事实证明,和软件工程一样,面向对象也不是银弹,而只是一种新的软件方法而已。
软件架构:软件架构的出现有其历史必然性。到了 20 世纪 90 年代“软件架构”开始流行,创造了“组件”概念。我们可以看到,“模块”“对象”“组件”本质上都是对达到一定规模的软件进行拆分,差别只是在于随着软件的复杂度不断增加,拆分的粒度越来越粗,拆分的层次越来越高。
架构设计的目的:为了解决复杂度带来的问题
软件系统中高性能带来的复杂度主要体现在两方面,一方面是单台计算机内部为了高性能带来的复杂度;另一方面是多台计算机集群为了高性能带来的复杂度。
高可用是指系统无中断地执行其功能的能力,代表系统的可用性程度,本质是通过“冗余”来实现。常见高可用类型包括计算高可用,存储高可用。
可扩展性指系统为了应对将来需求变化而提供的一种扩展能力,当有新的需求出现时,系统不需要或者仅需要少量修改就可以支持,无须整个系统重构或者重建。由于软件系统固有的多变性,新的需求总会不断提出来,因此可扩展性显得尤其重要。对架构师要求:
低成本可以是人力成本,服务器成本。低成本给架构设计带来的主要复杂度体现在,往往只有“创新”才能达到低成本目标。这里的“创新”既包括开创一个全新的技术领域(这个要求对绝大部分公司太高),也包括引入新技术,如果没有找到能够解决自己问题的新技术,那么就真的需要自己创造新技术了。
安全本身是一个庞大而又复杂的技术领域,并且一旦出问题,对业务和企业形象影响非常大。
从技术的角度来讲,安全可以分为两类:
一类是功能上的安全;功能安全其实就是“防小偷”,本质上是因为系统实现有漏洞。常见的安全问题,例如:常见的XSS攻击、CSRF攻击、SQL注入等。
另一类是架构上的安全。如果说“功能安全”是为了防小偷,那么架构安全就是为了防强盗。
规模带来的复杂度主要原因在于“量变引起质变”,当数量超过一定阈值后,复杂度会发生质的变化。常见的规模带来的复杂度有:
合适原则,合适优于业界领先。真正优秀的架构都是在企业当前人力、条件、业务等各种约束下设计出来的,能够合理地将资源整合在一起并发挥出最大功效,并且能够快速落地,不要面向简历去设计架构,高大上的架构不等于适用。
简单原则,简单优于复杂。面对系统结构、业务逻辑和复杂性,我们可以编写出复杂的系统,但在软件领域,复杂代表的是“问题”。架构设计时如果简单的方案和复杂的方案都可以满足需求,最好选择简单的方案。其实,简单比复杂更加困难。
演化原则,演化优于一步到位。业务在发展、技术在创新、外部环境在变化,这一切都是在告诫架构师不要贪大求全,或者盲目照搬大公司的做法。应该认真分析当前业务的特点,明确业务面临的主要问题,设计合理的架构,快速落地以满足业务需要,然后在运行过程中不断完善架构,不断随着业务演化架构。
识别复杂度主要的复杂度问题列出来,然后根据业务、技术、团队等综合情况进行排序,优先解决当前面临的最主要的复杂度问题。
(1)构建复杂度的来源清单——高性能、可用性、扩展性、安全、低成本、规模等。
(2)结合需求、技术、团队、资源等对上述复杂度逐一分析是否需要?是否关键? “高性能”主要从软件系统未来的TPS、响应时间、服务器资源利用率等客观指标,也可以从用户的主观感受方面去考虑。 “可用性”主要从服务不中断等质量方面去考虑。 “扩展性”则主要从功能需求的未来变更幅度等方面去考虑。
(3)按照上述的分析结论,得到复杂度按照优先级的排序清单,越是排在前面的复杂度,就越关键,就越优先解决。
确定了系统面临的主要复杂度问题后,方案设计就有了明确的目标,我们就可以开始真正进行架构方案设计了
架构设计备选方案的工作更多的是从需求、团队、技术、资源等综合情况出发,对主流、成熟的架构模式进行选择、组合、调整、创新。
(1)根据自己的经验设计一个方案。因为可选的模式有很多,组合的方案更多,往往一个问题的解决方案有很多个;如果再在组合的方案上进行一些创新,解决方案会更多。简单原则,适合原则。
(2)看看业界优秀的方案是什么,和我们业务场景比较,比较关键复杂度问题
(3)设计多个备选方案,3-5个左右,备选方案有差异,
列出我们需要关注的质量属性点,然后分别从这些质量属性的维度去评估每个方案,再综合挑选适合当时情况的最优方案。质量属性按照优先级排序,首先挑选满足第一优先级的,如果方案都满足,那就再看第二优先级……以此类推。不要纠结,当差不多的时候,先落地比争论摇摆要强
详细方案设计就是将方案涉及的关键技术细节给确定下来。
关系型数据库。单个数据库无法满足业务需要,必须考虑数据库集群的方式。高性能数据库集群的第一种方式“读写分离”,将访问的压力分散到集群的多个节点上去。第二种方式“分库分表”分散访问压力,分散存储压力。常见的开源框架:淘宝TDDL,360的Atlas。
高性能NOSQL:常见的NoSQL 方案有如下4 类:
缓存:缓存就是为了弥补存储系统在这些复杂业务场景下的不足,其基本原理是将可能重复使用的数据放到内存中,一次生成、多次使用,避免每次使用都去访问存储系统。缓存常见问题有缓存雪崩、缓存穿透、缓存预热、缓存热点等。
计算高性能主要集中在两方面:
(1 )尽量提升单服务器的性能,将单服务器的性能发挥到极致。
(2 )如果单服务器无法支撑性能,设计服务器集群方案。
单机服务器高性能。单机高性能关键之一网络编程模型涉及到两个关键点:I/O 模型和进程模型
集群高性能。高性能集群的本质很简单,通过增加更多的服务器来提升系统整体的计算能力。高性能集群的复杂性主要体现在负载均衡和分配算法。
上述三种负载均衡的分类架构,各有自己的所长,其实可以遵循一个组合原则来使用它们。
根据算法期望达到的目的,大体可以分为这么几类:
(1)任务平分类:负载均衡系统将接收到的任务平均分配给服务器进行处理
(2)负载均衡类:负载均衡系统根据服务器的负载来进行分配,
(3)性能最优类:负载均衡系统根据服务器的响应时间来进行任务分配
(4)Hash类:负载均衡系统根据任务中的某些关键信息进行Hash运算
CAP理论:在一个分布式系统(指互相连接并共享数据的节点的集合)中,当涉及读写操作时,只能保证一致性(Consistence)、可用性(Availability)、 分区容错性(Partition Tolerance)三者中的两个,另外一个必须被牺牲。
可以注意上述描述中着重强调了两点:互相连接和共享数据。因为分布式系统并不一定会互联和共享数据,只有满足这两点的系统才符合CAP理论。CAP原理是忽略网络延迟的。
BASE是Basically Available(基本可用)、SofState(软状态)和Eventually Consistency(最终一致性)三个短语的缩写,其核心思想是即使无法做到强一致性(CAP的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性(Eventual Consistency)。
当谈到数据一致性时,CAP、ACID、BASE难免都会被拿出来进行讨论的,原因在于这三者都是和数据一致性相关的理论。ACID,数据库事务的特性。CAP,分布式系统设计理论。BASE,也是分布式系统设计理论,延伸了CAP理论中AP方案。
FMEA(Failure mode and effects analysis,故障模式与影响分析)又称为失效模式与后果分析
FMEA 是一种在各行各业都有广泛应用的可用性分析方法,通过对系统范围内潜在的故障模式加以分析,并按照严重程度进行分类,以确定失效对于系统的最终影响。
回到软件架构设计领域,FMEA 并不能指导我们如何做架构设计,而是当我们设计出一个架构后,再使用 FMEA 对这个架构进行分析,看看架构是否还存在某些可用性的隐患。
在架构设计领域,FMEA的具体分析方法如下:
存储高可用的本质是通过将数据复制到多和存储设备,通过数据的冗余方式来实现高可用。常见高可用存储架构:主备(备库只是一个备份),主从(主机负责读写,备库可以用来读),主主(都可以负责读写,保障数据能够被双向复制),数据分散集群(急群众的每台服务器会负责存储一部分数据,同时也会备份一份数据),数据按照地理级别分区主备架构一些复杂点:主备状态判断(常见直连方式,中介方式,客户端判断),数据一致性。
在数据一致性中,常见技术有分布式事务例如 两阶段提交,三阶段提交;一致性算法例如Paxos,Raft,ZAB。
计算高可用的主要设计目标是当出现部分硬件损坏时,计算任务能够继续正常运行。因此计算高可用的本质是通过冗余来规避部分故障的风险,单台服务器是无论如何都达不到这个目标的。所以计算高可用的设计思想很简单:通过增加更多服务器来达到计算高可用。计算高可用架构的设计复杂度主要体现在任务管理方面,即当任务在某台服务器上执行失败后,如何将任务重新分配到新的服务器进行执行。计算高可用架构:主备,计算集群。
从“异地多活”和“接口级别故障”两个业务场景中,考虑如何保障业务高可用。
异地多活是指,正常情况下用户无论访问哪一个地点的业务系统,都能够得到正确的业务服务;若某个地方业务异常的时候,用户访问其他不同地理位置正常的业务系统,也能够得到正确的业务服务。
异地多活架构设计是为了应对在一些极端场景下,服务器出现故障如机房断电、地震等等。但是,要实现异地多活的代价比较高,一方面系统的复杂度会变变高;另一方面,系统实现的成本也会变高。
设计技巧:异地多活设计技巧:保证核心业务的异地多活、保证核心数据最终一致性、采用多种手段同步数据、只保证绝大部分用户的异地多活。
异地多活方案主要针对系统级别的故障,而接口级别故障,顾名思义就是针对接口级别的故障。其典型表现就是,系统没有宕机、网络没有中断,但是业务出现问题。内部可能是程序bug问题,外部可能是黑客攻击、大量请求访问等等。解决办法一般分为三种:降级、熔断、限流、排队。
软件系统与硬件和建筑系统最大的差异在于软件是可扩展的,一个硬件生产出来后就不会再进行改变。而我们需要不断地让软件系统具备更多的功能和特性,满足新的需求或者顺应技术发展的趋势。
如何避免扩展时改动范围太大,是软件架构可扩展性设计的主要思考点。可扩展架构的基本思想就是:“拆”。就是将原本大一统的系统拆分成多个规模小的部分,扩展时只修改其中一部分即可,无须整个系统到处都改。
可以减少改动范围,降低改动风险。3种拆分思路,面向流程拆分、面向服务拆分、面向功能拆分。
1.面向流程拆分:将整个业务流程拆分为几个阶段,每个阶段作为一部分。面向流程拆分:展示层--》业务层--》数据层--》存储层
2.面向服务拆分:将系统提供的服务拆分,每个服务作为一部分。面向服务拆分:注册服务、登录服务、信息管理服务、安全设置服务。
3.面向功能拆分:将系统提供的功能拆分,每个功能作为一部分。面向功能拆分:手机号注册、邮箱注册、手机号登录、邮箱登录、课程信息管理、成绩信息管理、修改密码、找回密码。
分层架构是很常见的架构模式,它也叫 N 层架构,通常情况下,N 至少是 2 层。例如,C/S 架构、B/S 架构。常见的是 3 层架构(例如,MVC、MVP 架构)、4 层架构,5 层架构的比较少见,一般是比较复杂的系统才会达到或者超过 5 层,比如操作系统内核架构。
无论采取何种分层维度,分层架构设计最核心的一点就是需要保证各层之间的差异足够清晰,边界足够明显,让人看到架构图后就能看懂整个架构,这也是分层不能分太多层的原因。
分层架构另外一个典型的缺点就是性能,因为每一次业务请求都需要穿越所有的架构分层。
SOA 的全称是 Service Oriented Architecture,中文翻译为“面向服务的架构”。为了应对传统 IT 系统存在的问题,SOA 提出了 3 个关键概念。ESB:ESB 的全称是 Enterprise Service Bus。松耦合:松耦合的目的是减少各个服务间的依赖和互相影响。服务:所有业务功能都是一项服务。
微服务架构(Microservice Architecture)是一种架构概念,微服务架构是一种将单应用程序作为一套小型服务开发的方法,每种应用程序都在其自己的进程中运行,并与轻量级机制(通常是HTTP资源的API)进行通信。这些服务是围绕业务功能构建的,可以通过全自动部署机制进行独立部署。这些服务的集中化管理已经是最少的,它们可以用不同的编程语言编写,并使用不同的数据存储技术。
SOA 和微服务本质上是两种不同的架构设计理念,只是在“服务”这个点上有交集而已。
微服务具体有哪些坑:
微服务架构最佳实践:
服务粒度:“三个火枪手”原则,即一个微服务三个人负责开发。“三个火枪手”的原则主要应用于微服务设计和开发阶段,如果微服务经过一段时间发展后已经比较稳定,处于维护期了,无须太多的开发,那么平均 1 个人维护 1 个微服务甚至几个微服务都可以。当然考虑到人员备份问题,每个微服务最好都安排 2 个人维护,每个人都可以维护多个微服务。
拆分方法:基于业务逻辑拆分,基于可靠性拆分,基于性能拆分,基于可扩展拆分(将系统中的业务模块按照稳定性排序,将已经成熟和改动不大的服务拆分为稳定服务,将经常变化和迭代的服务拆分为变动服务)
微内核架构(Microkernel architecture)模式也被称为插件架构(plugin architecture)模式。可以用来实现基于产品的应用, 比如Eclipse,在微内核的基础上添加一些插件,就可以提供不同的产品,如C++, JAVA等。
微内核包含两个组件: core system 和 plug-in modules。应用逻辑被分隔成核心系统和插件模块,可以提供可扩展的,灵活的,特性隔离的功能。
微内核的架构本质就是将变化部分封装在插件里面,从而达到快速灵活扩展的目的,而又不影响整体系统的稳定。
微内核的核心系统设计的关键技术有:插件管理(有哪些插件,怎么加载,什么时候加载)、插件连接(插件怎么连接到核心系统)和插件通信(插件之间的通信)。相关框架技术:OSGi 的全称是 Open Services Gateway initiative。
架构图如下图所示。这张图基本涵盖了互联网技术公司的大部分技术点,不同公司只是在具体的技术实现上稍有差异,但不会跳出这个框架的范畴。