软件的可申缩性-ebay实践

1:按功能分割

  • 相关的功能部分应该合在一起,不相关的功能部分应该分割开来——不管你把它叫做 SOA、
    功能分解还是工程秘诀。而且,不相关的功能之间耦合程度越松散,就越能灵活地独立伸缩
    其中的一部分。
  • 在编码层次,我们无时不刻都在运用这条原则。JAR 文件、包、Bundle 等等,都是用来隔离
    和抽象功能的机制。
  • 在应用层次, 将不同的功能划分成几个应用程序池。销售功能由一组应用服务器运行,
    投标功能由另一组负责,搜索又是另外一组服务器。
  • 在数据库层次,我们也采取同样的做法。没有无所不包的单一数据库,相反一
    组数据库主机存放用户数据、一组存放商品数据、一组存放购买数据……总
  • 最佳实践

2:水平切分

  • 按功能分割对我们的帮助很大,但单凭它还不足以得到完全可伸缩的架构。即使将功能一一
    解耦,单项功能的资源需求随着时间增长,仍然有可能超出单一系统的能力。我们常常提醒
    自己,“没有分割就没有伸缩”。在单项功能内部,我们需要能把工作负载分解成许多我们有
    能力驾驭的小单元,让每个单元都能维持良好 的性能价格比。这就是水平分割出场的时候
    了。
  • 在应用层次,由于 eBay 将各种交互都设计成无状态的,所以水平分割是轻而易举之事。用
    标准的负载均衡服务器来路由进入的流量。所有应用服务器都是均等的,而且任何服务器都
    不会维持事务性的状态,因此负载均衡可以任意选择应用服务器。如果需要更多处理能力,
    只需要简单地增加新的应用服务器。
  • 数据库层次的问题比较有挑战性,原因是数据天生就是有状态的。我们会按照主要的访问路
    径对数据作水平分割(或称为“sharding”)。例如用户 数据目前被分割到 20 台主机上,每
    台主机存放 1/20 的用户。随着用户数量的增长,以及每个用户的数据量增长,我们会增加
    更多的主机,将用户分散到更多的机器上去。商品数据、购买数据、帐户数据等等也都用同
    样的方式处理。用例不同,我们分割数据的方案也不同:有些是对主键简单取模(ID 尾数
    为 1 的放到第一台主机,尾数为二的放到下一台,以此类推),有些是按照 ID 的区间分割
    (1-1M、1-2M 等等),有些用一个查找表,还有些是综合以上的策略。不过具体 的分割方
    案如何,总的思想是支持数据分割及重分割的基础设施在可伸缩性上远比不支持的优越。

#3:避免分布式事务

  • 看到这里,你可能在疑惑按功能划分数据和水平划分数据的实践如何满足事务要求。毕竟,
    几乎任何有意义的操作都要更新一个以上的实体——立即就可以举出用户和商品的例子。
    正统的广为人知的答案是:建立跨资源的分布式事务,用两段式提交来保证要么所有资源全
    都更新,要么全都不更新。很不幸,这种悲观方案 的成本很可观。伸缩、性能和响应延迟

  • 都受到协调成本的反面影响,随着依赖的资源数量和客户数量的上升,这些指标都会以几何
    级数恶化。可用性亦受到限制,因为所有依赖的资源都必须就位。实用主义的答案是,对于
    不相关的系统,放宽对它们的跨系统事务的保证。
  • 左右逢源是办不到的。保证跨多个系统或分区之间的即时的一致性,通常既无必要,也不现
    实。Inktomi 的 Eric Brewer 十年前提出的 CAP 公理是这样说的:分布式系统的三项重要指标
    ——一致性(Consistency)、可用性(Availability)和分区耐受性(Partition-tolerance)——
    在任意时刻,只有两项能同时成立。对于高流量的网站来说,我们必须选择分区耐受性,因
    为它是实现可伸缩的根本。对于 24x7 运行的网站,选择可用性也是理所当然的。于是只好
    放弃即时一致性(immediate consistency)。
  • 在 eBay,我们绝对不允许任何形式的客户端或者分布式事务——因此绝不需要两段式提交。
    在某些经过仔细定义的情形下,我们会将作用于同一个数据库 的若干语句捆绑成单个事务
    性的操作。而对于绝大部分操作,单条语句是自动提交的。虽然我们故意放宽正统的 ACID
    属性,以致不能在所有地方保证即时一致性,但现实的结果是大部分系统在绝大部分时间都
    是可用的。当然我们也采用了一些技术来帮助系统达到最终的一致性(eventual consistency):
    周密调整数据库操作的次序、异步恢复事件,以及数据核对(reconciliation)或者集中决算
    (settlement batches)。具体选择哪种技术要根据特定用例对一致性的需求来决定。
  • 对于架构师和系统的设计者来说,关键是要明白一致性并非“有”和“没有”的单选题。现
    实中大多数的用例都不要求即时一致性。正如我们经常根据成本和其他压力因素来权衡可用
    性的高低,一致性也同样可以量体裁衣,根据特定操作的需要而保证适当程度的一致性。

 

