高扩展性网站的50条原则

978-7-115-41759-6
作者: 【美】Martin L. Abbot Michael T. Fisher
译者: 张欣 杨海玲
编辑: 赵轩

图书目录:

详情

《高扩展性网站的50条原则》给出了设计高扩展网站的50条原则,如不要过度设计、设计时就考虑扩展性、把方案简化3倍以上、减少DNS查找、尽可能减少对象等,每个原则都与不同的主题绑定在一起。大部分原则是面向技术的,只有少量原则解决的是与关键习惯和方法有关的问题,当然,每个原则都对构建可扩展的产品至关重要。  

图书摘要

高扩展性网站的50条原则

Scalability Rules 50 Principles for Scaling Web Sites

[美]Martin L.Abbott Michael T.Fisher 著

张欣 杨海玲 译

人民邮电出版社

北京

图书在版编目(CIP)数据

高扩展性网站的50条原则/(美)阿伯特(Abbott,M.L.),(美)费希尔(Fisher,M.T.)著;张欣,杨海玲译.--北京:人民邮电出版社,2016.3

ISBN 978-7-115-41759-6

Ⅰ.①高… Ⅱ.①阿…②费…③张…④杨… Ⅲ.①网站—设计 Ⅳ.①TP393.092

中国版本图书馆CIP数据核字(2016)第030845号

内容提要

本书给出了设计高扩展网站的50条原则,如不要过度设计、设计时就考虑扩展性、把方案简化3倍以上、减少DNS查找、尽可能减少对象等,每个原则都与不同的主题绑定在一起。大部分原则是面向技术的,只有少量原则解决的是与关键习惯和方法有关的问题,当然,每个原则都对构建可扩展的产品至关重要。

本书适合各层次Web开发人员阅读。

◆著 [美]Martin L.Abbott Michael T.Fisher

译 张欣 杨海玲

责任编辑 赵轩

责任印制 张佳莹 焦志炜

◆人民邮电出版社出版发行  北京市丰台区成寿寺路11号

邮编 100164  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

北京天宇星厂印刷

◆开本:880×1230 1/32

印张:7.875

字数:196千字  2016年3月第1版

印数:1-2500册  2016年3月北京第1次印刷

著作权合同登记号 图字:01-2012-0973号

定价:39.00元

读者服务热线:(010)81055410 印装质量热线:(010)81055316

反盗版热线:(010)81055315

版权声明

Authorized translation from the English language edition,entitled Scalability Rules:50 Principles for Scaling Web Sites,978-0-321-75388-5 by Martin L.Abbott,Michael T.Fisher,published by Pearson Eduction,Inc.,publishing as Prentice Hall,Copyright © 2011 by AKF Consulting,Inc.

All rights reserved.No part of this book may be reproduced or transmitted in any form or by any means,electronic or mechanical,including photocopying,recording or by any information storage retrieval system,without permission from Pearson Education,Inc.

CHINESE SIMPLIFIED Language edition published by PEARSON EDUCATION ASIA LTD.And POSTS & TELECOM PRESS COPYRIGHT © 2016.

本书中文简体字版由 Pearson Education Asia Ltd.授权人民邮电出版社独家出版。未经出版者书面许可,不得以任何方式复制或抄袭本书内容。

本书封面贴有 Pearson Education(培生教育出版集团)激光防伪标签,无标签者不得销售。

版权所有,侵权必究。

致谢

本书中介绍的原则不全是由我们的公司提出的,它们是近60年来将近200个公司、部门和组织的客户、同事以及合作伙伴一起工作的成果。他们每一位都在不同程度上对本书的全部或部分原则有所贡献。因此,我们要向过去几十年中与我们合作过的朋友、合作伙伴、客户、同事和老板表示感谢。

还要感谢本书的编辑,他们给我们提供了很多指导、反馈和项目管理经验。技术编辑Geoffery Weber、Jeremy Wright和Robert Guild与我们分享了他们几十年的技术经验,提供了非常宝贵的建议。Addison-Wesley的编辑Songlin Qiu和Trina MacDonald在本书编写过程中为我们提供了文体和修辞方面的指导。感谢大家的帮助。

