Facebook工程发布技术的幕后故事
Facebook的总部位于美国加州的Menlo Park,这里曾经是Sun公司的驻地。在其入口处,一个“赞”的标志牌(“赞”就是一个竖大拇指的姿势)赫然树立。当我最近造访Facebook园区时,一群年轻人正在这个标志牌前,争先恐后地用手机拍照留念。
多亏了大卫·芬奇的电影《社交网络》,全球数以百万计的影迷都知晓了这么一个疯狂的故事,Facebook从一个大学宿舍里创建的试验项目,发展成了世界第二大互联网站点。但是,就仿佛你喜欢跑车但不了解引擎技术一样,很少有人知道,Facebook每天处理数以亿计的用户请求,实际上需要非常复杂的技术架构。这些精巧的技术架构,同Facebook的传奇故事一样引人入胜。
我最近才得到了这次难得的机会,得以造访Facebook的总部,亲身体验一下那里的风土人情。Facebook给了我独家的采访权,去探究他们在部署新功能和应用方面的幕后故事。我看到了第一手的技术资料,例如公司的新发布设计方案:为公共主页添加“时间线”特性。
当我穿过园区的前门,走入环绕主建筑的街道时,我发现,这条街被命名为:“黑客之路”。就像Facebook的创始人Mark Zuckerberg当年在他写给投资人“公开信”中提到的那样,当时他正在为Facekbook融资以筹集启动资金。他还把“黑客的方式”注入了公司的管理层和开发团队。在我采访Facebook的两天时间里,我充分意识到发布工程技术的重要性。伴随着网站规模的迅速增长,这项技术扮演了关键的角色,将“黑客方式”融入到应用当中。
Menlo Park园区占地面积很大,各式建筑布满其中;给我的感觉仿佛进入了一个小型的城市,而不是一个公司的园区。在这些建筑里,有趣的涂鸦式壁画和幽默海报随处可见。除了行政办公区,Facebook的开发人员大多数都在开放式的环境下办公。办公桌分列整齐排列,工位沿着公共桌位对齐排开,员工与员工之间没有阻隔物,便于交流。
每栋建筑物内都有专门的会议室,在那里,员工们可以讨论问题而不影响他人工作。每栋建筑的会议室都以完全不同的主题风格进行命名。例如,在建筑物1中,会议室以Monty Python电影中的喜剧包袱进行命名。而另一个建筑物中,会议室则会用美剧进行命名。当你步入第三栋建筑,你会不禁一笑,这个会议室的名字叫做: “JavaScrpt语言精粹”(JavaScript:The Good Parts),这明显是根据Doug Crockford那本影响深远的技术书籍命名的。
我最终来到了发布工程技术组所在的区域。就像其他的开发人员一样,发布工程技术组也是用开放式的工位布局。但是他们的特色在于:把办公区布置成了而一个酒吧!
这个房间原本就在两个廊柱之间有一部分墙体。当发布工程组进驻以后,他们就把这部分空间变成了一个带工作台的酒吧,并称之为“hotfix吧”,hotfix是关键的软件补丁的意思。全组人员就在沿着酒吧放置一张桌子上办公。
我就是在这个酒吧遇到了Chuck Rossi,发布工程组的负责人。Rossi的工位距离吧台最近,吧台上的饮料对他来说触手可及。他可是软件行业的元老级人物,曾经在Google和IBM工作过。我与Rossi进行了一个下午愉快的交谈,讨论了他和他的团队如何进行Facebook更新工作,以及这些日常工作的重要性。
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是如何做自动化测试的》。)
准备工作
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积极和消极的评论。这非常有用,因为当用户在一个社交网站里遇到问题的时候,他往往会去另一个社交网站抱怨他遇到的问题。
我在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喜欢的“赎罪物”;发布工程组有数量可观的酒水供应,有些就来自于那些寻求恢复自己“因果报应”分数的开发者。
未来
我同Rossi谈到了他的愿景:Facebook的部署策略将影响到这个公司的技术架构的进化方向。他说未来的开发技术将让他的小组能够戏剧性地加速部署更新的过程,把所有的构架和部署时间大大缩短,远低于目前的30分钟。
当前正在进行的开发项目之一,就是一个企图代替HipHop优化器的项目。Facebook的开发者正在创建他们自己的字节码格式和运行时环境,并称其为HipHop虚拟机,用以支持下一代的Facebook平台。当这个项目结束时,公司能够将PHP源代码编译成字节码,并在这个虚拟机上执行。
迁移到受控代码模型,就仿佛Java或者.NET一样,能够给Facebook带来全面的灵活性。除了提供许多其他的优点,Rossi解释说,这将显著地影响部署过程。届时公司将不必再将1.5G的二进制代码部署到所有的服务器,而是仅仅需要推送一些小的字节码变量,来表示那些部分发生了改变。Facebook甚至能在程序运行的时候将这些更新字节码拼接起来,而不需要重新启动。
当仅仅需要几分钟就能完成部署的情况变成可能,而不再需要大规模的部署过程的时候,也就是Facebook摒弃其传统的更新时间表,全面进入增量部署的时候,就仿佛边开发边部署一样。采用这种方式,能够给公司的开发人员带来更加敏捷的开发模式。
在周二的更新过程完成以后,Rossi和他的小组分析了系统,确保更新没有给系统造成问题。然后,他们就在hotfix酒吧喝上几杯,表示庆祝。
当一天结束,我离开Facebook园区时,我再次信步经过了“黑客之路”标志牌,我沉思着,发布工程组在Facebook中扮演了重要的角色,把这个软件推向大众,但是他们的工作又是如此的不为人所知,就仿佛是透明的一样。
Facebook系统向时间线档案布局迁移,将增加社交网络平台在用户经历分享和用户个人叙事记录方面的功能。提供这些功能的基础技术构架本身就有许多经历和故事,这些都是Facebook独特的开发者文化的象征。
英文原文: Ryan Paul 编译: 伯乐在线 – 黄小非
【如需转载,请标注并保留原文链接、译文链接和译者等信息,谢谢合作!】