#4:用异步策略解耦程序

  • 提高可伸缩性的另一项关键措施是积极地采取异步策略。如果组件 A 同步调用组件 B,那么
    A 和 B 就是紧密耦合的,而紧耦合的系统其可伸缩性特征是各部分必须共同进退——要伸缩
    A 必须同时伸缩 B。同步调用的组件在可用性方面也面临着同样的问题。我们回到最基本的
    逻辑:如果 A 推出 B,那么非 B 推出非 A。也就 是说,若 B 不可用,则 A 也不可用。如果
    反过来 A 和 B 的联系是异步的,不管是通过队列、多播消息、批处理还是什么其他手段,
    它们就可以分别地伸缩。而且,此时 A 和 B 的可用性特征是相互独立的——即使 B 受困或
    者死掉,A 仍然能够继续前进。
  • 整个基础设施从上到下都应该贯彻这项原则。即使在单个组件内部也可通过 SEDA(分阶段
    的事件驱动架构,Staged Event-Driven Architecture)等技术实现异步性,同时保持一个易于

  • 理解的编程模型。组件之间也遵守同样的原则——尽可能避免同步带来的耦合。在多数情
    况下, 两个组件在任何事件中都不会有直接的业务联系。在所有的层次,把过程分解为阶
    段(stages or phases),然后将它们异步地连接起来,这是伸缩的关键。

 

#5:将过程转变为异步的流

  • 用异步的原则解耦程序,尽可能将过程变为异步的。对于要求快速响应的系统,这样做可以
    从根本上减少请求者所经历的响应延迟。对于网站或者交易系统,牺牲数据或执行的延迟时
    间(完成全部工作的实践)来换取用户的延迟时间(用户得到响应的时间)是值得的。活动
    跟踪、单据开付、决算和报表等处理过程显然都 应该属于后台活动。主要用例过程中常常
    有很多步骤可以进一部分解成异步运行。任何可以晚点再做的事情都应该晚点再做。
  • 还有一个同等重要的方面认识到的人不多:异步性可以从根本上降低基础设施的成本。同步
    地执行操作迫使你必须按照负载的峰值来配备基础设施——即使在任务最重的那一天里任
    务最重的那一秒,设施也必须有能力立即完成处理。而将昂贵的处理过程转变为异步的流,
    基础设施就不需要按照峰值来配备,只需要满足平均负载。而且也不需要立即处理所有的请
    求,异步队列可以将处理任务分摊到较长的时间里,因而起到削峰的作用。系统的负载变化
    越大,曲线越多尖峰,就越能从异步处理中得益。

 