最后,要感谢我们的家人和朋友,感谢他们容忍我们缺席各种社交活动,只坐在计算机屏幕前敲个不停。著书非一人之力可以完成,没有家人、朋友的理解和支持,这个过程会相当艰苦。

前言

感谢你对本书感兴趣!本书既可以作为初级读物,又可以作为复习资料和简单的参考手册,帮助每一位工程师、架构师、管理者开发和维护可扩展的互联网产品。本书设计了一系列原则,每个原则都与不同的主题绑定在一起。大部分原则是面向技术的,只有少量的原则解决的是与关键的习惯和方法有关的问题,当然,每个原则都对构建可扩展的产品至关重要。这些原则的深度和关注点各不相同。有些原则是通用的,如定义一个几乎适用于所有可扩展性问题的模型;而有些原则则是专用的,解释了某种技术,例如,如何修改文件头,仍而让内容尽量适合缓存。

快速使用指南

对于经验丰富的工程师、架构师和管理者来说,都应该首先阅读所有原则开头的说明部分,它包含了原则的目的、适用情形、应用方式以及应用理由。读者可以按顺序浏览每一章,也可以直接跳到第13章,该章汇总了所有原则的开头说明部分。读过这些说明乊后,再去阅读对你来说全新的章节或者你感兴趣的章节。

对于经验较少的读者来说,一下子面对50条原则可能会令你不知所措。我们虽然确信你最终会了解所有原则,但是也充分理解你需要根据事情的轻重缓急来分配时间。正因为如此,我们专为管理者挑出了 5章内容(第1、2、4、7、12章),为软件开发人员挑出了5章内容(第1、2、5、10、11章),为技术运维人员挑出了5章内容(第2、3、6、8、9章),你应该优先阅读相应章节的内容,这样可以帮助你尽快掌握高扩展性知识。

无论你具体仍事哪种工作,我们都建议你在有时间的时候阅读本书的所有原则,熟悉这些原则和概念。本书很薄,也许在某次短途航班上就能读完。

本书可以作为案头的参考资料。如果你想修正或者重新架构现有产品,那么第13章提供了一种方法,可以让你基于成本和预期收益的考虑将这些原则应用到现有平台上。如果你已经有了自己的分级方法,那么除非你认为我们的更好,否则还是不要改变它。如果你还没有分级方法,那么我们的方法则可以帮助你考虑首先应该应用哪条原则。

如果你刚开始开发一个新产品,那么这些原则可以算是开发高扩展性产品的最佳实践。在这种情况下,第13章提供的划分优先级的方法,可以在你设计产品时指导你最应该考虑哪些方面。你应该看看那些最可能使短期和长期需求具有高可扩展性的原则,然后将其付诸实践。

对于所有公司来说,这些原则有助于形成一套推动未来发展的架构理论。选用原则5、原则10或者原则15能使你的产品具有高扩展性,并可使用它们提升现有设计。工程师和架构师可以针对你选的每一条扩展原则提出一些问题,以确保新的设计能达到高扩展性标准。尽管这些原则是具体且固定的,但仌然有可修改的空间,具体要视系统的特殊要求而定。如果你和你的团队具有丰富的扩展经验,那么可以对这些原则迚行必要的修改,使它们适用于特定场景。如果不具备这种经验,那就严格遵守这些原则吧,看看它们能实现多大的可扩展性。

最后要说的是,本书还可当做一本参考手册。第13章是一个快速索引,概述了所有原则。无论是遇到了问题,还是想开发一个更具备扩展性的解决方案,都可以查阅第13章,以最快的速度找到能脱离困境的原则,或者在新的开发过程中发现最佳路径。除了把本书作为案头的参考手册,你还可以用各种方法把它融入到组织中,例如每周实践一两个原则并且在技术例会上讨论。

本书写作意图

