《人月神话》书中描述,软件从产生到现在,有一项从未停下的斗争,即:和软件复杂度的尔虞我诈,任何软件设计的迭代更新都是从有序到无序的趋势,因此出现每到达一定阶段就需要进行的“重构”阶段。其中,架构设计的主要目的:为了解决复杂度带来的问题。系统复杂度又分为“高可用”、“高性能”和“可扩展”。那么在进行架构设计时,就需要从这几方面考虑,结合系统的实际场景,选择合适的架构设计方案。
当我们谈到一辆车的时候,会想到它的发动机、电气设备、轮毂、车身骨架、安全配置等,这些结构和组件的定义是既定标准。
同样,在做系统架构的时候,也总有几个出场频率很高的关键词,模块、组件、框架等,可以理解从不同角度、维度去描述、去解释“架构”,看下构成它的代表词们的含义是什么。
开发中经常提到模块和组件,例如:我们有可复用的日志组件、限流组件,我们分为登录模块、支付模块等等,在使用过程中多少会有一种混合的感觉,参考维基百科中对二者的定义:
模块
软件模块(Module)是一套一致且互相有紧密关联的软件组织,它包含程序和数据两部分,现代软件开发往往利用模块作为合成的单位。
组件
软件组件定义为自包含的、可编程的、可重用的、与语言无关的软件单元,软件组件可以很容易的被用于组装应用程序。
概念描述很清晰,从逻辑角度拆分后得到的单元就是模块,从物理的的角度拆分后得到的、可复用的就是组件。划分模块的目的是职责分离,划分组件的原因是单元复用,组件的英文是Component,也就是零件的作用,即组成整体的、可复用一部分。
在实际开发中,当我们依据职责划分的、由数据和程序组成的逻辑单元可称作模块,比如电商系统的登录模块、履约模块、物流模块等;当然,也可依据于物理角度划分出日志组件、熔断组件以及配置中心等可复用的能力型组件。
框架和架构,相比于组件和模块感觉界限更加模糊。框架(Framework),描述形似大楼总设计的概况系统图,对于有开发经验的同学来说,接触较多的如Spring Framework;架构(Architecture),是一种很宽泛的概念,似乎框架也属于架构的一种表达方式,参考标准定义:
框架
软件框架(Software Framework)通常指为了实现业界标准或完成特定基本任务的软件组件规范,也指为了实现某个软件组织规范时,提供规范所要求之基础功能软件产品。
基础规范、基础功能产品,强调的是规范下的基础产品,例如MVC是一种开发规范,Spring MVC是基础规范MVC的基础产品。
架构
软件架构是指软件系统的“基础结构”,创造这些基础结构的准则,以及对这些结构的描述。
基础结构,即Architecture,系统的顶层架构:
可以通过系统、子系统、模块、组件等关系描述出来系统的顶层架构情况,就可以称之为架构。
那么我们为什么要做架构设计呢?
回想软件开发历史,从机器语言到汇编语言,再到现在的高级语言,无疑是在和复杂性做坚决的斗争,架构设计亦然,本质是为了解决复杂度带来的问题。
在系统发展中,堆砌代码、走一步看一步,相信达到一定程度后,该系统无法在维护,因为不知道系统现在是什么的结构、没有模块、组件区分,一个大杂烩的混乱程度可想而知。
清晰的系统架构,能够让人一眼知晓改动所带来的连带影响,能够在一定程度、一定范围、一定时间支持和兼容发生的改变,任何系统的发展都是逐渐混乱的,而架构要做的,就是即将到达临界点时,作出优化和进步,让系统持续稳定。
当面对架构的设计过程中,会有很多的开放架构参考,无论是成熟的开源组件架构设计,还是大型平台的架构形态,都是可以进行比较和对比参考的有利资源,那么当我们面对如此之多的例子,如果做到相对正确的取舍呢?过度copy可能走到过度设计的漩涡,而简单设计也有可能导致后续效率不理想。实际上并没有一套很完成的规范来指导架构设计,可能更多的是依赖于架构师的经验和判断,但是其中仍然有些共性的规则。
如果公司组建一个电商系统,有时候带着美好的憧憬,可能是和JD或Taobao一样的大型平台,开始借鉴他们的架构设计,着手于自己系统的设计开发。试想,他们的平台架构是经过了时间和历史,流量等考验,一步一步进步而来,且确实有如此的体量。
反观,这些复杂设计放在一个新的电商平台,可能是灾难性的建设,这样大型平台建设,复杂度是极高的,无论是技术栈、还是团队规模无法支撑快速建设,反而延误了业务抢占市场的先机。任何系统都是随着业务逐渐演化的,不是一步登天,什么样的体量需要什么样子的体系支撑,过多则接近过度设计,浪费人力物力。
有时候作为程序员,对于设计出复杂系统会是一种骄傲感,复杂意味着需要足够的技术水平和逻辑能力的支撑,然而这种复杂对于其他接手的人确实一种累赘,复杂在另一方面代表着系统的复杂度高,一定程度反映了系统的抽象和拆分做的超出水平线,需要做“瘦身”,模块简单且逻辑简单。
上面提到,任何系统都是随着业务逐渐演化的,伴随着业务的复杂度提高,量级提升等因素,必然需要更加稳定、高效的系统支撑。对于软件来说,变化是本质,业务的变化势必会对底层支撑带来变化,这个变化是跟随业务的演化而进化的。
性能是影响到用户体验的重量级因素,流畅的系统使用体验能够让用户流连忘返,因此高性能是几乎所有系统必须着重考虑的架构点。
存储技术高速发展,现在除了关系型数据库,还有非关系型的数据库,包括kv缓存数据库、图数据库以及文档数据库等,但现在来看关系型数据库依旧凭借ACID特性和功能强大的SQL查询当主流。当然,任何组件必然是有其擅长点和短板,传统关系型数据库在性能上是有瓶颈的,当软件业务迅猛发展,在海量数据且要求高速度下,传统数据库无法满足场景,需考虑集群的方式来提升性能。
一般来讲,关系数据库集群的构造方式,分为两种,一种是读写分离,通过读与写的访问区分,减少单一数据库的读压力,另一种,是分库分表,可以在读写两个方面减少平分压力。
读写分离的原理即将读写访问分散到不同的节点上,基本架构也比较清晰简单,一个主机,一些从机,主机负责写请求,数据同步至从机上,读请求则会到从机器上,基本实现如下:
结构简单清晰,自然问题也一眼便知道,即从机的数据延迟,数据从主机器到从机器经过网络传输和sql重放都需要时间处理,而这个时间差随着网络的抖动具有不确定性,也出现了很多解决或者缓解这种延迟的方法:
一般来讲,读写分离是较为常用的数据库集群方式,也很多对于无法忍受延迟的业务点或者黄金流程走主节点,其他走读,在不影响主要业务的情况下,分散数据库集群的压力。
读写分离能够分散读的压力,但是没有分散写的压力,当数据量达到千万级甚至更高,那么单数据库下的存储能力则会达到瓶颈,读写都会出现性能问题。为了避免单库单表过大,则会进行拆分,也就是所谓的分库分表。
** 业务分库:**不同业务数据存储与不同数据库,物理上隔离,比如重要数据单独库,其他边缘业务,数据量少可共用一库。带来的问题也显而易见,跨库无法join(虽然现在一般场景下禁止join的出现)、无法跨库事务、成本的提升。
** 分表:**将数据分库能够支撑千万级甚至更好的用户级服务,如果单表无法承受数据量级,也依然会出现问题,这个时候就要对表维度进行拆分,即分表。
分表依据维度不同分类为水平分表和垂直分表,水平分表即条的区分,比如哪些条件下的记录在一个表中,垂直分表,则是字段的拆分,某些大体量的字段单独拆出来作为新表存在,可能原来查询一次,现在要查询两次两张表满足。水平拆分相比于垂直拆分复杂性高些,主要在于记录的路由阶段,要将数据尽量平均分配,且有规则分配,因为查询时也依据规则查询。一般路由规则分为范围路由、Hash路由、配置路由等方式,原理简单,可自行了解。
虽然关系型数据库广泛应用,还是无法覆盖很多场景,或者说是不擅长,优点在正确的场景下即是优点,在错误的场景下变成缺点,比如:
在这些背景下,NoSQL应运而生,NoSQL = Not only SQL,常见的NoSQL方案有如下几种:
前面提到当业务以及数据达到一定的阈值后,当初的靠存储系统是无法满足时效的要求,比如:读多写少、需要复杂运算得到的数据等等场景,缓存概念就是为了解决这些场景下数据访问问题,一次生成、多次使用,避免每次访问存储系统,耗时耗力。缓存的概念也是比较宽泛的,像上述提到的在一定场景下可做为缓存使用,包括Memcache等。缓存的引入解决了部分问题的同时,同样也带了复杂性,数据的准确、实时性等属性需要在存储系统和缓存组件间进行设计保证。
访问到达缓存,没有查询到数据,进而到存储系统查询,如果突发流量到来,会对存储系统进行大量访问,直至崩溃。一般这种场景发生在于缓存数据不存在,或者存储系统生成缓存数据缓慢,业务设置缓存查询不到则存储系统查询。
可以做些设计保证或者矫正缓存数据的正确性和及时性,这样认为缓存数据不存在,也不需要查询存储系统。
缓存雪崩是指缓存失效引起的系统性能急剧下降的情况,当缓存过期时,系统需要按照条件重新生成缓存,如果此时请求量起来,会导致很多请求内进行缓存重建,也就是通过存储系统进行缓存的计算和存储,如果请求量大会导致存储系统压力过大,且请求hang在重构缓存中,还会拉垮整个应用系统,一连串的连锁反应发生。
一方面可以使用更新锁改善,只有一个或N个线程进行缓存过期后的重建,其他请求线程等待或者直接返回空等业务处理。
另一方面,可以使用后台定时更新机制、消息机制,去主动进行缓存的更新,定时更新的时效性差一些,但是方案简单,消息队列的方式实效性较好,但是引入消息队列,增加了一些复杂性,这样就不需要在业务请求线程中进行缓存的构建。
缓存热点比较好理解,缓存中的某一个缓存是使用率极高的数据,也就是热点,例如redis时常提到的热点key,存在热点是对缓存系统的考验,大多数请求大该热点,意味着这些请求全部到达集群的一个机器,负载和压力是大的,很经典的例子,微博热点,如果某一个爆发热点在某一个缓存机器,试想结果。
一般解决热点key,就是将热点打散处理,减轻单台服务器压力,还是刚刚提到的微博热点,可以通过热点增加序号,分散成多份相同缓存数据,在不同服务器上,达到分散压力的效果。
前面描述了存储的高性能,主要目标是对存储系统的一些压力分散、安全设计的方案,那么对于业务系统中,关于业务逻辑的处理在于计算的高性能,将单服务器的性能发挥到极致,将集群的性能发挥到最大,那么业务的性能也会大幅度提高。
单服务器的性能和服务器采用的网络模型息息相关,如何管理连接、如何处理请求,和操作系统IO模型以及进程模型息息相关。下面看下主要的网络模型。
process per Connection,每次来一个新的连接,则新建一个进程处理这个连接。
针对PPC的缺点进行改进,fork子进程的动作提前,不在新连接进来在进行fork操作,这样当新连接进来,只有一个子进程能获取连接成功,然后监听事件进行操作。缺点容易出现“惊群效应”,多子进程同时监听新连接,所有子进程都会唤醒,最后只有一个成功。
thread per Connection,每次来一个新的连接,则新建一个线程处理这个连接,和进程相比,线程比较轻量,且线程共享内存空间,通信也是比较方便的,目标在于解决和弱化PPC中的进程重量以及父子进程通信的问题。但是线程间的共享和互斥有死锁的风险,且线程切换还是会出现调度的损耗。
prethread实际上和prefork是类似逻辑,也是提前创建好线程,在新连接进来能够快速处理,提高效率。
对于PPC、TPC最大问题就是进程、线程的浪费,处理完连接后会销毁,后续新连接进来在创建,那么是否可以做到复用的模型呢?使用池化的概念,通过复用,减少新建这种重操作,也就是现在流行的网络模型,IO多路复用,多路指多个连接,复用则是指多个连接复用一个阻塞对象,这样进程/线程无需轮训所有连接,当有事件到达,操作系统则会通知线程/进程,结束阻塞状态进行处理。这种IO多路复用结合线程池,也就是现在的Reactor模型。
该模型由Reactor和处理资源池(线程池/进程池)组成,Reactor负责事件的监听和分发,资源池则负责具体的事件处理,根据不同场景,Reactor数量可以有所变化,资源池同样也可变化。
单服务器无论如何优化,都会到达其天花板,这个时候就是需要加服务器的时候了,也就是构建服务器集群的时机,通过多台服务器提升系统总体的计算能力,既然作为集群对外提供能力,那么该集群就有一个最基本要求,同样的输入和逻辑,无论在哪一台具体机器执行,都应该返回预期相同输出结果,因此相比于集群本身,在任务分配这部分也是重要的一部分。
对于任务分配器,现在更多的叫法:负载均衡器,对于负载均衡,本身含义是为了负载的均衡,实际上还有针对不同任务做不同的路由分配的实际使用含义,不仅仅是为了单元的负载达到平衡状态。
目前来讲负载均衡系统一般分为3种:DNS负载均衡、硬件负载均衡和软件负载均衡。
DNS负载均衡是最简单的负载均衡算法,一般用来实现地理级别的负载均衡。常见一个域名,如果是北方用户访问则通过DNS返回的是北京机房,如果是南方用户,则返回南京、杭州机房IP,作为地理级别的负载均衡,粒度比较粗,负载均衡的算法也是比较少,但是作为应用来讲,我们无须关系DNS这一层,用起来也是比较简单且成本低。但是也有如下缺点:
硬件负载均衡是使用单独的硬件设备实现负载均衡,类比于路由器、交换机这种基础网络设备,时下比较主流的负载均衡设备包括F5和A10,这种硬件设备价格相比于软件负载均衡都比较贵,适合业务量大、对负载均衡有很高要求的公司。
优点
具备颇多优点,但是价格是比较昂贵的,成本也是一个重要的考量因素,因为是硬件支持的设备,所以在扩展性上可能较差;
软件负载均衡就是使用软件实现的负载均衡,常见的有Nginx和LVS,Nginx是基于软件的7层负载,LVS是Linux的4层负载均衡,不同层次的均衡意味着支持的范围,Nginx支持Http、email等协议,LVS是4层负载均衡,则和具体协议无关,都可以实现。软件负载均衡相比于硬件在性能上是差了数量级的,一般也是没有防护安全的,需要自己进行这方面的建设,但是在扩展方面是比较有优势的。开源的负载均衡让我们做一些业务定制更加方便。
三者在各方面具有各自优势和缺点,可以结合使用,取长补短。
首先根据域名通过DNS进行地理上的负载均衡给到一个合适的机房IP,在该机房IP下使用硬件负载均衡设备,找到合适的服务器集群,当请求到达软件负载均衡,则根据业务设定负载均衡策略选择合适机器分发请求,返回结果。
负载均衡算法是决定负载均衡效果的绝对因素,一般来说算法分为几大类:
对于高可用,是在软件开发中常常提到的设计原则,系统的高可用意味着需要应对各种可能对系统造成崩溃的风险。对于高可用也沉淀了一些评估理论,CAP、BASE理论等,是对系统多维度评估的指标。
CAP理论也称作布鲁尔定理,是由加州大学伯克利分校计算机科学家埃里克-布鲁尔提出的一个猜想,后在2002年麻省理工学校的塞斯-吉尔伯特发布了对CAP理论的证明,使得CAP理论成为计算机领域的一个定理。
对于CAP理论在最开始并没有很详细的定义,是作为一个猜想提出,后来证明成立后,有了比较准确的定义,简单翻译为:对于一个分布式计算机系统,不可能同时满足一致性(Consistence)、可用性(Availability)、分区容错性(Partition Tolerance)三个设计约束。
后续有了更加严谨的定义:在一个分布式系统(互相连接并共享数据节点的集合)中,当涉及读写操作时,只能保证一致性(Consistence)、可用性(Availability)、分区容错性(Partition Tolerance)中两个,另外一个必须牺牲。
相比于上一个,第二个定义更加严谨,提出了是在什么样子的分布式系统下,比如memcache互相不共享数据,则不适用,Mysql集群互相连接共享数据,则适用;且在该系统中什么样子的操作范围下,读写操作下适用。下面分别描述下三个指标的含义。
**定义:**对于一个指定的客户端来说,读操作保证能返回最新的写操作结果。
可以回想一下,我们一般讲“一致性”,是不是默认从某个客户端的角度来形容的?当然“自己”也是一种客户端,所以一致性的最新定义也是从客户端出发。
另外,强调“拿到最新的结果”,这个对比于另外一个对一致性的定义“所有节点在同一时刻都能看到相同的数据”更加具有普适性和合理性,通常在事务进行中,是有一定的阶段性的,比如无法读取未提交数据、事务回滚等阶段,这个时候所有节点间的数据可能是不一致的,相对,能够拿到最新数据,对这个过程是包容的。
**定义:**非故障的节点在合理的时间内返回合理的响应。
故障节点本身不具备服务性,需要排除故障节点;在合理时间返回合理的响应,一方面在时间上进行说明,另一方面说明反应是合理的、符合业务需要的。
**定义:**当系统出现网络分区后,系统能够继续“履行职责”。
“网络分区”相信都比较熟悉,在zk中详细介绍这一概念,分区容错性,表示出现这种情况,仍然能继续工作,完成本职工作。
不要以为在所有时候都只能选择两个特性。在不存在网络失败的情况下(分布式系统正常运行时),C和A能够同时保证。只有当网络发生分区或失败时,才会在C和A之间做出选择。
CAP中,有一个潜在规则,一般满足CP或者AP,而不是CA,首先在分布式环境中,依赖网络进行信息传输,网络不是100%可靠的,发生故障也是比较常见的,所以网络分区时常发生,如果我们选择了CA放弃了P,那么发生网络分区的时候,如果保证C,必然禁止写入,那么此时和A也是冲突的,所以一般会选择CP或AP。
CAP理论在对应中,往往是针对数据粒度而言的,并不是整个系统,如果说某个系统满足CP或者AP,要么是对系统不够了解,要么就是系统各模块组件确实都满足相同指标。但是就一般而言,CP和AP都有各自适合的场景,或者说是各个场景都有适合自己的策略,比如账号数据,比较重要涉及到一些重要参数,则适合CP,而用户信息相比较而言不那么重要,可满足AP,如果说整个系统都适用CP或AP,可能对于某些场景是不适合的。因此CAP理论落地实践不要大一统,而是要就模块或者单元数据而言。
BASE是Basically Available(基本可用)、Soft State(软状态)、Eventually Consistency(最终一致性)的缩写描述,核心思想是对CAP理论的延伸和补充,如果不能做到CAP中强一致性,但是可以做到最终一致性。
**定义:**分布式系统在出现故障时,允许损失部分可用性,保证核心可用。
重在理解核心和部分,需要业务自己斟酌哪些是黄金流程是必须保证可用性,哪些是在一定情况下相比较而言可以割舍的。
**定义:**允许系统存在中间状态,该状态期间不会影响系统整体可用性。
主要是说明数据的一致性,也就是对过程中存在这样的状态和阶段的描述。
**定义:**系统中数据最终会保证一致性。
在CAP理论中提到的AP,并不少完全放弃一致性,而是出现短暂或者一定情况下的不一致,BASE中的最终一致性则是对该情况的延展,在分区期间无法保证一致性,但是故障接触后能够恢复至最终一致性。
FMEA(Failure mode and effect analysis)故障模式与影响分析,是一种在各行各业都有广泛使用的一种可用性分析方法,通过对系统范围内潜在的故障模式加以分析,并按照严重程度加以分析,以确定失效对系统的最终影响。
FEMA最开始应用于美国军方开始使用,后在航天制造领域也积极推广,在使用范围增大的背景下,汽车行业也渐渐适应,所以说它是一种分析方法论,能够应用于各行各业。
在架构设计领域使用FEMA的具体分析过程如下:
1. 给出初始的架构图
2. 假设架构中某个组件出现故障
3. 分析此故障对系统的影响范围
4. 根据分析结构,判断架构是否需要做优化和调整
在视觉上看,FEMA是一张故障分析表,描述了系统哪些功能、哪些组件出现问题的影响和级别等信息,示例如下:
以下汽车行业的示例:
可以看到主要包含功能点、故障模式、等级、严重程度、故障原因、措施、规划等,一张表描述系统可能的故障以及影响、应对措施,能够清晰的概括描述系统薄弱点,尽可能全面的了解系统可能发生的故障,提前做好应对防患于未然。
作为系统,除了系统本身功能可用,仅次于此重要的可能就是数据了,数据即基础,数据是系统前进的基石,无论是用户画像、客户分析,还是准确推荐、兴趣爱好等,都离不开数据,一定程度来讲,系统玩的就是数据。那么存储数据的介质也变得尤为重要,前面描述了对于存储的高性能引入了缓存的概念,本点则是对于存储的高可用,保障基础可用,才是保证0前面的1。
存储的高可用在于处理存储的单点和备份问题,也就是数据的复制,通过数据冗余的方式达到高可用,其复杂性主要体现在如何应对复制过程中的延迟问题和中断问题带来的数据一致性的结果,因此建设高可用存储,需要考虑几个问题:
常用的存储架构方式有主备、主从、主主、集群和分区,每一种架构方式自有其适合的场景,恰当的使用,发挥架构模式的优点和长处。
基本实现:
优缺点:
优点:
缺点:
基本实现:
优缺点:
优点:
缺点:
是指两台机器都是主机,互相进行数据的复制,适用场景需要考虑,有些场景是不能进行双向数据复制的,比如库存等,客户端可以任选其一进行读写操作。
基本实现:
优缺点:
优点:
缺点:
主从、主备和主主的存储高可用架构模型,实际上意味着单或者双机器是能够满足的数据的存储的,如果数据量极大,则需要考虑集群的方式。比如一主多从、一主多备等等,实际上也是上述架构变种和优化。
变化点:
数据分散到多个主机上,每个主机存储部分数据,那么数据分配的均等性、出现故障的容错性以及机器不够下的可伸缩性,需要重点考虑。
相对于数据集群方式,数据分散集群方式需要有一个角色执行数据分配算法,而前者则全部都承担读写,不需要进行分配。
Hadoop作为大数据存储,采用数据分区方式,也就是数据分散存储的方式,使用独立服务器执行数据分区算法,这台机器叫Namenode。与Hadoop不同的是,ElasticSearch通过选举选择一台服务器来做数据分区,叫做master node。
一般来说数据分散集群适合大数据量存储,优秀的可伸缩性适合支持海量数据,而数据集群方式则适合数据量不大场景。
如果采用了数据集群的方式,那么这些数据分布到不同的集群节点上,跨节点的事物是一个实现起来有难度的场景。通常有以下几种,2PC、3PC、TCC、本地消息表以及事物消息等方式。
分布式事务算法是为了保证分散在多个节点上的数据统一提或回滚,以满足ACID的要求,而分布式一致性算法是为了保证同一份数据在不同节点上的一致性,以满足CAP中的CP要求。比如Paxos、Raft以及ZAB协议。
计算高可用类比于存储高可用,也就是当出现硬件异常等情况导致任务无法进行时,通过计算高可用的架构设计,任务能够继续正常进行,所以和存储类似,计算高可用核心也是冗余,相对于存储,计算核心在于任务,对于任务高可用的执行,主要关注两点:
主备模式比较简单,和存储高可用的主备类似,无需进行主备之间进行数据的复制。
基本实现:
根据备机状态不同,可分为冷备和温备:
主从计算高可用架构和存储高可用类似,主、从机都会执行计算任务,任务分配器需要一定的规则来分配计算任务到主机和从机。
基本实现:
相比于主备架构,主从则尽可能的利用资源,发挥了从机的性能;但是对于任务分配器则是增加哀乐难度,需要进行任务的分配。
主从和主备的计算高可用架构模式,原理相似,通过冗余一台机器来提高性能,或增加准备执行机器,通过人工操作快速切换故障机器,效率比较低下,且机器的数量伸缩性较差,对于高计算量的任务不能更好的支撑,所以出现集群方案。
集群方案根据执行任务的服务器节点角色的不同,一类是对称集群方式,即集群中所有服务器节点都是相同的,任务可以到任何节点执行计算,非对称集群方式;另一类是非对称集群方式,服务器几点分为不同的角色,不同的角色执行不同的计算任务。
对称集群也可以称作负载均衡集群,旨在均匀的分配任务,最大限度压榨集群服务器性能。
基本实现:
负载均衡集群,良好的机器伸缩性,对于任务的计算天然制成扩展和收缩,当然,有了集群,任务的均匀分配也是比较重要的,能够让每台服务器有条不紊的执行任务,避免忙的忙死,闲的闲死。
非对称集群中服务器节点拥有不同的角色,不同角色执行不同任务,规则则需要任务分配器去执行。以Master-Slave为例,集群中存在一个master和多个slave节点,任务分配器需要感知节点类型,从而进行不同种类的任务分配。
基本实现:
业务高可用,即业务功能的高可用,保证系统在大部分故障场景下依然提供正常服务,和存储的高可用、计算高可用采用类似的理论支撑。前面已经介绍了二者的常用架构模式,那么在一些情况下,可能出现全部服务器故障,某个地区的大型灾害,火灾、水灾、地震等,导致所在地区机房不可用,尽管在其他地区有备机,但是启用还需要一定的时间,这个时间内可能导致系统的瘫痪,无法提供正常服务,该场景下,异地多活设计理论出现。
异地多活重点在于异地、多活。异地,也就是不同地区,地理位置上的不同;多活,则意味着多处提供正常的业务服务。因此一个系统是否做到了异地多活,可以通过这俩个方面测评:
和“活”对应的是“备”,备机在前面也提到,是一种备份的机制,达不到立即提供业务服务的状态,需要一定的启动过程,所以相对于活来讲,备在于备份,出现了极端故障,在进行业务启动提供功能服务。
如果业务的价值相对于做异地多活的复杂度和成本来讲不值当,也可以采用备的方式,当然,有条件做到异地多活时比较有保障的业务架构方式。
根据地理位置上的距离划分,架构方式分为同城异区、跨城异地、跨国异地等方式。
1.同城异区
同城异区指同一个城市,不同的区域,比如北京的海淀区和通州区建设机房,两个机房间通过高速网络连接在一起,提供相同的业务服务,就是同城异区架构。可以看出其距离也就是在几十千米左右,这个距离是无法应对整个地区的灾难的,比如新奥尔良停电等,但是这种大的灾难是极其少见的,更多的还是某个机房停电、机房火灾、机房线断等局部机房故障的场景,那么同城异区就能发挥其优势,距离短,机房间的网络延迟几乎可忽略。
2.跨城异地
跨城异地指跨越城市地区的多活,比如北京机房、广州白云机房这种距离,能够应对我们上面提到的新奥尔良停电这种大区域性的故障,但是这种方式带来的弊端也显而易见,就是距离带来的延迟,光纤传输速度20万千米,中间经过设备等处理,也会加大延迟,并且距离大了,中间的异常因素就会变多,光线断了、骨干网异常等。距离带来的延迟,会放大数据一致性问题,所以要根据业务情况进行架构模式的挑选。
3.跨国异地
顾名思义,就是在国家地区的距离下设立提供服务的机房。这种更大的距离,延迟几乎是致命的,对于用户来讲无法忍受,那么跨国异地存在的意义时什么呢?实际上,跨国异地更多的是针对不同地区用户提供服务,亚马逊中国为中国用户服务,亚马逊美国为美国用户服务,如果中国用户访问美国亚马逊,是无法同步使用的。还有一种场景,只读业务下的多活,比如访问美国谷歌和中国谷歌,都是进入到通用搜索引擎上,返回相同结果。
1.核心业务异地多活
异地多活时为了保证业务的高可用,能够使所有业务具有异地多活当然是最好的,异地多活本身是具有复杂性和很高的成本在的,并且有些业务下是不太适合做异地多活的,假如我们使用异地多活下的存储方式,每个分区存储规则下的数据,当下有用户注册的场景,本身路由到A注册存储,然后数据同步到B,达到异地多活的容灾,如果在A发生故障,导致还没同步到B,因为我们做了这种业务高可用,可以把该用户路由到B去注册,此时B无法感知A是否已经注册了用户,就可能会发生冲突和数据的异常,如果不允许路由到B注册,那么就失去了所谓的多活,所以部分业务场景下要做斟酌。
相比之下,登录就很适合做多活了,按照上面的模式,每个分区时存储了所有用户的信息的,就算路由故障切换,也能正常登录,达到容灾的效果。一切功能建立在登录的基础上,登录算是很重要的核心业务,所以我们需要考量业务是否适合、是否核心,做异地多活。
3.核心数据最终一致性
前面提到存储的高可用有主从、主备、集群等方式,异地多活本质上也是通过冗余备份方式达到容灾效果,一旦涉及到数据同步,就不得不提到这把双刃剑带来的弊端,就是同步带来的延迟处理,这个是物理上的限制,只能优化或者曲径通幽处的来处理。
接口级故障指的是系统没有宕机,网络也没有中断,但业务相应慢,大量访问超时/异常,负载过高等。或者可能系统的其他接口还能正常使用,只有个别出问题的接口无法正常支持业务。产生这个问题的主要原因两种:
核心解决思路:优先保障核心业务,保证绝大部分用户可用。
核心思想:丢车保帅,优先保证核心业务。当非核心业务流程出现问题时,可以通过后门/配置/降级系统等手段进行降级操作,让问题流程在降级时间段内不可用,将所有资源用于核心业务。比如问题时间内业务不发送日志;电商系统支持查看和购买商品,但不支持评论和留言功能。
熔断和降级的概念类似,都是采用了“丢车保帅”的核心思想。但不同之处在于降级是针对自身系统出问题时的应对方案;熔断时针对下游依赖系统出问题时的应对方案。
比如当前系统A的x功能依赖系统B,当系统B出现问题(响应过慢、响应异常),那系统A的x功能响应也随之变慢,或者不可用。为了避免这种情况发生,可以设置个阈值,比如1分钟内80%的响应超过1秒;1分钟内失败率达到50%的时候,就可以进行熔断,走熔断兜底流程不再调用系统B的接口(之间返回报错信息、给定默认值、跳过该步骤等)。
限流是从用户访问压力的角度来考虑的,当访问量超过系统允许的阈值后,超过部分的请求将被拒绝。
是限流的一个变种,当访问量超过阈值时,不是直接拒绝丢弃,而是进入排队系统,等有资源的时候再进行访问。比如12306的抢票排队系统。
由于软件系统是不断更新和发展的,所以如何避免扩展时改动范围太大,也是软件可扩展性设计的重要思考点。可扩展架构的基本思想就是“拆”。按照不同的思路,主要有3种拆分方式:
分层架构通常情况下至少2层,一般不超过五层,核心是需要保证各层之间的差异清晰,边界明显,调用层层传递。常见的分层架构:
SOA架构的三个关键概念:服务、ESB和松耦合,解决了传统IT系统重复建设和扩展效率低的问题,但本身引入的总线概念具有更高的复杂性,改造成本大。
微服务拆分的问题:
拆分的太细(服务间关系复杂、链路长、维护困难) – 运维成本高
基础设施不健全 – 自动化
不轻量,没有服务治理 – 复杂度高
微服务拆分的建议:
基于业务拆分:将单独的功能模块拆分出来(可以参考DDD领域划分)
基于可扩展拆分:比较稳定的服务可以力度粗一些,不稳定发展中的服务可以单独拆出来
基于可靠性拆分:核心服务和非核心服务隔离,确保核心服务高可用
微内核架构也被称为插件化架构,是面向功能进行拆分的可扩展性架构,报错核心系统和插件模块两个部分。其中核心系统功能稳定,插件模块可以跟随业务功能需要不断扩展,通过隔离变化到插件的方式提供了灵活性和可扩展性。
记录、学习、使用