作者:费伟伟
上海华瑞银行数字银行开发中心
架构设计关注点
架构师在进行架构设计的时候除了业务功能架构还需要关注技术架构的高性能、高可用和可扩展,对于系统技术架构来说主要的复杂度都是来源于对高性能、高可用和可扩展性的要求,一旦系统涉及到高性能、高可用、可扩展其中一条,那么在制定技术架构的时候就要进行这方面技术架构的考虑。
高性能
1)单机复杂度
- 多进程:多进程虽然要求每个任务都有独立的内存空间,进程间互不相关,但从使用角度来看,多个任务之间能够在运行过程中通讯才能让设计更加灵活。否则两个进程在运行过程中不能通讯会导致效率低而且设计更加复杂。为了解决这个问题,大拿们就设计出了解决方案:操作系统进程管道、消息队列、信号量、共享存储等。
- 多线程:在一个进程内部为了也支持并行任务,很多架构设计又使用了多线程,但这些多线程都共享一份进程数据,所以为了保证数据的正确性,又出现了互斥锁机制,我们常见的JDK中的JUC就是一个为了解决线程间数据共享的package。
2)集群复杂度
目前大部分系统的交易量通过单机性能都没办法支撑,必须采用机器集群的方式来达到高性能。
横向扩展集群:横向扩展集群需要有一个负载均衡来进行流量分发,常见的负载分发方式就是增加一个流量分配器,可能是一个硬件网络设备F5,可能是一个软件网络设备LVS,也可能是负载均衡软件Nginx,也有可能是自己开发的路由网关。一个具备流量负载均衡的分配器,需要具备多样的分配算法,是采用轮询算法、权重分配、还是按照ip hash。
-
纵向扩展集群:纵向扩展指的是功能层面的拆分形成的集群,通过负载均衡增加机器节点可以解决单机器节点处理性能的瓶颈问题,但是如果业务本身非常复杂,那么单纯的增加机器其实是无法实现成倍线性的性能提升。为了解决单体应用的性能问题就是进行服务拆分,把一个单体应用按照服务领域进行合理的拆分后部署。拆分服务后的优势有以下几点:
a. 简单的系统更容易做到高性能,代码架构逻辑简单更容易进行优化;
b. 可以针对单个任务进行扩展,更细粒度的针对单独的性能瓶颈服务进行横向扩容,更有针对性的性能优化;
高可用
保证系统高可用的核心思路就是实现冗余,实现计算、存储、网络 3方面的冗余来保证高可用。
计算高可用:计算高可用主要是依靠对服务器增加冗余节点来保证,这里需要考虑计算高可用方案,在保证应用无状态的前提下可以是简单的使用负载均衡器进行软负载,也可以是1主N备、N主N备。一般的应用节点只要保证计算无状态那么就可以采用简单高效的负载均衡多节点方案,如果系统要保证数据一致性那么高可用方案就要采用1主N从或者N主N从方案,例如Mysql主从方案、redis cluster方案等。
-
存储高可用:对于需要存储数据的系统来说,整个系统的高可用设计关键点和难点就在于存储高可用。存储与计算相比,本质上的区别在于,将数据从一台机器搬到另一台机器,需要经过线路进行传输,线路传输的速度是毫秒级别的,虽然毫秒时间很短,但是对于高可用系统来水,这就会造成数据不一致的情况。除了物理传输速度限制,传输线路本身也可能存在可用性问题,导致线路中断,并且线路故障一般少则十几分钟,长则几个小时。在传输线路中断的情况下,就意味着存储无法进行同步,在这段时间内整个系统的数据是不一致的。
分布式领域的CAP定理,从理论上论证了存储高可用的复杂性,CAP定理说的是存储高可用不可能同时满足“一致性、可用性、分区容错性”,最多满足其中两个,这就要求我们在做架构设计的时候要进行取舍。
-
高可用决策算法:无论计算高可用还是存储高可用,保证高可用的基础都是状态决策,即系统要能判断当前的状态是正常还是异常,如果出现了异常就要采取行动来保证高可用。
a. 独裁制:独裁制决策指的是存在一个独立的决策主体,负责收集信息然后进行决策,所有冗余的个体,都将自身的状态信息发送给决策者。
独裁制决策方式不会出现决策混乱的问题,因为只有一个决策者,但问题也正是在于只有一个决策者。当决策者本身故障时,整个系统就无法实现准确的决策,也就存在了管理节点的单点问题。
b. 协商制:协商制指的是两个独立的个体通过交流信息,然后根据规则进行决策,最常用的协商式决策就是主备决策。
两台服务器同时以备机身份启动,两台服务器建立通讯,其中一台服务器根据一定的主备决策算法作出决策成为主机,另一台保持备机身份。协商式决策架构不复杂,规则也不复杂,难点在于,如果两台服务器之间的信息交换出现问题,比如网络中断,此时的状态决策就会成为问题。
如果备机在连接中断的情况下认为主机故障,那么备机需要升级为主机,但实际上此时主机并没有故障,那么系统就会出现两个主机,这样会存在节点管理混乱出现问题。
备机如果在连接中断的情况下不认为主机有故障,则此时如果主机真的发生故障,那么系统就没有主机了,这样会导致集群出现无主的局面。
为了解决上面提到的这两种情况,一般我们会通过增加更多的连接,3连接是常用的方式,但这也不能保证绝对的没问题。
c. 民主制:民主制决策指的是多个独立的个体通过投票的方式来进行状态决策,ZK采用的就是选举leader的方式。
民主制决策和协商制决策比较类似,其基础都是独立的个体之间交换信息,每个个体作出自己的决策,然后按照多数取胜的规则来确定最终的状态,选举算法为了保证公平和理性一般都很复杂,例如ZK的Paxos就非常复杂。除了复杂的算法,民主制决策还有个缺陷就是脑裂。脑裂的根本原因就是,原来统一的集群因为连接中断,造成了两个独立分隔的子集群,每个子集群独立进行选举,于是选出了两个主机,这就产生了脑裂。
如图所示,产生了两个子集群,一个子集群的主机是2,另一个集群的主机是5,这时系统出现了两个主机依然违背了系统的设计原则,两个主节点都会各自作出决策,整个系统状态就会混乱。为了解决脑裂的问题,民主式决策的系统一般会采用投票节点数必须超过系统总节点数一半的规则来处理,因此节点4和节点5没有超过一半,则不能进行选举,因此最后选出的节点2作为主节点,但是在极端情况下,节点1,2,3真的都挂了,那么就算节点4,5还是正常的也无法提供服务,这也是该方案唯一的缺陷,不过触发该问题的可能性较低。
可扩展
设计一个系统架构一方面要保证系统的高性能和高可用,还有一方面也是需要非常重要的,那就是可扩展性。系统保证可扩展性是非常困难的,扩展性的设计是一件很复杂的事情,而且没有通用的标准可以简单套上去,更多的是依靠架构师的经验和直觉。
架构设计考虑扩展性的目的就是为了应对需求变更,常见的应对需求变化的方案有两种:
第一种应对变化的常见方案是将变化封装在一个变化层,将不变的部分封装在一个独立的稳定层。无论是变化层依赖稳定层,还是稳定层依赖变化层都是可以的,需要根据具体场景来设计。
如果系统需要支持XML、JSON、PB三种接入方式,那么最终的架构就是图中的形式1.
如果系统需要同时支持Mysql、Oracle、DB2三种数据库存储方式,那么最终架构就是图中的形式2.
可扩展性涉及的难点在于需要拆分出变化层和稳定层,对于哪些属于变化层,哪些属于稳定层,很多时候并不是像前面介绍的例子那么简单那么明确,这时候就要依靠对业务的理解和架构设计经验设计出变化层和稳定层,这时候可以考虑采用DDD的设计思想进行变化层和稳定层的设计。
在变化层和稳定层之间需要提炼出一个抽象层,抽象层的接口要保证稳定,具体实现层可以根据具体业务进行定制开发,当加入新功能时,只需要增加新的实现,无需修改其他层次。
架构设计流程
识别复杂度
架构设计第一步就是识别系统复杂度,所以在我们设计架构时,首先就要分析系统的复杂性,只有分析出系统的复杂性,后续的架构设计方案才不会偏离方向。
如果一个系统的复杂度本来是业务逻辑太复杂,功能耦合严重,架构师却设计了一个TPS达到5w的高性能架构,即使这个架构最终的性能再优秀也没有意义,因为架构没有解决正确的复杂性问题。
架构的复杂度主要来源于高性能、高可用、可扩展这3个方面,但架构师在具体判断复杂性的时候,不能生搬硬套,认为任何时候架构设计都必须同时满足这3方面要求。实际上大部分场景下,复杂度只有其中的某一个,少数情况下包含其中两个,如果真的出现同时需要解决3个复杂性,也必须排个优先级。
为了避免过度设计,正确的做法是将主要的复杂度问题列出来,然后根据业务,技术,团队等综合情况进行排序,优先解决当前面临的最主要问题的复杂度问题。对于按照复杂度优先级解决的方式,存在一个普遍的担忧,如果按照优先级来解决复杂问题,可能会出现解决了优先级排在前面的复杂度后,解决后续复杂度的方案需要将已经落地的方案推到重来。这个担忧理论上是可能的,但现实中几乎不可能出现。
识别复杂度对架构师来说是一个挑战,因为原始业务需求里并没有哪个地方会说明复杂度在哪,需要架构时在理解需求的基础上进行分析。
设计备选方案
有经验的架构师需要对目前主流的技术非常熟悉,对已经经过验证的架构模式烂熟于心,然后根据自己对业务的理解,挑选合适的架构模式进行组合,再对组合后的方案进行修改和调整。
技术经过这么多年的发展,新技术层出不穷,但是经过时间考验,已经被各种场景验证过的成熟技术其实更多,已经形成了一些架构套路。例如高可用的主备方案、集群方案、高性能负载均衡、多路复用、可扩展的分层、插件化等技术,在我们明确了目标后,按图索骥就能够找到可选的解决方案。只有当这种方式完全无法满足需求的时候,才会考虑逆行方案的创新,而事实上方案的创新绝大部分情况下也都是基于已有的成熟技术。
架构师需要设计多套备选方案,但方案数量可以说是无穷无尽的,架构师也不可能穷举所有方案,可以按照以下方式进行执行:
- 备选方案的数量以3-5个最佳,少于3个方案可能是因为思维狭隘,经验不足,考虑不周全;多于5个则需要耗费大量的精力和时间,并且方案之间的差别可能并不明显。
- 备选方案的差异要比较明显,例如主备方案和集群方案差异就很明显,或者同样是主备方案,用ZK做主备决策和用keepalived做主备决策的差异就很明显,但是用ZK做主备策略,一个检测周期是1分钟,一个检测周期是5分钟,这就不是架构上的差异,而只是实现细节上的差异。
- 备选方案的技术不要只局限于已经熟悉的技术。设计架构时,架构师需要将视野放宽,考虑更多可能性。很多架构师积累了一些成功经验,出于快速完成任务和降低风险的目的,可能就不自觉的倾向于使用一些熟悉的技术,对于新的技术有所担心。
评估方案
评估和选择备选方案中最好的方案那就必须经过严格的方案评估。具体的操作方式为,列出我们需要关注的属性点,然后分别从这些属性的围堵去评估每个方案,再综合挑选适合当时情况的最优方案。
常见的方案属性点有:性能、可用性、硬件成本、项目投入、复杂度、安全性、可扩展性、用户体验等。在评估这些属性点时,需要遵循架构设计原则,合适原则、简单原则、演进原则,避免贪大求全,基本上某个属性能够满足一定时期内业务发展就可以了。
假如做一个购物网站,现在的TPS是1000,如果我们预期一年内能够发展到TPS2000,在评估方案性能时,只要能超过2000的都是合适方案,而不是说淘宝网站TPS是每秒10w,我们的购物网站也要按照淘宝的标准来设计。
有的架构师会说,如果我们业务发展迅速,业务一年翻了10倍,TPS从1000到1w,那岂不是到时候要重新做方案。
这种情况存在,但是发生的概率太小,如果每次都考虑这些小概率事件,那么会导致方案过度设计,投入浪费。考虑这个问题的时候遵循演进原则避免过度设计、一步到位的想法。这样设计出的架构,就算真的出现极端情况,业务飞速发展,那就算重新做方案和系统重构,代价也是可以接受的,那时候业务快速发展,钱已经不是问题,公司会不惜一切成本投入资源做重构。
可以参考这个表格进行对设计出的备选方案进行环评:
架构属性 | 方案1 | 方案2 | 方案3 |
---|---|---|---|
性能 | |||
复杂度 | |||
硬件成本 | |||
可运维性 | |||
可靠性 | |||
人力投入 | |||
用户体验 |
详细方案设计
选择好了方案后就到了最后的详细方案设计阶段,有了前面的铺垫这个阶段就最简单了。本篇中主要讲述的是技术架构设计部分,在一个技术方案中还有很大的篇幅是业务架构,业务架构的设计比技术架构要简单,业务架构的设计更多的还是依赖架构师对业务的熟悉和对周边系统的了解。业务架构上的变化不会那么大,并且学习门槛比较低,技术架构的学习需要依赖长期的学习和不断的实践才能提升。
详细方案设计基本就是按照公司的模版将前面选出的备选方案按照方案模版对号入座做一定的详细描述就可以了。
总结
本篇文章主要介绍了架构设计中架构师需要关注高性能、高可用、可扩展这几个关注点,对于每个关注点需要注意的内容都做了介绍,也讲述了架构设计的流程,识别复杂度、设计备选方案、评估方案到最后的详细方案设计。本篇更多的还是介绍架构设计流程的方法论,更多的还是要在实际项目中不断积累经验,不断使用该套流程去验证这套架构设计流程方法论。