《架构真经:互联网技术架构的设计》大道至简

简介:

本节书摘来自华章出版社《架构真经:互联网技术架构的设计》一书中的第1章,第1节,作者 小象学院 杨 磊,更多章节内容可以访问云栖社区“华章计算机”公众号查看。



 大道至简

无论从哪个角度来看,杰瑞米·金都有一个成功和绚丽的职业生涯。20世纪90年代中期,在互联网大潮来袭之前,杰瑞米参与了海湾网络公司SAP项目的成功实施。从此,杰瑞米投身互联网大潮,任Petopia.com公司的技术副总裁。他经常调侃说,在Petopia.com公司的这段经历,相当于从“硬汉拓展营大学”取得了“现实世界的工商管理硕士”。离开Petopia.com后,杰瑞米加入eBay,作为总监负责新一代商务平台V3的架构。如果说杰瑞米在Petopia.com完成了财经专业的课程,那么eBay(后升任副总裁)为他提供了前所未有的系统可扩展性方面的教育。杰瑞米也曾在LiveOps做过三年常务副总裁,现任沃尔玛实验室首席技术官。

eBay为杰瑞米积累了丰富的经验,包括架构需要简化。2001年杰瑞米加入了eBay,那时的eBay与少数像亚马逊和谷歌这样的公司一样方兴未艾,在线交易初具规模。2001年全年,eBay的商品总销售额为27.35亿美元[1],同期,沃尔玛在全球的销售额是1913亿美元(未包括在线交易)[2],亚马逊31.2亿美元[3]。然而,在这耀眼的成功背后却隐藏着eBay黑暗的过去。

1999年6月,一起持续了将近24小时的宕机事件[4]让eBay几乎濒临死亡宣判。1999年6月宕机事件后的几个月里,eBay的网站又接二连三地出现了几次不同规模的宕机,尽管引起每次宕机的原因有所不同,但是,问题的根源都指向该网站无法应付空前急剧增长的大量用户请求。这些宕机事件彻底改变了该公司的文化。确切地说,这些事件使eBay关注于以高可用和高可靠的标准来约束其所提供的服务。

1999年,eBay卖出的大部分商品都是以拍卖方式完成的。与常见的在线交易相比,拍卖是一种很独特的交易方式。首先,拍卖的周期很短;其次,临近预定的投标截止时刻,往往会出现大量意外的投标(写交易)和查询(读交易)。相对而言,传统平台上的大多数交易都均匀分布,并带有典型的季节性;尽管eBay平台可以展示数以百万计的商品,但在任意给定时刻,全部用户的活动都集中在少数商品上。这为数据库的负载带来了独特的难题,例如,由于负载主要集中在少数商品上,支撑业务的数据库(那时是单一数据库)不得不在物理记录和逻辑记录的冲突中苦苦挣扎。这也证实了数据库是eBay网站反应慢甚至彻底崩溃的原因。

杰瑞米在eBay的第一项任务是带领团队重新定义eBay的软件架构,目标是防止类似1999年6月的宕机事件再次发生。与此同时,还需要考虑业务的飞速增长以及拍卖形式所遭遇的各种困难,使这项任务变得异常复杂。这个内部命名为V3的项目需要采用Java来重构eBay的商务引擎(之前是C++),这次系统重构还可以顺便解决数据库沿X、Y和Z轴分库的问题(详见本书第2章)。

该团队试图确保系统的各个部分都可以无限扩展,并能以最快的速度来解决任何系统故障,以把故障所带来的影响降到最低。“我的主要教训是,”杰瑞米说,“我们试图把系统各个部分的复杂性和业务的关键性与实际的拍卖过程一视同仁。对网站上的各种功能,从图片展示到eBay用户评价系统(经常称为反馈),我们均采取类似的高可靠性解决方案。”

“要知道,”杰瑞米接着说,“2001年那时候几乎没有什么公司经历过eBay那样大规模的线上交易。因此,不论是供应商还是开源组织都无法帮助我们解决问题。只能靠我们自己去发明创造,如果有选择,我宁可不去做这些事。”

乍一看我们很难梳理出杰瑞米所吸取的经验教训。假如所有子系统做得和拍卖子系统一样坚不可摧呢?杰瑞米笑着说,“其实并不是每个子系统都像拍卖子系统那么复杂。以用户评价引擎为例。在短期内,这个子系统并不需要像拍卖子系统那样需要应付大量的数据资源竞争。因此,也就不需要把同样的架构原则应用在该系统上。如果该子系统在短期内对某些数据资源的竞争并不像交易那样严重,那么该系统甚至可以在保持高可用性的情况下更加有效地扩展。更为重要的是,对每个商品的交易,用户评价子系统都需要进行一次写操作,而拍卖子系统则可能每秒钟都需要做几百次写操作。与简单的减库存不同,这是一个复杂的比较过程,需要把当前的投标价与所有其他的投标价进行比较,然后重新计算出新的投标价。但是,我们却把解决其他问题与拍卖等量齐观,特别是要求其他子系统也能够承受空前大量的用户请求,事实上,这些请求只集中在一小部分数据上,而且发生在拍卖的最后几秒钟内。”