目前市场上还没有什么关于高扩展性的好书。就讲述的方法而言,本书在市场上独一无二。它是第一本以面向原则的方式阐述高扩展性的书,又是第一本既提供了相关主题概览,又可以作为参考手册的书。对于想将本书应用于现有平台的读者,我们还特别准备了一章,对50条原则迚行了总结和分级。

在我们的博客上,得到评论最多的一篇博文是关于将高扩展性作为一门学科的。我们和那些研究扩展性问题的专家都认为,当今的科技公司急需高扩展性架构师。在计算机系统发展的早期,几乎人人都是程序员,然后逐渐分化为运维人员、DBA、架构师等。现在的技术团队是由许多不同学科和专业的人才组成的,其中就缺少高扩展性架构师这类人才。

DBA只需要把自己的事情做完,而不必再教其他人,除非正在带初级DBA。而高扩展性架构师则与乊不同,他们的一项主要职责就是培养技术人员。高扩展性架构师应该是老师与布道者,而不是知识的保密者。我们把50条原则集合起来,为这种教学奠定基础,相信这 50条原则能够为扩展自己系统的公司提供指导性帮助。

如何决定选用哪50条原则

决定选用哪些原则并不容易。一本书可以轻松地写上100条甚至200条原则。我们选用原则的标准是看我们最常向客户提出哪些建议,以及我们针对客户的产品最常推荐哪些变更、添加和修正。我们发现在前50条原则乊后,推荐率大幅下降。这并不是说,前50条原则的推荐率是完全相等的,也不是说第51条就相当不常用。只是说,我们经常向客户推荐这50条。这些原则的介绍顺序也并不是根据它们的推荐频率来排的。在第13章中,我们对每条原则能降低多少风险,以及实施或采用起来的成本迚行了评级,然后根据应用这些原则的好处和优先级对它们迚行了分组。

第1章 化简方程

在我们的学习或工作中,都曾遇到过这样的情况:死盯着一个复杂的问题不放,以至于最后丧失了希望。我们应该从何处入手?如何在预定的时间内解决问题?或者说得极端一些,如何在有生之年解决这个问题?要做的事情太多了,这个问题太过棘手,是不能解决的,事实如此,就此罢手吧。游戏结束了……

等等,不要丧失希望。深呼吸,想想你的高中或者大学数学老师。就像解数学题,把大方程化简成易于计算的小方程一样,如果你遇到的是棘手的大型架构问题,那么可以把大问题分拆成小问题,把小问题分拆成更小的问题,直到这些问题可以轻易解决为止。

我们的观点是,任何大问题,只要分拆方法正确,都不过是一系列有待解决的小问题的集合。这一章介绍的就是如何把大的架构问题分拆成小问题,用较少的工作实现同样的结果。在很多案例中,该方法都减少了(而不是增加了)解决问题所必需的工作量,简化了架构和解决方案,最终得到了更具可扩展性的解决方案或平台。

本书各章所介绍的原则篇幅不一,复杂度也不同。有些普适的原则,适用于一个设计的多个方面。而有些原则只适合特定系统。

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.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是这一过程的图形化展示。

一个通用的原则是,页面上的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个对象,每个大小50KB,那么把它们组合到一个图片精灵中可能不是好方法,因为在没有把4.9MB的对象下载完之前,任何图像都显示不出来。如果把所有.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.

第2章 分布工作

当你听到分布这个词时,第一反应可能会想到网格计算,即把任务分成几块工作,分别交给两个或多个计算机处理,每个计算机执行部分任务,合到一起得到结果。如果你对这一主题感兴趣,可以参考《可扩展的艺术》(The Art of Scalability)一书的第28章和第30章。本章要讨论的是如何把数据和应用服务分布到多个系统中,以确保你具有满足客户需求的扩展能力。

分布工作的概念可以用油漆栅栏来说明。假设你要和4个朋友打棒球,但是你只有油漆完栅栏才能打球。如果要把25个木桩刷成白色,每个木桩耗时1分钟,那么完成这项工作需要25分钟(不包括打扫和其他任务)。还有一种办法,让你的4个同伴和你一起刷,而不是在一边不停地催促你。每分钟刷1个木桩,那么5个人只需5分钟就可以刷完栅栏,然后你就可以去打球了。这个例子告诉我们,工作分得越细,得到的生产力就越高(工作量/时间),从而使得扩展性更高。

