来自Google、Amazon和Facebook等7大知名互联网的系统扩展经验
来自 Google、Amazon 和 Facebook 等 7 大知名互联网的系统扩展经验
编者注:本文为历史博文归档;涉及 JDK、框架与工具链版本请以当前官方文档为准。引用外链图片可能失效,阅读时请注意时效性。
本文出自澳大利亚一位 ID 为 Dodgy Coder 的程序员于 2012 年 4 月发布的博客文章。他从 High Scalability 上整理和总结了 Google、YouTube、Twitter、Amazon、Ebay、Facebook 和 Instagram 等 7 家知名互联网公司的系统扩展经验。值得注意的是,有些资料时过境迁,已经不再反映最新情况,但是核心的理念和许多具体经验还是非常宝贵的学习资料,值得一读。
不难发现,这 7 个公司都有以下共同的 6 大理念:
- 保持简单——随着时间推移,复杂性会自然出现。
- 自动化一切——包括灾难恢复。
- 不断迭代——想扩展到更高水平?必须准备好忍痛弃用现在能工作的某个组件。
- 选择合适的工具——但也不怕自己动手打造。
- 使用缓存——在适当的地方。
- 根据场景,在数据的一致性和可用性之间做取舍。
下面来分别看一下 7 大公司的经验吧。

一、Google
可靠的存储(Reliable Storage)
可靠、可扩展的存储基本上是任何应用程序的核心。GFS(Google File System)是 Google 的核心存储平台——它是一个大型分布式结构化的日志文件系统,Google 在其中存放了大量的数据。为什么会自建系统,而不是使用其他已有的产品?因为 Google 需要对系统有绝对的掌控力,同时这个平台也正是 Google 之所以成为 Google 的地方。GFS 使他们获得了跨数据中心的高可靠性、扩展到数以千计个节点的能力、提供巨大的读写带宽、支持以 GB 为单位的大数据块处理和跨节点分布操作以降低瓶颈的高效技术。
基础设施成为竞争优势
Google 可以更快、更便宜并且在规模上罕有匹敌地发布新的互联网服务。许多公司与 Google 的想法并不相同,他们把基础设施看成负担,花钱的事儿。新旧两类公司使用的技术完全不同,在系统开发上也少有共识。
在平台的基础上构建应用程序
大平台方式有一个经常被忽略的优势,就是初级开发者也能很快并自信地开发健壮的应用程序。如果每个项目都需要建立分布式基础设施,那么你很快将会陷入困境,因为懂得这么去做的开发者非常少。协同效应并不总是空谈,从整个系统上着眼改善,可以帮助到建立在这个系统上的所有应用程序或项目。比如:改善了文件系统就可以让所有项目都立即而且透明地(指上层开发者和使用者都无需操心)获益。如果每个项目都使用不同的文件系统,那么在整个技术栈上的改进将不会带来持续不断的增益。
自动化和恢复
建立自管理系统,让工作不需要停机进行。这样允许你更容易地进行以下操作:在多台服务器间重新调配资源、动态添加容量、将机器下线以及从容地处理升级。
建立不断进化的基础设施
并行地执行一个耗时(CPU 绑定的)操作,并取优胜者。这尤其适合在 CPU 富余而 IO 不足的情况。
不要忽视学术界
学术界有很多很棒的思想,只不过还没有进入生产环境。你现在看到 Google 所做的事情其实都并不新鲜,只是没有大规模部署而已。
考虑数据压缩
当由许多机器组成的大型集群受限于 IO 时,压缩不失为一良策。