在弄清楚这个问题后,我们开始思考如果把V3的某些部分做得比其他部分更复杂会带来什么影响。“那很容易,”杰瑞米说,“总体来看V3是成功的,如果让我们重新来过,或许我们有机会以更低的成本或更快的速度完成它或两者兼顾。此外,因为有些部分过于复杂,换句话说,问题并不需要那么复杂的解决方案,因而造成这些部分的维护成本比较高。这也是我在沃尔玛和LiveOps所学习与应用的架构原则:问题的复杂度要与解决问题的方法及成本相匹配。每个问题解决方案的复杂度都不同,要用最简单的方法取得最佳的效果。从扩展性、可维护性或者可复用性的角度看,拥有一个标准的平台或者开发语言看起来似乎很理想,然而,采用一个基于开源项目、新开发语言或者新平台的简单解决方案可能会大幅度地降低成本、缩短上市时间,甚至为客户带来创新的产品。”

杰瑞米的故事告诉我们不要把简单的问题复杂化,换句话说,尽量保持问题解决方案的简单。我们认为复杂问题只是一个关于有待解决的小而简问题的集合。本章就讨论如何把大事化小,从而事半功倍。

本书的许多章节都会提到规则,这些规则会随着系统的规模和复杂度而变化。有些比较宏观,适用于多种不同的设计环境。有些则比较微观,只适用于某些特定系统的实施。

规则1——避免过度设计

内容:  在设计中要警惕复杂的解决方案。

场景:  适用于任何项目,而且应在所有大型或者复杂系统或项目的设计过程中使用。

用法:  通过测试同事是否能够轻松地理解解决方案,来验证是否存在过度设计。

原因:  复杂的解决方案实施成本过高,而且长期的维护费用昂贵。

要点:  过于复杂的系统限制了可扩展性。简单的系统易维护、易扩展且成本低。

正如在维基百科中解释的那样,过度设计有两大类[5]:第一类指产品的设计和实施超过了实际的需求。出于完整性,我们对此做简单的讨论,与第二类相比,实际上第一类对可扩展性的影响很小。第二类指所完成的产品过于复杂。如前所述,我们更关心第二类过度设计对可扩展性的影响。但是要先讨论一下超过实际需求的含义。

为了讨论第一类超过实际需求的过度设计,我们首先要解释一下“实际”两个字的含义,“实际”就是指“能够使用”。例如,设计一款家用空调,在室外可以达到热力学温度0K,在室内可以达到300℉,这是在浪费资源,毫无必要。与此相对的是,设计和制造一款空调机,能够在室外-20℉时把室内加热到可以舒适生活的环境温度。过度设计有过度使用资源的情况,包括为研发和实施硬件、软件解决方案付出的较高费用。如果因为过度设计,造成系统的研发周期过长,影响公司产品的发布计划。这些都会直接影响股东的利益,因为较高的成本导致较低的收益,较长的研发时间会影响公司的收入。对比初始产品定义与产品首次发布,如果项目范围扩大了,那就是过度设计的一个表现。

一个更接近我们生活的例子是研发一个打卡系统,它可以支撑一个公司相当于地球总人口100倍数量的员工打卡的需求。在打卡软件的生命周期内,地球人口增长100倍的概率极其渺茫。那么多人都为一个公司工作的机会也很小。我们当然希望把系统建设得可以扩展以满足客户的需求,但是我们并不想浪费时间来实施和配置远远超过我们实际需求的系统能力(参见规则2)。

第二类过度设计是指把一件事情做得过于复杂和以复杂的方式去完成一个任务。简单地说,它包括让某些事物超过实际需要过度工作,让用户费不必要的精力去完成一件事,让工程师付出很大的努力去理解不必要的需求。下面将对过度设计的这三个方面进行深入分析。

让某些事物超过实际需要过度工作到底意味着什么?杰瑞米·金提到的研发构成eBay网站的全部功能与拍卖过程的急迫需求,就是一个让某些事物(例如用户评价系统)超过实际需求过度工作的最好例子。再举现实生活中的一些其他例子。假如你打发手下去杂货店。他同意了,你告诉他每样东西都拿一件,然后在排队付款前给你回个电话。电话来了,你告诉他,其实你只想从那些已经装满的购物篮中挑一些东西,让他把其他不要的东西都放回去。你可能会说:“实际上没有那么荒唐!”但是,你是否曾经在代码中执行过select (*) from schema_name.table_name这样的SQL语句,然后从结果返回集中选取个别几行数据呢?(参见第8章中的规则35。)前面杂货铺的例子与select(*)异曲同工。在代码中,你加了多少行条件判断来处理小概率事件?你用什么样的顺序来评估和判断这些条件?你先处理那些大概率事件吗?你是否经常让数据库再次返回刚刚请求过的结果集?你是否经常请求重新产生刚刚显示过的HTML页面?这样的问题比比皆是(本可以取最近的正确答案,却做不必要的重复工作),而且很容易被忽略,为此,我们特别准备了一整章的内容(第6章)来讨论此话题。你应该明白我们这么做的原因了吧!

让用户费不必要的精力是什么意思?在大多数情况下,少即是多。很多时候,为了试图保持系统的灵活性,我们常会努力把尽可能多且不常用的功能塞进系统里。生活并不是总需要多样性的点缀。在大多数情况下,用户只想不受任何干扰,尽快地从A点到达B点。如果市场上99%的用户根本就不在意能否把微博的内容导入.pdf文件中,那么就不要去设置一个选择,问用户是否要把微博的内容另存为.pdf文件。如果用户对把文件从.wav格式转换成MP3格式感兴趣,就说明他们已经对音乐保真不在意,那就别再用可转换高保真压缩FLAC文件的新功能来分散用户的注意力了。

