🏠1 架构基础
- 想要搞清楚架构到底指什么,架构与框架的区别,就需要了解梳理系统、子系统、模块、组件、框架和架构
1.1系统与子系统
1.1.1系统
wiki:系统泛指由一群有关联的个体组成,根据某种规则运作,能完成个别元件不能单独完成的工作的群体,它的意思是“总体”、“整体”、“联盟”
提炼:
- 关联:有关联的个体组成才能是系统->发动机+PC≠系统,发动机+轮子+汽车相关=汽车系统
- 规则:每个个体都按照指定规则运作,不是各自为政,规则包括分工、协调
- 能力:系统能力≠单体能力相加,而是有一种新的能力,这是个体之间相互作用带来的结果
1.1.2子系统
子系统的定义和系统定义是类似的,只是观察角度有差异,一个系统可以是另一个更大系统的子系统
- 微信是一个系统,聊天,登录,支付,朋友圈等是子系统
- 朋友圈又有评论、点赞等子系统
- 评论又包括防刷子系统,审核子系统,发布子系统等
1.2模块与组件
- 模块和组件两个概念在实际工作中容易混淆,如下描述
- Mysql模块主要负责存储数据,Es模块主要负责数据搜索
- 我们有安全加密组件、审核组件
- APP的下载模块使用了第三方的组件
wiki-模块:Module是一套一致且互相有紧密关联的软件组织,包含程序和数据结构两部分。现代软件开发往往利用模块作为合成的单元,模块是可能被分开的单位,使得它们可用
wiki-组件:自包含、可编程、可重用的、与语言无关的软件单元,软件组件可以很容易的被用于组装应用程序
1.3框架与架构
- 框架和架构的概念比较相似,且有较强的关联关系
wiki-框架:指的是为了实现某个业界标准或完成特定基本任务的软件组件规范
,提供规范所要求之基础功能的软件产品
wiki-架构:指软件系统的“基础结构”,创造这些基础结构的准则
,以及对这些结构的描述
- 只有明确说明了“基础结构”这个概念是从什么角度分解的,才能说明什么是架构什么是框架,如下
- 以学生管理系统为例:
- 从物理部署角度触发,学生管理系统的架构如下
- 从开发规范角度:采用标准的MVC框架开发,因此架构又变成了MVC架构
1.4重新定义架构
架构:软件架构指软件系统的顶层结构!
- 首先,“系统由一群关联的个体组成”,这些“个体”可以是“子系统”“模块”“组件”等,架构需要明确系统包含哪些“个体”
- 其次,系统中的个体需要“根据某种规则”运作,框架需要明确个体运作和协作的规则
- 第三,wiki百科的架构定义中用到了“基础结构”这个说法,我们更改为“顶层结构”,可以更好的
区分子系统和系统
,避免
将系统架构和子系统架构混淆导致架构层次混乱
🎯2 架构设计的目的
2.1架构设计的误区
架构设计的目的?
- 架构很重要,所以要做架构设计 ✘
- 架构重要不是要做架构设计的原因:不做架构系统就不能跑?做了架构设计,能提高开发效率?
- 不是每个系统都要做架构设计吗 ✘
- 这就是麻木,盲目的跟风思想
- 公司流程要求系统开发过程中必须有架构设计 ✘
- 就像“架构师总要做点事”,“一天总得吃点饭”
- 为了高性能、可用、可扩展,所以要做架构设计 ✘
- 虽然这三个原因确实是架构设计带来的结果,但是如果带着这个观点去做架构设计,迟早有天要G
- 上来就追求高性能、可用、可扩展,会造成最后的架构复杂无比,项目落地遥遥无期
2.2以史为鉴
- 机器语言 1940之前
- 使用二进制码0-1表示指令和数据,输出数据,编程会头晕眼花
- 汇编语言 20世纪40年代
- 符号语言,使用地址符号,或者标号代替指令或者操作数地址
- 本质上面向机器编程,一个简单的功能,可能会更加的复杂
- 高级语言 20世纪50年代
- 为了解决汇编编程麻烦的问题,又出现了Fortran,LISP,Cobol…
- 第一次软件危机与结构化程序设计 20世纪60年代~70年代
- 软件质量低下,项目无法如期完成,水手一号火箭因为软件发射失败等等,《人月神话》也是这个时期由布鲁克斯写的
- 诞生了结构化程序设计:自顶向下,逐步细化,模块化
- 第二次软件危机与面向对象 20世纪80年代
- 由于业务需求的复杂度增加,编程应用的领域扩大,第二次软件危机很快就到来了
- 原因在于软件的生产速度跟不上硬件和业务的发展
- 促使了面向对象的发展(1967年就有面向对象的概念)
- 软件架构 20世纪60年代~90年代起
- 在20世纪60年代就已经有软件架构的概念,但是在90年代才流行
- 随着软件系统规模的增加,计算相关的算法和数据结构不再构成主要的设计问题,系统与系统之间的组织结构成为了新的问题
- 表现在系统规模大,耦合强,开发效率低,修改扩展困难,难排查
2.3架构设计的真正目的
架构设计的目的是为了解决复杂度带来的问题
ℹ️3 复杂度来源
架构设计是为了解决软件复杂度是指导原则,但关键点是为什么架构设计会解决复杂度
3.1高性能
-
对性能的追求是技术发展的根本驱动力:马车->蒸气火车->内燃机->电气机车,2G->3G->4G->5G,i7-8750->i7-13700
-
软件系统也存在同样的现象,简单的科学运算->Google每秒几万次搜索…
-
但是技术发展带来了性能上的提升,不一定带来复杂度的提升
-
往往一些
全新的领域的技术
才会给系统带来复杂度- 因为系统在设计时需要在这些技术间进行判断选择或组合
- 如同飞机不能取代火车,火车不能取代高铁一样,因为不同的东西有其特殊的比较因素
-
软件系统中高性能带来的复杂度体现在两方面
- 一方面是
单台
计算机内部为了高性能带来复杂度 - 一方面是
多台
计算机机群为了高性能带来的复杂度
- 一方面是
单机复杂度
- 计算机内部复杂度最关键的地方在于操作系统,CPU的能力发挥全靠操作系统
- 操作系统和性能最相关的是进程和线程
- 但是线程,进程不相关联,为了让流程变得灵活,需要任务能够在运行中进行通信->管道,MQ,信号量,共享存储
- 进程内部的任务也需要并行处理,又出现了线程,为进程的子任务
- 线程之间共享进程数据,所以又出现了锁
- 总结来说
- 操作系统通过不断进化以适应硬件尤其是CPU性能的发展
- 从最初的批处理到进程、线程的引入
- 再到多核多处理器架构的利用,逐步提升系统并行处理能力和资源利用率
- 同时也带来了诸如进程间通信、多线程并发控制等一系列复杂的技术挑战。
- 在设计高性能软件系统时,需要根据业务需求灵活运用这些技术点,并非最新或最复杂的技术就是最佳选择。
集群的复杂度
- 硬件发展飞速,但是在互联网时代,业务的发展速度远超硬件发展速度
- 为了支持某一复杂业务,单机不够,需要多台机器,并且之间需要配合达到高性能
任务分配
每台机器都可以处理完整的业务,不同的任务发到不同的机器->lb负载均衡
- 需要增加任务分配器,如:交换机或者软件网络设备,或负载均衡器,需要综合考虑性能、成本可维护性、可用性等方面
- 任务分配器和真正的业务服务器之间的连接交互能力需要选择合适的连接方式,和连接管理方式。
- 连接的建立,检测,重连该如何处理
- 分配器的分配算法,轮询、权重分配,负载分配,如果是负载分配还需要任务服务器自己上次负载状态
当Qps达到很高的高度时,就会把一(分配器)对n(执行)的结构转变为n对n,如下图
- 出现了新的问题,如果n对n,那么用户如何选择对于的任务分配器
- DNS轮询,智能DNS,CND等设备和方法
任务分解
任务分解是通过将整个任务流程分解,或者说“拆服务”,如下
- 将子业务拆分,将庞大的业务系统拆分,不管从开发的角度,还是故障排查的角度都能在一定程度上带来提升
- 前提是
拆的好
,有必要拆
- 前提是
- 分解的优点
- 简单的系统更容易做到高性能:系统简单,更容易找到关键性能点进行优化
- 可以针对单个任务进行扩展:在单体架构下,往往影响系统的就那一两个功能,将功能拆分后,就可以对特定的功能拓展机器,提升性能
3.2高可用
高可用是指“系统
无中断
执行其能力”,代表系统的可用性程度
- 无中断(关键):从单个硬件或者软件的层面出发,不可能做到无中断
- 自身:比如硬件故障、老化问题,软件会有bug,软件的复杂度越来越高和庞大
- 外部因素:断电,断网,灾害等等
- 系统的高可用方案五花八门,但是万变不离其宗,本质上都是通过“冗余”来实现高可用
- 虽然冗余方案和高性能看起来很像都是堆机器
- 但本质上有根本区别:高性能在于
扩展处理性能
,高可用在于冗余处理单元
- 但本质上有根本区别:高性能在于
- 冗余增强了可用性,但是也带来的复杂度
计算高可用(计算->业务的逻辑处理)
计算有一个特点:无论在哪台机器上进行计算,只要逻辑算法相同,参数一样,那么产出也是一样的
- 所以将计算从一台机器迁移到另一台机器对业务没有影响,那么计算高可用的复杂度在哪呢?
单机变双机的简单架构
- 复杂度在于
- 增加一个任务分配器,要综合考虑性能、成本、可维护性、可用性等方面
- 任务分配器的连接管理
- 任务分配器的分配算法
高可用集群架构
- 可以看出集群 的分配算法可能更加复杂
- 可以是1主3备,2主2备,3主1备,4主0备,没有哪一种是最好的,要根据实际业务需求分析判断,比如ZK就是1主多备,Memcached采用全主0备,Redis采用3主6从+哨兵
存储高可用
对于存储数据的系统来说,系统的高可用设计关键点就在于存储高可用
- 存储与计算相比的本质区别:将数据从一台机器搬到另一个机器,需要经过线路进行传输。
- 线路传输是毫秒级:同一个机房能做到毫秒级,但是分布在各地的机房要做到线路传输,如果算上
网络稳定
、网络分区
、物理线路故障
等问题,那么延迟将会达到500毫秒到秒级
- 线路传输是毫秒级:同一个机房能做到毫秒级,但是分布在各地的机房要做到线路传输,如果算上
- 因为
业务=数据+逻辑
如果数据没能成功同步,对于整个集群来说,可能就会出现数据不一致,那么就会出现业务表现不一致,如下
无论正常情况还是异常情况下的传输都会导致系统在某个时间点的数据不一致,从而导致业务不一致,但是不做冗余,系统的整体的高可用又无法保证
所以存储高可用的难度不在于如何备份数据,而在于如何减少和规避数据不一致对业务造成的影响
高可用状态决策(判断是否可用)
- 无论计算高可用还是存储高可用,基础都是“状态决策”,也就是系统要能够判断当前机器是否是正常还是异常
- 异常就要采用行动来保证数据一致性
- 但是通过冗余来实现高可用,状态决策本质上就不可能做到完全正确这是相矛盾的
独裁式决策
- 存在一个决策主体,一个leder
- 主要负责收集信息然后进行决策
- 存在多个上报者
- 主要将自己的状态信息发送给决策者
- 独裁式不会出现脑裂类似的情况,因为只有一个决策者
协商式决策
- 两个独立的个体通过交流信息,然后根据规则进行决策,最常用的就是主备决策
- 规则如下
- 2台服务器启动时都是备机
- 2台服务器建立连接
- 2台服务器交换状态信息
- 某1台服务器做出决策成为主机,另1台服务器继续保持备机身份、
- 可以发现协商式的架构和规则并不复杂,真正的难点在于交换信息发生了问题(网络分区),此时决策该怎么做
- 如果一旦发生联系不上另一个机器就认为其出现故障,但是如果只是网络问题,那么就会出现两个主机
- 如果不认为主机故障,那么当主机真的故障了怎么办
- 采用多个连接,只要有一个连接可用就能使用
- 多连接又会存在,如果多个连接之间的信息不同,该听谁的
所以协商式在某些场景下总会有一些问题
- 多连接又会存在,如果多个连接之间的信息不同,该听谁的
民主式决策
- 民主式决策是指多个独立的个体通过投票的方式进行状态决策。
- 如ZK选举Leder时就采用这种方式
- 民主和协商很像,都是独立个体交换信息,按照多数取胜的规则来决定状态,不同在于民主比协商决策规则更复杂
- 民主式有一个固有缺陷:脑裂,来源于集群由于网络分区导致两个子集群各自做出了两套决策
- 为了解决脑裂,民主决策系统一般采用超过系统总结点数一半的规则来处理
综上(无论采取什么方案,都会有一定的问题,选哪个还是要进行分析、判断和抉择)
3.3可扩展性
可扩展性是指系统为应对将来需求变化而提供的一种扩展能力
- 可扩展性越强,可能在新的需求出现时修改的代码量更少,无需整个系统重构
- 面相对象思想提出就是为了更好解决可扩展性的问题
预测变化
-
软件系统与硬件或者建筑相比,有一个很大的差异:软件可以在发布后不断修改和演进,可以扩展,但是建筑等却不行。
-
但是如果我们只在乎当下,不去预测变化,或者推演判断架构将来的变化和缺陷,那么就会出现一个需求大改一次系统,这和建房子推翻重造有什么区别
-
所以我们要去在有限的条件内去预测系统的变化,但是
人不是神,不会全部预测正确,并且如果总是去预测所有变化也是不现实的,并且预测的结果过于离谱需要投入的资源很多也是没有必要 -
所以预测变化的复杂性在于:
- 不能每个设计点都考虑扩展性
- 不能完全不考虑可扩展性
- 所有的预测都存在出错的可能
-
对于架构师来说,把握预测程度和提升预测结果的准确性是很复杂的事情
- 没有通用的标准
- 只能靠直觉和经验
应对变化
-
即使所有的变化都能准确预测,那么预测后用什么方案来解决也是一个问题,方案用错了即使预测对了也没用
-
第一种应对变化的常见方案是将“变化”封装在一个变化层,不变化的就放在“稳定层”
- 无论变化依赖稳定,还是稳定依赖变化都可以,如下两个架构就是不同的依赖情况
-
支持XML、JSON、ProtocolBuffer接入方式,接入方式为变化层
-
支持MySQL、Oracle、DB2数据存储,数据存储为变化层
-
无论采用哪种形式,通过剥离变化和稳定层来应对变化,都会带来两个复杂行问题
- 谁是稳定层谁是变化层,现实的例子不会如同我们举的例子一样,不同的人有不同的理解,怎么界定稳定和变化是一个复杂的问题
- 设计变化和稳定层之间的接口,稳定层的接口肯定要稳定,变化层的接口需要抽象出共同点,还要保证新功能加入时不影响原有接口
-
第二个问题可以通过设计模式解决,比如装饰器模式
3.4低成本
-
一般情况下成本并不是我们重点关注的目标
- 当我们的架构方案只涉及几台或十几台服务器时,成本并不高
- 但是当这个方案涉及几百上千台服务器时,成本就会变成一个非常重要的考虑点
-
可以想象如果一台机器的成本一年为2万元,那么如果1万台机器节省20%,一年就可以节省4000万成本
-
当我们设计高性能和高可用架构时通用手段都是通过增加更多服务器来满足其要求
- 而低成本正好和其要求相反
- 所以在设计高性能高可用时,还要评估方案是否满足成本目标
- 但是成本也不是一成不变的😏
-
低成本给架构设计带来的复杂度就在于:往往只有
创新
才能达到低成本目标- 这里的创新既包括开创一个全新的技术领域,也包括引入新技术(没找到可以解决问题的新技术,那就只能自己创造新技术)
- 例如:
- NoSQL的出现就是解决关系型数据库无法因对高并发访问带来的访问压力
- 全文搜索引擎的出现是为了解决关系型数据模糊查询低效问题
- Hadoop的出现是为了解决传统文件系统无法应对海量数据存储和计算的问题
- FaceBook为了解决PHP低效,做了类似Java的JVM的虚拟机运行字节码
- 微博将传统的Redis/MC + MySQL方式,扩展为Redis/MC + SSD Cache + MySQL,SSD Cache作为二级缓存,解决了Redis等成本高,容量小,也解决穿透DB带来的数据库压力
- Kafka消息系统,解决了Linkedin为了处理每天 5千亿 个事件的大数据消息消费
- 创造技术对于小公司来说是性价比很低的事,往往引入新技术来达到低成本目标
- 而大公司有可能去创造新技术达到低成本->大公司独有的资源、技术、时间
3.5安全
从技术角度讲,安全分为两类:
功能上
的安全与架构上
的安全
3.5.1功能安全
-
常见的XSS攻击、CSRF攻击、SQL注入、Windows漏洞、密码破解等
- 让黑客有机可乘的是系统本身就有漏洞,黑客的能通过多种手段分析你的系统漏洞,然后进行攻破,手段很多
- 所以功能安全更像是填补漏洞,《保卫系统》
-
功能安全更多是和具体的编码相关,与架构关系不大。但是很多开发框架内嵌了常见的安全的功能能尽量避免一些安全问题
-
我们所能知道的是,漏洞是不断被发现的,已知的漏洞可以修复,但是新的漏洞很可能就需要被攻破后才能进行补救
-
所以系统架构设计的功能安全不是一劳永逸的
3.5.2架构安全
-
功能安全是防漏洞,那么架构安全就是防“强盗”,一个“炸弹”给你扬了,故意破坏有时候比漏洞安全更可怕
-
传统的架构安全主要依靠防火墙,进行隔离网络,将网络划分不同的区域,制定出不同区域的访问控制策略
可以看到整个系统根据不同分区部署多个防火墙来保证安全 -
防火墙虽然吊,但是在传统的银行和企业应用领域较多
-
互联网领域防火墙应用场景并不多,主要是互联网的业务具有高并发和海量用户访问的特点,防火墙性能支撑不了
- 尤其是DDOS攻击,轻则几个G,上不封顶
- 海量DDOS攻击用防火墙来抗,就需要部署大量防火墙成本高
-
综上所述,互联网系统的架构安全目前并
没有太好
的设计手段实现,更多是依靠运营商或者云服务商强大的带宽和流量清洗能力
3.6规模
规模带来的复杂度:
量变引起质变
- 有一些系统,没有高性能、双中心高可用、扩展性等要求,但反而会显得很复杂
- 因为往往这类系统的功能很多、逻辑分支多
- 发展时间长,功能的不断叠加,让人一看一抹黑
3.6.1功能增加导致
功能越做越多,导致系统复杂度指数级上升
例如:某个系统最开始只有三大功能,后来增加到8个,复杂度如下:
3个功能:3(自身)+3(功能交互)=6
8个功能:8(自身)+28(功能交互)=36
可以发现具备8个功能的系统复杂度要更高
3.6.2数据增加导致
数据越来越多,系统复杂度发生质变
为什么数据量增大会导致系统复杂度变高?
以MySQL数据量达到10亿行举例子
- 添加索引会更慢,可能几个小时,你会想“我擦这么久”,是的就是这么久,并且这几个小时中,表是没办法插入数据的,业务卡死
- 修改表结构和添加索引存在类似问题
- 即使有索引,数据索引性能也会降低
- 数据库备份耗时增加,主从同步时间间隔增加,数据不一致的情况会更严重
所以,当单表数据量太大时,就需要拆表,拆表也会带来复杂度
- 按照什么规则拆表,维度是哪个,如用户表,是按id分还是注册时间分
- 拆完怎么查询?
📖4 小结
- 系统:泛指由一群有关联的个体组成,根据某种规则运行,能完成个别元件不能单独完成的工作群体
- 子系统:定义和系统类似,只不过他是更大系统的一部分
- 软件模块:是一套一致而互相紧密关联的软件阻止,分别包含程序和数据结构
- 软件组件:为自包含、可编程、可重用的、与语言无关的软件单元,可以很容易被组装应用程序中
- 软件框架:为了实现某个业界标准或完成特定任务的软件组件规范
- 软件架构:软件系统的顶层结构
- 同一软件系统从不同角度进行分解会得到不同架构
- 架构设计的主要是为了解决软件系统复杂度带来的问题
- 主要的软件复杂度:高性能、高可用、高扩展、低成本、安全、规模几种