《从0开始学架构》学习笔记(一)

最近购买了极客时间推出的李运华的课程——《从0开始学架构》,本人经过听音频和文字阅读,整理出相关笔记,目的是方便从此再次阅读。再次感谢李运华的讲解,购买连接:从0开始学架构 资深技术专家的实战架构心法程序员

开篇词 | 照着作,你也能成为架构师redis

想成为架构师,梦想是美好的,但道路是曲折的,这应该不是我的天资的问题,而是架构设计自己的一些特性所致。算法

  1. 架构设计的关键思惟是判断和取舍,程序设计的关键思惟是逻辑和实现。
  2. 架构设计没有体系化的培训和训练机制
  3. 程序员对架构设计的理解存在不少舞曲(好比,架构必定具有高可用、高性能等)

这个专栏涵盖:数据库

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

经过本专栏的学习,你会收获:编程

  • 清楚地理解架构的相关的概念、本质、目的
  • 掌握通用的架构设计原则
  • 掌握架构标准的设计流程
  • 深刻理解已有的架构模式
  • 掌握架构演进和开源系统使用的一些技巧

 只要你努力,技术的梦想必定会实现。设计模式

精彩留言:缓存

 

 

 

1、架构基础安全

01 | 架构究竟是指什么?性能优化

梳理几个有关系而又类似的概念:服务器

A. 系统与子系统

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

(注意:这里的“能”指的是“能力”,系统能力与个体能力又本质差异,系统能力不是个体同理之和,而是生产了新的能力。好比,汽车可以载重前行,发动机不能。)

B. 模块与组件

软件模块(Module)是一套一致而互相有紧密关连的软件组织。它分别包含了程序和数据结构两部分。现代软件开发每每利用模块做为合成的单位。模块的接口表达了由该模块提供的功能和调用它时所需的元素。模块是可能分开被编写的单位。这使它们可再用和容许人员同时协做、编写及研究不一样的模块。软件组件定义为自包含的、可编程的、可重用的、与语言无关的软件单元,软件组件能够很容易被用于组装应用程序中。(维基百科)

总结:模块和组件都是系统的组成部分,只是从不一样的角度拆分系统而已。

C. 框架与架构

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

提炼一下其中关键部分:

  1. 框架是组件规范:例如,MVC 就是一种最多见的开发规范,相似的还有 MVP、MVVM、J2EE 等框架。
  2. 框架提供基础功能的产品:例如,Spring MVC 是 MVC 的开发框架,除了知足 MVC 的规范,Spring 提供了不少基础功能来帮助咱们实现功能,包括注解(@Controller 等)、Spring Security、Spring JPA 等不少基础功能。

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

单纯从定义的角度来看,框架和架构的区别仍是比较明显的,框架关注的是“规范”,架构关注的是“结构”。框架的英文是 Framework,架构的英文是 Architecture。Spring MVC 的英文文档标题就是“Web MVC framework”。

“从业务逻辑的角度分解,“学生管理系统”的机构是:

从物理部署的角度分解,“学生管理系统”的架构是:

 

 

从开发规范的角度分解,“学生管理系统”能够采用标准的 MVC 框架来开发,所以架构又变成了 MVC 架构:

 

这些“架构”,都是“学生管理系统”正确的架构,只是从不一样的角度来分解而已,这也是 IBM 的 RUP 将软件架构视图分为著名的“4+1 视图”的缘由。

从新定义架构

“软件架构指软件系统的顶层结构”——李运华

首先,“系统是一群关联个体组成”,这些“个体”能够是“子系统”“模块”“组件”等;架构须要明确系统包含哪些“个体”。

其次,系统中的个体须要“根据某种规则”运做,架构须要明确个体运做和协做的规则。

第三,维基百科定义的架构用到了“基础结构”这个说法,我改成“顶层结构”,能够更好地区分系统和子系统,避免将系统架构和子系统架构混淆在一块儿致使架构层次混乱。

精选留言:

02 | 架构设计的历史背景

机器语言(1940 年以前)

1.机器语言(1940 年以前)

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

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

为了解决机器语言编写、阅读、修改复杂的问题,汇编语言应运而生。汇编语言又叫“符号语言”,用助记符代替机器指令的操做码,用地址符号(Symbol)或标号(Label)代替指令或操做数的地址。

为了解决机器语言编写、阅读、修改复杂的问题,汇编语言应运而生。汇编语言又叫“符号语言”,用助记符代替机器指令的操做码,用地址符号(Symbol)或标号(Label)代替指令或操做数的地址。

机器语言:1000100111011000
汇编语言:mov ax,bx
.section .data
  a: .int 10
  b: .int 20
  format: .asciz "%d\n"