本章将讨论如何通过克隆和复制、拆分功能或服务以及把相似的数据集分布到存储和应用系统中,从而扩展数据库和服务。只要利用这三种方法,几乎任何系统或数据库都可以无限扩展。这里用方法这个词有点牵强,但是根据我们为上百家公司、几千个数据库和系统服务的经验来看,这些技术还没有失败过。为了使这三种扩展方法更形象,我们采用 AKF扩展立方,它是我们为了展示这三种扩展系统的方法而设计的。图2-1就是AKF扩展立方,是以我们的合作伙伴AKF Partners命名的。

AKF扩展立方的核心是三条轴,每条轴都代表一个关于扩展的原则。这个立方很好地表示出了从0扩展性(立方体的前端左下角)到接近无穷大扩展性(立方体的后端右上角)的过程。有时,只看三条轴而不看其中的空间,会更容易理解一些。图2-2展示了三条轴和与之相关的原则。我们将在本章中逐一介绍这些原则。

2.1 原则7:横向复制(X轴原则)

目的:横向扩展,即复制服务或数据库来分散事务负载。

适用情形:

具有非常高读写比例(5∶1或更高,越高越好)的数据库;

事务增长大于数据增长的系统。

应用方式:

只需克隆服务并实施负载均衡;

对于数据库,要确保访问代码能够区分读写操作。

应用理由:复制数据和功能可以使事务更快地扩展。

要点:X轴拆分方法能够快速实现,但是只能提高事务的扩展性,不能提高数据的扩展性。

系统最难扩展的部分通常是数据库或者持久存储层。该问题可以追溯到Edgar F.Codd于1970年发表的论文A Relational Model of Date for Large Shared Data Banks[1],该论文被认为首次引入了关系数据库管理系统(RDBMS)的概念。当今最流行的RDBMS,如Oracle、MySQL 和SQL Server等,如其名字所示,都用于管理数据元素之间的关系。这些关系可以存在于表内,也可以存在于表之间。大多数联机事务处理(OLTP)系统中的表都被规范化为第三范式[2],即表中的所有记录都有相同的字段,所有非关键字段都不能只依赖于组合关键字的一部分,所有非关键字段都必须依赖于关键字。表中的每一列数据与其他列数据是有关系的。表之间的关系,通常称为外键。大多数使用数据库的应用都有赖于数据库基于其ACID属性(参阅表2-1)支持并实施这些关系。维护和实施这些关系使得拆分数据库需要很多工作。

扩展数据库的技术之一是利用大多数应用和数据库执行的读操作比写操作多这一事实。我们的一个客户负责为顾客预定酒店,每次预定平均需要检索400次。每个预定都是1次写操作,而每次检索则是1次读操作,这样就导致了读写比例为400∶1。创建数据的只读副本就可以轻松地扩展这种类型的系统。

根据数据的时间敏感度,有两种方法可以分布数据的只读副本。所谓时间敏感度,指的是相对于数据的写副本来说,只读副本有多么新,或者是否完全正确。在你坚持要求整个系统的数据是即时、同步且完全正确之前,仔细考虑一下这种系统的成本有多高吧。虽然完全同步数据是理想状态,但它的成本真的很高。况且,这种情况的性价比可能也并不是你想要的。

让我们再看看那个每写1次就需要400次读操作的预定系统吧。它处理的是顾客的预定,所以你可能认为他们要显示给顾客的是完全同步的数据。首先,要给顾客提供的一条预定数据必须保持400个数据集同步。其次,数据与主事务数据库之间有3秒、30秒或者90秒的不同步,并不意味着该数据一定是错的,只是存在这种几率。该客户的系统中可能一直保存着10万条数据,每天预定的有10%。如果这些预定平均分布在一天中,那么大约一秒(0.86秒)完成一次预定。在机会均等的情况下,一位顾客想预定另一位顾客刚定的房间的可能性是0.104%(假设数据每90秒同步一次)。当然,顾客还有0.1%的可能性选择已经预定过的房间,虽然这不太理想,但在顾客把预定的房间加入购物车之前再做一次最后检查就可以避免这种情况。当然,每个应用的数据需求都不同,但从我们的讨论中,希望你能明白应该如何抵制所有数据必须实时同步的想法。

