从零开始学架构V2-初识架构设计-1

一、架构设计的主要目的

为了解决软件系统复杂度带来的问题

二、复杂性来源

软件的架构设计是一个非常复杂的过程;基于业务&技术现状、公司成本、团队规模、团队技术能力、近三年业务发展规模预测、技术发展趋势等条件筛选出合适的技术、编写多种架构设计、讨论并给出最终解决方案,每一步都是需要消耗大量的精力,接下来我们单从技术角度了解下设计出合适的架构面临的挑战,同时也是架构复杂性的来源。

2.1 高性能

随着互联网的普及伴随着用户的逐渐增多,从零散用户到十万级、千万级、亿级,对系统的性能要求也在不断变高,根据“3秒定律”限制了系统响应速度最大不能超过一定阈值。
那该如何应对海量的请求呢?
在机器数量不变的情况下必然需要对单机性能进行优化。以服务器为例:从apache -> nginx/tomcat可以看出一个用户请求对应一个进程,发展到一个请求对应一个线程,再到多个用户请求到一个线程,在业务不变情况下通过调整服务器类型也可以大幅提高并发数量。
即便单机性能优化到极致,在绝对的数量面前单机仍然无法应对排山倒海的流量,于是乎单机架构逐渐向集群架构发展;架构的变化不能改变业务逻辑,为此将请求的处理过程整体抽象问题计算和存储,针对不同的分类处理方式也不一样。
计算:接受请求并按照业务规则处理并返回结果。
存储:将接受的请求、计算过程、处理结果等持久化到磁盘。
无论计算高性能还是存储高性能,核心都是通过增加机器来提高整体处理请求的数量和性能;那常见实现思路是怎样的呢?
计算的高性能思路:

  1. 非侵入式:业务逻辑通过横向扩展机器方式,经负载均衡分发请求到多台机器上,常见技术有NGINX、APACHE等。
  2. 侵入式:将一个请求拆分成多个处理过程,由多个机器同时处理用于解决单机性能不足的问题。

存储的高性能思路: 将海量数据分散到多个机器中存储,当查询数据时通过算法/规则合并结果响应给上游,常见的实现有MySQL的分库分表,hadoop的HDFS等。

2.2 高可用

各大互联网公司对外宣称自己服务的可用性为X个9,那这背后是如何做到的呢?
为应对海量请求的服务从单机架构转换成集群架构,集群规模达到一定数量后硬件难免出现磁盘故障、机器宕机、断网等问题;如果某个服务只有一台机器,出现影响故障势必影响整体业务运转,为了规避硬件问题带来的不可用风险,常用的应对方案就是冗余备份;当故障发生时需要系统自动剔除故障机器,使服务自动恢复;为了实现此目标需要将主服务机器的全量数据同步给备用机器;主备服务之间相互检测对方的服务状态(启动中、正常、故障灯)、数据变更等,这些都需要进行数据的传输,当两台机器在同一个机房,数据的传输速度比较快,延迟问题不是特别明显,当跨机房、城市、国家时数据同步延迟就是一个必须考虑的问题;
简而言之,在高可用中核心是如何降低数据的延迟、故障的自动切换,目前常见的的高可用实现方式有独裁式、协商式、民主式,接下来我们分别来介绍下。

2.2.1 独裁式

介绍: 独裁式是指定一台机器作为独立的决策主体,负责收集信息然后进行决策,我们姑且称它为“决策者”;所有冗余的个体都将状态信息发送给决策者,我们姑且称它为“上报者”。当某个上报者故障,由决策者决策哪个上报者顶替故障节点。
图示:

独裁式的决策方式不会出现决策混乱的问题,因为只有一个决策者,但问题也正是在于只有一个决策者。当决策者本身故障时,整个系统就无法实现准确的状态决策。如果为决策者本身做一套状态决策,那就陷入一个递归的死循环了。

2.2.2 协商式

事件万物总是相对的,有独裁就有民主,有压迫就有反抗,哈哈。协商式是最简单的民主式了。
民主式是两台及以上服务器之间相互协商选择出一个决策者,由决策者收集客户端请求及冗余机器信息并作出决策,如果决策者挂了则剩余机器重新协商选择新的决策者;当有新的节点增加进来可能会进行新的协商得出决策者。
协商式是两个独立的个体通过交流信息,然后根据规则进行决策,最常用的协商式决策就是主备决策。
图示:

此种架构启动方式为:

  1. 初识阶段:2台服务器状态都是备机。
  2. 通信阶段:2台服务器建立网络通信连接。
  3. 协商阶段:2台服务器交换状态信息,作出决策其中一台服务器成为主机,另一台继续作为备机。

整个过程比较容易理解,民主式的难点服务器间状态同步、信息交换,当网络出现问题该如何做决策。
本小节为协商式,先看看协商式的状态同步问题:

当主备两台机器之间的网络出现故障,对外提供服务的网络正常,此时备机是否需要顶替上去?如果顶替上去那过一会儿网络恢复则会出现两台主机。如果不顶替上去,万一主机真的是挂了则使用去了备机的作用。
这里就不给出答案了,可以自行思考一下。