二、YouTube
越简单越好
寻找问题领域的最简解决方案。这里存在许多复杂的问题,但是选择解决方案的首要前提就是不能复杂。随着时间的发展,复杂性会一直存在,而最简单和最松散的解决方案是始终适用的。这样做的原因是保持解决问题的灵活性,反之你则会把自己逼入角落。你将会失去对程序的控制,同样当你试图解决时,问题将变的越来越复杂,你会变得无路可走。
数据“欺骗”:知晓如何在数据上作假
最快的函数调用就是根本上没有发生。当你需要做一个持续增加的计数器时,比如说一个浏览计数,你需要为每次的更改做数据库调用。或者你可以每隔一段的时间做一次调用,或者是一个随机数量做更改——但是人们可能就会认为它是实时显示的。你必须要知道如何在数据上作假。
抖动(Jitter)
如果你的系统不存在抖动,将会因为用户在同一时间对同一个资源进行请求产生 Thundering Herd(“惊群效应”)。对于一个流行的视频,YouTube 会尽可能的为其做缓冲。最流行的视频可能会缓冲 24 小时。如果所有缓存同时到期,将会造成上面所说的 Thundering Herd。通过抖动,你可以设置随机的时间(18-30 小时)。这将阻止事情在同一个时间发生,并且保证很长时间内请求的顺利完成。
近似正确
用户所见就是你系统的状态。如果用户看不到你系统中存在的偏移和不一致,那么这些问题从本质上来说根本“不存在”。如果你正在一个页面上发布评论,而这时候某些用户刚好打开了这个页面,那么这些用户在半秒内可能根本看不到你的评论,然而那些阅读这个页面的用户根本不会在意这个事情。这种情况就允许你稍微的进行“作弊”,因为你的评论并没有达到全局一致性。如果真的去做这个全局上的一致性,那将会投入大量的开销,同样也是牛刀杀鸡——因为你并不是在做金融系统,所以你可以作弊。

三、Twitter
实现 API
Twitter 的 API 流量是 Twitter 网站的 10 倍,API 是 Twitter 增长用户数量最重要的手段。保持服务的简单,允许开发者在自己基础设施上建立服务,并且想出比 Twitter 自己更好的应用程序点子。所谓众人拾柴火焰高,集思广益才能做更好的创新。所以开放你的应用,并且让其保持简单,这样就可以和其他人的应用程序进行整合。(当然,后来在赢利压力下,Twitter 过河拆桥,将有史以来最有活力的 API 生态链生生干掉了。)
使用你清楚的东西
Twitter 使用了一堆消息传送。对用户发布的消息进行排队,然后分发给指定的用户。Twitter 最主要的功能就是扮演消息传递的桥梁,架起不同格式(SMS、Web、IM 等等)之间的消息传送。在后台同步发送消息去清除朋友的缓存,而不是单独的进行。Twitter 开发者对 Ruby 最为熟悉,所以他们抛弃 DRb 转至 Starling(一个 Ruby 编写的分布式队列系统)。分布式队列系统将队列写入磁盘,以防止系统崩溃。以 Twitter 的经验,大部分的性能提升不是语言的选择而是应用程序的设计。(这一点也不完全正确了,Twitter 因为性能,后来从 Ruby 整个迁移到了 Scala/JVM。)
知道何时进行缓存以及缓存什么
举个例子,获得你朋友的状态是很复杂的,包括了安全等多个隐患。所以,取代对数据库进行查询,朋友的状态将会被放入缓存。永远都不会接触到数据库。90% 的请求都是 API 请求,所以他们在前端基本上不做任何页面缓存。因为 Twitter 的页面都对时间敏感,这样做(缓存页面)没有任何好处。

