在上一篇《知名网站的技术发展历程 》中,介绍了一些知名网站在发展的过程中技术的演变,在这篇文章中则会根据这些网站的发展经验,来总结通常网站是如何来应对可伸缩性、可用性、高性能以及低成本这四方面的挑战的。

在上一篇文章中,介绍了一些Alexa排名较前的网站的技术发展历程,在这篇文章中,将结合提及到的网站的技术发展历程,来总结下网站在可伸缩性、可用性、高性能以及低成本四点上通常采用的技术。
对于一个网站而言,最重要的是要支撑住不断增长的访问量和数据量,如果能做到简单的增加机器即可支撑,那自然是最好的,但这就要求技术上必须付出一定的努 力,这也是网站都追求的可伸缩性(Scalability),而在做到可伸缩性后,不断增加的机器也带来了成本的上升,因此发展到了一定规模后,成本也会 成为关注的重点。
除了可伸缩性外,可用性对于各网站而言也非常重要,一个经常不能使用的网站必被用户抛弃,这也是为什么各网站都很强调全年的可用性。
除了上面这三点外,还有另外一点影响也很明显,就是性能,美国的一个对网站访问速度的调查数据:1/4的用户因为一个网站载入的时间超过4秒而放弃这个网 站,50%的手机用户会放弃一个超过10秒还没载入完成的网站,其中的3/5用户将不再光顾这个网站,而Google对其数据分析后,发现搜索结果只要慢 1/4秒,一天的搜索量就要减少800万。

可伸缩
可伸缩分为垂直伸缩和水平伸缩,垂直伸缩为通过升级机器的硬件来解决问题,水平伸缩为通过增加机器来解决问题,不同网站在可伸缩上采用了不同的策略,例如Google完全依赖水平伸缩来解决问题,而其他网站多数是先依赖垂直伸缩来解决数据存储的问题。
垂直伸缩要求的是软件上要能在硬件升级的情况下,发挥出硬件的能力,例如可配置的并行数、内存等,硬件的发展速度非常迅猛,网站的机器的配置自然也是每年都在升级,因此其实我们的软件时刻都在被检验是否能垂直伸缩。
水平伸缩主要要解决的是如何做到仅通过增加机器就解决问题,主要又分成了应用层和存储层两个层次来解决。
在应用层要做到水平伸缩,通常采用的策略是Share Nothing/Stateless,状态信息放入客户端或存储层(例如用户的Session信息放入Cookie,或放入服务器端的缓存系统),应用系 统做到了无状态,那么在访问量上涨后只需要增加机器即可,在应用系统由单台增加到多台组成Cluster时,这时就会有负载均衡的引入,可能是硬件的也可 能是软件的。
在应用层的结构演变上,我们会发现,随着网站的不断发展,以上提到的各家网站在应用层上最后形成的结构几乎都完全一样,均为前端Web系统Cluster + Services Cluster,前端Web系统和Services到了一定的阶段后通常又会按照一定的规则来进行拆分,如按业务领域等,可见SOA其实在大网站中是落地 了的,而不像企业领域中宣传的那么虚。
在存储层要做到水平伸缩,难度就比应用层大多了,从前面各家网站的发展历程也可看出,很多时候都在解决存储层的问题,而且可以看出,很多网站由于业务发展的压力较大,一开始都会选择采用垂直伸缩的方式来解决存储层的问题。
存储层主要是Cache、文件和数据三种类型。
Cache可以看到基本上各家网站都采用了Memcache来保证伸缩性,在规模大了,也会碰到一些问题,具体可以看看facebook所做的改造,现在也有些网站开始采用redis了,但redis本身不具备可伸缩性,需要自行进行改造。
文件的存储可以看到各家网站都采用了分布式文件系统来保证伸缩性,思路多数都源于GFS,但由于存储的文件的大小不同、对响应时间和吞吐量的要求不同,各家网站可能都各自实现。
数据的存储上可以看到各家网站基本都采用了分库分表的策略,分库分表后由于很多关系型数据库的特性(例如自增ID、join、事务等)都难以再使用了,对 应用的设计会带来一些挑战,不过最大难点还是在于需要根据业务来考虑如何分是合适的,在最近几年也有一些网站开始采用NoSQL来更好的做到数据存储的可 伸缩性。
存储层为了做到可伸缩性,通常采用的方案是单点写(是指在某个粒度的单点,例如对HBase而言,是单行数据一定是在同一台机器上进行写操作),但读可能 是多点,读多点的话主要要考虑不一致的问题,无论读是单点还是多点,数据都会在软件层面做到写多份,策略主要有同步写多份、投票写多份、异步复制最终一致 等(例如HDFS、Cassandra、Zookeeper),采用自动分裂的方法来实现数据量增长时的自动伸缩,采用一致性hash或根据某种规则的自 动balance策略等来实现机器增减时的相应处理,同时也需要有感知机器增减的方法(例如采用zookeeper)。
从上面可以看到存储层上要做到可伸缩,技术上的难点很多,这也是为什么大规模的网站都不在数据库上做复杂的运算,而只是把其当做一种存储来使用,例如存储过程这种就更是基本不会出现了,以降低数据库的压力。
除了应用层和存储层需要做到可伸缩外,硬件层面的可伸缩在设计系统时也是需要考虑的,例如硬件负载设备只能支撑到一定的流量,同样也需要考虑如何让其能够可伸缩。
除了尽可能做到系统可伸缩外,减少对系统的压力也是缓解系统的可伸缩面临挑战的方法,例如批量提交、最终一致、youtube提到的“欺骗”、适当的正确性等策略。
可伸缩性是网站从小到大要经历的第一关挑战,而且随着网站的不断发展,还要不断的做技术改造才能保证网站在每个不同的阶段(例如同机房阶段、同城多机房阶段、异地多机房阶段)都具备优秀的可伸缩性,不过幸运的是不同阶段的可伸缩性方案都已经比较成熟。

