构建可扩展的系统正成为越来越热的话题。主要是因为越来越多的人使用计算机,无论是交易量还是预期业绩都大幅增长。

本文仅涉及一般性的原则。

一般原则

“可扩展性”不等于“原始性能”

  • 可伸缩性是减少由于对性能、成本、可维护性等诸多方面增长带来的不利影响。
  • 例如:运行在一个机箱内的各部件,当负荷小,将有更高的性能,但它不是可扩展的,因为当负载增加到超出了机器的能力时性能急剧下降。

 理解系统设计的目标环境负荷条件

  • 增长维数及增长率,例如:用户数,交易量,数据量。
  • 测量并了解其目标,如响应时间、吞吐量 。

明白谁是你的重点客户

  • 区分轻重缓急,若不能处理所有事情时你知道该牺牲什么。

向外扩展,而非向上扩展

  • 横向扩展系统(添加更多廉价机),而不是垂直扩展系统(升级到更强大的机器) 。

让你的代码模块化和简单

  • 以新代码替换旧代码却不影响系统其它部分的能力,使得你可以实验不同的放来快速优化系统。
  • 不要以任何(包括业绩相关的)理由牺牲代码的模块性。

不要猜测系统的瓶颈,而要度量它

  • 系统的瓶颈是频繁执行的缓慢代码。不要优化很少执行的缓慢代码。
  • 编写性能单元测试,以便可以收集组件级别的细粒度得性能数据。
  • 建立一性能实验室,以便易于进行终端到终端的性能改进措施 。

计划增长 

  • 做定期的容量规划。 收集使用情况统计,预测增长率。

常用技术

服务器集群(实时访问)

  • 如有大量的独立(可能同时进行)请求,那可采用服务器集群(配置基本相同的计算机),其前端为负载均衡
  • 应用本身必须是无状态的,因此可仅依据载入条件分发请求,而不是其他因素
  • 传入请求由负载均衡器分发到不同的机器,因此负载跨群中的服务器散布和共享。
  • 该架构允许横向扩展,负载增加时,只需向集群中添加更多的服务器实例。
  • 与云计算结合本这策略更有效,向群中添加更多的虚拟机实例仅是一API调用。

数据分割

  • 将数据分成多个数据库,使数据访问的负载可以分布在多个服务器。
  • 从本质上讲,数据是有状态的,所以必须有一个确定的机制来调度数据请求到相关的数据服务器。
  • 数据分区机制也需要采取必要的数据访问模式。
  • 需要被一起访问数据应置于同一服务器
  • 一个更为复杂的方法是可以根据数据访问模式的转变不断迁移数据。
  • 大多数分布式键/值存储这样做 。

Map/Reduce(批量并行处理)

  • 该算法本身需要被并行化。这通常意味着执行步骤应是相对独立的。
  • 这通常意味着执行的步骤应该是相对互相独立的。这通常意味着执行的步骤应该是相对互相独立的。这通常意味着执行的步骤应该是相对互相独立的。谷歌的Map/Reduce模型是一很好的框架。

内容分发网络(静态缓存)

  • 这是内容静态媒体的普遍方法这个想法是创建该服务器的地理分布在许多副本的内容。想法是依据地理分布创建许多内容副本服务器。
  • 用户请求将被路由到最近的包含副本的服务器

缓存引擎(动态缓存)

  • 这是一个时间与空间的权衡。
  • 些执行可能反复使用相同的输入参数重新设置与其重做了相同的输入参数相同的执行,我们可以记住上一次执行的结果。与其重做相同输入参数相同的执行,不如记住上次执行的结果。
  • 这通常实现为一个查询缓存与其重做了相同的输入参数相同的执行,可以记住上次执行的结果。
  • Memcached的和EHcache是流行的缓存包。

资源库 

  • DBSession和TCP连接的创建是昂贵的,使它们实现多请求复用。

计算出近似结果

  • 不计算出准确答案,权衡速度和精度。
  • 现实生活中,某种程度的误差通常是可以容忍的;

源头过滤

  • 尝试更多的上游处理(数据是生成的)而不是下游处理,因为它减少了数据的繁繁殖量。

异步处理

  • 调用请求可以返回结果。但如果直至处理进程的后期才需要结果,因此在发出该调用请求时没必要立即等待,而是继续处理其他事务,直至需要结果的时刻。
  • 此外,若等待线程处于空闲状态,但消耗系统资源。对于高交易量,空闲线程数是(arrival_rate * processing_time,如果arrival_rate高),它可以是一个很大的数字,则该系统是运行在一个非常无效的模式。
  • 本例中的服务调用可使用异步处理模式更好地处理。这通常有两种方式:回调和轮询
  • 在回调模式下,调用者在请求调用时需提供一个响应处理程序。调用本身将立即返回当前状态,实际工作在服务器端完成。工作完成后,响应作为单独的线程返回, 该线程将执行先前注册的响应处理程序。一些协调之间需要一种可以调用线程和回调线程。调用线程和回调线程需要一些协调。
  • 在轮询模式中,调用本身将立即返回一个“未来”处理,调用者可以做其他的事情,然后查询“未来”处理看是否有响应准备好。在本模式中,没有多余的线程被创建所以无需额外的线程的协调。

实现设计考虑

  • 采用有效的算法和数据结构。分析经常执行(及热点)逻辑的的时间(CPU)和空间(内存)复杂性。例如,仔细决定查询时使用哈希表还是二叉树。
  • 分析多线程同时访问共享数据的情况,认真分析同步方案,并确保细粒度锁足用,同时提防死锁的可能性,以及如何检测或预防;错误的并发访问模式对系统的可扩展性影响很大,也可以考虑使用无锁数据结构(例如,Java的几个并发包)。
  • 在逻辑分析内存使用情况确定在何处创建新的对象,何处使它们被垃圾收集。一定要意识到大量临时对象的创建将加重垃圾收集器的负载。
  • 但是,没必要权衡性能与代码的可读性(例如不要尝试一个方法绑定过多逻辑),让虚拟机处理此类问题。