最后,我们讨论最常见的问题,就是研发者把软件写得异常复杂以至于其他的工程师无法轻而易举地理解。这种做法曾经风靡一时,实际上还有看谁能把代码写得复杂到其他人难以理解的比赛。组织者会把奖牌发给那些能把代码写到高级研发者在进行代码复查时欲哭无泪的人。复杂性成了知识分子的囚笼,电脑编程的极客们在这个囚笼里为争夺组织的优势而互相厮杀。那些有兴趣一展身手的极客们,通常都远离实战,躲在安全屋里操作以免对股东价值带来潜在的破坏,建议他们参加国际C语言混乱代码大赛(详情参见www0.us.ioccc.org/index.html)。除此之外的人要清楚地认识到自己的工作是研发简单、易懂的解决方案,并通过这些方案为股东保持和创造价值。

我们都应该努力把代码写得通俗易懂。对一个好工程师的真实度量,是看他能多快简化一个复杂的问题(见规则3),然后构思出一个易于理解并可以维护的解决方案。浅显易懂的方案可以让初中级工程师快速上手。浅显易懂的方案也意味着在系统纠错过程中可以很快地查出问题,确保系统可以更快地恢复正常。浅显易懂的方案能够增强组织和平台的可扩展性。

有一个非常好的验证办法可以用来确定方案是否过于复杂,让负责解决该复杂问题的工程师把自己的解决方案展现给公司内的不同技术团队。参与活动的技术团队成员要在经验水平和公司任期方面有不同的代表性(区分经验和任期是因为有些刚加入的工程师,虽然经验丰富,但是对公司并不太了解)。要想通过这个测试,每个技术团队都应该能轻松地理解方案,并可以在无人协助的情况下,向其他不知情的人描述该方案。如果有任何一个技术团队对此方案表示不理解,那么就应该针对该方案是否过于复杂进行深入的辩论。

过度设计是可扩展性的大敌之一。设计一个超过实际需要的方案就是在浪费金钱和时间。更进一步来说,这种方案会浪费系统的处理资源,增加系统扩展的成本,限制系统的整体可用性(系统能扩展到什么程度)。构建过于复杂的解决方案与此相似。过度工作的系统会增加成本并限制平台最终的规模。那些让用户费很多精力的系统,会在增加用户数量和快速开展业务时遇到瓶颈。复杂到难以理解的系统会扼杀组织的生产力,加大工程师团队扩大的难度,也提高了为系统增加新功能的难度。

规则2——方案中包括扩展

内容:  提供及时可扩展性的DID方法。

场景:  所有项目通用,是保证可扩展性的最经济有效的方法(资源和时间)。

用法:

Design (D)设计20倍的容量。

Implement (I)实施3倍的容量。

Deploy (D)部署1.5倍的容量。

原因:  DID为产品扩展提供了经济、有效、及时的方法。

要点:  在早期考虑可扩展性可以帮助团队节省时间和金钱。在需求发生大约一个月前实施(写代码),在客户蜂拥而至的几天前部署。

我们公司致力于帮助客户解决他们的扩展性需求。你可能想象得到,客户经常问我们,“什么时候该在可扩展性上投入?”有些轻率的回答是,最好在需要的前一天投入和部署。如果你能够做到在需要改善可扩展性方案的前一天部署,那么这笔投资的时机最佳(及时),而且有助于实现公司财务和股东利益的最大化。这与戴尔(Dell)带给世人的按需订制系统和准时生产相似。

让我们面对现实,诸如及时投入和部署根本就不可能,即使可能,也无法确定具体的时间,而且会带来很多风险。比在需要前一天投入和部署稍逊一筹的,是AKF合伙公司在思考可扩展性时用的DID(设计-实施-部署)方法。这几个步骤与众所周知的认知阶段一致:思考问题和设计方案,为方案构建系统和编写代码,实际安装或者部署方案。这种方法既不主张也不需要瀑布模型。我们认为敏捷方法遵守这样一个过程,顾名思义,也非常需要人类的参与。你无法为自己所不知道的问题设计一个解决方案,而且如果没有设计,也不可能制造或发布产品。无论哪种开发方法(敏捷、瀑布、混合或其他),我们的每个设计都应该是基于一套定义和指导我们如何设计的架构原则和标准。

设计(D, Design)

我们先从一个概念开始,讨论和设计很明显要比我们在代码中具体实现该设计成本更低。鉴于成本相对低,我们可以未雨绸缪,讨论和草拟好如何扩展平台的设计。例如,我们显然不想部署比现在的生产环境需要高10倍、20倍甚至100倍的容量。但是,讨论和决定如何扩展到这些维度的成本相对来说比较低。然后,在DID方法的D(设计)阶段聚焦在扩展到20倍和无限大之间。因为聘请“思想家”来思考“大问题”,所以我们的智力成本很高。然而,由于我们不编写代码或部署昂贵的系统,所以技术和资产成本较低。通过召集可扩展性大会,把领导者和工程师团队聚集在一起,共同讨论产品的扩展瓶颈,这是在DID设计阶段发现和确定需要扩展部分的一个好办法。表1-1列出了DID的各个阶段。

表1-1 扩展的DID过程

    设  计    实  施    部  署

扩展的目标  20倍~无限  3~20倍 1.5~3倍

智力成本    高  中  低到中

