网站需要具有良好的可伸缩性,来应对不断增长的访问量和数据量。《程序员》杂志5月刊的《可伸缩性的10年探索:知名网站的技术发展历程》一文中介绍了一些Alexa排名较前的网站的技术发展历程,本文将结合提及的Google、Facebook、Twitter等网站的技术发展历程,总结它们在可伸缩性、可用性、高性能以及低成本四点上通常采用的技术。
可伸缩
可伸缩分为垂直伸缩和水平伸缩两类,垂直伸缩通过升级机器的硬件来解决问题,水平伸缩通过增加机器来解决问题。不同网站在可伸缩上采用了不同的策略。例如,Google完全依赖水平伸缩来解决问题,而其他网站多数是依赖垂直伸缩来解决数据存储问题。
垂直伸缩要求软件要能在硬件升级时,发挥出硬件的能力,例如可配置的并行数、内存等。硬件的发展速度非常迅猛,网站的机器配置自然也是每年都在升级,因此软件时刻都在被检验是否能垂直伸缩。
水平伸缩主要解决的是如何仅通过增加机器就能解决问题,一般通过应用层和存储层两个层次来解决。
在应用层做到水平伸缩,通常采用的策略是Share Nothing/Stateless,将状态信息放入客户端或存储层(例如用户的会话信息放入Cookie,或放入服务器端的缓存系统)。此时,在访问量上涨后增加机器即可。在应用系统由单台增加到多台组成集群时,需要引入负载均衡,可能是硬件的也可能是软件的。
我们发现,就应用层的结构演变而言,前面提到的Google、Facebook、Twitter等网站在应用层上最后形成的结构几乎完全一样,均为前端Web系统集群 + Services集群。通常到了一定阶段后,前端Web系统和Services又会按照一定的规则来进行拆分,如按业务领域等。可见,其实SOA在大网站中已经落地,而不像企业领域中宣传的那么虚。
存储层实现水平伸缩,难度就比应用层大多了。从Google、Facebook、Twitter等几家网站的发展历程也可看出,解决存储层问题需要花费大量的时间和精力,而且由于业务发展的压力较大,很多网站开始会采用垂直伸缩方式来解决存储层的问题。
实现存储层的可伸缩性,通常采用的方案是单点写(指在某个粒度的单点,对HBase而言,单行数据一定在同一台机器上进行写操作),但读可能是多点。读多点,主要需要考虑不一致的问题。无论读是单点还是多点,数据都会在软件层面做到写多份,策略主要有同步写多份、投票写多份、异步复制最终一致等(例如HDFS、Cassandra、ZooKeeper),采用自动分裂方法来实现数据量增长时的自动伸缩,采用一致性Hash或根据某种规则的自动均衡策略等来实现机器增减时的相应处理,同时也需要有感知机器增减的方法(例如采用ZooKeeper)。
可以看出,要在存储层上实现可伸缩,技术上的难点很多。这也是为什么大规模网站都不在数据库上做复杂的运算,而只是把其当做一种存储信息的方式来使用。存储过程等基本不会出现,以降低数据库的压力。
除了应用层和存储层需要做到可伸缩外,在设计系统时硬件层面的可伸缩也是需要考虑的。例如,硬件负载设备只能支撑到一定流量,同样也要考虑如何让其能够可伸缩。
除了尽可能做到系统可伸缩外,减少对系统的压力也是缓解系统的可伸缩所面临挑战的方法,例如批量提交、最终一致、YouTube提到的“欺骗”、适当的正确性等策略。
可伸缩性是网站从小到大要经历的第一关挑战,同时随着网站的不断发展,还要不断地做技术改造才能保证网站在每个阶段(如同机房阶段、同城多机房阶段、异地多机房阶段)都具备优秀的可伸缩性。不过,幸运的是不同阶段的可伸缩性方案都已经比较成熟了。
可用性
可伸缩性能保证网站在访问量和数据量不断增长的情况下生存下来,同时也从某种程度上保证了网站的可用性。但除此之外,要保障系统的可用性,还需付出很多努力。
设计高可用性系统时,避免单点是其中最重要的一点,一般采用主备或集群等方法来避免单点。例如,负载均衡设备、MySQL等通常采用主备方式。在BigTable里采取了一种比较特殊的方法来避免Tablet Server的单点,即通过Chubby来感知Tablet Server的健康状况,如发现Tablet Server挂掉了,则由Master将此Tablet Server负责的Tablet迁移到其他的Tablet Server上。
除了避免单点外,降低耦合也是设计高可用性系统时应考虑的重点。通常采取异步化来降低耦合,将非关键逻辑和关键逻辑隔离开。例如,打开天猫的商品详情时,都会有相关商品的推荐。如果这个推荐系统出问题的话,就会导致商品详情也看不到了,因此这里可以异步载推荐系统,通常采用AJAX带超时的方式来实现。
Google、Facebook等网站在总结多年的可用性系统设计经验时,列入了同一条设计原则,即保持简单。简单的方案一般比较容易掌控,而复杂的方案一方面实现难度大,另一方面出现问题时很难排查。
可控性也是各网站强调的重点,可控性意味着一切代码都在掌控之下,并且最好是每个使用到的部分都由专业人员负责(对可用性要求越高,在这点上要求也就越高)。这样在出现问题时才能清楚是哪个地方造成的,并且可以自行排查和解决。如不可控,则意味着一旦出现问题,就得依赖第三方来排查,时间上非常不可控。这也是网站不愿意采用商用软件的重要原因。
编写高质量软件是保障系统高可用性的重要一环,可控性是其基础。Google、Facebook等网站在总结保障高可用的经验中均提到了监控/报警、容错、自我保护等策略。
监控/报警是软件自身能够保障高可用的重要策略,就像是汽车的仪表盘一样,可以告诉你油还剩多少、速度是多少、胎压是否正常等重要信息。对于软件而言,同样需要让外部获取到其运行的状况。例如,Google的软件都会提供一个HTML页面供使用者或开发人员访问(用过Hadoop的人也会发现这个特征)。在这个页面上可通过key/value的方式来获取系统的一些运行指标。对于RPC系统而言,Google会采集所有的正常请求、错误请求以及其消耗时间的分布状况(>0.05s、>0.1s等)。除了监控系统的运行状况外,也提供了其他一些方式以便外部能简单判断系统运行是否正常,例如cURL某页面等。对于不正常的现象,要及时报警,尽可能做到在故障尚未影响到用户时解决掉。
软件的正常运行需要依赖很多外部因素,例如机房、硬件、数据库、服务等,而所有依赖的部分都有可能出现故障(要坚信这点,互联网的特色是所有小概率事件都会发生)。在设计软件时需要考虑当依赖方出问题时,如何保障软件的可用性,因此一定要做一些容错处理。为了避免机房故障,各网站通常会租用或建设多机房。例如,Google采用IDE硬盘来存储文件,不做RAID,于是采用复制三份的策略来避免硬盘故障导致数据丢失。
软件一般会提供多种功能,有的重要、有的不重要的。而由不重要的功能异常导致重要的功能出现问题,显然不合算,因此在设计软件时需要充分考虑异常隔离,不互相影响。例如在Google的系统设计中会采用Prioritized Request等策略。
James Hamilton的那篇著名的论文《On Designing and Deploying Internet-Scale Services》中提到的Graceful Degradation,即为优雅降级。通常采用的方法是在故障将要出现或出现后,关闭系统的某些功能来降低故障产生的影响。例如,网站上有些操作可能特别耗资源,而这些资源的消耗又可能影响到核心功能,因此一旦出现影响,就可以关闭这些功能保障核心功能可用。降级可以帮助系统临时绕开故障,而产生故障的原因需要在事后排查。
交付具有高可用特征的软件是开发人员的重要职责。而对一个网站而言,软件不是一次性交付的,也不是好几年才升级一次,需要频繁交付,因此维护软件是保障高可用的重要环节。
多数情况下,系统的不可用是由变更造成的,因此如何降低变更对系统可用性造成的影响成为了各网站都关注的重点。Google在发布新产品时通常采取“滚木移石”方法,而Facebook则通常采用Dark Launch方法,降低变更带来的影响。
人工处理系统变更是故障产生的隐患,因此各网站基本都会推荐多种工具来实现系统变更的自动化,例如采用Puppet来实现自动化部署。
除了发布这个重要环节外,处理故障也是维护方面的重要工作。系统总有出现故障时,如何快速处理故障以降低对故障可用性的影响也成为网站一直关注的重点。前面已经讲述了可控性对解决故障的帮助。除可控性外,各网站也在研究其他方法。Facebook采用FBAR来自动处理部分故障,这显然可从某种程度上降低故障产生的影响。
性能
前面提到的可控性同样也是保证性能的重点,只有明确知道调用的每个API及所依赖环境(包括软硬件)的细节原理,才能编写出高性能软件。
从系统结构上来看,为了保障高性能,各大网站都采用了类似的方法。首先是前端Web系统这块,都采用了可编译为机器码的方式,即便Facebook采用的是PHP,其仍然研发了一个可自动转化为C++代码的产品来提升运行效率。
设计系统时,应考虑将没有前后依赖的逻辑并行化处理,或者将大的请求进行拆分。例如,Google会先将所搜索的内容进行分词,然后并行进行索引查询,从而提高响应速度。
基本上各大网站都极度依赖缓存,原因在于,内存的访问速度远快于磁盘。在依赖缓存的场景中,最需要做到的是数据一致性的合理保障。一个典型的场景是数据更新时保障缓存一致性的策略。要将缓存的更新和数据的更新做成一个事务显然有不小的难度,此时网站常采用一个简单的策略来保障,就是先失效缓存,再更新数据,等到下次系统去访问此数据时,才更新到内存。
除了缓存外,可以看到各大网站都采用了CDN,CDN可以让静态文件更靠近用户,便于用户快速获取。而除了让静态文件更靠近用户外,多IDC的建设除了提升可用性外,还可以让动态数据更靠近用户。当然,这在技术上的实现难度会比CDN高很多。
除了结构上的这几点外,技术创新是提升系统性能的重要方法。例如,Google提高了TCP的初始拥塞窗口值等。而要做到技术创新,显然可控性是基础。
成本
随着网站规模的不断扩大,系统的运行和维护成本将会成为公司中支出的重要部分。例如,有数据表明,腾讯每年支付给运营商的费用在总支出中占比排行第二(2010年为208亿)。网站规模越大,成本控制就越重要(潜台词是在网站规模不是很大时,也许支撑业务快速发展更重要)。例如,性价比是Google设计系统时重要考量的指标。有些网站会采用x元/每千次PV来计算成本。
有些性能优化需要增加成本(如缓存和CDN),而有些性能优化是可以降低成本的(如Google对索引结构的优化),因此性能优化通常也是降低成本的一种方法。网站规模大了以后,规模效应可以让有些性能优化带来的成本降低非常明显。
硬件不断升级,而软件系统层面上又更多的是靠集群来支撑,因此通常很难完全消耗硬件资源,虚拟化就成为一种不错的降低成本的方法。虚拟化具备很好的隔离机制,避免了应用间的互相影响,因此落地的难度不大。
除了依靠虚拟化来降低成本之外,Google采用了自行实现的一种Shared Environment方法来降低成本。Shared Environment可根据不同类型的资源消耗,动态组合(例如分时)部署到同一台机器上,充分利用资源。
如前所述,网站主要是靠可伸缩性存活下来的,因此随着规模的扩大,必然会有大量的机器。比如,Google有上百万台机器,Facebook有几十万台机器。在这么大规模下,自行根据应用特征设计机器,会带来很大的成本下降,因此Google、Facebook都自行设计机器和数据中心。从PUE上可以看出,Google、Facebook自行设计数据中心带来的成本降低非常可观。
各网站的情况不同,应对以上四点挑战所采用的方法不同,每个阶段都有自己适用的解决方案。例如,Google成立初期的主要业务是搜索,主要竞争的是技术,功能次之,而Facebook、eBay等网站的竞争压力主要在业务功能上,因此在成立之初必然会有不同的侧重点。不用想着一开始就把网站做成Google、Facebook等现在的结构,适合自己的就是好的。
很多开发人员在加入规模较大的网站后,会觉得系统结构已经稳定了,没有发展的空间,但从上面各网站的发展历程来看,可以看出网站对技术的要求是在不断演变的。通过观察大网站的发展历程,并结合公司的业务背景、知识结构等来判断其下一步的发展,对个人成长是有很大帮助的。同时可以借此储备一些技术,以把握技术演变时的机会,获得更大的成长。如果开发人员加入了规模尚小的网站,且自身技术储备不错的话,就有机会亲身经历网站从小到大的演变了。但这要看个人如何把握。
图1 发展到一定规模后的网站结构
围绕可伸缩性、可用性、性能和成本这四个方向,在网站发展到一定规模后,通常会演变成如图1所示的结构。
除了在可伸缩性、可用性、性能和成本这四方面的技术挑战外,网站还面临其他很多方面的挑战,例如海量数据分析和挖掘、网站安全、业务发展的灵活性支撑、人员增长后庞大的软件管理等,因此构建一个支撑大访问量、长期发展、低成本运行的网站是需要有坚实的技术背景作为支撑的。
作者林昊,目前就职于淘宝,2007-2010年负责设计和实现淘宝的服务框架,此服务框架在淘宝大面积使用,每天承担了150亿+的请求;2011年开始负责HBase在淘宝的落地,目前淘宝已有20个以上的在线项目在使用HBase。