delphij's Chaos

选择chaos这个词是因为~~实在很难找到一个更合适的词来形容这儿了……

17 Aug 2009

可伸缩性 Scalability

关于Scalability这个词的中文译法,目前还没有一个非常确切的定论。一些文献中将其翻译为"可扩充性",而"扩充"指的主要是Scale up;而在一些实际的用法中,Scalability还包括Scale down,因此,“可伸缩性"也许是比较贴近原文意思的说法。

互联网应用的可扩充性体现在两个方面:其一,是能够对系统容量进行扩充,也就是说,这个系统各个组件能够提供的服务的量,能够在需要的时候予以扩充(特别是通过添加新的服务器等等)。其二,是这种扩充是有效率的扩充,即,增加硬件投入时,其投入与所产生的效果是接近甚至达到成比例增加的。通常说来,“可扩充"同时暗含的需求是用户的使用习惯尽可能保持不变。如果我们关注某一具体的计算节点,可扩充性还应体现于提高计算节点性能,例如增加其CPU数量或内存容量时,能够相应地改善系统的容量或响应时间,等等。

而另一方面,“可缩减性"主要指的则是说一套系统能够运行在尽可能少的软硬件环境之中。对于大型互联网公司而言,这一点可能并不重要,而对初创公司来说这一点则非常重要。

在设计互联网应用的时候,充分地考虑系统的可伸缩性,能够极大地减少日后的维护开销,并帮助决策者对于投资所能获得的回报进行更加精准的估计;另一方面,高可伸缩性的系统往往会具有更好的容灾能力,从而提供更好的用户体验。

与解决很多其他问题类似,改善可伸缩性最常用的方法就是分治法(Divide and Conquer)。分治属于大道理一类,在实践中,我们比较常用的分治策略包括:

  • 提高计算的可并行度。简单的计算,例如连续执行的加法,可以将中间结果的计算并行完成;而复杂一些的计算,例如搜索,也可以通过类似的方式分派到不同的单元中进行,并最终在另外的地方完成汇总。这种策略,比较适合于数据集中数据之间直接关联度不大、生成数据集比输入数据集小很多,并且汇总计算本身引起的开销较小的情形。
  • 增加能够完成同一类任务的计算/服务单元数。这类做法中,计算/服务单元仅仅完成某种将输入变换为输出的工作,并且,这类工作不太依赖于其与外界交互产生的状态,简而言之,对于输入数据,计算/服务单元能够自行完成一个环节的计算任务并给出输出。这种策略比较适合于存在,或可能成为瓶颈的位置。举例来说,对于读多写少的应用,通过适当增加cache环节(这些cache是全冗余的,也就是不同的机器之间可以完全地相互取代),就能够有效地提高其负载能力。
  • 消除单一故障点/瓶颈。如果一个系统中某些数据只存在一份,或某种计算只能在某一点上完成,那么这些环节就会成为单一故障点,或性能瓶颈。消除这类问题需要更巧妙的设计,简单地将数据复制到更多的机器上并不能够从根本上解决问题,因为在这种结构中"主"节点仍会成为瓶颈。有时,为了达到这个目的需要进行一些折衷设计,例如分段提交的方法,在容忍一部分竞态条件(race condition)的前提下避免另一些、不能容忍的竞态条件。这类做法往往会导致局部吞吐量受到负面影响,或使设计变得复杂。

个人的一些经验和用到的技巧:

  1. 减少不必要的节点间通讯。让计算能够在尽可能多的地方进行这个目标,与尽可能让计算在靠近数据存储的地方进行并不矛盾。说的更露骨一些,不能变成现金的流量和服务器端计算都要尽可能减少。
  2. 让一个会话中不太重要,但共享并修改同一份数据的部分,尽可能避免其在不同的计算节点之间漂移。这与减少不必要的节点间通讯的目标一致,同时,还能够有效地降低程序设计的复杂性。
  3. 减少环节数。需要n个环节完成的一个事物,就会有n个可能发生问题的点。每个环节能够承担其任务的节点越多,在那个环节发生问题的可能性就越小。
  4. 可能的时候,尽量将计算推迟到拥有尽可能多相关数据,或靠近这些数据的节点进行。例如,如果一个用户界面中需要对用户可见的200行数据按照用户需要进行排序或筛选,就可以考虑把排序过程放在客户机一端,而不一定是在服务器上去做,因为服务器做这件事并不一定更快,而另一方面这一设计也会减少服务器在提供响应时话费的时间,而无需增加额外的网络流量。
  5. 设计好容灾方案,特别是故障转移方案。假设每个节点发生问题的概率一样,系统中的节点越多,用户访问全部节点时,遇到一个有问题的节点的机会就越多。好的容灾方案则能够让用户最终真的看到的问题数量与节点数接近成反比,因此,合理的故障转移方案能够让用户尽可能不看到节点发生故障的情形。
  6. 避免借助"外力"去检测问题。例如,如果在一台前端服务器上的查询客户端的查询过程本身也是进行健康检查/反馈的过程,除了能够节省少量用于健康检查的带宽开销之外,还能够提供更及时有效的反馈。
  7. 不必过分强调单个节点的性能,除非那个节点没有办法扩充为较大的、可并发的系统(这种情况非常罕见)。为了强调单个节点性能而牺牲可并行或者可并发性能是得不偿失的。
  8. 在设计网络时,尽量避免将不相关的网络所在的物理设备连接到一起,必要时才通过VLAN来进行隔离。

以上是一些个人经验的总结,纯属抛砖引玉。