工程成本    低  高  中

资产成本    低  低到中  高到很高

总成本  低到中  中  中

 

实施(I, Implement)

随着时间的推移,我们逐步接近对未来扩展预想的需求,于是开始编写软件实现设计。我们把规模需求的范围缩小到更接近现实,例如当前规模的3~20倍。“规模”在这里指被视为扩展最大瓶颈的系统组件,这部分最需要亟待解决以实现业务目标。可能在有些情况下,把当前的规模扩大100倍或更多倍的成本与扩大20倍没有区别。假设如此,也许我们可以一次完成所需要的改变,而非反复折腾。如果我们要基于用户属性取模,然后把用户分散到多个(N个)数据库系统,那就会是这种情况。我们可能会定义一个可配置的变量Cust_MOD,其取值范围是1(现在)~1?000(5年内)。这种改变的实施成本确实不会随着规模N的变化而变化,所以我们可以把Cust_MOD的取值范围定义得尽可能大。这类改变以工程时间计算的成本很高,以智力时间计算的成本中等,以资产计算的成本较低,因为只打算在第一阶段部署1或2的模数,就没有必要在当前部署比现有规模大100倍的系统。

部署(D, Deploy)

DID的最后阶段是部署(D)。仍以前面的取模为例,我们希望以及时的方式部署系统,没有理由因为资产空闲而稀释股东的价值。如果是一家适度高增长的公司,也许我们可以把最大产能提高到1.5倍;如果是一家超高增长的公司,也许我们可以把最大产能提高到5倍。我们经常引导客户利用云计算来应付突发请求,这样就没有必要把33%的资产放在那里等待突然爆发的用户活动。在部署阶段,资产的成本比较高,其他的成本则在从低到中的范围内。该阶段的总成本往往是最高的,部署相当于现有规模100倍的系统容量将会使许多公司破产。记住,扩展具有弹性,它既可以扩张也可以收缩,我们的解决方案应该认识到这两个方面。因此,灵活性是关键,因为你需要响应客户的请求,随着规模的收缩和扩张,在系统之间调整容量。

对可扩展性的设计和思考的成本相对低,因此应该经常进行。理想情况下,这些活动会产生某些书面文件,可以作为基础文档在需要时快速参考。架构或设计解决方案的整体成本更高,可以留在日后处理,而且实际上也没必要在生产中实现。正如取模的案例,我们可以根据需要进行参数的修改和发布,而不需要购买100倍的容量。最后,遵循这个过程可以在需求发生前安排好设备采购,这也许需要提前6周从主要设备供应商那里订货,或者安排系统管理员跑到本地服务器商店去直接购买。显然,在基础设施即服务(IaaS)的环境下,我们没有必要在需求到来前购买容量,在系统接近所需和接近实时的情况下,可以在部署阶段很容易地把计算资产“旋转”起来。

规则3——三次简化方案

内容:  在设计复杂系统时,从项目的范围、设计和实施角度简化方案。

场景:  当设计复杂系统或产品时,面临着技术和计算资源的限制。

用法:

采用帕累托(Pareto)原则简化范围。

考虑成本优化和可扩展性来简化设计。

依靠其他人的经验来简化部署。

原因:  只聚焦“不过度复杂”,并不能解决需求或历史发展与沿革中的各种问题。

要点:  在产品研发的各个阶段都需要做好简化。

鉴于规则1主要是关于避免超过“有用的”(实际的)需求和降低复杂度,本规则聚焦简化包括从感知需求到实际设计和实施在内的一切。规则1是关于抑制某些事情过于复杂的冲动,而规则3则是关于试图采用本文所述的方法来进一步简化方案。有时候我们告诉客户把这条规则想成“问三个如何”:如何简化方案范围?如何简化方案设计?如何简化方案实施?

如何简化方案范围

对这个简化问题的答案是不断地应用帕累托原则(也叫80-20原则)。收益的80%来自于20%的工作?对此,直接的问题是,“你收入的80%是由哪些20%的功能实现的?做得很少(工作的20%)同时取得显著的效益(价值的80%),解放团队去做其他的工作。如果删除产品中不必要的功能,那么可以做五倍的工作,而且产品并没有那么复杂!减少五分之四的功能,毫无疑问,系统将会减少功能之间的依赖关系,因而可以更高效率和更高本益比地进行扩展。此外,释放出的80%的时间可用于推出新产品,以及投资于思考未来产品可扩展性的需求。

在保持大多数好处的前提下,思考如何减少不必要的功能,在这个问题上,我们并不孤单。以前有个公司叫37signals,现在改名为Basecamp,他们是这个概念的强力支持者,他们曾在《Rework》[6]一书并经常在《You Can Always Do Less》[7]微博上讨论减少工作的必要性和机会。的确,由艾瑞克·里斯提出,并经过马蒂·凯甘传播的“最小化可行产品”的概念,得到了“以最小的努力获得经过验证的最大化客户感知数量”[8]的佐证。这种聚焦“敏捷”的方法允许我们快速发布简单和容易扩展的产品。这样做,我们可以获得更大的产品吞吐量(组织可扩展性),可以花费更多的时间专注于以更具扩展性的方式构建最小产品。因为简化方案覆盖的范围使我们要处理的事情少了,所以可以获得更多的计算能力。如果不相信,你可以回到前面去阅读杰瑞米·金的故事及其总结的经验教训。假如当年eBay团队简化了诸如用户评价子系统的功能范围,V3项目将会以更低的成本、更快的速度,把相对而言同样的价值交付给最终的消费者。