可用性
可伸缩性保证了网站在不断增长的访问量和数据量的情况下生存下来,这也一定程度保证了网站的可用性,但除了这点外,要保障系统的可用性,还得付出很多的努力。
在设计高可用性的系统时,避免单点是其中最重要的一点,通常采用主备和集群的方法来避免单点,例如负载均衡设备、MySQL等通常采用主备的方式,在 BigTable里采取了一种比较特殊的方法来避免Tablet Server的单点:通过Chubby来感知Tablet Server的健康状况,如发现Tablet Server挂掉了,则由Master将此Tablet Server负责的Tablet迁移到其他的Tablet Servers上。
除了避免单点外,降低耦合也是设计高可用性系统时应考虑的重点,通常可以采取异步化来降低耦合,将非关键逻辑和关键逻辑隔离开,例如在淘宝上买一件商品, 确认购买了后,需要旺旺通知卖家,如果旺旺通知卖家这个过程是同步通知的话,对于系统来说无疑增加了一个风险点,因此可以将旺旺通知卖家的步骤变为异步的 方式来做,这种场景的异步通常又采用消息中间件来实现;还有另外一种场景也经常采用异步化的方式来降低耦合,例如打开天猫的商品详情时,都会有相关商品的 推荐,如果这个推荐系统出问题的话,就会导致商品详情也看不到了,因此这里也可以采用异步化来加载推荐系统,在这种场景中通常采用Ajax带超时的方式来 实现。
各网站在总结多年的可用性系统设计的经验时,有一个设计原则被所有网站列入:保持简单,简单的方案意味着容易掌控,而复杂的方案一方面意味着实现难度大,另一方面意味着出现问题时很难排查。
可控性也是各网站强调的重点,可控性就意味着一切的代码都在掌握下,并且最好是每个使用到的部分都有专业的人士(对可用性要求越高,在这点上要求也就越 高),这样才能清楚在出现问题的时候是哪个地方造成的,并且可以自行排查和解决,如不可控,则意味着一旦出现问题,就得依赖第三方来排查,那这个时间就非 常不可控了,这也是网站不采用商用软件的重要原因。

编写高质量软件是保障系统高可用性的重要一环(可控性也是编写高质量软件的基础),但软件几乎不可能做到0 bug,各网站在总结自己保障高可用的经验中提到了一些策略用于保障系统的高可用,这些策略主要包括:监控/报警、容错、自我保护、隔离、和降级,需要做 到高可用的软件在交付时都应具备这些特性。
监控/报警是软件自身能够保障高可用的重要策略,就像是汽车的仪表盘一样,可以告诉你油还剩多少,速度是多少,胎压是否正常等重要信息,对于软件而言,同 样需要让外部可以获取到其运行的状况,例如Google的软件都会提供一个html的页面供使用者或开发人员访问(用过Hadoop的人也会发现这个特 征),在这个页面上可通过key/value的方式来获取系统的一些运行指标,对外RPC的系统Google会采集所有的正常请求、错误请求以及其消耗时 间的分布状况(>0.05s的,>0.1s等),除了监控系统的运行状况外,也需要提供一些方式以便外部能简单判断系统运行是否正常,例如 curl某页面等,对于不正常的现象要进行即时的报警,以尽可能做到在故障尚未影响到用户时进行解决。
软件会依赖很多外部的因素,例如机房、硬件、数据库、服务等,而所有依赖的部分都是有可能出现故障的(要坚信这点,互联网的特色是所有小概率事件都会发 生),在设计软件时需要考虑当依赖方出问题时,如何能保障软件本身的可用性,因此一定要做一些容错的处理,例如对于机房故障,各网站通常都会租用或建设多 机房来避免;又例如Google采用IDE硬盘来存储文件,不做Raid,于是采用了复制三份的策略来避免硬盘故障导致数据丢失。
软件通常会接受外部的输入,而有些时候输入条件的不符合预期可能会导致软件的故障,在我们的系统中曾经出现的一个故障案例:某系统对外提供了批量查询的功 能,结果有一次客户端提交了一个查找10000个用户的批量查询,导致系统由于内存不够出现了故障,因此在设计软件时需要保障处理自己能力范围的请求,对 于超出能力的请求可以考虑直接抛错,还有一种常见的是后端处理变慢导致雪崩效应的故障,这种在软件上通常会采用超时、限流等方式来避免,对于这些可能出现 的故障在设计软件时都应考虑采取一些保护的措施来保护自身的可用性。
软件通常提供了多种功能,这些功能会有重要的和不重要的,如果由于不重要的功能异常导致重要的功能出现问题,显然是不合算的,因此在设计软件时需要充分的考虑异常的隔离,不互相影响,例如在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上进行搜索,Google会进行分词,然后并行的进行索引的查询,从而提高响应速度。
基本上各大网站都极度依赖Cache,这原因很明显,内存的访问速度远快于磁盘,依赖Cache的场景中最需要做到的是数据一致性的合理保障,一个典型的 场景是数据更新时保障Cache一致性的策略,Cache的更新和数据的更新如果要做成一个事务显然有不小的难度,此时网站常采用一个简单策略来保障,就 是先失效Cache,再更新数据,等到下次系统去访问此数据时,才更新到内存。
除了Cache外,可以看到各大网站都采用了CDN,CDN可以让静态文件更加的靠近用户,以便用户快速获取,而除了让静态文件更靠近用户外,多IDC的建设除了提升可用性外,还可实现让动态数据更靠近用户,当然,这个在技术上的实现难度会比CDN高很多。
除了结构上的这几点外,技术创新是提升系统性能的重要方法,例如Google提高TCP的初始拥塞窗口值等,而要做到技术创新,显然可控性就是基础了。