四、Amazon
使用 SOA
Amazon 的架构都是松耦合的,并且围绕着服务建立。一个面向服务的体系结构(SOA),基于他们可以快速及独立的建立软件的多个组件,允许他们更快的向市场上投放。Amazon.com Web 页就是一个类似的应用程序服务器。这样的话这个应用程序同时服务了网络服务接口、用户服务应用程序以及卖方接口。
使用 API 打造你的系统,你将围绕你的应用程序建立起一整套的生态系统。围绕着服务展开将给你更高的灵活性——你可以并行的进行操作,因为所有的输出都是一项服务。禁止客户端直接对数据库进行访问,因为不会涉及到客户端,所以你的服务将拥有更好的扩展性和可靠性。这点很像谷歌的改变某个组件让建立在整个系统或平台上的应用程序都获益。
根据场景在数据的一致性和数据的可用性之间做取舍
既然扩展你就必须做分片,所以你必须为特定的系统做高一致性或者高可用性的选择。你必须发现有效性和一致性上的重叠部分,根据服务的需求选择一个合适的方案。举个结账系统的用例:你总是希望将请求作为购物车的添加项,因为它产生了收入。在这个用例中,你就选择了高可用性。错误就隐藏在了客户方面,并且由其提出:当客户进行提交时,你必须对一致性进行重点对待,因为不同的服务(信用卡处理、运输、操作、报告)都将同时访问数据,并且每个都有各自数据一致性的需求。
拥抱失败
对失败抱平常心,它可能会经常出现,所以拥抱它。比如,使用一个快速重启和快速恢复方案。选用一个合适的数据传输,服务正常运行的几率将接近 100%。建立一个自我修复、自我组织、无人值守类型的操作。
只用你需要的
让设计保持简单,确定设计中没有隐藏的需求及依赖性。将技术程度降到最低,你只需要一些解决问题的必须技术。确保这些技术不会带来更多的复杂性,慎重甚至是不选择一些特定的方法或者技术堆栈。有些地方他们使用 JBoss/Java,但他们只选用 Servlet,而不使用余下的几个 J2EE 框架。使用 C++ 来处理请求,使用 Perl/Mason 来建立目录。
根据客户的反馈来指定决策
使用测量和客观的讨论去区分好坏。给客户一个切实的选择来测试哪个更好,并且通过这些测试制定决策。这点通常使用类似 A/B 测试和 Web Analytics 等技术实现。如果你产生决策上的问题,那么将其编码,让更多的人使用,从而清楚哪个选择才是你真正想要的。
扩展性即竞争优势
和 Google 一样,基础设施同样是 Amazon 竞争优势所在。他们可以简单的在原始服务上建立非常复杂的应用程序。他们可以独立的进行扩展操作,保持无与伦比的系统可用性,在不需要大规模的重配置下就可以快速的推出新服务。

五、eBay
切分一切
如果你不能对其进行切分,那么你就不能对其进行扩展。通过功能和数据,将所有东西都切割成容易控制的组块。
处处异步
通过事件驱动队列和传输管道,连接起所有的组件。
拥抱故障
监视所有发生的事情,别间断服务——即使有些部分开始发生故障。最小化和控制依赖性,使用抽象的接口和虚拟化技术,组件中包含一个 SLA,用户从 SLA 违规中恢复。自动化所有事情,组件需要自动调整,而系统则需要自我调节和完善。
拥抱不一致
在需要使用 CAP 原理的地方挑选好每个特征,如果选择非分布式事务,不一致性可以通过操作顺序来最小化,通过异步恢复和调整实现最终一致性。
保存所有的数据
数据驱动最佳的机遇、预测和推荐的发现,所以保存所有。清楚哪些数据是有权威的,哪些数据没有,进行不同的对待。
基础设施:给指定的工作分配合适的工具
需要最大化的使用每个资源:数据(内存)、处理(CPU)、时钟时间(延时)等。没有通吃的策略,区分规模对待。由商用、工业服务器共同组成。