如何简化方案设计

简化后缩窄了的项目范围,可以使后续的设计和实施工作更加容易。简化设计与过度设计的复杂性密切相关。消除复杂性相当于在工作中忽略无关紧要的活动,简化就是寻找一条捷径。规则1中的例子说明了在数据库中只查询需要的数据,select (*) from schema_name.table_name became select (column) from schema_name.table_name。简化设计的方法表明,首先要看在本地,像内存这样的信息共享资源中是否已经有了数据请求。消除复杂性涉及做更少的工作,而简化设计涉及更快和更容易地完成工作。

想象一下,我们想要读取一些源数据,并根据该数据的中间特征符号进行一些计算,然后把特征符号和计算结果捆绑在一起存入对象。在许多情况下,这里的每一个动词都可能被分解成一系列服务。实际上,这种方法看起来类似于现在的流行算法MapReduce。这种方法并不复杂,所以它不违反规则1。但是,假如我们知道要读取的文件很小,而且不需要跨文件来组合特征字符,那么有可能不把它分解为服务,而是直接通过一个简单的应用来实现更有道理。让我们回到前面考勤卡的例子,如果目标是计算个人的工作小时数,那么克隆多个单体应用来从考勤卡队列读取数据并进行计算就有道理了。简单地说,简化设计的步骤要求以易于理解、低成本、高效益和可扩展的方式来完成工作。

如何简化方案实施

最后,我们来讨论一下实施的问题。与规则2的DID扩展过程保持一致,我们把实施定义为解决方案的代码实现。这里我们遇到了一些问题,诸如使用递归或迭代是否更有意义。我们是否应该定义一个特定大小的数组,或者准备好在需要时动态地分配内存?对于解决方案,需要自己研发还是利用开源项目?或者从市场上采购?所有这些问题的答案都指向一个共同的主题:“如何利用其他经验和已经存在的解决方案来简化方案实施?”

因为不可能在每件事上都做到最好,所以我们应该首先寻找被广泛采用的开源或第三方解决方案来满足需求。如果那些都不存在,那么我们应该看看在组织内部是否有人已经准备了可扩展的解决方案来解决问题。在没有专有解决方案的情况下,我们应该再从外部看看是否有人已经描述了一种可以合法复制或模仿的可扩展方案。只有无法在这三项中找到合适的选择情况下,我们才会开始尝试自己创建解决方案。最简单的实施几乎总是那些有过实施经历并通过实践证明了的可扩展方案。

规则4——减少域名解析

内容:  从用户角度减少域名解析次数。

场景:  对性能敏感的所有网页。

用法:  尽量减少下载页面所需的域名解析次数,但要保持与浏览器的并发连接平衡。

原因:  域名解析耗时而且大量解析会影响用户体验。

要点:  减少对象、任务、计算等是加快页面加载速度的好办法,但要考虑好分工。

本书中的许多规则都聚焦在SaaS解决方案的后端架构上,但本规则却让我们考虑客户的浏览器。如果用过任何基于浏览器的调试工具,如火狐的插件Firebug[9],或者Chrome的标准研发人员工具,当加载服务页面时,你会观察到一些有趣的结果。最可能引起你注意的事情之一是网页上那些大小差不多的对象,其下载时间却不同。仔细分析你会看到有些对象在下载开始时有个额外的步骤,那就是域名解析。

域名服务系统(DNS)是互联网或其他任何使用互联网协议(TCP/IP)的网络基础设施中最重要的部分之一。与电话簿类似,它可以把一个网站的域名(www.akfpartners.com)解析为对应的IP地址(184.72.236.173)。域名服务系统由一系列分布式数据库系统构成,其节点被称为域名服务器。层次结构的顶部由根域名服务器组成。每个域至少有一个权威域名服务器用来发布有关该域的信息。

使用多层缓存可以使域名转换为IP地址的过程更快完成,缓存部署在包括浏览器、计算机操作系统、互联网服务提供商等许多层级上。今天的网页可能包含来自于多个互联网域的数以百计甚至数以千计的对象,缓存的使用显著地提高了域名解析的性能。因为每个域都需要进行域名解析,如果把这些解析请求都累加起来,用户就会明显地感觉时间延迟。

在深入讨论减少域名解析之前,我们需要在高层次上先了解一下大多数浏览器是怎么下载页面的。这并不意味着要对浏览器进行深入研究,但是了解这些基础知识将有助于优化应用的性能和提高可扩展性。事实上,几乎所有的网页都是由许多不同的对象组成的(图片、JavaScript、CSS文件等),浏览器就是基于此,通过并发连接拥有同时下载多个对象的能力。浏览器对每个服务器或网关代理的最大持久并发连接数有限制。根据HTTP/1.1 RFC协议[10],这个最大值应该设置为2。但是,现在有许多浏览器都忽略此限制,把最大值设置成6或者更大。我们将基于此功能在下一个规则中讨论如何优化网页的下载时间。我们暂时先聚焦于把网页分解成许多个对象,然后通过多个连接下载。