.section .text
.global _start
_start:
  movl a, %edx  
  addl b, %edx  
  pushl %edx
  pushl $format
  call printf
  movl $0, (%esp)
  call exit
除了编写自己复杂,还有另一个复杂的地方在于:不一样 CPU 的汇编指令和结构是不一样的。

除了编写自己复杂,还有另一个复杂的地方在于:不一样 CPU 的汇编指令和结构是不一样的。

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

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

  • Fortran:1955 年,名称取自”FORmula TRANslator”,即公式翻译器,由约翰·巴科斯(John Backus)等人发明。
  • LISP:1958 年,名称取自”LISt Processor”,即枚举处理器,由约翰·麦卡锡(John McCarthy)等人发明。
  • Cobol:1959 年,名称取自”Common Business Oriented Language”,即通用商业导向语言,由葛丽丝·霍普(Grace Hopper)发明。

这些语言让程序员不须要关注机器底层的低级结构和逻辑,而只要关注具体的问题和业务便可。

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

20 世纪 60 年代中期开始爆发了第一次软件危机,典型表现有软件质量低下、项目没法如期完成、项目严重超支等,由于软件而致使的重大事故时有发生。例如,1963 年美国(http://en.wikipedia.org/wiki/Mariner_1)的水手一号火箭发射失败事故,就是由于一行 FORTRAN 代码错误致使的。

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

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

结构化编程的风靡在必定程度上缓解了软件危机,然而随着硬件的快速发展,业务需求愈来愈复杂,以及编程应用领域愈来愈普遍,第二次软件危机很快就到来了。第二次软件危机的根本缘由仍是在于软件生产力远远跟不上硬件和业务的发展。

第一次软件危机的根源在于软件的“逻辑”变得很是复杂,而第二次软件危机主要体如今软件的“扩展”变得很是复杂。

结构化程序设计虽然可以解决(也许用“缓解”更合适)软件逻辑的复杂性,可是对于业务变化带来的软件扩展却无能为力,软件领域迫切但愿找到新的银弹来解决软件危机,在这种背景下,面向对象的思想开始流行起来。

软件架构的历史背景

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

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

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

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

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

咱们能够看到,“模块”“对象”“组件”本质上都是对达到必定规模的软件进行拆分,差异只是在于随着软件的复杂度不断增长,拆分的粒度愈来愈粗,拆分的层次愈来愈高。《人月神话》中提到的 IBM 360 大型系统,开发时间是 1964 年,那个时候结构化编程都尚未提出来,更不用说软件架构了。若是 IBM 360 系统放在 20 世纪 90 年代开发,无论是质量仍是效率、成本,都会比 1964 年开始作要好得多,固然,这样的话咱们可能就看不到《人月神话》了。

03 | 架构设计的目的

架构设计的真正目的到底是什么?

架构设计的主要目的是为了解决软件系统复杂度带来的问题。

架构设计并非要面面俱到,不须要每一个架构都具有高性能、高可用、高扩展等特色,而是要识别出复杂点而后有针对性地解决问题。

简单的复杂度分析案例:

假设咱们须要设计一个学生管理系统:

  • 性能:一个学校大约1~2万人,学生管理系统的访问率并不高,所以性能要求并不高,存储用MySQL彻底能胜任,缓存能够不用,Web服务器用Nginx绰绰有余。
  • 可扩展性:学生管理系统的功比较稳定,可扩展性不强。
  • 高可用:宕机2小时对学生影响可能不大,能够不作负载均衡,不用考虑异地多活这类复杂的方案。学生信息的存储比较重要,所以须要考虑存储高可靠。还要考虑:机器故障、机房故障,针对机器故障可设计MySQL同机房主备方案;针对机房故障,可考虑设计MySQL跨机房同步方案。
  • 安全性:基本知足:Nginx提供ACL控制、用户帐号密码管理、数据库访问权限控制。
  • 成本:服务器使用数量很少。

精选留言:

 04 | 复杂度来源:高性能

软件系统中高性能带来的复杂度主要体如今两个方面:

  1. 单台计算机内部为了高性能带来的复杂度
  2. 多太计算机集群为了高性能带来的复杂度

单机复杂度

计算机内部复杂度最关键的地方就是操做系统。计算机性能的发展本质上是悠硬件发展驱动的,尤为是CPU的性能发展。

操做系统和性能相关的就是进程线程

  • 最先的计算机没有操做系统,只有输入、计算和输出功能。这样的处理性能效率很低。
  • 为解决手工操做带来的低效,批处理应运而生,性能就有了很大的提高。(缺点:计算机一次只能执行一个任务,若是某个任务须要从I/O设备(例如磁带)读取大量的数据,在I/O操做的过程当中,CPU实际上是空闲的,浪费了部分资源)
  • 为进一步提高性能,人们发明了“进程”,用进程来对应一个任务,每一个任务都有本身的独立内存空间,进程间互不相关,由操做系统来进行调度。(此时的CPU尚未多核和多线程的概念,为了达到多进程并行的目的,采起了分时的方式。同时,进程间通讯的各类方式被设计出来,包括管道、消息队列、信号量、共享存储等。多进程让多任务可以并行处理,但自己缺点:单个进程内部只能串行处理,而实际上不少进程内部的子任务并不要求是严格按照时间顺序来执行的,也须要并行处理。)
  • 为解决进程的缺点,人们发明了线程。(同时,为保证数据的正确性,又发明了互斥锁机制。有了多线程后,操做系统调度的最小单位就变成了线程,而进程变成了操做系统分配资源的最小单位。多进程多线程虽让多任务并行处理的性能大大提高,但本质仍是分时系统,并不能实现真正意义上的多任务并行)
  • 多个CPU可以同时执行计算任务,实现真正意义上的多任务并行:目前这样的解决方案有 3 种:SMP(Symmetric Multi-Processor,对称多处理器结构)、NUMA(Non-Uniform Memory Access,非一致存储访问结构)、MPP(Massive Parallel Processing,海量并行处理结构)。

操做系统发展到如今,若是咱们要完成一个高性能的软件系统,须要考虑如多进程、多线程、进程间通讯、多线程并发等技术点,并且这些技术并非最新的就是最好的,也不是非此即彼的选择。在作架构设计的时候,须要花费很大的精力来结合业务进行分析、判断、选择、组合,这个过程一样很复杂。

虽然,计算机操做系统和硬件的发展已经很快了,可是在进入互联网时代后,业务的发展速度远远更超前了。例如:

  • 2016 年“双 11”支付宝每秒峰值达 12 万笔支付。
  • 2017 年春节微信红包收发红包每秒达到 76 万个

 单机的性能没法支撑业务需求的增加,必须采用机器集群的方式来达到高性能。可是,经过大量的机器来提高性能,并不只仅是增长机器这么简单,下面是针对几种方式的加单分析:

1.任务分配:

任务分配的意思是指,每台机器均可以处理完整的业务任务,不一样的任务分配到不一样的机器上执行。

例如:从最简单的一台服务器变两台服务器:

此时架构上明显要复杂多了,主要体如今:

  • 须要增长一个任务分配器,这个分配器多是硬件网络设备(例如,F五、交换机等),多是软件网络设备(例如,LVS),也多是负载均衡软件(例如,Nginx、HAProxy),还多是本身开发的系统。选择合适的任务分配器也是一件复杂的事情,须要综合考虑性能、成本、可维护性、可用性等各方面的因素。
  • 任务分配器和真正的业务服务器之间有链接和交互(即图中任务分配器到业务服务器的链接线),须要选择合适的链接方式,而且对链接进行管理。例如,链接创建、链接检测、链接中断后如何处理等。
  • 任务分配器须要增长分配算法。例如,是采用轮询算法,仍是按权重分配,又或者按照负载进行分配。若是按照服务器的负载进行分配,则业务服务器还要可以上报本身的状态给任务分配器。

 假设性能要求继续提升,要求每秒提高到10万次:

这个架构比 2 台业务服务器的架构要复杂,主要体如今:

  • 任务分配器从 1 台变成了多台(对应图中的任务分配器 1 到任务分配器 M),这个变化带来的复杂度就是须要将不一样的用户分配到不一样的任务分配器上(即图中的虚线“用户分配”部分),常见的方法包括 DNS 轮询、智能 DNS、CDN(Content Delivery Network,内容分发网络)、GSLB 设备(Global Server Load Balance,全局负载均衡)等。
  • 任务分配器和业务服务器的链接从简单的“1 对多”(1 台任务分配器链接多台业务服务器)变成了“多对多”(多台任务分配器链接多台业务服务器)的网状结构。
  • 机器数量从 3 台扩展到 30 台(通常任务分配器数量比业务服务器要少,这里咱们假设业务服务器为 25 台,任务分配器为 5 台),状态管理、故障处理复杂度也大大增长。

上面这两个例子都是以业务处理为例,实际上“任务”涵盖的范围很广,能够指完整的业务处理,也能够单指某个具体的任务。例如,“存储”“运算”“缓存”等均可以做为一项任务,所以存储系统、运算系统、缓存系统均可以按照任务分配的方式来搭建架构。此外,“任务分配器”也并不必定只能是物理上存在的机器或者者一个独立运行的程序,也能够是嵌入在其余程序中的算法,例如 Memcache 的集群架构。

2.任务分解

经过任务分配的方式,可以突破单台机器处理性能的瓶颈,经过增长更多的机器来知足业务的性能需求,但若是业务自己也愈来愈复杂,单纯只经过任务分配的方式来扩展性能,收益会愈来愈低。

为了可以继续提高性能,咱们须要采起第二种方式:任务分解。

那为什么经过任务分解就可以提高性能呢?

1.简单的系统更加容易作到高性能

系统的功能越简单,影响性能的点就越少,就更加容易进行有针对性的优化。而系统很复杂的状况下,首先是比较难以找到关键性能点,由于须要考虑和验证的点太多;其次是即便花费很大力气找到了,修改起来也不容易。

2.能够针对单个任务进行扩展

当各个逻辑任务分解到独立的子系统后,整个系统的性能瓶颈更加容易发现,并且发现后只须要针对有瓶颈的子系统进行性能优化或者提高,不须要改动整个系统,风险会小不少。

虽然系统拆分可能在某种程度上能提高业务处理性能,但提高性能也是有限的。理论上的性能是有一个上限的,系统拆分可以让性能逼近这个极限,但没法突破这个极限。所以,任务分解带来的性能收益是有一个度的,并非任务分解越细越好,而对于架构设计来讲,如何把握这个粒度就很是关键了

精选留言:

05 | 复杂度来源:高可用

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

 “无中断”的干扰因素有不少:硬件出现故障、软件Bug、外部环境的不可控,不可避免性,地震水灾等。因此,系统的高可用方案五花八门,可是本质都是经过“冗余”来实现高可用。

通俗点来说,就是一台机器不够就两台,两台不够就四台;一个机房可能断电,那就部署两个机房;一条通道可能故障,那就用两条,两条不够那就用三条(移动、电信、联通一块儿上)。

高可用的“冗余”解决方案,单纯从形式上来看,和以前讲的高性能是同样的,都是经过增长更多机器来达到目的,但其实本质上是有根本区别的:高性能增长机器目的在于“扩展”处理性能;高可用增长机器目的在于“冗余”处理单元。

 1.计算高可用

这里的“计算”指的是业务的逻辑处理。计算有一个特色就是不管在哪台机器上进行计算,一样的算法和输入数据,产出的结果都是同样的,因此将计算从一台机器迁移到另一台机器,对业务并无什么影响。

单机变双机的简单架构示意图:

 

 

这个双机的架构图和上期“高性能”讲到的双机架构图是同样的,所以复杂度也是相似的,具体表现为:

  • 须要增长一个任务分配器,选择合适的任务分配器也是一件复杂的事情,须要综合考虑性能、成本、可维护性、可用性等各方面因素。
  • 任务分配器和真正的业务服务器之间有链接和交互,须要选择合适的链接方式,而且对链接进行管理。例如,链接创建、链接检测、链接中断后如何处理等。
  • 任务分配器须要增长分配算法。例如,常见的双机算法有主备、主主,主备方案又能够细分为冷备、温备、热备。

上面这个示意图只是简单的双机架构,再看一个复杂一点的高可用集群架构:

这个高可用集群相比双机来讲,分配算法更加复杂,能够是 1 主 3 备、2 主 2 备、3 主 1 备、4 主 0 备,具体应该采用哪一种方式,须要结合实际业务需求来分析和判断,并不存在某种算法就必定优于另外的算法。例如,ZooKeeper 采用的就是 1 主多备,而 Memcached 采用的就是全主 0 备。

2.存储高可用

存储与计算相比,有一个本质上的区别:将数据从一台机器搬到到另外一台机器,须要通过线路进行传输。

  1. 正常状况下的传输延迟:线路传输的速度是毫秒级别,同一机房内部可以作到几毫秒;分布在不一样地方的机房,传输耗时须要几十甚至上百毫秒。(例如,从广州机房到北京机房,稳定状况下 ping 延时大约是 50ms,不稳定状况下可能达到 1s 甚至更多。)
  2. 异常状况下的传输中断:传输线路可能中断、可能拥塞、可能异常(错包、丢包),而且传输线路的故障时间通常都特别长,短的十几分钟,长的几个小时都是可能的。

综合分析,以上两点都会致使系统的数据在某个时间点或者时间段是不一致的,而数据的不一致又会致使业务问题;但若是彻底不作冗余,系统的总体高可用又没法保证,因此存储高可用的难点不在于如何备份数据,而在于如何减小或者规避数据不一致对业务形成的影响。分布式领域里面有一个著名的 CAP 定理,从理论上论证了存储高可用的复杂度。也就是说,存储高可用不可能同时知足“一致性、可用性、分区容错性”,最多知足其中两个,这就要求咱们在作架构设计时结合业务进行取舍。

高可用状态决策

一个本质的矛盾:经过冗余来实现的高可用系统,状态决策本质上就不可能作到彻底正确。下面我基于几种常见的决策方式进行详细分析。

1. 独裁式

独裁式的决策方式:

  • 优势:不会出现决策混乱的问题,由于只有一个决策者。
  • 缺点:当决策者自己故障时,整个系统就没法实现准确的状态决策。若是决策者自己又作一套状态决策,那就陷入一个递归的死循环了。

2. 协商式

协商式决策指的是两个独立的个体经过交流信息,而后根据规则进行决策,最经常使用的协商式决策就是主备决策。

这个架构的基本协商规则能够设计成:

  • 2 台服务器启动时都是备机。
  • 2 台服务器创建链接。
  • 2 台服务器交换状态信息。
  • 某 1 台服务器作出决策,成为主机;另外一台服务器继续保持备机身份。

协商式决策的架构不复杂,规则也不复杂,其难点在于,若是二者的信息交换出现问题(好比主备链接中断),此时状态决策应该怎么作。若是备机在链接中断的状况下认为主机故障,那么备机须要升级为主机。

下面分为三种状况:

第一种状况:若是备机在链接中断的状况下,实际上主机并无故障,那么系统就出现了两个主机,这与设计初衷(1 主 1 备)是不符合的。

第二种状况:若是备机在链接中断的状况下不认为主机故障,则此时若是主机真的发生故障,那么系统就没有主机了,这一样与设计初衷(1 主 1 备)是不符合的。

第三种状况:若是为了规避链接中断对状态决策带来的影响,能够增长更多的链接。

例如,双链接、三链接。这样虽然可以下降链接中断对状态带来的影响(注意:只能下降,不能完全解决),但同时又引入了这几条链接之间信息取舍的问题,即若是不一样链接传递的信息不一样,应该以哪一个链接为准?实际上这也是一个无解的答案,不管以哪一个链接为准,在特定场景下均可能存在问题。

综合分析,协商式状态决策在某些场景老是存在一些问题的。

3. 民主式

民主式决策指的是多个独立的个体经过投票的方式来进行状态决策。例如,ZooKeeper 集群在选举 leader 时就是采用这种方式。

民主式决策和协商式决策比较相似,其基础都是独立的个体之间交换信息,每一个个体作出本身的决策,而后按照“多数取胜”的规则来肯定最终的状态。不一样点在于民主式决策比协商式决策要复杂得多,ZooKeeper 的选举算法 Paxos,绝大部分人都看得云里雾里,更不用说用代码来实现这套算法了。

除了算法复杂,民主式决策还有一个固有的缺陷:脑裂。

从图中能够看到,正常状态的时候,节点 5 做为主节点,其余节点做为备节点;当链接发生故障时,节点 一、节点 二、节点 3 造成了一个子集群,节点 四、节点 5 造成了另一个子集群,这两个子集群的链接已经中断,没法进行信息交换。按照民主决策的规则和算法,两个子集群分别选出了节点 2 和节点 5 做为主节点,此时整个系统就出现了两个主节点。这个状态违背了系统设计的初衷,两个主节点会各自作出本身的决策,整个系统的状态就混乱了。

为了解决脑裂问题,民主式决策的系统通常都采用“投票节点数必须超过系统总节点数一半”规则来处理。

如图中那种状况,节点 4 和节点 5 造成的子集群总节点数只有 2 个,没有达到总节点数 5 个的一半,所以这个子集群不会进行选举。这种方式虽然解决了脑裂问题,但同时下降了系统总体的可用性,即若是系统不是由于脑裂问题致使投票节点数过少,而真的是由于节点故障(例如,节点 一、节点 二、节点 3 真的发生了故障),此时系统也不会选出主节点,整个系统就至关于宕机了,尽管此时还有节点 4 和节点 5 是正常的。

综合分析,不管采起什么样的方案,状态决策都不可能作到任何场景下都没有问题,但彻底不作高可用方案又会产生更大的问题,如何选取适合系统的高可用方案,也是一个复杂的分析、判断和选择的过程。

精选留言:

06 | 复杂度来源:可扩展性

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

  • 软件系统固有的多变性,新的需求总会不断提出来,所以可扩展性显得尤为重要。
  • 在软件开发领域,面向对象思想的提出,就是为了解决可扩展性带来的问题。
  • 设计模式,更是将可扩展性作到了极致。

设计具有良好可扩展性的系统,有两个基本条件:正确预测变化、完美封装变化

预测变化的复杂性在于:

  • 不能每一个设计点都考虑可扩展性。
  • 不能彻底不考虑可扩展性。
  • 全部的预测都存在出错的可能性。

对于架构师来讲,如何把握预测的程度和提高预测结果的准确性,是一件很复杂的事情,并且没有通用的标准能够简单套上去,更可能是靠本身的经验、直觉。没有明确标准,不一样的人理解和判断有误差,而最终又只能选择一个判断。

应对变化

即便是经验丰富的架构师,在预测到全部的变化的可能性,也不能保证可扩展性就很容易获得实现,预测准确,方案不适合,也是一件很麻烦的事情。

第一种应对变化的常见方案是将“变化”封装在一个“变化层”,将不变的部分封装在一个独立的“稳定层”。

不管是变化层依赖稳定层,仍是稳定层依赖变化层都是能够的,须要根据具体业务状况来设计。例如,若是系统须要支持 XML、JSON、ProtocolBuffer 三种接入方式,那么最终的架构就是上面图中的“形式 1”架构,也就是下面这样。

 

若是系统须要支持 MySQL、Oracle、DB2 数据库存储,那么最终的架构就变成了“形式 2”的架构了,你能够看下面这张图。

不管采起哪一种形式,经过剥离变化层和稳定层的方式应对变化,都会带来两个主要的复杂性相关的问题。

  1. 系统须要拆分出变化层和稳定层
  2. 须要设计变化层和稳定层之间的接口

第二种常见的应对变化的方案是提炼出一个“抽象层”和一个“实现层”。抽象层是稳定的,实现层能够根据具体业务须要定制开发,当加入新的功能时,只须要增长新的实现,无须修改抽象层。这种方案典型的实践就是设计模式和规则引擎。考虑到绝大部分技术人员对设计模式都很是熟悉,我以设计模式为例来讲明这种方案的复杂性。以设计模式的“装饰者”模式来分析,下面是装饰者模式的类关系图。

图中的 Component 和 Decorator 就是抽象出来的规则,这个规则包括几部分:

  1. Component 和 Decorator 类。
  2. Decorator 类继承 Component 类。
  3. Decorator 类聚合了 Component 类。

这个规则一旦抽象出来后就固定了,不能轻易修改。例如,把规则 3 去掉,就没法实现装饰者模式的目的了。装饰者模式相比传统的继承来实现功能,确实灵活不少。

例如,《设计模式》中装饰者模式的样例“TextView”类的实现,用了装饰者以后,可以灵活地给 TextView 增长额外更多功能,好比能够增长边框、滚动条、背景图片等,这些功能上的组合不影响规则,只须要按照规则实现便可。但装饰者模式相对普通的类实现模式,明显要复杂多了。原本一个函数或者一个类就能搞定的事情,如今要拆分红多个类,并且多个类之间必须按照装饰者模式来设计和调用。

精选留言:

07 | 复杂度来源:低成本、安全、规模

前面已经讲了高性能、高可用和可扩展性,今天我来聊聊复杂度另外三个来源低成本、安全和规模。

1.低成本

当咱们设计“高性能”“高可用”的架构时,通用的手段都是增长更多服务器来知足“高性能”和“高可用”的要求;而低成本正好与此相反,咱们须要减小服务器的数量才能达成低成本的目标。所以,低成本本质上是与高性能和高可用冲突的,因此低成本不少时候不会是架构设计的首要目标,而是架构设计的附加约束。

低成本给架构设计带来的主要复杂度体如今,每每只有“创新”才能达到低成本目标。这里的“创新”既包括开创一个全新的技术领域(这个要求对绝大部分公司过高),也包括引入新技术,若是没有找到可以解决本身问题的新技术,那么就真的须要本身创造新技术了。

相似的新技术例子不少:

  • NoSQL(Memcache、Redis 等)的出现是为了解决关系型数据库没法应对高并发访问带来的访问压力。
  • 全文搜索引擎(Sphinx、Elasticsearch、Solr)的出现是为了解决关系型数据库 like 搜索的低效的问题。
  • Hadoop 的出现是为了解决传统文件系统没法应对海量数据存储和计算的问题。

再来举几个业界相似的例子:

  • Facebook 为了解决 PHP 的低效问题,刚开始的解决方案是 HipHop PHP,能够将 PHP 语言翻译为 C++ 语言执行,后来改成 HHVM,将 PHP 翻译为字节码而后由虚拟机执行,和 Java 的 JVM 相似。
  • 新浪微博将传统的 Redis/MC + MySQL 方式,扩展为 Redis/MC + SSD Cache + MySQL 方式,SSD Cache 做为 L2 缓存使用,既解决了 MC/Redis 成本太高,容量小的问题,也解决了穿透 DB 带来的数据库访问压力(来源:http://www.infoq.com/cn/articles/weibo-platform-archieture )。
  • Linkedin 为了处理天天 5 千亿的事件,开发了高效的 Kafka 消息系统。
  • 其余相似将 Ruby on Rails 改成 Java、Lua + redis 改成 Go 语言实现的例子还有不少。

不管是引入新技术,仍是本身创造新技术,都是一件复杂的事情。引入新技术的主要复杂度在于须要去熟悉新技术,而且将新技术与已有技术结合起来;创造新技术的主要复杂度在于须要本身去创造全新的理念和技术,而且新技术跟旧技术相比,须要有质的飞跃。

2.安全

安全自己是一个庞大而又复杂的技术领域,而且一旦出问题,对业务和企业形象影响很是大。例如:

  • 2016 年雅虎爆出史上最大规模信息泄露事件,逾 5 亿用户资料在 2014 年被窃取。2
  • 016 年 10 月美国遭史上最大规模 DDoS 攻击,东海岸网站集体瘫痪。
  • 2013 年 10 月,为全国 4500 多家酒店提供网络服务的浙江慧达驿站网络有限公司,因安全漏洞问题,致 2 千万条入住酒店的客户信息泄露,由此致使不少敲诈、家庭破裂的后续事件。

正由于常常可以看到或者听到各种安全事件,因此大部分技术人员和架构师,对安全这部分会多一些了解和考虑。

从技术的角度来说,安全能够分为两类:

  1.功能上的安全:

  例如,常见的 XSS 攻击、CSRF 攻击、SQL 注入、Windows 漏洞、密码破解等,本质上是由于系统实现有漏洞,黑客有了可乘之机。

  从实现的角度来看,功能安全更多地是和具体的编码相关,与架构关系不大。如今不少开发框架都内嵌了常见的安全功能,可以大大减小安全相关功能的重复开发,但框架只能预防常见的安全漏洞和风险(常见的 XSS 攻击、CSRF 攻击、SQL 注入等),没法预知新的安全问题,并且框架自己不少时候也存在漏洞(例如,流行的 Apache Struts2 就屡次爆出了调用远程代码执行的高危漏洞,给整个互联网都形成了必定的恐慌)。

  因此功能安全是一个逐步完善的过程,并且每每都是在问题出现后才能有针对性的提出解决方案,咱们永远没法预测系统下一个漏洞在哪里,也不敢说本身的系统确定没有任何问题。换句话讲,功能安全其实也是一个“攻”与“防”的矛盾,只能在这种攻防大战中逐步完善,不可能在系统架构设计的时候一劳永逸地解决。

  2.架构上的安全:

  架构设计时须要特别关注架构安全,尤为是互联网时代,理论上来讲系统部署在互联网上时,全球任何地方均可以发起攻击。

  传统的架构安全主要依靠防火墙,防火墙最基本的功能就是隔离网络,经过将网络划分红不一样的区域,制定出不一样区域之间的访问控制策略来控制不一样信任程度区域间传送的数据流。例如,下图是一个典型的银行系统的安全架构。

 

 

从图中你能够看到,整个系统根据不一样的分区部署了多个防火墙来保证系统的安全。

防火墙的功能虽然强大,但性能通常,因此在传统的银行和企业应用领域应用较多。但在互联网领域,防火墙的应用场景并很少。由于互联网的业务具备海量用户访问和高并发的特色,防火墙的性能不足以支撑;尤为是互联网领域的 DDoS 攻击,轻则几 GB,重则几十 GB。2016 年知名安全研究人员布莱恩·克莱布斯(Brian Krebs)的安全博客网站遭遇 DDoS 攻击,攻击带宽达 665Gbps,是目前在网络犯罪领域已知的最大的拒绝服务攻击。这种规模的攻击,若是用防火墙来防,则须要部署大量的防火墙,成本会很高。例如,中高端一些的防火墙价格 10 万元,每秒能抗住大约 25GB 流量,那么应对这种攻击就须要将近 30 台防火墙,成本将近 300 万元,这还不包括维护成本,而这些防火墙设备在没有发生攻击的时候又没有什么做用。也就是说,若是花费几百万元来买这么一套设备,有可能几年都发挥不了任何做用。就算是公司对钱不在意,通常也不会堆防火墙来防 DDoS 攻击,由于 DDoS 攻击最大的影响是大量消耗机房的出口总带宽。无论防火墙处理能力有多强,当出口带宽被耗尽时,整个业务在用户看来就是不可用的,由于用户的正常请求已经没法到达系统了。防火墙可以保证内部系统不受冲击,但用户也是进不来的。对于用户来讲,业务都已经受到影响了,至因而由于用户本身进不去,仍是由于系统出故障,用户其实根本不会关心。

基于上述缘由,互联网系统的架构安全目前并无太好的设计手段来实现,更多地是依靠运营商或者云服务商强大的带宽和流量清洗的能力,较少本身来设计和实现。

3.规模

不少企业级的系统,既没有高性能要求,也没有双中心高可用要求,也不须要什么扩展性,但每每咱们一说到这样的系统,不少人都会脱口而出:这个系统好复杂!为何这样说呢?关键就在于这样的系统每每功能特别多,逻辑分支特别多。特别是有的系统,发展时间比较长,不断地往上面叠加功能,后来的人因为不熟悉整个发展历史,可能连不少功能的应用场景都不清楚,或者细节根本没法掌握,面对的就是一个黑盒系统,看不懂、改不动、不敢改、修不了,复杂度天然就感受很高了。

规模带来复杂度的主要缘由就是“量变引发质变”,当数量超过必定的阈值后,复杂度会发生质的变化。常见的规模带来的复杂度有:

1. 功能愈来愈多,致使系统复杂度指数级上升

例如,某个系统开始只有 3 大功能,后来不断增长到 8 大功能,虽然仍是同一个系统,但复杂度已经相差很大了,具体相差多大呢?我以一个简单的抽象模型来计算一下,假设系统间的功能都是两两相关的,系统的复杂度 = 功能数量 + 功能之间的链接数量,经过计算咱们能够看出:

  • 3 个功能的系统复杂度 = 3 + 3 = 6
  • 8 个功能的系统复杂度 = 8 + 28 = 36

能够看出,具有 8 个功能的系统的复杂度不是比具有 3 个功能的系统的复杂度多 5,而是多了 30,基本是指数级增加的,主要缘由在于随着系统功能数量增多,功能之间的链接呈指数级增加。下图形象地展现了功能数量的增多带来了复杂度。

经过肉眼就能够很直观地看出,具有 8 个功能的系统复杂度要高得多。

2. 数据愈来愈多,系统复杂度发生质变

与功能相似,系统数据愈来愈多时,也会由量变带来质变,最近几年火热的“大数据”就是在这种背景下诞生的。大数据单独成为了一个热门的技术领域,主要缘由就是数据太多之后,传统的数据收集、加工、存储、分析的手段和工具已经没法适应,必须应用新的技术才能解决。目前的大数据理论基础是 Google 发表的三篇大数据相关论文,其中 Google File System 是大数据文件存储的技术理论,Google Bigtable 是列式数据存储的技术理论,Google MapReduce 是大数据运算的技术理论,这三篇技术论文各自开创了一个新的技术领域。

即便咱们的数据没有达到大数据规模,数据的增加也可能给系统带来复杂性。最典型的例子莫过于使用关系数据库存储数据,我以 MySQL 为例,MySQL 单表的数据因不一样的业务和应用场景会有不一样的最优值,但无论怎样都确定是有必定的限度的,通常推荐在 5000 万行左右。若是由于业务的发展,单表数据达到了 10 亿行,就会产生不少问题,例如:

  • 添加索引会很慢,可能须要几个小时,这几个小时内数据库表是没法插入数据的,至关于业务停机了。
  • 修改表结构和添加索引存在相似的问题,耗时可能会很长。
  • 即便有索引,索引的性能也可能会很低,由于数据量太大。
  • 数据库备份耗时很长。
  • ……

所以,当 MySQL 单表数据量太大时,咱们必须考虑将单表拆分为多表,这个拆分过程也会引入更多复杂性,例如:

拆表的规则是什么?以用户表为例:是按照用户 id 拆分表,仍是按照用户注册时间拆表?拆完表后查询如何处理?以用户表为例:假设按照用户 id 拆表,当业务须要查询学历为“本科”以上的用户时,要去不少表查询才能获得最终结果,怎么保证性能?

综合分析,协商式状态决策在某些场景老是存在一些问题的。
软件架构的历史背景
20 世纪 60 年代中期开始爆发了第一次软件危机,典型表现有软件质量低下、项目没法如期完成、项目严重超支等,由于软件而致使的重大事故时有发生。例如,1963 年美国(http://en.wikipedia.org/wiki/Mariner_1)的水手一号火箭发射失败事故,就是由于一行 FORTRAN 代码错误致使的。
这些语言让程序员不须要关注机器底层的低级结构和逻辑,而只要关注具体的问题和业务便可。
Fortran:1955 年,名称取自”FORmula TRANslator”,即公式翻译器,由约翰·巴科斯(John Backus)等人发明。LISP:1958 年,名称取自”LISt Processor”,即枚举处理器,由约翰·麦卡锡(John McCarthy)等人发明。Cobol:1959 年,名称取自”Common Business Oriented Language”,即通用商业导向语言,由葛丽丝·霍普(Grace Hopper)发明。
汇编语言(20 世纪 40 年代
相关文章
相关标签/搜索