六、Facebook
扩展需要多次的迭代
解决方案通常是在工作的开始时提出,然而随着发展你必须对其进行修改——已经使用了一年的方案,以后可能不再适用。一个好的例子就是图片,Facebook 现在(文章撰写时)每秒需要服务 12 亿张图片。第一代的思想就非常简单,没有考虑到扩展到如此规模,只注重功能上的实现。Uploader 会将文件储存为 NFS 格式,而原数据将会保存在 MySQL 中。这个方案只用了 3 个月,但是这并不重要,在上市时间上他们赢得了巨大的竞争优势,同样功能上的特点比深思扩展方案来的更加重要。第二代则使用了不同的访问方式对其进行优化,鉴于较小的图片访问频度会比较高,所以对其使用了缓存,他们同样开始使用 CDN(内容分发网络)。第三代则是一个 overlay 系统,让 Facebook 可以在原有的文件系统上使用 BLOB 存储。图片被存储到一个二进制的 BLOB,因为你清楚 BLOB 中图片的字节偏移量,所以每张图片对磁盘只进行一次 IO 操作。
不要重复设计一个方案,让其保持简单
在你对系统进行横向扩展时,只使用你需要用到的。找到方案中需要重做的地方,进行优化,或者着手重新建立堆栈中需要修改的部分。Facebook 花费了大把的时间去优化 PHP,最终完成了 HipHop 的编写,完成了 PHP 到 C++ 的转换,这为他们节省了大量的内存和 CPU 开销。然而你不需要从第一天就着手做这个事情,在完全重写一门语言之前你需要做的是聚焦产品的特性。
针对工作选用正确的工具,并且接受这个选择所带来的开销
如果你需要使用 Python,并选择了它进行开发,但是必须要认识到这个选择是有开销的:通常是部署、监视、运营等方面。如果选择了一个面向服务的体系结构(SOA),你必须自己动手建立大部分所需的后端,这需要大把的时间。通过 LAMP 你可以省下许多开销,但是一旦你真的选择了 LAMP 堆栈,类似服务的配置以及监视将是随之要面对的问题。随着你对这个服务了解的加深,你必定会自费力气做重复的工作。
正确的公司文化
建立一个可以促进生产的内部环境,并根据需求不断的进行完善。在进行正确的编码和做出正确的产品之前,你首先需要定义正确的公司文化;没有一个正确的文化,公司将不会得到发展。

七、Instagram
利用现有的云基础设施
不要去做重复的事情,你可以使用可靠并且得到证实的技术。Instagram 在 Amazon 的 EC2 云计算基础设施上运行了 100 多个 Ubuntu 11.04 实例,他们同样还使用了 Amazon ELB,其中包括 3 个 nginx 实例以及自动的故障恢复(撰稿日期)。图片被储存在 Amazon S3 上,他们还使用了 Amazon CloudFront 作为 CDN,这么做可以有助于世界各地的图片加载时间。
异步的任务队列
当一个用户决定将 Instagram 上的图片分享到 Twitter 或者 Facebook 时,或者当他们需要给发布的图片发送一个实时的通告,他们把任务推送给开源的 Gearman 任务管理框架。使用异步队列意味着当“重载”在后台进行时,媒体上传可以快速完成。大约有 200 个工作者(Python 编写)忙于任务队列的处理,处理服务中自己分割的份额。
推送通知
他们使用一个开源 Apple Push Notification Service(APNS)提供程序 pyapns(基于 Twisted),每天稳定地为 Instagram 解决 10 亿推送消息的任务。
实时的系统级监控
对于拥有 100 多个 EC2 实例的 Instagram 来说,对系统进行实时的全方位监控无疑是重中之重。他们使用 Munin 进行系统级监视,这个监视工具在系统任何操作超过正常范围时都会发出警报。他们开发了 Munin 的定制插件,基于 Python-Munin 之上,监视非系统级事件。他们使用 Pingdom 进行服务的外部监视,并且使用 PagerDuty 处理通知和事件。而 Python 的错误报告,他们使用 Sentry,一个开源的 Django 应用。在任何给定的时间,他们可以实时地登录并了解系统中出现了什么错误。
选择性使用 NoSQL 技术(比如 Redis)
Redis 驱动了主 feed,活动 feed、会话系统(会话系统后端代码见 这里)以及其他 相关系统。Redis 所有的数据都需要写入内存,所以他们在 EC2 上为 Redis 运行了几个 Quadruple Extra-Large Memory 实例,并且不定期给任何给定系统做跨 Redis 的分片。
关于 Instagram 的详细架构可以参考:Instagram 工程师背后技术秘密
原文链接:Scalability lessons from Google, YouTube, Twitter, Amazon, eBay, Facebook and Instagram
时效性说明:本文整理自 2012 年的技术博文,部分架构方案(如 Twitter 从 Ruby 迁移至 Scala、Instagram 早期栈等)已随技术发展发生变更。文中提及的具体工具版本与数据规模请以各公司最新官方文档为准,核心设计理念仍具参考价值。
版权声明:本文为原创文章,版权归 戴老师的博客 所有,转载请联系博主获得授权。
如果对本文有什么问题或疑问都可以在评论区留言,我看到后会尽量解答。