在网页中,每个不同的域都关联着一个或多个对象,而每个域都需要进行一次域名解析。该解析可能在中间的缓存中完成,也许需要多次往返访问域名服务器。例如,假设我们有一个简单的互联网页面,它包含4个对象:(1)HTML页面本身和指向其他对象的文本和指令,(2)用于布局的CSS文件,(3)用于菜单选项的JavaScript文件,和(4)JPG图像。HTML页面来自于我们公司网站的主域名(akfpartners.com),但是,CSS和JPG则由网站的子域名(static.akfpartners.com)提供,而JavaScript连接至谷歌(ajax.googleapis.com)。在这种情况下,浏览器首先接收请求前往www.akfpartners.com,这需要对akfpartners.com域名进行一次解析。在HTML下载后,浏览器将对其进行分析,结果发现它需要从static.akfpartners.com下载CSS和JPG文件,这需要进行另一次域名解析。最后,页面文件分析后发现还需要从另一个域下载JavaScript文件。取决于浏览器和操作系统中域名服务缓存数据的新鲜程度,域名解析可能从基本上不费什么时间到长达几百毫秒。图1-1对这种情况进行了描述。

作为一条通用的规则,网页上的域名解析的次数越少,网页的下载性能就越好。把所有对象都放在同一个域里会带来问题,前面的讨论已经对最大并发连接数的限制做了暗示。我们将在下一个规则中更详细地探讨这个问题。

 

图1-1 网页对象下载时间

规则5——减少页面目标

内容:  尽可能减少网页上的对象数量。

场景:  对性能敏感的所有网页。

用法:

减少或者合并对象,但要平衡最大并发连接数。

寻找机会减轻对象的重量。

不断测试确保性能的提升。

原因:  对象数量的多少直接影响网页的下载时间。

要点:  对象和服务对象的方法之间的平衡是一门科学,需要不断地测量和调整。这是在客户的易用性、可用性和性能之间的平衡。

正如我们在规则4中所讨论的那样,网页包括许多不同的对象(HTML、CSS、图像、JavaScript等)。浏览器分别独立下载这些对象,而且这些下载经常是并行的。改进网页性能,从而提高可扩展性的最简单方法之一,是减少对象的数量(页面对象需要较少的服务意味着服务器可以服务更多的网页)。大多数页面最大的违规者是图形对象。让我们来看看谷歌的搜索页面(www.google.com),这是极简主义的典范[11]。在写作本书时,谷歌的页面上只有少量对象,包括几个.png文件,还有一些脚本和样式表。在我们非常不科学的实验中,搜索页面的加载时间大约在300毫秒之内。我们的客户有一个在线杂志,其网站的主页有300多个对象,其中200个是图像,平均加载时间超过12秒。这个客户并没有意识到页面性能不好使其损失了有价值的读者。2009年谷歌发布了一份白皮书,声称测试表明搜索延迟如果增加400毫秒将使每日搜索量减少大约0.6%[12]。从那时起,许多客户纷纷向我们表示,用户活跃程度的增加与较快的页面响应相关。

减少页面上的对象数量是提高性能和可扩展性的好方法,但在你动手删除所有的图像之前还有其他的一些事情要考虑。显然首先要把重要信息传达给客户。如果没有图像,网页看起来会像1992年万维网的项目页面,据称那是世界上首个互联网的页面[13]。因为需要图像、JavaScript和CSS文件,你的第二个考虑可能是把所有类似的对象都放进一个文件。这个主意不坏,事实上CSS图像精灵正是这个目的。图像精灵是把一些小图像组合成一个较大的图像,可以通过CSS来单独显示其中的任何一个图像。这样做的好处是图像的请求数量显著减少。回到对谷歌搜索页面的讨论,搜索页面上的两个图片中有一个是精灵,它大约由20多个较小的图像所组成,可以独立控制每个小图像的显示[14]。

到目前为止,我们已经介绍了通过减少页面的对象数量以提高性能和可扩展性的概念,但这必须要与需要图像、CSS和JavaScript来支撑的页面相平衡。接下来我们将介绍如何把这些要素组合成单个对象以减少浏览器渲染页面所必需的不同请求的数量。然而另外一方面,把所有的要素都组合成单个对象,将无法充分利用我们在规则4中讨论的每个服务器的最大并发持久连接数。回顾一下,从单一域名同时下载多个对象是浏览器的能力。如果所有要素都集中在一个对象中,那么浏览器可以同时下载两个或更多对象的能力无法起作用。现在我们需要考虑把这些对象拆分成几个较小的对象,以便同时下载。添加到方程式的最后一个变量是前面提到的服务器并发持久连接,这将把我们带回到规则4有关域名服务的讨论。

浏览器并发连接存在顶限是因为负责提供对象的每个域名服务都存在着资源限制。如果网页上的所有对象都来自单个域名(www.akfpartners.com),那么浏览器的最大并发连接数设置为多少,可以同时下载的对象最多就是多少。如前所述,虽然协议建议这个最大值设置为2,但许多浏览器已将默认值增加为6甚至更高。因此,最好把网页的内容(图片、CSS、JavaScript等)拆分成足够多的对象数,以充分利用大多数浏览器的该项功能。有一种技巧可以真正充分地利用浏览器的这种功能,那就是把不同的网页对象分别存储在不同的子域名上(例如static1.akfpartners.com,static2.akfpartners.com等)。浏览器把这些当成不同的域名对待,并允许每个子域名拥有自己的最大并发连接数。前面提到那个12秒页面加载时间的在线杂志客户就采用了该技巧,用7个子域名把平均加载时间减少到5秒以内。