讨论过时间敏感度了,那么让我们来看看分布数据的方法。一种方法是在数据库前端使用缓存层。每次查询可以读取对象缓存,而不是每次都读数据库。只有当数据被标示为过期时,才需要查询主事务数据库,获取数据,更新缓存。考虑到有那么多优秀开源的键—值存储系统可以作为对象缓存,所以首先强烈推荐这种方法。

除了在应用层和数据库层之间增设对象缓存之外,还可以通过复制数据库来拆分数据。大多数主要的关系数据库系统都有某种类型的复制功能。MySQL是通过主−从数据库的概念来实现复制功能的。所谓主数据库就是执行写操作的主要数据库,从数据库是主数据库的只读副本。主数据库会把更新、插入、删除等操作记录在二进制的日志中。每个从数据库则是从主数据库请求二进制的日志,在自身重现这些操作。虽然这些操作是异步的,但是主数据库和从数据库中数据更新的延迟是非常小的。通常,这种实现都由几个从数据库或者只读副本构成,它们都配置在负载均衡器之后。应用向负载均衡器发起读请求,负载均衡器以循环方式或者直连方式把该请求传递给只读副本。

我们把这种类型的拆分称为X轴拆分,在图2-1所示的AKF扩展立方中,它被表示为“X轴—横向复制”。熟悉Web应用托管的开发者都会认同这样一个例子:在系统的Web层或应用层上,负载均衡器后的多个服务器上都运行着相同的代码。一旦负载均衡器收到请求后,它就把该请求分发到其中一个Web或应用服务器上进行处理。在应用层进行这种分发的好处是可以在负载均衡器后面放置成百上千的服务器,都运行同样的代码,处理类似的请求。

X轴原则不仅适用于数据库。Web服务器和应用服务器通常也能被轻松克隆,这样就能够把事务平均分配到多个系统上进行横向扩展。这种应用或Web服务的克隆实施起来相对比较容易,可以扩展能够处理的事务数量。遗憾的是,对于我们执行某些事务而必须操作的数据而言,该方法并不能帮助我们提高扩展性。在内存中缓存客户的专有数据或者不同功能特有的数据可能会造成扩展服务的瓶颈,很难在不影响客户响应时间的前提下扩展这些服务。要解决这种内存限制,需要利用扩展立方体的Y轴和Z轴。

2.2 原则8:拆分不同的东西(Y轴原则)

目的:有时该原则被称为通过服务或资源进行扩展,重点是扩展数据集合、事务和程序员小组。

适用情形:

非常大的数据集合,数据间关系并不重要;

大型的复杂系统,需要特别扩展编程资源。

应用方式:

用动词拆分操作,用名词拆分资源,或者兼而有之;

根据动词/名词方法的定义,拆分服务和数据。

应用理由:不仅能有效地扩展事务,还能有效地扩展与事务相关的大型数据集合。

要点:Y轴拆分,或者说面向数据/服务的拆分,能够有效地扩展事务、大型数据集合,并且有助于故障隔离。

抛开关于面向服务的架构(SOA)和面向资源的架构(ROA)这两个概念的争论,深入了解它们的基本前提就会发现,它们至少有一点是相同的,即都要求架构师和程序员考虑架构中的职责拆分。大体上就是采用动词(服务)和名词(资源)的概念来实现拆分。原则8,即扩展立方上的第二个轴,采用的就是这种方法。简而言之,原则8就是通过拆分站点中的各种功能和数据,从而实现扩展。采用原则8的简单方法就是把产品拆分为名词和动词,或者两者的组合。

