00内容简介
架构设计的相关特性
1.架构设计的思维和程序设计的思维差异很大。
架构设计的关键思维是判断和取舍,程序设计的关键思维是逻辑和实现。很多程序员在转换为架构师后,很难一开始就意识到这个差异,还是按照写代码的方式去思考架构,会导致很多困惑。
2.架构设计没有体系化的培训和训练机制。
大学的课程几乎没有架构设计相关的课程,架构设计的书籍更多的也只是关注某个架构设计点,没有体系化的架构设计书籍,导致程序员在学习上没有明确指导,只能自己慢慢摸索,效率低,容易踩坑。
专栏共分为 5 部分。
架构基础:将介绍架构设计的历史背景,阐述架构相关的概念以及架构设计的本质;提炼三个核心架构设计原则;详细描述架构设计的标准流程和步骤。
高性能架构模式:将介绍高性能数据库集群读写分离、分库分表两种方案,NoSQL 方案的典型特征和应用场景,缓存的架构设计三大要点;介绍 PPC、TPC、Reactor、Proactor 模型提升性能,以及负载均衡的分类与架构、算法与优缺点。
高可用架构模式:将介绍 CAP 原理的理解和应用、FMEA 分析方法;从主备、主从、主主、集群、分区详解常见的高可用存储架构;给出如何设计高可用计算架构;使用异地多活方案保障业务高可用的技巧和步骤。
可扩展架构模式:将概述可扩展模式及其基本思想,详解分层架构、SOA 架构、微服务及微内核架构。
架构实战:将理论与案例结合,在实战中落地专栏传递的架构原则、架构流程和架构模式。
架构设计方法论
架构设计的由来:
架构设计的目的:
架构设计面临的复杂度类型:团队协作效率低(拆分不合理)、高可用、高性能、扩展性、低成本、安全、规模等。
架构设计的原则是什么?
架构设计的一般流程:识别复杂度 ->涉及备选方案 ->评估和选择方案->详细设计
常见复杂度问题应对方案。
架构师的工作并不神秘,成熟的架构师需要对已经存在的技术非常熟悉,对已经经过验证的架构模式烂熟于心,然后根据自己对业务的理解,挑选合适的架构模式进行组合,再对组合后的方案进行修改和调整。
01:架构的简介,到底什么是架构?
软件架构指软件系统的顶层结构。
自己的提法:架构就是骨架结构;是软件系统的骨架结构;可以概括为三点:要素+结构+连接。要素可以是子系统、模块、应用服务;连接:定义模块之间的接口和交互方式、集成机制。
本篇是架构篇的开篇,梳理了与架构有关的几个容易混淆的概念,包括系统与子系统、模块与组件、框架与架构,解释了架构的定义。
系统和子系统
其实子系统的定义和系统定义是一样的,只是观察的角度有差异,一个系统可能是另外一个更大系统的子系统。
模块与组件
模块和组件都是系统的组成部分,只是从不同的角度拆分系统而已。
从逻辑的角度来拆分系统后,得到的单元就是“模块”;从物理的角度来拆分系统后,得到的单元就是“组件”。划分模块的主要目的是职责分离;划分组件的主要目的是单元复用。其实,“组件”的英文component也可翻译成中文的“零件”一词,“零件”更容易理解一些,“零件”是一个物理的概念,并且具备“独立且可替换”的特点。
我以一个最简单的网站系统来为例。假设我们要做一个学生信息管理系统,这个系统从逻辑的角度来拆分,可以分为“登录注册模块”“个人信息模块”“个人成绩模块”;从物理的角度来拆分,可以拆分为Nginx、Web服务器、MySQL。
02:架构设计的历史背景
编程语言分为两类:
一、低级语言
包括机器语言、汇编语言
低级语言更接近底层,适合编写一些对速度和代码长度要求高的程序和直接控制硬件的程序。
二、高级语言(更接近人类语言)
高级语言主要是相对于汇编语言而言的,它是较接近自然语言和数学公式的编程,基本脱离了机器的硬件系统,用人们更易理解的方式编写程序。
1、包括过程性语言,例如C、C++
2、说明性语言,例如SQL。用说明性语言写程序时,你只需要告诉计算机做什么,而不用告诉他怎么做。
3、脚本语言。脚本语言是某些应用系统提供的扩展语言,一般功能相对简单。例如HTML、JavaScript、office中的宏语言。
4、面向对象语言
文档地址
软件开发的进化历史:
本节回顾了软件开发进化的历史,以及软件架构出现的历史背景,从历史发展的角度,希望对你深入了解架构设计的本质有所帮助。
机器语言(1940年之前)
三大问题:难写、难读、难改。
对于人们来说,难理解、难掌握,所以只出计算机初级阶段被使用过。
汇编语言(20世纪40年代)
用符号代替机器语言的01代码,优点:好写、好写了很多。缺点:仍然很难写;不同的CPU需要不同的代码。
汇编语言执行前,需要用汇编系统将汇编语言编译成机器语言。
高级语言(20 世纪 50 年代)
第一次软件危机与结构化程序设计(20 世纪 60 年代~20 世纪 70 年代)
68/69年创造了软件危机一词,并提出了针对性的解决方法“软件工程”。虽然“软件工程”提出之后也曾被视为软件领域的银弹,但后来事实证明,软件工程同样无法根除软件危机,只能在一定程度上缓解软件危机。
差不多同一时间,“结构化程序设计”作为另外一种解决软件危机的方案被提了出来。结构化程序设计的主要特点是抛弃goto语句,采取“自顶向下、逐步细化、模块化”的指导思想。结构化程序设计本质上还是一种面向过程的设计思想,但通过“自顶向下、逐步细化、模块化”的方法,将软件的复杂度控制在一定范围内,从而从整体上降低了软件开发的复杂度。结构化程序方法成为了20世纪70年代软件开发的潮流。
第二次软件危机与面向对象(20 世纪 80 年代)
背景:然而随着硬件的快速发展,业务需求越来越复杂,以及编程应用领域越来越广泛,第二次软件危机很快就到来了。
第二次软件危机的根本原因还是在于软件生产力远远跟不上硬件和业务的发展。第一次软件危机的根源在于软件的“逻辑”变得非常复杂,而第二次软件危机主要体现在软件的“扩展”变得非常复杂。结构化程序设计虽然能够解决(也许用“缓解”更合适)软件逻辑的复杂性,但是对于业务变化带来的软件扩展却无能为力,软件领域迫切希望找到新的银弹来解决软件危机,在这种背景下,面向对象的思想开始流行起来。
面向对象编程(Object-Oriented Programming,简称OOP)是一种编程范型,它将数据和操作封装在对象中,并通过类和继承实现代码的复用和扩展。
面向对象语言是建立在面向对象方法基础上建立的,面向对象方法的基本思路是:客观世界有很多各式各样的对象组成,每种对象都有自己的内部状态和运行规律,不同对象之间的相互作用和联系就构成了各种不同的系统。
面向对象语言,比较直观地反应了客观世界,便于将复杂的问题分解成若干个子问题,因此近年来在大型软件开发中得到了广泛应用。
面向对象语言的优点:
1、易扩展(不是可扩展):由于封装、继承、多态的特性,自然设计出高内聚、低耦合的系统结构,使得系统更灵活、更容易扩展,然后成本较低。
2、效率高:面向对象,接近人类的思考方式
面向对象编程的主要特点包括:
封装(Encapsulation):封装是将数据和操作封装在对象中,隐藏对象的内部实现细节,只暴露必要的接口。这有助于保护数据和实现隐藏的细节,减少错误和破坏的可能性。
继承(Inheritance):继承是面向对象编程的一个重要特征,它允许一个类继承另一个类的属性和方法。这可以减少代码的重复编写,并提高代码的复用性。
多态(Polymorphism):多态是指一个接口可以有多种实现方式,或者一个对象可以表现出多种形态。这有助于实现代码的灵活性和可扩展性。
抽象(Abstraction):抽象是通过隐藏不必要的细节,只展示对象的主要特征和行为。这有助于简化复杂的问题,使代码更加易于理解和维护。
软件架构的历史背景
软件架构开始流行是在20世纪90年代。
卡内基·梅隆大学的玛丽·肖(Mary Shaw)和戴维·加兰(David Garlan)对软件架构做了很多研究,他们在 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.”
简单翻译一下:随着软件系统规模的增加,计算相关的算法和数据结构不再构成主要的设计问题;当系统由许多部分组成时,整个系统的组织,也就是所说的“软件架构”,导致了一系列新的设计问题。
只有规模较大的软件系统才会面临软件架构相关的问题。
例如:
- 系统规模庞大,内部耦合严重,开发效率低;(效率问题)
- 系统耦合严重,牵一发动全身,后续修改和扩展困难;(效率问题)
- 系统逻辑复杂,容易出问题,出问题后很难排查和修复。(质量问题)
软件架构的出现有其历史必然性。
20 世纪 60 年代第一次软件危机引出了“结构化编程”,创造了“模块”概念;有了软件工程的概念。
20 世纪 80 年代第二次软件危机引出了“面向对象编程”,创造了“对象”概念;
到了 20 世纪 90 年代“软件架构”开始流行,创造了“组件”概念。我们可以看到,“模块”“对象”“组件”本质上都是对达到一定规模的软件进行拆分,差别只是在于随着软件的复杂度不断增加,拆分的粒度越来越粗,拆分的层次越来越高。
03:架构设计的目的
公司的战略:降低成本,提供差异化商品或服务。
技术目的:满足业务功能,用最快的速度(效率)、最便宜的价格(成本),满足业务功能,并且不出问题(质量)。
从上篇分享的架构设计的历史背景,可以看到,整个软件技术发展的历史,其实就是一部与“复杂度”斗争的历史,架构的出现也不例外。简而言之,架构也是为了应对软件系统复杂度而提出的一个解决方案,通过回顾架构产生的历史背景和原因,我们可以基本推导出答案:架构设计的主要目的是为了解决软件系统复杂度带来的问题。
简单的复杂度分析案例:大学的学生管理系统,其基本功能包括登录、注册、成绩管理、课程管理等。
性能:不复杂,用MySQL+Nginx就行。
可用性:宕机一小时也没事。但是数据不能丢、MySQL要求主备,最好是异地。
安全性:Nginx 提供 ACL 控制、用户账号密码管理、数据库访问权限控制。
成本:
本节分析了架构设计的误区,结合上篇讲的架构设计的历史背景,给出架构设计的主要目的是为了解决软件系统复杂度带来的问题,并分析了一个简单复杂度的案例,希望对你有所帮助。
04:复杂度来源 - 高性能
05:复杂度来源 - 高可用
计算高可用
系统的高可用方案五花八门,但万变不离其宗,本质上都是通过“冗余”来实现高可用。
数据+ 逻辑= 业务
存储高可用
存储高可用的难点不在于如何备份数据,而在于如何减少或者规避数据不一致对业务造成的影响。
高可用状态决策
独裁式
协商式
民主式:民主式决策指的是多个独立的个体通过投票的方式来进行状态决策。例如,ZooKeeper集群在选举leader时就是采用这种方式。
06:复杂度来源 - 可扩展性
在软件开发领域,面向对象思想的提出,就是为了解决可扩展性带来的问题;
设计具备良好可扩展性的系统,有两个基本条件:正确预测变化、完美封装变化。
预测变化
软件系统与硬件或者建筑相比,有一个很大的差异:软件系统在发布后还可以不断地修改和演进,这就意味着不断有新的需求需要实现。
预测变化的复杂性在于:
不能每个设计点都考虑可扩展性。
不能完全不考虑可扩展性。
所有的预测都存在出错的可能性。
对于架构师来说,如何把握预测的程度和提升预测结果的准确性,是一件很复杂的事情,而且没有通用的标准可以简单套上去,更多是靠自己的经验、直觉。
(自己注:所以①、准备的识别业务模型,现实中业务模型是相对稳定的。②、与业务、PM沟通业务规划,这些对于应对可扩展性,也就是应对软件的变化,是很有用的。)
应对变化
第一种应对变化的常见方案是将“变化”封装在一个“变化层”,将不变的部分封装在一个独立的“稳定层”。
第二种常见的应对变化的方案是提炼出一个“抽象层”和一个“实现层”。
07:复杂度来源 - 低成本、安全、规模
低成本
低成本给架构设计带来的主要复杂度体现在,往往只有“创新”才能达到低成本目标。
低成本的实现方式:
1、引入新的技术(熟悉新技术、并且与自己的技术结合)
2、自己创造新技术
3、开创一个全新的技术领域(这个要求对绝大部分公司太高)
安全
1、功能安全
例如,常见的XSS攻击、CSRF攻击、SQL注入、Windows漏洞、密码破解等,本质上是因为系统实现有漏洞,黑客有了可乘之机。功能安全其实就是“防小偷”。
2、架构安全
通过多级防火墙,架构安全就是防强盗
规模
规模带来复杂度的主要原因就是“量变引起质变”。
1、功能越来越多,导致系统复杂度指数级上升。
功能模块所、逻辑分支多
2、数据越来越多,系统复杂度发生质变。
3、历史包袱:n手代码、有些代码。后来的人由于不熟悉整个发展历史,可能连很多功能的应用场景都不清楚,或者细节根本无法掌握,面对的就是一个黑盒系统,看不懂、改不动、不敢改、修不了,复杂度自然就感觉很高了。
对于建筑来说,永恒是主题;而对于软件来说,变化才是主题。
08:架构设计三原则
合适原则
合适原则宣言:合适优于业界领先
简单原则
简单原则宣言:简单优于复杂
演化原则
演化原则序言:演化优于一步到位
09:架构设计流程:识别复杂度
架构设计的本质目的是为了解决软件系统的复杂性,所以在我们设计架构时,首先就要分析系统的复杂性。只有正确分析出了系统的复杂性,后续的架构设计方案才不会偏离方向;否则,如果对系统的复杂性判断错误,即使后续的架构设计方案再完美再先进,都是南辕北辙,做的越好,错的越多、越离谱。
如果运气真的不好,接手了一个每个复杂度都存在问题的系统,那应该怎么办呢?
正确的做法是将主要的复杂度问题列出来,然后根据业务、技术、团队等综合情况进行排序,优先解决当前面临的最主要的复杂度问题。
10:架构设计流程:识别备选方案
架构师的工作并不神秘,成熟的架构师需要对已经存在的技术非常熟悉,对已经经过验证的架构模式烂熟于心,然后根据自己对业务的理解,挑选合适的架构模式进行组合,再对组合后的方案进行修改和调整。
虽然软件技术经过几十年的发展,新技术层出不穷,但是经过时间考验,已经被各种场景验证过的成熟技术其实更多。例如,高可用的主备方案、集群方案,高性能的负载均衡、多路复用,可扩展的分层、插件化等技术,绝大部分时候我们有了明确的目标后,按图索骥就能够找到可选的解决方案。
11:架构设计流程:评估和选择备选方案
评估的角度
- 功能
是否满足 - 质量
性能、可用性、安全性、复杂度、可维护性 - 成本
硬件成本、项目人员投入 - 进度:
- 可扩展性:
评估的人员
文章中的例子,研发、测试、运维、业务主管都参与架构设计评审会。在例子中,他们都是相关方。
文章中的例子,MQ最终选的方案为MySQL存储。排除Kafka的原因主要有二①、可维护性略差,kafka部分是Scala编写的;②、可靠性不满足,因为业务上一点都不允许丢失,而Kafka的主要设计目标是高性能日志传输