在本规则和规则4中曾论述过,如果不考虑整个页面的重量和构成该页面对象的重量(字节数),那么关于页面速度的讨论将是不完整的。短小精悍是硬道理。有道是水涨船高,因为不断加大的带宽越来越容易获得,所以人们期待网络页面将更加丰富和更有“份量”。保持页面尽可能轻,以取得理想的效果是明智的。在页面必须很重的情况下,采用Gzip压缩以减轻页面的传输压力,并把页面的整体响应时间减到最少。

不幸的是,理想的网页应该有多少个对象以及多大重量没有绝对的答案。提高网页性能和可扩展性的关键是测试。这需要在必要的内容和功能、对象大小、渲染时间、总下载时间和涉及的域名数量等要素之间做好平衡。假设页面上有100个图像,每个50KB,把它们组合成一个精灵可能就不是一个好主意,因为直到整个4.9MB的对象下载完毕之前,该页面将无法显示任何图像。同样的概念也适用于JavaScript。如果把所有的.js文件合并为一个,那么在整个文件被下载前,页面将无法使用任何JavaScript功能。确保拥有最好的页面速度的唯一方法,就是对不同的组合进行测试,直到找到最合适的那个。

总之,页面上的对象越少性能越好,但是这必须与许多其他因素平衡。这些因素包括必须显示内容的大小,可以组合对象的多少,通过添加域名最大化并发连接的数量,页面的总重量以及惩罚是否有帮助等。虽然许多网站的性能改进技术都提及了这条规则,但是真正的重点是如何通过减少页面上的对象来提高性能和网站的可扩展性。除此之外,还应该考虑许多其他的性能优化技术,包括把CSS文件加载到页面的顶部、把JavaScript文件加载到页面的底部、缩小文件、使用缓存、延迟加载等。

规则6——采用同构网络

内容:  确保交换机和路由器源于同一供应商。

场景:  设计和扩大网络。

用法:

不要混合使用来自不同OEM的交换机和路由器。

购买或者使用开源的其他网络设备(防火墙、负载均衡等)。

原因:  节省的成本与间歇性的互用性及可用性问题相比不值得。

要点:  异构网络设备容易导致可用性和可扩展性问题,选择单一供应商。

作为一家公司,我们信奉技术不可知论,这意味着我们相信如果有正确的架构和部署,几乎任何技术都可以实现扩展。这种不可知论的范围包括从对编程语言的偏好到数据库供应商,直至硬件设备。但是对网络设备(诸如路由器和交换机)需要特别小心。几乎所有的供应商都声称在他们的设备上实现了标准协议(例如,互联网控制消息协议RFC 792[15],路由信息协议RFC 1058[16],边界网关协议RFC 4271[17]),允许来自不同供应商的设备之间进行通信,但是许多供应商也在其设备上实现了专有协议,如思科的增强型内部网关路由协议(EIGRP)。在我们的实践中,以及我们的许多客户那里,我们发现每个供应商对如何实现标准协议的解释经常是不同的。做一个类比,如果你曾经研发过网站页面的用户界面,并在诸如Internet Explorer、Firefox和Chrome等不同的浏览器上做过测试,那么你已经亲身了解了同一标准的不同实现会有多么大的不同。现在,想象一下如果同样的情况发生在网络内部会怎么样?把供应商A的网络设备与供应商B的网络设备混合使用是自找苦吃。

这并不是说我们偏爱某个供应商。只要供应商能提供一个可供参考的标准,其设备被网络流量比你大的客户使用,那么我们就没有什么问题。这个规则不适用于诸如集线器、负载均衡器和防火墙这样的网络设备。我们所关心的同构性网络设备,是指那些能够彼此通信以完成网络流量路由的设备。对于可能包含或不包含的所有其他网络设备,例如,入侵检测系统(IDS)、防火墙、负载均衡器和分布式拒绝服务保护设备(DDOS),我们的建议是选择最好的。对于这些设备,从功能、可靠性、成本和服务角度比较,选择最能满足你需要的供应商。

总结

本章围绕的是简化这个主题。讨论了防止复杂性(规则1),以及从初始需求或历史沿革开始简化产品直到最终实施的每一步(规则3),所得到的产品从技术角度来看容易理解,因此也容易扩展。如果尽早考虑扩展(规则2),即使不实施,我们仍然可以根据业务的需要做好解决方案。规则4和规则5教导我们通过减少对象的数量和减少下载对象所必需的域名解析,来减少浏览器必需要完成的工作。规则6教导我们要保持网络的简单和同构,以减少混合网络设备可能引起的可扩展性和可用性问题的机会。

注释

1.  “eBay Announces Fourth Quarter and Year End 2001 Financial Results,” http://

investor.ebay.com/common/mobile/iphone/releasedetail.cfm?releaseid= 69550&

CompanyID=ebay&mobileid=.

2.  Walmart Annual Report 2001, http://c46b2bcc0db5865f5a76-91c2ff8eba65983a1c33

d367b8503d02.r78.cf2.rackcdn.com/de/18/2cd2cde44b8c8ec84304db7f38ea/2001-annual-report-for-walmart-storesinc_130202938087042153.pdf.

3.  “Amazon.com Announces 4th Quarter Profit 2002,”http://media.corporate-ir.net/

media_files/irol/97/97664/reports/q401.pdf.

4.  “Important Letter from Meg and Pierre,” June 11, 1999, http://pages.ebay.com/outage-letter.html.