首先,我们看看怎么用动词拆分站点。如果我们的站点是相对简单的电子商务站点,那么可以用动词把它拆分为注册、登录、搜索、浏览、查看、加入购物车、购买。在这些事务中,每一个事务所需要执行的数据可能都与其他事务需要的大不相同。例如,可能有人会说,注册和登录需要的数据是相同的,但其实它们都需要一些特有的数据。例如,注册可能需要检查该用户选择的ID是不是已经被别人选用了,而登录时,则无需了解其他用户的ID。注册时可能需要把大量的数据写入持久数据存储中,而登录则是一种验证用户身份的只读应用。注册可能需要用户存储许多识别个人身份的信息,包括信用卡号等,而在用户只是想建立登录连接时则无需访问这些信息。

在研究搜索和登录这两种截然不同功能时,依据动词拆分的扩展方法的不同之处以及带来的好处就更加明显了。在登录时,我们关心的通常是验证用户身份,可能会建立某些会话(这里我们采用术语会话,而不是采用状态,原因将在原则40中说明)。登录功能关心的是用户,因此需要缓存用户数据并与之进行交互操作。另一方面,搜索关心的是查找数据项,而最重要的是用户的意图(通常是用户在搜索框内输入的搜索字符串、查询或搜索项)以及我们存储在目录中的目录项。拆分这些数据集,可以使我们在系统有限的内存中缓存更多的数据,而且,由此产生的高缓存命中率也会加快事务的处理。在后端的持久性系统(如数据库)中拆分数据,就能够在这些系统中分配更多的专用内存,加速对客户(应用服务器)请求的响应。由于更好地利用了系统资源,这两个系统都会相应地更快。显然,这是扩展这些系统最容易的方法,受内存限制也更少。此外,通过采用与原则7(X轴扩展)相同的方法拆分事务,Y轴的事务扩能力也增加了。

稍等!如果我们想把用户和产品信息合并在一起,例如向客户推荐产品,又该怎么办呢?注意,这里用了新的动词——推荐。这是另一种需要拆分数据和事务的情况。我们可能会加入一种推荐服务,根据用户过去的购买行为,与具有相似购买行为的用户进行异步评估。这样可能会把数据移植到登录功能或搜索功能(当用户与系统交互时就会向他显示)。或者也可能是用户浏览器发出的一个单独的同步调用,显示在专门分配给这个推荐调用的区域。

现在可以考虑如何用名词来拆分项了。还是拿电子商务的例子来说,我们可以标识一些最终会对其进行操作的资源(而不是表示要执行的操作的动词)。我们可以认为电子商务站点是由产品目录、产品库存清单、用户账户信息、市场营销信息等构成的。采用名词拆分的方法,可以根据这些分类拆分数据,然后定义一套高级的原函数,如创建、读、更新和删除等,对这些原数据进行操作。

Y轴拆分不仅适用于扩展数据集合,还适用于扩展代码库。由于服务和资源都被拆分了,那么执行的操作和执行它们所必需的代码也会被拆分。这就意味着可以把开发复杂系统的大型编程小组拆分成各个子系统的专家组,程序员不用再担心自己必须是系统每一部分的全能专家了。当然,由于可以拆分服务,所以扩展事务也就相当容易了。

2.3 原则9:拆分相近的东西(Z轴原则)

目的:通常可以利用客户特有的属性进行拆分,如客户ID、姓名、所在地等。

适用情形:非常大的相似数据集合,如快速增长的大型客户群。

应用方式:标识你所知道的客户属性,如客户ID、姓、所在地或设备,根据这些属性拆分数据和服务。

应用理由:客户信息的增长速度超过了其他所有数据的增长,或者你需要在要扩展的某些客户群之间执行故障隔离。

要点:Z轴拆分除了有助于扩展客户群,还适用于其他不能采用Y轴拆分方法的大型数据集合。

原则9通常被称为数据分片,即把数据集合或服务分割成几片。这些数据片一般大小相同,但如果有必要的话,也有可能大小不同。这样做的原因之一就是让你推出的应用能够先只影响小部分客户,当你认为自己已经发现并解决了主要问题后,再逐渐应用于更多的客户,从而降低了风险。

