复杂度来源

高性能

软件系统中高性能带来的复杂度主要体现在两方面,一方面是单台计算机内部为了高兴嗯呢带来的复杂度,另一方面是多台计算机集群为了高性能带来的复杂度。

追求良好的用户体验和满足业务增长的需要,所以需要高性能,可以通过从垂直于水平两个维度来考虑:

  • 垂直维度主要是针对单台计算机,通过升级软、硬件能力实现性能提升。该方案比较适合业务阶段早期和成本可接受的阶段,简单直接,但是受到成本与硬件能力天花板的限制
  • 水平维度则主要针对集群系统,利用合理的任务分配与任务分解实现性能的提升。该方案带来的好处要在业务发展的后期才能体现出来,当业务达到一定规模,水平维度是技术发展的必由之路

单机复杂度

计算机内部复杂度最关键的地方就是插座系统,操作系统是软件系统的运行环境,操作系统的复杂度直接决定了软件系统的复杂度。

操作系统和性能最相关的就是进程和线程。

进程就是一个独立的任务,独显内存空间,进程间互不相关,进程间通信的方式有:管道、消息队列、信号量、共享存储等。

线程方式进程内部的子任务,共享同一份进程数据,为了保证数据的正确性,发明了互斥锁机制。

操作系统分配资源的最小单位是进程,调度的最小单位是线程

集群复杂度

任务分配

指每台机器都可以处理完整的业务任务,不同的任务分配到不同的机器上执行。能够突破单台机器处理性能的瓶颈,通过增加更多的机器来满足业务的性能需求

任务分解

当业务更复杂,单纯任务分配来扩展性能,收益会越来越低。因此通过将复杂任务分解,能够将原来大一统但复杂的业务系统,拆分成小而简单但需要多个系统配合的业务系统。

任务分配提升性能的原因:

  • 简单的系统更加容易做到高性能
  • 可以针对单个任务进行扩展

任务分解带来的性能收益还是有一个度的,并不是任务分解越细越好。

高可用

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

系统的高可用方案本质上都是通过“冗余”来实现。高性能增加机器的目的在于“扩展”处理性能;高可用增加机器目的在于“冗余”处理单元。

网站高可用的主要技术手段是服务于数据的冗余备份与失效转移,同一服务组件部署在多台服务器上;数据存储在多台服务器上互相备份。

高可用的解决方法不是解决,而是减少或者规避,高可用的设计过程其实也是一个取舍的过程,这也是为什么系统可用性永远只说几个九,永远缺少那个一

计算高可用

这里的计算指业务的逻辑处理,特点是无论在哪台机器上进行计算,同样的算法和输入数据,产出的结果都是一样的。

与高性能扩展机器相比,高可用需要任务分配器增加分配算法,常见的双机算法有主备、主主、主备方案又可以细分为冷备、温备、热备。多机方案更加复杂,ZooKeeper采用的是1主多备,Memcached采用的是全主0备。

存储高可用

存储与计算相比,需要将数据从一台机器搬移到另一台机器。

经过线路进行传输有延迟,会产生数据不一致,按照“数据+逻辑=业务”这个公式来套的话,数据不一致,即使逻辑一致,最后的业务表现就不一样。

线路还有可能出现故障,意味着存储无法进行同步,这段时间整个系统的数据是不一致的。

存储高可用的难点不在于如何备份数据,而在于如何减少或者规避数据不一致堆业务造成的影响

分布式领域里面的CAP定理,从理论上论证了存储高可用的复杂度,即不可能同时满足“一致性、可用性、分区容错性”,最多满足其中两个。

高可用状态决策

计算高可用和存储高可用的基础都是“状态决策”,即系统需要能够判断当前的状态是正常还是异常,如果出现了异常就要采取行动来保证高可用。

通过冗余来实现的高可用系统,状态决策本质上就不可能做到完全正确

决策方式有:

独裁式

指存在一个独立的决策主体称为“决策者”,负责收集冗余个体的信息并且进行决策,该方式不会出现决策混乱的问题,但是一旦决策者出现问题,整个系统就无法实现准的状态决策

协商式

指两个独立的个体通过交流信息,然后根据规则进行决策,最常用的方式是主备决策,其基本协商规则可以设计成:

  1. 2台服务器启动时都是备机
  2. 2台服务器建立连接
  3. 2台服务器交换状态信息
  4. 某一台服务器做出决策,称为主机;另一条服务器继续保持备机身份

难点在于主备连接中断,状态决策应该怎么做。

民主式

指多个独立的个体通过投票的方式来进行状态决策。ZooKeeper集群在选举Leader时就是采用这种方式:

民主式会出现“脑裂”问题,即原来统一的集群因为连接中断,造成了两个独立分隔的子集群单独进行选举选出不同的2个主机。

为了解决脑裂问题,民主式决策的系统一般都采用“投票节点数必须超过系统总节点数一半”规则来处理。该方式虽然解决了脑裂问题,但是同时降低了系统整体的可用性,如果真的出现故障导致选举多个主机,都没有满足超过一半节点数条件,就没有主节点,整个系统就相当于宕机了。

可扩展性

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

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

预测变化

预测变化的复杂性在于:

  • 不能每个设计点都考虑可扩展性
  • 不能完全扩容可扩展性
  • 所有的预测都存在出错的可能性

应对变化

应对变化的常见方案是将“变化”封装在一个“变化层”,将不变的部分封装在一个独立的“稳定层”。变化层依赖稳定层还是稳定层依赖变化层都是可以的,但是都会带来两个复杂性相关问题:

  • 系统需要拆分出变化层和稳定层
  • 需要设计变化层与稳定层之间的接口

应对变化的另一个方案是提炼出一个“抽象层”和一个“实现层”,前者是稳定的,后者可以根据具体业务需要定制开发,当加入新的功能时,只需要增加新的实现,无须修改抽象层。典型实践是设计模式和规则引擎。

设计模式的核心是封装变化,隔离可变性。

低成本、安全、规模

低成本

通过减少服务器的数量达到降低成本的目标,本质上与高性能与高可用冲突,因此很多时候低成本不是架构设计的首要目标,而是架构设计的附加约束。

低成本给架构带来的主要复杂度体现在,往往只有“创新”才能达到低成本目标。列如:

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

创造新技术复杂度更高,因此一般中小公司基本都是靠引入新技术来达到低成本的目的;而大公司因为有足够的资源,技术和时间,更有可能创造新的技术来达到低成本的目标。

安全

从技术的角度来讲,安全可以分为两类:一类是功能上的安全,一类是架构上的安全

功能安全实际上是在“防小偷”,是一个逐步完善的过程,往往是在问题出现后才能有针对性的提出解决方案,我们永远无法预测系统下一个漏洞在哪里,也无法说自己的系统绝对安全。

架构安全是“防强盗”,传统的架构安全主要依靠防火墙,防火墙最基本的功能就是隔离网络,通过将网络划分成不同的区域,制定出不同区域之间的访问控制策略来控制不同信任程度区域间的数据流。

互联网系统的架构安全目前并没有太好的设计手段来实现,更多地是依靠运营商或者云服务器商强大的带宽和流量清洗的能力,较少自己来设计和实现。

规模

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

  • 功能越来越多,导致系统复杂度指数级上升
  • 数据越来越多,系统复杂度发生质变

Note: Cover Picture