2.2.3 民主式

在协商式中已经介绍了什么是民主式,定义就不在重复叙述了。
图示:

民主式与协商式类似,都是通过协商来选定一个决策者,启动方式也是三个阶段,差异点在于民主式相对于协商式每个阶段会复杂很多:

启动阶段 民主式 协商式
通信阶段 多台机器时间通信线路有n(n-1)/2条 只有一条
协商阶段 协商算法复杂,以目前raft算法为例相信大多数人看的也是云里雾里的,更别说Paxos了;整个协商可能会进行很多轮,协商过程也比较耗时 二选一,so easy

民主式也会面对协商式的网络的问题,其中比较出名就是“脑裂”问题。

从图中可以看到正常状态的时候只有一个主节点,其他节点作为备节点;当节点D、E与A、B、C节点失去链接,此时A、B、C重新协商A继续为主对外提供服务,D、E形成了一个局域网,通过协商式单独形成一个集群并对外提供服务,导致整体服务出现错乱。
业内解决脑裂的常见方式是“投票节点数必须超过系统总节点数一半”规则来处理。继续以上图来说,D、E形成的子集群节点数量为2,原总结点数量为5,没有达到总节点数的一半,因此这个子集群废弃。这种方式虽然解决了脑裂问题,但同时降低了系统整体的可用性,即如果系统不是因为脑裂问题导致投票节点数过少,而真的是因为节点故障(例如,A、B、C真的发生了故障),此时系统也不会选出主节点,整个系统就相当于宕机了,尽管此时还有D和E是正常的。

2.2.4 问题与思考

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

2.3 可扩展性

做技术的都听过一句话“唯一不变的就是变化”,本小节要介绍的就是应对将来需求变化而提供的一种扩展能力,当有新的需求出现时,系统不需要或者仅需要少量修改就可以支持,无须整个系统重构或者重建。
想要设计出扩展性较强的系统核心就是识别变化点、封装稳定项
以某个小功能开发举例说明,假设现在要开发一个结算系统:普通商家按照订单实时结算,重点商家汇总订单金额对账无误后隔天结算;不同的商家类型、不同的活动、不同订单时段、商家星级等结算时抽佣比例不同,这些维度可以任意组合,经过计算最终得出结算值。 第一步:识别变化点,找变化的部分和不变的部分。
怎么找?怎么感觉都是变化的部分 / 都是不变的部分呢?这确实是难点,开发过几年技术实现应该不是大问题,难的就是一眼识破其本质的能力。简单说一条个人经验:一般在产品提出需求的时候会有很多提示,根据产品的提示和自己的经验来寻找这两个部分;看一下上面的需求,不同的商家有不同的结算方式,这里不变的是结算类型(这里说的是type),变化的是具体得结算类型(是指具体得type值);商家类型、订单时间等多个维度组合得出结算的抽佣比例,计算抽佣规则是变化的,但计算过程是固定的,这样就把变化和不变的部分找出来了。
第二步:封装稳定项。根据不变的抽取接口,根据变化的部分抽取实现类
基于第一步可以抽象以下接口:

// 计算结算值表达式字段接口
public interface SettlementAttr{
    public Object getAttrValue(Message message);
}
public interface MerchantTypeSettlementAttr implements SettlementAttr {
    public Object getAttrValue(Message message){
        // 返回商家类型
    }
}
public interface OrderTimeSettlementAttr implements SettlementAttr {
    public Object getAttrValue(Message message){
        // 返回订单时间
    }
}

public class SettlementRule{
    public SettleValue getSettlementValue(Message message){
        // 获取计算抽佣值表达式
        // 通过 settlementAttr.getAttrValue 获取表达式字段值
        // 将表达式值 代入表达式 计算得出最终抽佣值
    }
}

public interface SettlementType{
    public SettleOrder settlement(Message message);
}
public class NormalMerchant implements SettlementType{
    public SettleOrder settlement(Message message){
        // 实时结算
    }
}
public class KeyMerchantMerchant implements SettlementType{
    public SettleOrder settlement(Message message){
        // 隔天结算
    }
}

这里的设计都是基于已有业务和可预知的变化点,如果业务上结算抽佣值就是简单的抽佣1-10点订单抽佣1%,重点商家再次基础上减少0.1%,那么上文的设计就是过度设计。当然设计不是仅限于某个场景而做的,基于某个场景而做的设计很多时候就变成了业务翻译。当前提业务的需求结算抽佣值就是简单的抽佣1-10点订单抽佣1%,重点商家再次基础上减少0.1%,但业务有说目前这块不会太固定,需要经常修改,那直接按照if 判断订单时间、商家类型就是业务翻译,毫无扩展性可言。

3.4 低成本、规模等

3.4.1 低成本