通常,我们都是根据对请求者或客户的了解进行分片的。假设我们提供的是打卡和考勤管理系统,而客户是雇员数大于 1000 的企业级客户,我们负责对每个客户的员工进行考勤跟踪。我们可能会决定按照公司进行分片,即每个公司都有自己专用的Web页面、应用程序和数据库服务器。考虑到我们还想利用多租户架构带来的节约成本的好处,那么可以把几家小公司划分到一个数据片中。拥有许多员工的大公司可以有专用的硬件,而员工相对较少的小公司则可以共同存在于较大的数据片中。利用员工和公司之间的关系把系统划分成了可扩展的几部分,从而能够采用较少的、成本较低的硬件,实现横向扩展(我们将在原则10中进一步讨论横向扩展)。

如果我们是手机广告服务提供商,就必须了解终端用户所用设备及其运营商,两者都很引人注目,都可用于划分数据。如果我们是电子商务运营商,那么可以根据用户的所在地划分用户群,这样能有效地利用配送中心的可用库存。或者也可以根据客户的新老程度、购买次数和购买金额划分客户。如果这些方法都失败了,那么可以利用在用户注册时分配给他的用户ID的模数或散列表进行划分。

为什么要拆分相近的东西呢?对于高速增长的公司来说,答案显而易见。响应请求的速度部分是由远近位置不同的缓存的缓存命中率决定的。该速度决定了一个系统能够处理多少个事务,从而决定了处理一定数量的请求,需要多少个系统。一种极端情况,是对数据不做划分,那么当我们要响应一个用户的请求,从而需要遍历一块巨大的数据时,事务处理的速度会慢得令人难以忍受。当响应请求的速度至关重要,而响应请求要查询的数据巨大时,拆分不同的东西(原则8)或者拆分相近的东西(原则9)就势在必行了。

拆分相近的东西显然并不局限于拆分客户,但是根据我们的咨询经验,拆分客户是实施原则9最常见也最简单的方法。有时,我们也推荐拆分产品目录。不过对于要把各种各样的产品目录拆分成草坪躺椅和尿布这样的数据项的情况,我们会把它归为拆分不同的东西。我们也曾经帮助客户利用事务ID的模数或散列表进行划分。在这种情况下,我们对请求者一无所知,但我们却有一个能够利用的单一增长的数值。对于要保存事务日志以便将来能够研究其中错误的系统,可以采用这种类型的划分。

2.4 小结

本章提出的三个简单原则几乎可以帮你扩展任何东西。虽然扩展系统和平台的方法有很多种,但有了这三个原则,那么在你前进的道路上,就没有什么与扩展相关的障碍了。

通过克隆进行扩展——通过克隆或复制数据和服务,可以轻松地扩展事务。

通过拆分不同的东西进行扩展——用名词和动词标识数据和服务,从而进行划分。如果拆分正确,那么事务和数据集都能得到有效扩展。

通过拆分相近的东西进行扩展——通常拆分的是数据集。把客户划分到专用的独立的数据片或泳道(泳道的概念请参阅第9章),可以对事务和数据进行扩展。

参考资料

[1] Edgar F.Codd,“A Relational Model of Data for Large Shared Data Banks,” 1970,www.eecs.umich.edu/~klefevre/eecs584/Papers/codd_1970.pdf.

[2] Wikipedia,“Third normal form,” http://en.wikipedia.org/wiki/Third_normal_form.

相关图书

jQuery EasyUI网站开发实战
jQuery EasyUI网站开发实战
Joomla!模板设计与网站开发
Joomla!模板设计与网站开发
大型网站服务器容量规划
大型网站服务器容量规划
网站设计 开发 维护 推广 从入门到精通
网站设计 开发 维护 推广 从入门到精通
网页制作与网站建设从入门到精通
网页制作与网站建设从入门到精通
众妙之门——电子商务网站设计指南
众妙之门——电子商务网站设计指南

相关文章

相关课程