最近阅读了一本架构方面的入门图书叫《从零开始学架构:照着做,你也能成为架构师》,部分内容比较不错,先做书摘总结,以便加深印象与未来回顾学习。
本文是该书第一部分,是书中第一、二、三章,主要介绍架构基础、架构设计原则、架构设计流程,涉及到架构概念、架构设计共性原则、架构设计四大流程等内容。
目录
▪第一章 架构基础
▪第二章 架构设计原则
▪第三章 架构设计流程
▪其他相关摘要
第一章 架构基础
- 概念:架构、框架、系统、子系统、模块、组件。 相关概念随时间推演会更新,这里不再摘要,需要的时候可以直接查询维基百科。
- 书中架构定义:软件架构指软件系统的顶层结构。
- 软件开发进化历史:
- 机器语言时代(20世纪40年代前):最早的软件开发使用机器语言,直接使用二进制码0和1来表示机器可以识别的指令和数据。机器语言有“太难读、太难写、太难改”的特点。
- 汇编语言时代(20世纪40年代):汇编语言即符号语言,用助记符代替机器指令操作码,用地址符号(Symbol)或标号(Label)代替指令或操作数的地址。汇编语言本质仍是面向机器,需要精确了解计算机底层的知识。另外不同CPU的汇编指令和结构是不同的。
- 高级语言时代(20世纪50年代):Fortran(1955)、LISP(1958)、Cobol(1959)。
- 第一次软件危机与结构化程序设计(面向过程的程序设计)(20世纪60年代~20世纪70年代):原因为软件“逻辑”变得复杂,典例IBM System/360,促成了《人月神话》的编撰总结。提出“软件危机”,提出软件工程的解决方法,并在此期间形成了结构化程序设计方法(“自顶向下、逐步求精”)。第一个结构化的程序语言Pascal在此时代诞生,同时创造了“模块”的概念。
- 第二次软件危机与面向对象的程序设计(20世纪80年代):原因为软件“扩展”变得复杂。在此期间C 、Java、C#推动并发展了面向对象程序设计的发展直到现在,面向对象成为主流开发思想,创造了“对象”的概念。
- 软件架构发展时代(20世纪60年代-20世纪90年代-至今):Dijkstra在20世纪60年代相关论文及文章中已涉及到软件架构概念;20世纪90年代在Microsoft等大公司才兴起。而这个阶段从大公司兴起的原因是只有大公司开发的软件系统才具备大规模,其软件才面临软件架构相关的问题。软件架构创造了“组件”的概念。
- 架构设计的主要目的是为了解决复杂度带来的问题。
- 复杂度来源主要有高性能、高可用、可扩展性、低成本、安全、规模等几种。
- 高性能复杂度来源相关摘要:
- 高性能复杂度来源体现:
- 单台计算机内部为了高性能带来的复杂度;
- 多台计算机集群为了高性能带来的复杂度。
- 如果我们要完成一个高性能的软件系统,需要考虑多进程、多线程、进程间通信、多线程并发等技术点,而且这些技术并不是最新的就是最好的,也不是非此即彼的选择。在做架构设计的时候,需要花费很大精力来结合业务进行分析、判断、选择、组合,这个过程同样复杂。
- 通过大量及其来提升性能,并不仅仅是增加机器这么简单,让多台及其配合起来达到高性能目的是一个复杂的任务。
- 集群的任务分配:
- 需要增加一个任务分配器,可能是硬件(F5、交换机)、软件网络设备(LVS)、负载均衡(Nginx、HAProxy),也有可能是自研系统。需要考虑性能、成本、可维护性、可用性等方面。而任务分配器如果从一变多,则需要将不同的用户分配到不同的任务分配器上,常用有DNS轮询、智能DNS、CDN、GSLB设备等。
- 任务分配器和真正的业务服务器之间有连接和交互,需要选择合适的连接方式,并且对连接进行管理。而任务分配器如果从一变多,则变成了多对多的网状结构,而且整个集群的状态管理、故障处理复杂度大大增加。
- 任务分配器需要增加分配算法。例如轮询、权重分配、负载分配。
- 集群的任务分解:
- 若业务越来越复杂,我们可以将其拆分为更多的组成部分,将大一统复杂的系统拆分成小而简单的系统。这样做可以让每个小系统可以更容易做到高性能,另外也可以针对单个任务进行扩展。
- 但系统也不能拆分太细,因为拆分系统互相调用通道是网络传输的方式,系统间调用次数会呈指数级上升,从而性能不升反降。
- 虽然系统分解可提升一定性能,但有限,主要性能决定仍在业务的逻辑本身。
- 如何把握任务分解的颗粒度是个关键问题。
- 高性能复杂度来源体现:
- 高可用复杂度来源相关摘要:
- 无论单个软件还是硬件都不可能做到无中断,例如硬件故障与软件BUG,外部环境不可抗力更无法避免。
- 所有系统高可用方案万变不离其宗,本质上都是通过“冗余”来实现高可用。
- 高性能增加及其的目的在于“扩展”处理性能,高可用增加及其的目的在于“冗余”处理单元。
- 高可用场景分为:计算高可用、存储高可用、高可用场景决策。
- 计算高可用:
- 需要增加一个或多个任务分配器。
- 任务分配器和真正的业务服务器之间有连接和交互。
- 存储高可用:
- 存储高可用的难点不在于如何备份数据,而在于如何减少或者规避数据不一致对业务造成影响。
- 存储高可用不可能同时满足“一致性、可用性、分区容错性”,最多满足其中两个,这就要求我们在做架构设计时要结合业务进行取舍。
- 高可用状态决策:
- 无论计算高可用,还是存储高可用,其基础都是“状态决策”,即系统需要能够判断当前状态是正常的还是出现了异常,如果出现了异常就要采取行动来保证高可用。
- 常见的决策方式:
- 独裁式:一个仲裁者,多个上报者。瓶颈为仲裁者。
- 协商式:两个独立个体通过交流信息根据规则决策。瓶颈为两者信息交互。
- 民主式:多个独立个体投票,例如ZooKeeper。瓶颈在决策算法复杂,同时有“脑裂”(一个决策集群出现两个或多个分裂决策集群)的情况出现。
- 对于高可用系统来说,毫秒的延时意味着整个系统在某个时间点上,数据肯定是不一致的。按照“数据 逻辑=业务”这个公式来套的话,数据不一致,即使逻辑一致,最后的业务表现就不一样了。
- 可扩展性的发展与意义:面向对象思想的提出,就是为了解决可扩展性带来的问题,后来的设计模式,更是将可扩展性做到了极致(《设计模式》一书的副标题就是“可复用面向对象软件的基础”)。得益于设计模式的巨大影响力,几乎所有的技术人员对于可扩展性都特别重视。
- 设计具备良好可扩展性的系统,有两个基本条件:正确预测变化、完美封装变化。
- 预测变化复杂性相关摘要:
- 不能每个设计点都考虑可扩展性。
- 不能完全不考虑可扩展性。
- 所有的预测都存在出错的可能性。
- 应对变化的方案:
- 第一种是将“变化”封装在一个“变化层”,将不变的部分封装在一个独立的“稳定层”。
- 第二种是提炼出一个“抽象层”和一个“实现层”,抽象层是稳定的,实现层可以根据具体业务需要定制开发,当加入新的功能时,只需要增加新的实现,无需修改抽象层。
- 软件工程的设计模式要点:规则引擎和设计模式类似,都是通过灵活的设计来达到可扩展性的目的,但“灵活的设计”本身就是一件复杂的事情,不说别的,光是把23种设计模式全部理解和记住,都是一件很困难的事情。
- 低成本复杂度相关摘要:往往只有“创新”才能达到低成本目标。这里的“创新”既包括开创一个全新的技术领域(这个要求对绝大部分公司太高),也包括引入新技术,如果没有找到能够解决问题的新技术,那么真的就需要自己创造新的技术。
- 安全复杂度相关摘要:
- 功能安全:框架只能预防常见的安全漏洞和风险(常见的XSS攻击、CSRF攻击、SQL注入等),无法预知新的安全问题,而且框架本身很多时候也存在漏洞。
- 架构安全:传统架构安全主要依靠防火墙,防火墙的功能虽然强大,但性能一般,所以在传统的银行和企业应用领域应用较多。但在互联网领域,防火墙的应用场景并不多。因为互联网的业务具有海量用户访问和高并发的特点,防火墙的性能不足以支撑;尤其是互联网领域的DDoS攻击,轻则几GB,重则几十GB。
- 规模复杂度相关摘要:
- 规模带来复杂度的主要原因是“量变引起质变”,当数量超过一定阈值后,复杂度会发生质的变化。
- 常见的规模带来的复杂度情况:
- 功能越来越多,导致系统复杂度指数级上升:这的主要原因在于随着系统功能数量增多,功能之间的连接呈指数级增长。
- 数据越来越多,系统复杂度发生质变。
第二章 架构设计原则
- 对于架构设计来说,本质上是不确定的,同样的一个系统,A公司和B公司做出来的架构可能差异很大,但最后都能正常运转;相比编程来说,架构设计并没有像编程语言那样的语法来进行约束,更多的时候是面对多种可能性时进行选择。
- 架构设计共性的原则是:合适原则、简单原则、演化原则。
- 合适原则:
- “合适优于业界领先”。
- “将军难打无兵之仗”。没那么多人,却想干那么多活,是失败的第一个主要原因。
- “罗马不是一天建成的”。没有那么多积累,却想一步登天,是失败的第二个主要原因。
- “冰山下面才是关键”。没有那么卓越的业务场景,却幻想灵光一闪成为天才,是失败的第三个主要原因。
- 简单原则:
- “简单优于复杂”。
- 软件领域的复杂性体现:
- 结构的复杂性:
- 组成复杂系统的组建数量更多,同时这些组建之间的关系也更复杂。
- 某个组建改动,会影响关联的所有组件,这些被影响的组建同样会继续递归影响更多的组件。
- 定位一个复杂系统中的问题总是比简单系统更加困难。
- 逻辑复杂性:
- 单个组件承担了太多的功能。
- 采用了复杂的算法,复杂算法导致的问题主要是难以理解,进而导致难以实现、难以修改,并且出了问题难以快速解决。
- 结构的复杂性:
- 架构设计时如果简单的方案和复杂的方案都可以满足需求,一定要选择简单的方案,《UNIX编程艺术》总结的KISS(Keep It Simple, Stupid!)原则一样适应于架构设计。
- 演化原则:
- “演化优于一步到位”。
- 对于建筑来说,永恒是主题;而对于软件来说,变化才是主题。
- 软件架构设计类似于大自然“设计”一个生物:
- 首先,设计出来的架构要满足当时的业务需要。
- 其次,架构要不断地在实际应用过程中迭代,保留优秀的设计,修复有缺陷的设计,改正错误的设计,去掉无用的设计,使得架构逐渐完善。
- 最后,当业务发生变化时,架构要扩展、重构、甚至重写;代码也许会重写,但有价值的经验、教训、逻辑、设计等(类似生物体内的基因)却可以在新架构中延续。
- 架构师在进行架构设计时需要牢记这个原则,时刻提醒自己不要贪大求全,或者盲目照搬大公司的做法,而是应该认真分析当前的业务特点,明确业务面临的主要问题,设计合理的架构,快速落地以满足业务需要,然后再运行过程中不断完善架构,不断随着业务演化框架。
第三章 架构设计流程
- 识别复杂度
- 架构设计的本质目的是为了解决软件系统的复杂性,所以在我们设计架构时,首先就要分析系统的复杂性。
- 架构的复杂度主要来源于“高性能”“高可用”“可扩展”等几个方面,但架构师在具体判断复杂性的时候,不能生搬硬套,认为任何时候都从这三个方面进行复杂度分析就可以了。
- 实际上绝大部分场景下,复杂度只是其中的某一个,少数情况下包含其中两个。一般不会三个都包括。
- 若真接收了每个复杂度都存在问题的系统,不要幻想一次架构重构解决所有问题,应该一个个来解决问题。
- 正确的做法是将主要的做法是将主要的复杂度问题列出来,然后根据业务、技术、团队等综合情况进行排序,优选解决当前面临的最主要的复杂度问题。
- 设计备选方案
- 成熟的架构师首先对已经存在的技术非常熟悉,对已经经过验证的架构模式烂熟于心,然后根据自己对业务的理解,挑选合适的架构模式进行组合,再对组合后的方案进行修改和调整。
- 常见错误及正确做法:
- 设计最优秀的方案。“简单原则”要求,挑选合适自己业务、团队、技术能力的方案才是好方案。
- 只做一个方案。架构师需要设计多个备选方案,备选方案数量以3-5个为最佳,备选方案的差异要比较明显,备选方案的技术不要只局限于已经熟悉的技术。
- 备选方案过于详细。备选阶段关注的是技术选型,而不是技术细节,技术选型的差异要比较明显。
- 评估和选择备选方案
- 方法是“360度环评”:列出我们需要关注的质量属性点,然后分别从这些质量属性的维度去评估每个方案,再综合挑选适合当时情况的最优方案。
- 常见的方案质量属性点有:性能、可用性、硬件成本、项目投入、复杂度、安全性、可扩展性等。
- 在评估上述2的质量属性时,需要遵循架构设计原则1“合适原则”和原则2“简单原则”,避免贪大求全,基本上某个质量属性能够满足一定时期内业务发展就可以了。
- 通常情况下,如果某个质量属性评估和业务发展有关系(例如,性能、硬件成本等),需要评估未来业务发展的规模时,一种简单的方式是将当前的业务规模乘以2~4即可,如果现在的基数较低,可以乘以4,如果现在基数较高,可以乘以2.
- 在方案选择上,正确的做法是“按优先级选择”,即设计师综合当前的业务发展情况、团队人员规模和技能、业务发展预测等因素,将质量属性按照优先级排序,首先挑选满足第一优先级的,如果方案都满足,那就再看第二优先级……以此类推。
- 详细方案设计
- 详细方案设计就是将方案涉及的关键技术细节给确定下来。
- 详细设计方案阶段可能遇到的一种极端情况就是在详细设计阶段发现备选方案不可行,一般情况下主要的原因是备选方案设计时遗漏了某个关键技术点或关键的质量属性。
- 架构师不但要进行备选方案设计和选型,还需要对备选方案的关键细节有较深入的理解。
- 通过分步骤、分阶段、分系统等方式,尽量降低方案复杂度,方案本身的复杂度越高,某个细节推翻整个方案的可能性就越高,适当降低复杂性,可以减少这种风险。
- 如果方案本身就很复杂,那么就采取设计团队的方式来进行设计,博采众长,汇集大家的智慧和经验,防止1、2个设计师时可能出现的思维盲点或经验盲区。
其他相关摘要
- 目前的大数据理论基础是Google发表的三篇大数据相关论文,其中Google File System是大数据文件存储的技术理论,Google BigTable是列式存储的技术理论,Google MapReduce是大数据运算的技术理论,这三篇技术论文各自开创了一个新的技术领域。