当我们的服务机器数量只有几台、几十台时,谈论"低成本"投入产出比较低,但机器数量达到上千、上万及以上时,成本就是一个值得讨论的事情了。 假设一台机器成本每年1000元,使用A方案需要2000台机器,使用B方案则只需要1000台机器,单从机器数量上来看成本节约了50%,换算金额每年大约节省了100万元(4c8g),如果把这些钱发给员工,想想都是一件很爽的事情,哈哈。言归正传,对于技术来说也体现了技术的价值,同样在也是精神上的一种满足。 在前面介绍了高性能、高可用都是通过增加机器应对海量流量并提供稳定的服务,而低成本则是减少机器,他们之间有所冲突,因此在做架构设计的时候需要进行考虑到低成本这一限制约束。 在保证高性能、高可用的同时想要实现低成本,往往需要一些创新。如Java web系统早期EJB + weblogic,到ssh+tomcat来降低成本。

3.4.2 规模

当看到大学毕业设计时写的系统、公司迭代多年的系统,要论复杂度肯定会毫不犹豫的说是公司迭代多年的系统复杂,就引出了规模复杂度之一的功能模块越多导致整体复杂度达到质变。当有N个模块是,最大的链接数量为n(n-1)/2,文字总是不太直观的表现复杂性,借用民主式启动时的图(左侧),实际中的模块数量远不止4个。 当模块数量达到一定数量后,复杂度呈指数级上升引起质变,这也是为何最近几年大家都开始推崇领域模型的原因,通过充血模型、领域设计来降低复杂度。 除了业务的复杂性外,数据的规模增长也使得系统越来越复杂。以MYSQL为例,数据量从万到百万、亿、百亿及以上时数据的操作会有所不同,主要体现在:

  1. 数据库的查询。亿级以上大多涉及到分库分表,读写分离,当查询分表数据时需要中间件自动解析SQL拆分多个子SQL分别去分表中查询、合并查询结果等;
  2. 表字段的变更。数据量越大,增加索引、修改字段都是非常耗时,表大一些可能需要几个小时。期间MYSQL会锁表,无法增删改,这对业务影响非常大。
  3. 数据备份。数据越多,备份时间越长,占用带宽时间也较长,整体成本也较多。

3.4.3 其他

影响架构复杂的因素还有很多,例如网络安全、公司发展阶段等,就不一一列举了。

4.架构设计原则

在网上搜索架构设计相关,会惊奇的发现每个人的设计思路差异还挺大的,相关的课程内容差异点也比较大,这是为什么呢?看的多了之后总结出来就是架构设计业界并没有一套标准,个人经验居多,这样就是很多时候在技术方案阶段,几个人争论应该用何种方案合适而争论的面红耳赤。在很多文章、课程中都提到了几个原则感觉还不错,在架构设计时可以少走一些弯路。

4.1 合适原则

很多技术人员都有很强的技术情结,当他们做方案或者架构时,总想不断地挑战自己,想达到甚至优于业界领先水平是其中一个典型表现,或新学到一门架构理念,想要在项目中大显身手,以展示自己优秀的才能,但现实是如果不考虑目前业务的现状、可预见的业务发展方向、规模、公司规模、团队技术能力等,那么设计出来的架构除了炫技术外就剩下“蹩脚”了。 所以,贴合实际场景进行设计,合适的才是最好的。

4.2 简单原则

相信没人主动愿意接手一个复杂系统,在满足当下及可预见的需求,采用最简单的架构设计方式。

4.3 演进原则

“罗马不是一日建成的”,相信都听说过这句话,表达了任何事物都有自己的发展运作规律,一步登天是不可能的,架构设计也是如此。 淘宝的架构迭代经过了很多次:

  1. 淘宝网刚创立时,源码是买的,修改了一些内容后历经一个月采用LAMP架构、单机架构快速上线。
  2. 网站注册用户越来越多发现数据库是瓶颈,将数据库切换到Oracle,后来又将后端代码切换成Java;单机也演变成集群架构;
  3. 通用组件拆分成分布式架构
  4. 迁移至阿里云;开始统一架构,从整体系统层面提高开发效率、运维标准、高可用、高性能、高扩展性、低成本等。

结合业务的现状、可预见的业务发展方向、规模、公司规模、团队技术能力等,只要符合这些限制条件,也能满足未来可预见的多方面需求就可以了。

四、FAQ

  1. 这么多需求,从哪里开始下手进行架构设计呢? 答: 通过熟悉和理解需求,识别系统复杂性所在的地方,然后针对这些复杂点进行架构设计。
  2. 架构设计要考虑高性能、高可用、高扩展……这么多高XX,全部设计完成估计要1个月,但老大只给了1周时间 答: 架构设计并不是要面面俱到,不需要每个架构都具备高性能、高可用、高扩展等特点,而是要识别出复杂点然后有针对性地解决问题。
  3. 业界A公司的架构是X,B公司的方案是Y,两个差别比较大,该参考哪一个呢? 答: 理解每个架构方案背后所需要解决的复杂点,然后才能对比自己的业务复杂点,参考复杂点相似的方案。其次,遵循这条准则能够让“老鸟”架构师有得放矢,而不是贪大求全。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章