在我们的学习或工作中,都曾遇到过这样的情况:死盯着一个复杂的问题不放,以至于最后丧失了希望。我们应该从何处入手?如何在预定的时间内解决问题?或者说得极端一些,如何在有生之年解决这个问题?要做的事情太多了,这个问题太过棘手,是不能解决的,事实如此,就此罢手吧。游戏结束了…… 等等,不要丧失希望。深呼吸,想想你的高中或者大学数学老师。就像解数学题,把大方程化简成易于计算的小方程一样,如果你遇到的是棘手的大型架构问题,那么可以把大问题分拆成小问题,把小问题分拆成更小的问题,直到这些问题可以轻易解决为止。 我们的观点是,任何大问题,只要分拆方法正确,都不过是一系列有待解决的小问题的集合。这一章介绍的就是如何把大的架构问题分拆成小问题,用较少的工作实现同样的结果。在很多案例中,该方法都减少了(而不是增加了)解决问题所必需的工作量,简化了架构和解决方案,最终得到了更具可扩展性的解决方案或平台。 本书各章所介绍的原则篇幅不一,复杂度也不同。有些普适的原则,适用于一个设计的多个方面。而有些原则只适合特定系统。 1.1 原则1:不要过度设计 目的:防止设计中出现复杂的解决方案。 适用情形:适用于任何项目,所有大型的或复杂的系统和项目都应该采用该原则。 应用方式:让同行来检查解决方案是否好理解,抵制过度设计的强烈欲望。 应用理由:复杂的解决方案实施成本高,而且会产生大量长期成本。 要点:过度复杂的系统会限制扩展能力。简单的系统更容易维护和扩展,且成本更低。 维基百科解释说,过度设计分为两大类[1]。一类是指设计与实现超出了有用需求的产品。出于完整性的考虑,我们只简单地讨论一下这个问题。相对于第二类问题来说,这类问题对可扩展性的影响较小。过度设计的另一类问题指过于复杂的产品。如前所述,我们最关心的是第二类问题对可扩展性的影响。不过,还是先来了解一下第一个问题吧。 要解释过度设计的第一类问题,即超出产品有用需求的问题,就要先搞清楚“有用的”这个术语的含义,这个术语在这里表示的只是“能够使用”。例如,为家庭住房设计一种空调,能够在室外温度为0开时把整个房子的温度加热到300华氏度,这毫无意义,纯属浪费,我们只需要一个能够在室外温度为20华氏度时把房子加热到舒适温度的产品。这种过度设计会产生过度的成本,其中开发的成本会更高,实施该方案的硬件和软件成本也会更高。如果研发这种过度设计系统的时间比研发有用系统的时间更长,还可能拖延产品的发布,对公司造成进一步的影响。成本高,利润就低。研发时间长,收入或收益就会被延迟,所有这些成本都会影响到利益相关者。范围蔓延,或者最初的产品定义和最初的产品发布之间的范围差异,是过度设计的一种表现。 说个更接近我们工作的例子,是开发一个员工打卡系统,这个系统能够处理的员工数量是整个地球上人数的100倍。在这个软件的使用期限内,地球上的人口升至100倍的可能性是微乎其微的,而所有人都为一家公司工作的可能性则更小。我们当然想让构建的系统满足客户需求,但也不想浪费时间来实现和部署远远超出需求的系统。 过度设计的第二类表现是使系统过度复杂,或者用复杂的方式来实现它。简而言之,就是要花费过大的力气去完成一项工作,或者是让用户花费过大的力气去完成一项任务,或者是让程序员花费过大的力气去理解一个功能。让我们来逐一分析过度复杂的系统的这三种情况。 什么是花费过大的力气去完成一项工作呢?现实世界有最简单的例子。假设你让某人去杂货店买东西,你告诉他,店里面的所有商品都拿一个,排队结账时给你打电话。等他打电话给你时,你再告诉他到底想要哪几个,让他从所拿的无数篮商品中选出来,然后把其他商品都倒在地上。你一定会说:“别开玩笑了。”可是,你在自己的代码中用过select (*) schema_name.table_name这样的SQL语句,只是为了从返回的集合中找出自己想要的结果吗(参见原则35)?我们这个杂货店的例子,和上述的select(*)正是异曲同工。在你的代码中,有几个条件语句是处理个别情况的,它们是按照什么顺序执行的?是不是最可能发生的情况最先执行?你是不是经常刚查询完一个结果,又重复查询一次?是不是经常刚显示了一个HTML页面,又重新创建它?这种情况(反复做一项工作)随处可见,却又经常被忽视,我们将专门用一章(第6章)来讨论这个主题。 什么是让一位用户花费过大的力气去完成一项任务呢?答案非常简单。在许多情况下,少就是多。为追求系统的灵活性,我们总是想给它硬加上尽可能多的奇怪功能。但生活的情趣并不总在于多种多样。许多时候,用户只是想无干扰地尽可能快地从A到达B。如果你的市场中有99%的用户不需要把日志文件存成.pdf文件,那么就不要构建一个提示框询问他们是否想把日志文件保存成.pdf文件。如果你的用户想把.wav文件转换成MP3文件,那么他们已经不在乎损失精度了,所以不必再提示他们转换成无损压缩的FLAC文件,那样只会干扰他们。 最后一种情况,就是软件复杂得让其他程序员难以理解。创建复杂的代码让他人难以理解曾经非常流行(还有过比赛)。有时,代码写得复杂,是为了让它比一般程序员所开发的代码运行更快。而更多的情况是,代码的复杂度(就其理解的难度而言)成了程序员才华的象征,或者说是功夫高低的象征。那些开发的代码能让做代码检查的高级开发人员欲苦无泪的人反而颇受推崇。复杂度成了智慧的牢笼,编程极客们会在公司内部争强好胜。对于乐此不疲的人来说,这是很好的比赛,但对于公司和股东来说,则要为一场无人关心的牢笼大赛买单。对于那些仍然沉浸于这场极客盛宴的人,如果不想损害利益相关者的利益,又想真刀真枪地拼一场,那建议你参加国际混淆C代码竞赛,网址是www0.us.ioccc.org/ main.html。 我们都应该努力去写让每个人都能理解的代码。衡量一个伟大程序员的真正标准,是他能够多快把一个复杂的问题简化(见原则3),多快能开发出一个既容易理解,又容易维护的解决方案。容易执行的解决方案意味着一般程序员就可以快速地掌握系统,为它提供支持。容易理解的解决方案则意味着在查找问题时能够更快地发现问题,从而以更快的方式把系统恢复到正常工作状态。容易执行的解决方案可以提高公司和解决方案的可扩展性。 要测试系统是否太复杂,一个很好的方法是让负责解决复杂问题的程序员把他的解决方案陈述给公司内的一组程序员。这组程序员应该代表公司内不同的编码水平,不同的工作年限(加入这一条,是因为可能有些有经验的程序员在公司的工作经验不多)。要通过这一测试,需要这组程序员中的每一位都能够轻松理解该解决方案,能够在无帮助的情况下向他人描述它,而不只是知道它。如果这组程序员中的任何一位不能理解该解决方案,那么就要小组讨论该系统是不是过度复杂了。 过度设计是可扩展性的一个敌人。开发一个超出有用需求的解决方案,既浪费金钱又浪费时间。此外,还可能进一步浪费处理资源,增加扩展成本,限制系统的整体扩展能力(即系统能被扩展到什么程度)。构建过度复杂的解决方案会造成类似的后果。运行吃力的系统会增加成本,限制最终发展规模。让用户用起来吃力的系统,会放慢吸引客户的速度,从而限制业务增长的速度。太复杂以至于难以理解的系统,则会扼制公司的生产力,让你无从增加程序员,或者难以给系统增加功能。 1.2 原则2:设计时就考虑扩展性(D-I-D方法) 目的:提供JIT(Just In Time)扩展性能力。 适用情形:适用于所有项目;此方法是确保可扩展性的成本(资源和时间)最低的方法。 应用方式: 设计(Design)20倍的容量; 实现(Implement)3倍的容量; 部署(Deploy)约1.5倍的容量。 应用理由:D-I-D给产品扩展提供了成本最低的JIT方法。 要点:提前考虑如何扩展解决方案,在实际需要前大概一个月(编程)实现它,或者在客户急需时提前几天实现它,会给开发团队节省很多金钱和时间。 我们公司的重点是帮助客户实现他们的扩展性需求,也许你可以想到,经常会有客户这样问:“我们应该何时对可扩展性进行投资?”不必经过大脑的答复是应该在需要该解决方案的前一天投资(部署)。如果你能够在需要扩展解决方案的前一天部署它,那么就会让投资行为“即时”发生,恰到好处,从而像Dell公司那样按需生产。这样做,会使你的公司效益和股东权益最大。 不过我们要面对的问题是,让投资和部署成为即时的是不可能的,即使可能,如果没有选对时机,也会带来很大的风险。退而求其次,部署扩展性方案的最好方法是AKF Partners的设计—实现—部署(Design-Implement-Deploy)方法,即D-I-D方法。这三个阶段与我们认识事物的三个阶段一致,即针对问题思考和设计解决方案、构建或编写该解决方案、实际地安装或部署它。这种方法不提倡也不需要瀑布模型。我们认为敏捷方法正是遵循的这个过程,体现了人的主观能动性。人们不会为还没有注意到的问题开发解决方案,而一个方案,如果还没有开发出来,也不可能被制造或发布出来。无论开发的方法是什么(敏捷模型、瀑布模型、混合模型等),开发的任何东西都需要基于一套成体系的理论和标准,它们定义并指导着我们该做什么。 1.2.1 设计 首先要说的是,讨论和设计什么东西,比真正用代码实现这一设计的投入少得多。考虑到设计的成本较低,那么在实际需要之前,可以讨论并草拟出能够使平台具有高扩展性的设计。但是,显然我们并不想在生产环境中投入比实际需要多10倍、20倍或者100倍的容量,关于如何将容量扩展到这种水平的讨论相对来说成本小得多。那么,在D-I-D扩展模型的设计(Design)阶段,重点就在于如何将平台的容量扩展到20倍以上,甚至到无穷大。我们的脑力成本是相当高的,因为需要雇佣“大思想家”来考虑“大问题”。但是编程成本和资产成本却是很低的,因为我们并没有编写代码,也没有部署系统。由小组的领导者和程序员参与的讨论扩展性问题的大会,能让人发现在D-I-D方法的设计阶段有哪些地方是必须扩展的。表1-1列出了D-I-D方法的各个部分。 表1-1 处理可扩展性的D-I-D方法 设 计 实 现 部 署 扩展目标 20倍到无穷大 3~20倍 1.5~3倍 脑力成本 高 中 低到中 编程成本 低 高 中 资产成本 低 低到中 高或非常高 总成本 低/中 中 中 1.2.2 实现 随着时间的流逝,我们所预见的对扩展性的需求就会临近,这时就需要在软件中实现(Implement)我们的设计了。我们要根据实际需要,把扩展的范围缩小,例如扩展到当前大小的3~20倍。这里使用“大小”这个词,指的就是被认为是系统扩展的最大瓶颈,因此极需要进行可扩展性修改的元素。也许存在这样的情况,即把系统扩展到当前大小的100倍(或更高)所需的成本和扩展到20倍的成本一样,那么我们还不如一次完成这些修改,而不是分成多次来做。在对用户需求进行模块化,把它们分布(或共享)到多(N)个系统和数据库中时,就可能发生这种情况。我们可以编写一个变量Cust_MOD,随着时间变迁,可以把它配置为1(当前)到1000(5年后)。这种修改带来的编程(或实现)成本不会随着N而变化,所以我们不如选择这种方法。这种修改,带来的是高编程成本、中等的脑力成本(在整个生命周期前期已经讨论过设计了),以及低资产成本,因为如果最初阶段我们只打算部署1倍或者2倍的模块,那么当前就没有必要部署100倍的系统。 1.2.3 部署 D-I-D方法的最后阶段是部署(Deployment)。仍然用上面介绍的模块化示例,我们想用即时方法部署系统,没有任何理由让资产闲置从而减少股东的收益。如果我们是一个较高速增长的公司,那么可以在生产环境中投入1.5倍的峰值容量。如果是个超高速增长的公司,则可以在生产环境中投入5倍的峰值容量。我们常常告诉客户,对于爆炸性的容量,要利用“云”,以免备用33%的资产去防范突然的客户活动增长。在部署阶段,需要高资产成本,而其他成本则属中低水平。这类情况的总体成本趋于最高,部署一个相当于需求的容量100倍的系统,会让很多公司倒闭。记住,扩展性是个灵活的概念,它可以是扩张,也可以是收缩,而我们的解决方案需要两方面都考虑到。因此,灵活性至关重要,你可能需要根据客户需求让解决方案中的不同系统进行扩张或者收缩。 从表1-1可以看到,虽然D-I-D方法的每个阶段都有不同的脑力、编程和资产成本,但整体成本却是基本一致的。关于扩展性的设计和思考成本相对较低,所以应该经常进行。这些活动最好形成文档,以便当有需求时,程序员就能迅速地根据文档编写代码。将设计好的解决方案编写(开发)成代码可以稍后再进行,开发的成本稍高,但是没必要在生产环境中真正实施它。我们可以像上面的模块化示例中所述的,修改少量代码,而无需再购买一个相当于现有容量100倍的系统。最后,采用这种方法,就可以只在有需求时再购买设备,可能是从主要设备供应商那里提前6周购买,或者极其紧急的情况下,让系统管理员去当地的服务器商店采购。 1.3 原则3:把方案一简再简 目的:在设计复杂系统时使用此原则简化方案的范围、设计和实施。 适用情形:在(编程或者计算)资源有限的情况下设计复杂系统或产品时使用。 应用方式: 用帕累托法则简化范围; 从成本效率和可扩展性出发简化设计; 利用他人的经验简化实施。 应用理由:只是着重于“不要复杂”不能解决需求、故事及事件编排和真正的实施带来的各种问题。 要点:在产品开发的各个方面都要简化需求。 原则1讲述的是如何避免超出“有用的”需求,减少复杂度,而本原则讨论的是另一途径,即通过设计和实施,简化你所看到的需求。原则1是要避免把事物变得过度复杂,原则3则是通过将要介绍的方法进一步简化解决方案。有时我们会告诉客户,对于该原则,要问3个“如何”,即如何简化范围,如何简化设计,如何简化实施。 1.3.1 如何简化范围 这个问题的答案是:经常应用帕累托法则(也称为80-20法则)。80%的成果来自于20%的工作吗?对于我们的情况,直接问“80%的收入来自于20%的功能吗”。少做(只做20%的工作)多得(得到80%的收益),你的开发组就能有时间做其他的事情了。如果去除产品中不必要的功能,那么你的工作效率就能提高5倍,产品的复杂度也会大大减小。如果只有1/5的功能,那么毫无疑问,功能之间的依赖关系就会减少,从而扩展起来更容易,扩展成本也会更低。此外,节省下来的80%的时间既可以用于开发新产品,也可以用于提前考虑产品将来的扩展需求。 不止是我们在思考如何在减少不必要功能的同时保留主要功能。37signals中的很多人对此方法都很拥护,他们在自己的书《重来》(Rework[2])和博客“You Can Always Do Less”(你可以做得少一点)[3]中都讨论过减少工作的必要性和所带来的好处。事实上,“最小可行产品”这一概念是由Eric Reis提出,由Marty Cagan传播开来的,它的依据是“用最小的努力得到最有效的客户需求”[4]这一理念。这种敏捷开发方法使我们可以快速地发布简单且容易扩展的产品。如此我们的公司就能够得到更大的产品生产力(公司可扩展性),把时间用于构建少数有更高可扩展性的产品上。通过简化范围,我们将具有更高的计算能力,同时工作得更少。 1.3.2 如何简化设计 范围缩小后,简化实施的工作就变得容易了。简化设计与过度设计的复杂度紧密相关。减少复杂度是删除工作中不必要的部分,而简化则是要找到一条捷径。在原则1中举过一个例子,把selelct(*) from schema_name.table_name改为select (column) from schema_ name.table_name,只查询你需要的结果。简化设计的方法则建议我们首先看看要查询的信息是不是已经存在于本地资源(例如本地内存)中了。减少复杂度是为了减少工作量,而简化设计是为了工作得更快更容易。 假设我们要读一些源数据,对这些源数据中的中间令牌进行计算,然后把这些令牌绑定起来。在许多情况下,这个假设中的每个动作都可以被分解成一系列服务。事实上,这种方法和流行的Map-Reduce算法采用的方法类似。这种方法并不过度复杂,所以不违背原则1。但是如果我们知道要读的文件很小,不需要跨文件绑定令牌,那么开发一个简单的整体式的应用,比把它分解为多个服务更合理。再看看前面的打卡系统的例子,如果目的只是计算每个员工的工作时长,那么用多个克隆的整体式应用读打卡系统的队列并执行计算则更合理。简而言之,简化设计这一步会要求我们用一种容易理解、低成本、可扩展的方式来完成工作。 1.3.3 如何简化实施 最后,来看看实施的问题。与原则2(实现可扩展性的D-I-D方法)一致,这里的实施定义为解决方案的实际编码工作。此时要面临的问题是用递归还是循环更合理?应该定义一个固定大小的数组,还是应该在需要时动态分配内存?应该开发一个解决方案,还是应该采用开源的解决方案,还是应该购买一个解决方案?这些问题有一个相同的考量,即如何利用他人的经验和现有的解决方案来简化我们的实施。 考虑到我们不可能事事精通,所以首先应该查找能满足我们需求的、被广泛采用的开源解决方案或者第三方解决方案。如果没有这样的方案,应该在公司内部询问是否有人已经开发了能解决该问题的可扩展方案。如果没有专用的解决方案,那么应该再从外部寻找,是否有人描述过解决该问题的可扩展方法,而且我们可以合法地复制或模仿?只有当这三种条件都不成立时,才应该尝试自己解决该问题。最简单的实施方法,都是已经被实施过且被证明是可扩展的方法。 1.4 原则4:减少DNS查找 目的:从用户角度减少DNS查找。 适用情形:所有性能至关重要的Web页面。 应用方式:减少下载页面所需的DNS查找,不过要权衡考虑浏览器对同时连接的限制。 应用理由:DNS查找需要花费大量的时间,大量的DNS查找会影响用户体验。 要点:减少对象、任务、计算等都可以加速页面载入,但同时也要考虑工作分解。 至此可见,减少就是提高性能和扩展性的代名词。虽然许多原则针对的是软件即服务(SaaS)的架构,但这个原则考虑的则是客户的浏览器。如果采用浏览器端的调试工具,如Mozilla Firefox的插件Firebug[5],那么在载入应用中的一个页面时,你就会发现有趣的结果。最惹人注意的结果之一,是页面中大小相近的对象下载的时长却不一样。进一步观察,你会发现在开始下载对象之前,都有一个额外的步骤:DNS查找。 域名服务器(DNS)是因特网或其他采用TCP/IP协议的网络最重要的基础设施之一。它可以把域名(如www.akfpartners.com)翻译成IP地址(如184.72.236.173),因此常常被比作电话簿。DNS是通过一个分布式数据库系统维护的,该数据库系统的节点是域名服务器。这种分层体系的最顶层是根域名服务器。每个域至少有一个权威的DNS服务器,用于发布关于该域的信息。 采用多层级缓存的方法可以加速把域名翻译为IP地址,缓存可在浏览器、操作系统、因特网服务提供商等各级进行。不过,在现实世界中,页面上都有成百上千的对象,许多对象来自于不同的域,下载每个对象的时间虽然微不足道,但是累加起来形成的时间差就会引起客户注意了。 我们深入讨论如何减少DNS查询之前,首先应该对大多数浏览器如何下载页面有更多的了解。这并不是说要深入研究浏览器,但是理解基础原理有助于你优化应用的性能和扩展性。几乎所有的Web页面都是由许多不同的对象(图像、JavaScript脚本、CSS脚本等)构成的,浏览器利用了这一点,可以同时连接下载多个对象。浏览器限制了对每个服务器或代理的最大同时持续连接数。根据HTTP/1.1 RFC[6]规定,这个最大连接数应该设置为2。但是许多浏览器都会忽略这个RFC,把最大连接数设置为6或者更大。在下一个原则中,我们将介绍如何利用这个功能优化页面的下载时间。现在,让我们把重点放在可以分成多个对象,并通过多个连接下载它们的Web页面。 Web页面上的一个或多个对象可能属于不同的域,每个域都需要在缓存或DNS域名服务器中进行DNS查找。例如,假设一个简单的Web页面具有以下4个对象:1)包含文本和其他对象相关指令的HTML页面本身;2)设置布局的CSS文件;3)设置菜单项的JavaScript文件; 4)JPG图像。HTML文件来自于我们的域(akfpartners.com),但CSS文件和JPG文件来自于子域(static.akfpartners.com),JavaScript文件则是链接到Google(ajax.googleapis.com)的。在这个例子中,浏览器首先收到访问www.akfpartners.com页面的请求,这就需要对域akfpartners.com进行DNS查找。当把HTML文件下载到浏览器中后,浏览器解析发现需要从static.akfpartners.com下载CSS和JPG文件,这就需要另一次DNS查找。最后,通过解析发现还需要从另外一个域下载一个外部的JavaScript文件。依靠浏览器、操作系统等对DNS缓存的刷新,这些查找花费的时间最多只有几百毫秒。图1-1是这一过程的图形化展示。 图1-1 对象下载时间 一个通用的原则是,页面上的DNS查找越少下载页面的性能越高。但是,把所有对象都放在一个域中也有不利的一面,我们在前面关于最大同时连接数的讨论中就暗示过这一点。这一主题将在下一个原则中详细讨论。 1.5 原则5:尽可能减少对象 目的:尽可能减少页面上的对象。 适用情形:所有性能至关重要的Web页面。 应用方式: 减少或合并对象,但要与最大同时连接数进行平衡; 测试修改过的页面,确保性能提高了。 应用理由:对象数量会影响下载时间。 要点:对象和提供它们的方法之间的平衡是一门学问,需要适时调整。这是客户的可用性、有用性和性能之间的平衡。 Web页面是由各种各样的对象(HTML、CSS、图像、JavaScript等)构成的,这就使得浏览器能够独立甚至并行地下载它们。提高Web页面的性能,从而提高扩展性(为一个页面提供的对象少,就意味着服务器能够多服务几个页面)的最简单方法之一就是减少页面上的对象。对大多数页面来说,造成性能问题的罪魁祸首都是图形化对象,如照片和图像。作为示例,让我们来看看Google的检索页面(www.google.com),如他们自己所述,该页面本质上就是极简的[7]。在编写本书时,Google的检索页面上只有5个对象:一个HTML文件、两个图像和两个JavaScript文件。我做了一个不算很科学的实验,载入该检索页面的时间约为300毫秒。再看一看与我们合作的一个在线杂志业,我们这个客户的主页有200多个对象,其中145个是图像,平均需要花费11秒以上才能载入该页面。这个客户并没有意识到,页面性能低会导致有价值的读者流失。Google于2009年发布过一个白皮书,声称测试表明检索延迟增加400毫秒,就会使每天的检索量减少将近0.6%。[8] 减少页面上的对象是提高性能和可扩展性的好方法,但是在你急于删除所有图像前,还需要考虑几点。首先,显然要考虑你想传达给客户的重要信息。如果没有图像,你的页面看起来就会像1992W3项目的页面,该页面据说是史上最早的一个Web页面。[9]由于你需要图像、JavaScript脚本和CSS文件,那么第二点需要考虑的就是把相似的对象合并到一个文件中。这个主意并不坏,事实上,还有一个专门的技巧,即CSS图片精灵。所谓图片精灵,就是一组小图像的集合,这些小图像被组合成一个较大的图像,使用CSS处理这幅图像就可以只显示其中一幅小图像。这样做的好处就是大大减少了所请求的图像数量。返回Google检索页面,该页面上的两个图像之一,就是一个图片精灵,它是由二十多个能够独立显示的小图像构成的。[10] 至此,我们已经讨论过,虽然减少页面上的对象可以提高性能和可扩展性,但是这种做法必须权衡考虑页面对现代外观的需求(图像、CSS文件和JavaScript)。接下来,我们讨论如何把这些对象组合成一个对象,从而减少浏览器生成页面所必需的请求。不过,这就有另外一点需要权衡,即把所有对象都组合到一个对象中,就不能利用我们在原则3中讨论的每个服务器的最大同时持续连接数了。简单重述一下,最大同时连接数指浏览器从一个域中同时下载多个对象的数量。如果所有内容都放在一个对象中,那么浏览器这种能同时下载两三个对象的能力就毫无用武之地了。现在,我们需要考虑把这些对象分布到几个小对象中,这样就能够同时下载它们。这个方程中的最后一个变量,就是关于上面介绍的每个服务器的同时持续连接数,而这又让我们回到了原则4中对DNS的讨论。 浏览器的同时连接功能是对提供对象的域的限制。如果页面上的所有对象都来自于一个域(www.akfpartners.com),那么浏览器设置的最大连接数就是最多可以同时下载的对象数。如前所述,这个最大数建议设为2,不过许多浏览器默认设置为6或者更高。因此,最好把你的内容(图像、CSS文件、JavaScript文件等)分成足够多的对象,以便充分利用浏览器的这一功能。能够真正利用浏览器这一功能的一个技巧是从不同的子域提供不同的对象(例如,static1.akfpartners.com、static2.akfpartners.com等)。浏览器会分别考虑这些域,能够并发地让每个域都达到最大连接数。前面我们提到过的在线杂志的客户,对载入时间11秒的页面使用了该技术,把对象分布到7个子域中,从而把平均载入时间减少到了5秒以下。 遗憾的是,对于理想的对象大小或应该采用多少个子域,没有绝对的答案。提高性能和可扩展性的关键还是测试页面。在必要的内容和功能、对象大小、显示时间、总下载时间、域等因素之间,都要进行平衡。如果页面上有100个对象,每个大小50 KB,那么把它们组合到一个图片精灵中可能不是好方法,因为在没有把4.9 MB的对象下载完之前,任何图像都显示不出来。如果把所有.js文件都组合到一个文件中,那么在没有把整个文件下载完之前,任何JavaScript功能都不能用。究竟哪种选择才是最好的,只有使用各种ISP连接速度在各种浏览器上测试页面之后才能确切知道。 总之,页面上的对象越少,网页性能就越好,但是必须与其他因素平衡。这些因素包括必须显示多少内容,多少对象可以组合起来,如何通过增加域最大限度地利用同时连接,页面总体大小以及限制对象数量是否有帮助等。虽然本原则涉及很多提高Web站点性能的技术,但真正的重点是如何通过减少页面上的对象、提高页面性能来提高站点的可扩展性。此外,还有很多优化性能的技术可以考虑,包括在页面顶部载入CSS文件、在底部载入JavaScript文件、减小文件、利用缓存、延迟加载等。 1.6 原则6:使用同一品牌的网络设备 目的:不要混用供应商的网络设备。 适用情形:在设计和扩展网络时使用。 应用方式: 不要混用不同供应商的网络设备(交换机和路由器); 其他网络设备(防火墙、负载均衡器等)要买品牌最好的。 应用理由:不应该为了省点钱,就去应付时不时出现的互操作性和可用性问题。 要点:不同品牌的网络设备可能会造成可用性和扩展性问题。最好只选择一个供应商。 我们都是技术中立的,即我们相信如果架构正确,部署正确,那么几乎任何技术都是可扩展的。这种中立涉及程序设计语言,以及数据库的供应商和硬件。其中一个忠告是关于路由器和交换机这样的网络设备的。虽然几乎所有的供应商都声称自己实现的是标准协议(如互联网控制消息协议RFC 792[11],路由信息协议RFC 1058[12],边界网关协议RFC 4271[13]),能够使不同供应商的设备之间互相通信,但还是有不少供应商采用的是专有协议,如思科的增强的内部网关路由选择协议(EIGRP)。根据我们以及客户的实践经验,每个供应商对如何实现某个标准的理解通常是不同的。就好比,如果你曾经为Web页面开发过用户界面,并在不同的浏览器(如IE、Firefox和Chrome)中测试过它,那么你就能发现标准实现起来会有哪些不同了。同样的道理,在自己的网络中混用了供应商A和供应商B的网络设备,那就是在自找麻烦。 这并不是说我们更倾向于某一家供应商,并非如此。只要该供应商的产品合格,被比你们大的客户(就网络流量而言)采用,那就没问题。该原则并不适用于集线器、负载均衡器和防火墙这样的网络设备。我们认为必须使用同一品牌网络设备的只是那些必须进行路由通信的设备。对于其他那些可有可无的网络设备,如入侵侦测系统(IDS)、防火墙、负载均衡器、分布式拒绝服务攻击防护设备等,我们推荐采用最好的品牌。对于这些设备,可以选择那些在功能、可靠性、成本和服务方面能够最好满足你的需求的供应商。 1.7 小结 本章讲述的是把事情变得更简单。防止出现复杂的情况(又叫做过度设计——原则1),从最初需求或用户故事到最终实现,简化开发的每一步(原则3),从而得到设计上容易理解因而容易扩展的产品。尽早开始考虑扩展性问题,即使不实施它,也要有解决方案,以备不时之需。原则4和原则5教会了我们如何减少页面上的对象以及下载这些对象所需的DNS查找,从而减少浏览器的工作量。原则6教会了我们如何让网络保持单一性,从而减少混用网络设备造成的扩展问题和可用性问题。 参考资料 [1] Wikipedia, “Overengineering,” http://en.wikipedia.org/wiki/Overengineering. [2] Jason Fried and David Heinemeier Hansson, Rework (New York:Crown Business, 2010). [3] 37Signals, “You Can Always Do Less,”Signal vs. Noise blog,January 14, 2010, http://37signals.com/svn/posts/2106-you-canalways-do-less. [4] Wikipedia, “Minimum Viable Product,” http://en.wikipedia.org/wiki/Minimum_ viable_product. [5] 欲获取和安装Firebug请访问http://getfirebug.com/. [6] 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. [7] The Official Google Blog, “A Spring Metamorphosis—Google’s NewLook,” May 5, 2010, http://googleblog.blogspot.com/2010/05/spring-metamorphosis-googles-new- look.html. [8] Jake Brutlag, “Speed Matters for Google Web Search,” Google, Inc.,June 2009, http://code.google.com/speed/files/delayexp.pdf. [9] World Wide Web, www.w3.org/History/19921103-hypertext/hypertext/WWW/ TheProject.html. [10] Google.com, www.google.com/images/srpr/nav_logo14.png. [11] J. Postel, Network Working Group Request for Comments 792,“Internet Control Message Protocol,” September 1981, http://tools.ietf.org/html/rfc792. [12] C. Hedrick, Network Working Group Request for Comments 1058,“Routing In- formation Protocol,” June 1988, http://tools.ietf.org/html/rfc1058. [13] Y. Rekhter, T. Li, and S. Hares, eds., Network Working Group Requestfor Comments 4271, “A Border Gateway Protocol 4 (BGP-4), January2006, http://tools.ietf.org/ html/rfc4271.
高扩展性网站的50条原则——第一章:化简方程
书名: 高扩展性网站的50条原则
作者:
出版社: 人民邮电出版社
原作名: Scalability Rules: 50 Principles for
译者: 杨海玲 | 张欣
出版年: 2012-6-3
页数: 238
定价: 35.00元
装帧: 平装
丛书: 图灵程序设计丛书
ISBN: 9787115275721