成本
随着网站规模的不断扩大,系统的运行和维护成本将会成为公司中支出的重要部分,例如有数据表明腾讯每年的支出中,支付给运营商的费用是其中占比排第二的部 分(2010年为208亿),因此网站规模越大,成本控制就越重要(潜台词是网站规模在不是很大的时候,也许支撑业务快速发展更重要),例如Google 在设计系统时price/performance是重要的考量指标,有些网站也会采用x元/每千次page views来计算成本。
性能优化有些是需要增加成本的(例如cache、CDN),而有些性能优化是可以降低成本(例如Google对Index结构的优化)的,因此通常性能优化也是降低成本的一种方法,网站规模大了后的规模效应可以让有些性能优化带来的成本降低非常的明显。
硬件的不断升级,而软件系统层面上又更多的是靠cluster来支撑,因此通常很难完全消耗硬件资源,虚拟化就成为一种不错的降低成本的方法,虚拟化具备了很好的隔离机制,避免了应用的互相影响,因此落地的难度不大。
除了依靠虚拟化来降低成本的方法外,Google则采用了自行实现的一种Shared Environment的方法来降低成本,Shared Environment可根据不同类型的资源消耗,动态组合(例如分时)部署到同一台机器上,充分的发挥机器的资源。
在前文中也讲到,网站主要是靠可伸缩存活下来,随着规模的扩大,必然会有大量的机器, Google有上百万台的机器,Facebook有几十万台机器,在这么大规模的情况下,自行根据应用特征设计机器,会带来很大的成本下降,因此 Google、Facebook都自行设计机器以及自行设计DataCenter,从PUE上就可以看到Google/Facebook自行设计 DataCenter带来的成本降低是非常可观的。

以上四点挑战会由于网站业务的不同、人员知识结构的不同、做事风格的不同以及所处的阶段不同而采用不同的方法去解决,每个阶段都有自己适用的解决方 案,例如Google在成立之初业务为搜索,相对而言搜索市场的竞争主要是技术,功能次之,而facebook/eBay等网站的竞争压力主要在业务功能 上,因此在成立之初的时候必然会有不同的侧重点,所以也不用想着一开始就去把网站做成Google/Facebook等现在的结构,选择适合自己网站的就 是好的,很多开发人员在加入规模较大的网站后,会觉得系统结构已经稳定了,没有发展的空间,但从上面各网站的发展历程来看,可以看出网站对技术的要求是不 断在演变的,通过观察大网站的发展历程,对自己结合公司的业务背景、知识结构等来判断其下一步的发展,是会有很大的帮助的,并且也可以借此储备一些技术, 以把握技术演变时的机会,获得更大的成长,如果是加入规模尚小的网站,技术储备做的好的话,就有机会亲身经历网站从小到大的演变了,因此都有机会,就看个 人如何把握。
围绕这四个方向的发展,通常网站在发展到一定规模后会演变成类似如下的结构:

除了在可伸缩性、可用性、性能以及成本这四点的技术挑战外,网站还面临了其他很多方面的挑战,例如海量数据的分析和挖掘、网站安全、业务发展的灵活性支 撑、人员增长后庞大的软件管理等等,因此构建一个支撑大访问量、长期发展、低成本运行的网站是需要有坚实的技术作为支撑的。

 

原文地址:http://blog.bluedavy.com/?p=396