Facebook工程发布技术的幕后故事
英文原文:Exclusive: a behind-the-scenes look at Facebook release engineering
Facebook 的总部位于美国加州的 Menlo Park,这里曾经是 Sun 公司的驻地。在其入口处,一个“赞”的标志牌(“赞”就是一个竖大拇指的姿势)赫然树立。当我最近造访 Facebook 园区时,一群年轻人正在这个标志牌前,争先恐后地用手机拍照留念。
多亏了大卫·芬奇的电影《社交网络》,全球数以百万计的影迷都知晓了这么一个疯狂的故事,Facebook 从一个大学宿舍里创建的试验项目,发展成了世界第二大互联网站点。但是,就仿佛你喜欢跑车但不了解引擎技术一样,很少有人知道,Facebook 每天处理数以亿计的用户请求,实际上需要非常复杂的技术架构。这些精巧的技术架构,同 Facebook 的传奇故事一样引人入胜。
Facebook 园区入口处
我最近才得到了这次难得的机会,得以造访 Facebook 的总部,亲身体验一下那里的风土人情。Facebook 给了我独家的采访权,去探究他们在部署新功能和应用方面的幕后故事。我看到了第一手的技术资料,例如公司的新发布设计方案:为公共主页添加“时间线”特性。
当我穿过园区的前门,走入环绕主建筑的街道时,我发现,这条街被命名为:“黑客之路”。就像 Facebook 的创始人 Mark Zuckerberg 当年在他写给投资人“公开信”中提到的那样,当时他正在为 Facekbook 融资以筹集启动资金。他还把“黑客的方式”注入了公司的管理层和开发团队。在我采访 Facebook 的两天时间里,我充分意识到发布工程技术的重要性。伴随着网站规模的迅速增长,这项技术扮演了关键的角色,将“黑客方式”融入到应用当中。
(伯乐在线另外配图,Justin Sullivan, Getty Images)
Menlo Park 园区占地面积很大,各式建筑布满其中;给我的感觉仿佛进入了一个小型的城市,而不是一个公司的园区。在这些建筑里,有趣的涂鸦式壁画和幽默海报随处可见。除了行政办公区,Facebook 的开发人员大多数都在开放式的环境下办公。办公桌分列整齐排列,工位沿着公共桌位对齐排开,员工与员工之间没有阻隔物,便于交流。
每栋建筑物内都有专门的会议室,在那里,员工们可以讨论问题而不影响他人工作。每栋建筑的会议室都以完全不同的主题风格进行命名。例如,在建筑物 1 中,会议室以 Monty Python 电影中的喜剧包袱进行命名。而另一个建筑物中,会议室则会用美剧进行命名。当你步入第三栋建筑,你会不禁一笑,这个会议室的名字叫做:“JavaScrpt 语言精粹”(JavaScript:The Good Parts),这明显是根据 Doug Crockford 那本影响深远的技术书籍命名的。
我最终来到了发布工程技术组所在的区域。就像其他的开发人员一样,发布工程技术组也是用开放式的工位布局。但是他们的特色在于:把办公区布置成了一个酒吧!
这个房间原本就在两个廊柱之间有一部分墙体。当发布工程组进驻以后,他们就把这部分空间变成了一个带工作台的酒吧,并称之为“hotfix 吧”,hotfix 是关键的软件补丁的意思。全组人员就在沿着酒吧放置一张桌子上办公。
我就是在这个酒吧遇到了 Chuck Rossi,发布工程组的负责人。Rossi 的工位距离吧台最近,吧台上的饮料对他来说触手可及。他可是软件行业的元老级人物,曾经在 Google 和 IBM 工作过。我与 Rossi 进行了一个下午愉快的交谈,讨论了他和他的团队如何进行 Facebook 更新工作,以及这些日常工作的重要性。
Chuck Rossi, Facebook 发布工程组负责人,坐在 Hotfix 酒吧前
Facebook的 BitTorrent 部署系统
Facebook 源代码大多是用 PHP 编程语言编写。PHP 是一门快速开发语言,但是相比于底层语言和部分高级语言,它的执行速度是个缺陷。为了改进基于 PHP 的架构的扩展性,Facebook 开发了一个特殊的优化器,“HipHop”。
HipHop 能将 PHP 转换为深度优化的 C++ 代码,后者能够编译成执行效率极高的本地二进制码。Facebook 于 2010 年将该项目以开源协议的形式发布,随后公司工程师报告,该项目将 Facebook 的 CPU 能耗降低了 50%。
因为 Facebook 的整个代码库都会被编译为单个的可执行文件,因此公司的部署过程会和传统的 PHP 环境中的部署大不相同。Rossi 告诉我,编译后的二进制文件,也就是包含整个 Facebook 功能的应用,大约有1.5G 的大小。每当 Facebook 更新了代码并生成了新的版本,那么新编译的二进制代码就必须被传送到公司的每一台服务器。
把1.5G 的二进制庞然大物传送到公司无法计数的服务器上,这是一个非凡的技术挑战。在探索了许多解决方案后,Facebook 决定开始使用 BitTorrent (也就是传说中的 BT 下载里面的 BT 协议——译者注),经典的 P2P 文件共享协议。BitTorrent 的特长,正是在不同的服务器之间传递大容量的数据文件。
Rossi 介绍道,Facebook 有自己的订制 BitTorrent 追踪器,这个追踪器被用来让 Facebook 基础构架中的单个服务器能够从其他的服务器获取数据片段,只要他们处于同一机架或节点就可以。这能有效地缩短总的时耗。
进行一次完整 Facebook 更新平均需要 30 分钟 —— 15分钟编译二进制代码,另 15 分钟把二进制可执行代码通过 BitTorrent 推送到 Facebook 服务器上。
当然,二进制代码仅仅是 Facebook 应用栈的一部分而已。还有许多外部资源需要引用 Facebook 的页面,包括 JavaScript, CSS 和图像资源。这些文件由内容分发网络(CDN)以分布式的形式存储和管理,遍布于不同地理位置的服务器上。
Facebook 通常每个工作日进行一次小的更新,每周进行一次大的更新,大更新一版是在周二下午进行。发布组负责管理这些更新,确保更新能够成功生效。
日常发布是 Facebook 开发哲学的重要组成部分。在公司早期的日子里,开发人员采用快速增量迭代的软件工程方法持续地对网站进行改进。这项敏捷技术在 Facebook 的进化过程中扮演了重要的角色,使其能够快速前进。
当 Facebook 任命 Rossi 担任发布工程组的领军人物时,他正负责调和快速开发模型和网站规模、复杂度快速增长之间的矛盾。要达到这一目的,就必须采用一些非常规的解决方案,例如 BitTorrent 部署系统。
在我与 Rossi 交谈的时间里,我发现,他解决 Facebook 部署难题的方法,就在于平衡实用性和准确性的程度。他一方面为部署的质量和健壮性都设立了很高的标准,另一方面却着眼于寻求灵活、顺应性强的解决方案。(译注:除 HipHop 之外, Facebook 这个庞大系统背后还有着其他诸多软件,详情请参考 2010 年的一篇旧文《揭秘 Facebook 背后的那些软件》)
测试
在我们最近的一些文章里,我提到了加速软件发布周期的挑战和回报。其中主要的挑战之一就是,如何在快速周期中保持软件的高质量,因为快速的周期缩短了 beta 测试的时间,没有足够的测试可能造成质量的下降。
质量测试在 Facebook 也是一个挑战,每天都带来新的改变。为了帮助发现问题,员工只要从公司内网登陆 Facebook,就会直接访问尚未正式发布的试验版本,也就是基于最新的代码的版本。当员工们想从内网访问当前正式版本的时候,他们必须使用另外的 IP 地址。
将测试版本设为内网的默认访问站点,能够让产品的新特性在正式整合前更多地暴露。测试版本有一个内建的 bug 报告工具,它使得员工在遇到问题的时候能够更方便地进行反馈。
Facebook 也使用自动测试工具来避免功能退化以及发现一些低级问题。公司有两套独立的测试机制,一套进行一些常规测试,对代码进行检查;另一套模仿用户的交互式行为,确保站点的交互行为正常。
在进行全面的更新之前,新代码首先被推送到“A2”层——Facebook 的少量公共服务器。这个阶段的测试把更新特性随机暴露给 Facebook 的部分正式用户,但只是所有用户的一小部分而已。这种机制给了 Facebook 的工程师很好的途径,去评估更新特性在正式产品环境中会有什么样的效果。(译注:关于 Facebook 做测试,Quora 上有个讨论帖,FB 工程师 Steven Grimm 给出了自己的回答:《Facebook 是如何做自动化测试的》。)
Chuck Rossi 和他的工作台
准备工作
Facebook 组建了自己的多人在线交谈系统(IRC),用以进行内部交流。许多公司的工程师在工作时间都保持潜水状态。根据 Rossi 所说,每个工作日平均约有 700 人在线。Facebook 的工具工程师创建了 IRC 机器人,能以多种方式将 IRC 整合到 Facebook 的开发和部署工作流中。
当 Rossi 准备开始实施更新时,他在 IRC 上初始化了一个 chekin 过程。此时所有的开发者,不管是提交更新或者没有提交更新,都会被通知,并要求回应,是否做好了系统全面更新的准备。
当某个开发者在几分钟内没有回应时,Rossi 就会给 IRC 机器人下达一个命令,让它通过不同的通信渠道去与这个开发者联系,包括 email 和短信的方式。就像 Rossi 跟我解释的那样,他通常希望在系统更新的时候,所有相关的开发人员都尽在他的掌握之中。
Facebook 开发文化重要的一个方面是,开发人员对他们所开发的的代码在最终产品中的行为负全部的责任。这种哲学完全映射了 DevOps 运动的思想,也就是鼓励打破软件开发和软件运维中间的障碍。
如果 Facebook 更新中的任何代码在最终产品中造成了问题,那么负责开发这段代码的开发者就要立即行动起来,确保问题能够尽快地解决。
发布
Rossi 在 Facebook 的办公桌上放着一台 30 英寸的 Dell 显示器,一个苹果 Mac 笔记本电脑,以及一个竖直显示器。在我周二与他待在一起的时间里,他的大部分工作都是通过浏览器和终端窗口(命令行界面)完成的。当他准备开始实施更新的时候,他在一个终端里输入了一行命令,然后整个过程就开始了。
我通过 Facebook 的基于 Web 的监控工具观察了整个更新过程。web 页面上有一个大的进度条,显示了公司服务器更新的进度。随着更新过程的进行,进度条不断前进。在最左侧的边缘,一个很细的红条标识出一小部分的系统更新失败,新的程序没有上传。
Rossi 说,在整个更新过程中,系统的一小部分更新失败的情况很常见,这多半是由于硬件原因造成的。比如,服务器也许因为存储空间不够,或者网络连接问题,致使无法通过 BitTorrent 传送文件,从而导致更新失败。总的来说,更新失败的服务器的数量很少,几乎不会造成什么问题。
当软件被成功部署到服务器上后,Rossi 描述了 Facebook 的构架是如何影响更新过程的。Facebook 的设计理念是无状态和分布式的,也就是说用户的会话不会被绑定到任何一台特定的服务器上。任何一个页面请求都有可能被 Facebook 架构中的任何一台服务器处理。
这种方法提供了很强的弹性。当 Facebook 实施更新时,完全不用担心对用户会话的序列化和迁移问题。在服务器接收完更新数据后,部署系统自动重启 Facebook 服务器上的可执行进程。在整个构架更新期间,无论是已经更新完成的服务器,还是仍在接收更新数据的服务器,都可以继续处理用户请求。
Facebook 在更新执行期间,网站仍然满负荷运作。一个常规的 Facebook 部署过程并不会要求整个网站关闭维护,也不会对网站造成什么其他的干扰。Rossi 说,采用非中断式的维护方式,是整个 Facebook 发布工程策略的重要特点。他甚至把这点看做是衡量 Web 软件工程质量的重要标志。
更新后检验
在更新完成以后,Rossi 检查了系统的各个方面,确保刚才的改变不会对系统造成不良的影响。他的团队通过一套复杂的分析工具随时监控 Facebook 的运行状态。这套工具的主仪表面板上包含了大量图标,以显示系统在流量、资源消耗、单个产品错误率,以及许多其他方面因素的变化。
通过观察这些关键数据的起伏变动,能够帮助 Facebook 识别系统问题的所在。在发生问题的时候,使用这些数据与历史数据进行对比,能够简化对问题起因的判断。发布工程组以及 Facebook 的其他工程师在更新刚刚完成的时候,对这些数据尤其地关注,以确认系统不存在异常现象。
如果有问题被检测到,例如系统的某个部分的错误率超出了预期,公司的工程师就开始深挖错误日志,查看到底发生了什么事情。Facebook 内部有专门的日志分析工具,用来查看代码变化与错误信息之间的关联。
Facebook 内部监控工具可以监控许多数据源,甚至包括监控 Twitter 里对 Facebook 的评论。这些监控信息被显示在一个统计图里,图中包含两条走势线,分别代表对 Facebook 积极和消极的评论。这非常有用,因为当用户在一个社交网站里遇到问题的时候,他往往会去另一个社交网站抱怨他遇到的问题。
发布工程组的成员在 Hotfix 吧小喝几杯,庆祝更新成功
我在 Facebook 采访时观察到的系统更新过程进行得很顺利;更新后没有产生技术问题或者 bug。图标显示,只有一个系统模块的日志信息有点小问题,但是经过 Rossi 的小组追根溯源后,发现这只是一个不重要的问题,因此也就作罢了。
只有失败者才会回退
尽管我在这儿的时候,并没有发生什么需要救火的紧急情况,但是 Rossi 为了满足我的好奇心,还是想我描述了当更新过程不那么顺利时,Facebook 是如何应对的。如果一个严重的 bug 在更新后被发现,发布工程组全体成员就会与相应的开发人员一起尽快解决问题。当问题解决后,Rossi 的小组会发布一个新的版本,并再次实施更新。
当我问及他,如果更新后系统 bug 难以修复,是否会将其回退到当前的版本时。他斩钉截铁地回答我:“只有失败者才会回退!”
他继续向我们解释,实际上,系统回退功能是有的,但是它仅仅在万不得已的时候才会使用。服务器会自动保存 Facebook 上一个版本的二进制代码,以备不时之需。
他还比喻说,把 Facebook 回退到上一个版本,就好像给一列火车拉紧急制动闸一样。人们不希望这种情况发生,实际上也确实很少发生。在他来到 Facebook 的几年时间里,回退功能只用过几次而已。
Facebook 的测试演习和工程师文化能够有效地放置 bug 被更新到终端产品代码中。如果一个开发者的代码出现了问题,并严重到需做部署后修改,那么这件事情就会被记录下来,并影响到 Facebook 对这个开发者的绩效考核结果。
公司的内部工具有一个 Facebook 激励机制,Rossi 用它来进行评分。Facebook 开发人员都有一个“因果报应”评分,该评分可由代码审查系统进行跟踪。在一个基于 Web 的仪表板工具中,Rossi 可以通过点击某个开发者姓名旁边的“顶”或者“踩”的按钮,来增减这个开发者的“因果报应”评分。
Rossi 使用的“顶”图标其实就是 Facebook 里一般用户使用的“顶”图标。“踩”图标和“顶”图标是一个图标,只是倒过来了而已。当 Rossi 向我展示这些图标的时候,他开玩笑说,他是全世界唯一一个可以在 Facebook 里使用“踩”按钮的人。
“因果报应”得分帮助 Facebook 辨别哪些员工更加努力,但是这个评分玩玩在代码评审阶段更加有效。当 Rossi 看到一个得分很低的开发这提出一个代码合并申请的时候,他就会知道,从这个员工那里接受代码可能会有很大的风险。
随着时间的推移,得分低的员工可以通过他们的良好表现来重新赢得分数,不过有的员工喜欢走走后门,通过给 Rossi“行贿”来增加他的好感。酒和纸杯蛋糕是 Rossi 喜欢的“赎罪物”;发布工程组有数量可观的酒水供应,有些就来自于那些寻求恢复自己“因果报应”分数的开发者。
Facebook 发布工程部门的 Hotfix 吧
未来
我同 Rossi 谈到了他的愿景:Facebook 的部署策略将影响到这个公司的技术架构的进化方向。他说未来的开发技术将让他的小组能够戏剧性地加速部署更新的过程,把所有的构架和部署时间大大缩短,远低于目前的 30 分钟。
当前正在进行的开发项目之一,就是一个企图代替 HipHop 优化器的项目。Facebook 的开发者正在创建他们自己的字节码格式和运行时环境,并称其为 HipHop 虚拟机,用以支持下一代的 Facebook 平台。当这个项目结束时,公司能够将 PHP 源代码编译成字节码,并在这个虚拟机上执行。
迁移到受控代码模型,就仿佛 Java 或者 .NET 一样,能够给 Facebook 带来全面的灵活性。除了提供许多其他的优点,Rossi 解释说,这将显著地影响部署过程。届时公司将不必再将 1.5G 的二进制代码部署到所有的服务器,而是仅仅需要推送一些小的字节码变量,来表示那些部分发生了改变。Facebook 甚至能在程序运行的时候将这些更新字节码拼接起来,而不需要重新启动。
当仅仅需要几分钟就能完成部署的情况变成可能,而不再需要大规模的部署过程的时候,也就是 Facebook 摒弃其传统的更新时间表,全面进入增量部署的时候,就仿佛边开发边部署一样。采用这种方式,能够给公司的开发人员带来更加敏捷的开发模式。
在周二的更新过程完成以后,Rossi 和他的小组分析了系统,确保更新没有给系统造成问题。然后,他们就在 hotfix 酒吧喝上几杯,表示庆祝。
当一天结束,我离开 Facebook 园区时,我再次信步经过了“黑客之路”标志牌,我沉思着,发布工程组在 Facebook 中扮演了重要的角色,把这个软件推向大众,但是他们的工作又是如此的不为人所知,就仿佛是透明的一样。
Facebook 系统向时间线档案布局迁移,将增加社交网络平台在用户经历分享和用户个人叙事记录方面的功能。提供这些功能的基础技术构架本身就有许多经历和故事,这些都是 Facebook 独特的开发者文化的象征。