#6:虚拟化所有层次

  • 虚拟化和抽象化无所不在,计算机科学里有一句老话:所有问题都可以通过增加一个间接层
    次来解决。操作系统是对硬件的抽象,而许多现代语言所用的虚拟机又是对操作系统的抽象。
    对象-关系映射层抽象了数据库。负载均衡器和虚拟 IP 抽象了网络终端。当我们通过分割数
    据和程序来提高基础设施的可伸缩性,为各种分割增加额外的虚拟层次就成为重中之重。
  • 在 eBay,我们虚拟化了数据库。应用与逻辑数据库交互,逻辑数据库再按照配置映射到某
    个特定的物理机器和数据库实例。应用也抽象于执行数据分割的路由逻辑,路由逻辑会把特
    定的记录(如用户 XYZ)分配到指定的分区。这两类抽象都是在我们自己开发的 O/R 层上实
    现的。这样虚拟化之后,我们的运营团队可以按需要在物理主机群上重新分配逻辑主机—
    —分离、合并、移动——而完全不需要接触应用程序代码。
  • 搜索引擎同样是虚拟化的。为了得到搜索结果,一个聚合器组件会在多个分区上执行并行的
    查询,但这个高度分割的搜索网格在客户看来只是单一的逻辑索引。
  • 以上种种措施并不只是为了程序员的方便,运营上的灵活性也是一大动机。硬件和软件系统

  • 都会故障,请求需要重新路由。组件、机器、分区都会不时增减、移动。明智地运用虚拟化,
    可使高层的设施对以上变化难得糊涂,你也就有了腾挪的余地。虚拟化使基础设施的伸缩成
    为可能,因为它使伸缩变成可管理的。

 

#7:适当地使用缓存

  • 最后要适当地使用缓存。这里给出的建议不一定普遍适用,因为缓存是否高效极大地依赖于
    用例的细节。说到底,要在存储约束、对可用性的需求、对陈旧数据的容忍程度等条件下最
    大化缓存的命中率,这才是一个高效的缓存系统的最终目标。经验证明,要平衡众多因素是
    极其困难的,即使暂时达到目标,情况也极可能随着时间而改变。
  • 最适合缓存的是很少改变、以读为主的数据——比如元数据、配置信息和静态数据。在 eBay,
    我们积极地缓存这种类型的数据,并且结合使用“推”和“ 拉”两种方法保持系统在一定
    程度上的更新同步。减少对相同数据的重复请求能达到非常显著的效果。频繁变更、读写兼
    有的数据很难有效地缓存。在 eBay,我们大多有意识地回避这样的难题。我们一直不对请
    求间短暂存在的会话数据作任何缓存。也不在应用层缓存共享的业务对象,比如商品和用户
    数据。我们有意地牺牲 缓存这些数据的潜在利益,换取可用性和正确性。在此必须指出,
    其他网站采取了不同的途径,作了不同的取舍,也同样取得了成功。
  • 好东西也会过犹不及。为缓存分配的内存越多,能用来服务单个请求的内存就越少。应用层
    常常有内存不足的压力,因此这是非常现实的权衡。更重要的一点,当你开始依赖于缓存,
    那么主要系统就只需要满足缓存未命中时的处理要求,自然而然你就会想到可以削减主要系
    统。但当你这样做之后,系统就完全离不开缓存了。现在主要系统没办法直接应付全部流量,
    也就是说网站的可用性取决于缓存能否 100%正常运行——潜在的危局。哪怕是例行的操作,
    比如重新配置缓存资源、把缓存移动到别的机器、冷启动缓存服务器,都有可能引发严重的
    问题。
  • 做得好,缓存系统能让可伸缩性的曲线向下弯曲,也就是比线性增长还要好——后续请求
    从缓存中取数据比从主存储取数据成本低廉。反过来,缓存做得不好 会引入相当多额外的
    经常耗费,也会妨碍到可用性。我还没见过哪个系统没机会让缓存大展拳脚的,关键是要根
    据具体情况找到适当缓存策略。

总结

可伸缩性有时候被叫做“非功能性需求”,言下之意是它与功能无关,也就比较不重要。这


么说简直错到了极点。我的观点是,可伸缩性是功能的先决条件——优先级为 0 的需求,
比一切需求的优先级都高。

希望以上最佳实践能对你有用,希望能帮助你从新的角度审视你的系统,无论其规模如何。

你可能感兴趣的:(数据库,应用服务器,负载均衡,虚拟化,缓存服务器,缓存系统)