5.  Wikipedia, “Overengineering,” http://en.wikipedia.org/wiki/Overengineering.

6.  Jason Fried and David Heinemeier Hansson, Rework (New York: Crown Business,

2010).

7.  37signals, “You Can Always Do Less,” Signal vs. Noise blog, January 14, 2010, http://

37signals.com/svn/posts/2106-you-can-always-do-less.

8.  Wikipedia, “Minimum Viable Product,” http://en.wikipedia.org/wiki/Minimum_viable_product.

9.  To get or install Firebug, go to http://getfirebug.com/.

10. R. Fielding, J. Gettys, J. Mogul, H. Frystyk, L. Masinter, P. Leach, and T. Berners-Lee, Network Working Group Request for Comments 2616, “Hypertext Transfer Protocol— HTTP/1.1,” June 1999, www.ietf.org/rfc/rfc2616.txt.

11. The Official Google Blog, “A Spring Metamorphosis—Google’s New Look,” May 5, 2010, http://googleblog.blogspot.com/2010/05/spring-metamorphosis-googles-new-look.html.

12. Jake Brutlag, “Speed Matters for Google Web Search,” Google, Inc., June 2009, http://services.google.com/fh/files/blogs/google_delayexp.pdf.

13. World Wide Web, www.w3.org/History/19921103-hypertext/hypertext/WWW/TheProject.html.

14. Google.com, www.google.com/images/srpr/nav_logo14.png.

15. J. Postel, Network Working Group Request for Comments 792, “Internet Control Message Protocol,” September 1981, http://tools.ietf.org/html/rfc792.

16. C. Hedrick, Network Working Group Request for Comments 1058, “Routing Information Protocol,” June 1988, http://tools.ietf.org/html/rfc1058.

17. Y. Rekhter, T. Li, and S. Hares, eds., Network Working Group Request for Comments 4271, “A Border Gateway Protocol 4 (BGP-4),” January 2006, http://tools.ietf.org/html/rfc4271.

相关文章
|
21天前
|
存储 Java 应用服务中间件
【分布式技术专题】「架构实践于案例分析」盘点互联网应用服务中常用分布式事务(刚性事务和柔性事务)的原理和方案
【分布式技术专题】「架构实践于案例分析」盘点互联网应用服务中常用分布式事务(刚性事务和柔性事务)的原理和方案
42 0
|
3月前
|
存储 缓存 监控
【分布式】大型互联网项目架构目标
【1月更文挑战第25天】【分布式】大型互联网项目架构目标
|
3月前
|
达摩院 Java Apache
惊动“达摩院”的分布式架构笔记:火于互联网,据说来自于清华
一个星期前,一本Java架构笔记突然在互联网上爆火。因为内容的深度和广度,甚至连阿里最牛的研发中心都被惊动了,而且作者一周后直接被阿里挖走后定级P8,据说作者来自于清华。
|
4月前
|
XML 监控 数据库
互联网架构演进:从传统单体到Service Mesh
互联网架构演进:从传统单体到Service Mesh
68 0
|
5月前
|
设计模式 SQL 安全
淘东电商项目(72) -互联网安全架构设计(责任链模式重构网关流程)
淘东电商项目(72) -互联网安全架构设计(责任链模式重构网关流程)
26 0
|
5月前
|
SQL 安全 API
淘东电商项目(71) -互联网安全架构设计(网关验证AccessToken)
淘东电商项目(71) -互联网安全架构设计(网关验证AccessToken)
25 0
|
5月前
|
SQL 安全 API
淘东电商项目(70) -互联网安全架构设计(搭建开放平台-OAuth)
淘东电商项目(70) -互联网安全架构设计(搭建开放平台-OAuth)
42 0
|
11天前
|
API 数据库 开发者
构建高效可靠的微服务架构:后端开发的新范式
【4月更文挑战第8天】 随着现代软件开发的复杂性日益增加,传统的单体应用架构面临着可扩展性、维护性和敏捷性的挑战。为了解决这些问题,微服务架构应运而生,并迅速成为后端开发领域的一股清流。本文将深入探讨微服务架构的设计原则、实施策略及其带来的优势与挑战,为后端开发者提供一种全新视角,以实现更加灵活、高效和稳定的系统构建。
18 0
|
10天前
|
Kubernetes 安全 Java
构建高效微服务架构:从理论到实践
【4月更文挑战第9天】 在当今快速迭代与竞争激烈的软件市场中,微服务架构以其灵活性、可扩展性及容错性,成为众多企业转型的首选。本文将深入探讨如何从零开始构建一个高效的微服务系统,覆盖从概念理解、设计原则、技术选型到部署维护的各个阶段。通过实际案例分析与最佳实践分享,旨在为后端工程师提供一套全面的微服务构建指南,帮助读者在面对复杂系统设计时能够做出明智的决策,并提升系统的可靠性与维护效率。
|
20天前
|
存储 监控 Kubernetes
探索微服务架构下的系统监控策略
在当今软件开发领域,微服务架构因其灵活性、可扩展性和容错性而日益受到青睐。然而,这种架构的复杂性也为系统监控带来了新的挑战。本文旨在探讨在微服务环境下实现有效系统监控的策略,以及如何利用这些策略来确保系统的健壮性和性能。我们将从监控的关键指标入手,讨论分布式追踪的重要性,并分析不同的监控工具和技术如何协同工作以提供全面的系统视图。