YouTube高扩展之道

YouTube开始定位是在线婚恋网站,但是最后演化成一个视频分享网站,现在每天视频的播放数高达40亿。经历过疯狂的增长过程,Youtube创始团队的工程师Mike Solomon学到很多东西,在2012年PyCon上分享了YouTube的工程经验:Scalability at YouTube(虽然是几年前的演讲,但是很多经验是不会过时的。)

这个演讲不是以架构为中心,没有讲解很多模块互相连接的框图。Mike完全可以大讲架构,他曾经研发YouTube的Servlet框架、视频索引、视频转码、全文检索、CDN等等系统。但是,这个演讲中他放眼全局,分享了多年技术实践中总结的的深厚经验。

对于我来说,从这个演讲中学到的最重要的经验就是:用极简单的工具完成大部分工作。在其他公司都在向更复杂的生态系统迁移的时候,YouTube一直保持着简单的技术栈。他们主要用Python编码,用MySQL做数据库,用Apache做web服务器。在这样一个庞大的网站中,新功能都是从一个简单的Python脚本开始,慢慢演化的。

这并不意味着YouTube没有很酷的技术,他们有,但是他们信奉的哲学,或者说行事方式是实用主义的-让整个系统顺利运转,而不是紧追新奇的技术概念。下面讲讲YouTube怎样演化成世界最大网站之一的。

统计

  • 每天40亿次播放
  • 每分钟上传60小时的视频
  • 3500万+个设备安装有YouTube客户端
  • 2010年营收翻倍
  • 视频个数翻了9个数量级的同时,工程师数量只翻了两个数量级
  • 100万行Python代码

技术栈

  • Python-YouTube大部分代码都是Python代码,你每次观看视频的时候,都在执行一堆Python脚本。
  • Apache - 你认为Apache已经被淘汰了?其实Apache是非常厉害的技术,可以保持技术栈的简单化。在YouTube,每个来自外部的请求都是经过Apache的。
  • Linux - Linux的好处在于,你总可以深入系统查看运行状态,不管你的应用表现如何,你都可以用Linux工具(如strace和tcpdump)来查看它的具体情况。
  • MySQL - 处处都用到。当你观看一段视频的时候,很多数据都来自于MySQL,有时用作关系数据库,有时用于存储二进制BLOB数据。如何调优,如何组织数据对于MySQL非常重要。
  • Vitess - YouTube发布的一个Go语言项目,作为MySQL的前端代理使用。它负责实时地优化查询,重写查询。当前Vitess通过RPC处理YouTube的每个数据库请求。
  • ZooKeeper - 分布式的配置服务器,用于存储配置信息。非常有趣的技术组件,要用好也不太容易。
  • Wiseguy - CGI Servelet服务器。
  • Spitfire - 一个模版系统。支持虚拟的语法树,可以快速实现转换。
  • 串行化格式 - 所有现存格式都太低效。Pickle很糟糕;ProtoBuf也太慢。最后他们自己实现了BSON的编码解码器,比所有你能下载到的串行化方案快10-15倍。

经验教训

  • YouTube之道:选择最简单最通用的解决方案,以保证应对各种问题的灵活性。一旦一个方案过度贴近需求(over-specified),你就把自己堵在墙角了。不要对系统做出太多限定,当你做出各种限定的时候,系统就会自然地变得更加复杂,让你没有退路。这就是可扩展性的关键所在,可扩展的系统不会阻碍你的任何改动,是透明的。这不是时髦的词汇而已,是解决复杂问题的道之所在。
  • 大型系统的设计准则:每个系统都要符合特定需求,要根据你的业务量体裁衣。
  • YouTube没有异步,所有操作都是同步的。
  • 不要盲从教条,遵循设计哲学-保持简洁。如果你做Code Review的时候,发现数千行代码需要改动,涉及很多代码文件,那么系统设计应该有更简单的方案。你的第一个Demo一定要简单,然后在此基础上迭代。
  • 再次强调,解决问题之道用一个词归纳就是简洁。找到能处理问题的最简单的技术。需要解决的问题可能会复杂,但是第一个解决方案一定不会很繁琐,复杂性会随时间自然而然地产生。YouTube很多系统都是从一个Python文件开始的,经过很多年的演变,慢慢变成了巨大的生态系统。所有的原型都是Python写的,并且这样一个原型可以存活很久不被淘汰。
  • 设计Review需要讨论的问题:
    • 第一个解决方案是怎样的?
    • 如何在此基础上迭代?
    • 这些数据将来会被如何使用?
  • 拥抱变化。YouTube一开始是一个婚恋网站,如果以此为目标做设计,可能就没有今天的YouTube了。一定要保持灵活性。
  • YouTube CDN - 一开始完全外包。后来发现成本太高,自己实现。如果你有好的硬件工程师,你也可以做一个很不错的视频CDN。制造一个很大的机架,塞满服务器,装好Lighttpd,然后复写404 handler-找到刚才没有找到的视频。这个系统花了两个礼拜完成,上线第一天就扛住了60G的视频流量。这再次证明了一点:你可以用很简单的工具完成很多事。
  • 测量(measure)很重要。Vitess开始用HTTP协议实现接口,尽管是C写的,但是它非常慢。所以,后来他们直接用python的Socket连接替换了HTTP,整体上节省了8%的CPU。HTTP的报文的封装其实非常昂贵。

扩展性技巧

这里没有什么新理论,但是你会发现有些核心理念可以应用于很多不同的场景中。

  • 分而治之
    很多扩展性的问题不过是把工作分成小块而已,这个原则可以应用在系统的各个层次。在web层,你可以有很多完全相同而又相互独立的web服务器,可以水平扩容,这是分治。在数据库层分片(Sharding)的核心,也是分治。你需要尽早考虑的是,怎样把工作分成小块并且在各块之间确保通信,这将会影响今后系统扩展的能力。在这里,Python语言动态特性的优势就体现出来了。不管你的API设计得多么糟糕,你都可以用替换、修改、修饰(stub/modify/decorate)等方法来规避问题。
  • 大致正确即可(可以稍微作弊)
    系统的状态就是它报告给用户的状态。如果用户不能看出系统的偏差,那它就没有偏差。举个例子,如果用户A发布一个评论,与此同时另外一个用户B加载了这个页面,他在300-400毫秒内可能看不到这条评论。用户B不会在乎,但是用户A在乎自己的评论已经发布。所以你可以作弊一下,在用户A的页面显示这条评论。你的系统不必保持全局一致的事务性,因为实现这样的系统非常昂贵,而且毫无必要。因为评论不是财务交易,作弊无伤大雅。
  • 因地制宜
    你了解系统需要的一致性模型吗?对于评论来说,最终一致性(Eventually Consistent)应该足够,但是租赁一部影片可不行。租赁涉及到金钱交易,所以我们必须尽力保证一致。可见,不同数据的一致性要求是不一样的。
  • 抖动(在系统内引入熵)
    在YouTube技术团队内部,熵是一个常用词。如果系统没有抖动,你就会遇到踩踏事件(Thundering Herds)。分布式系统很像气象系统,在这样的系统里面debug就像是天气预报一样毫无确定性可言。抖动带来的随机性可以避免一些东西在系统中累积。
    以缓存失效为例。对于热点视频,我们尽可能的多用缓存。最火的视频可能缓存24小时,但是如果所有缓存的失效时长相同,那么所有缓存服务器将在同一时刻失效,这会造成回源的踩踏事件。
    加入抖动就是让这些缓存在18-30个小时之间的随机时间失效。这样可以避免回源的流量叠加。这样的技巧在整个系统中随处可见。分布式系统本身有一种“自同步”的倾向,很多操作会自动对齐(line up), 而这对系统自身是毁灭性的。这种现象很有趣,比如一台机器的磁盘变慢,这时候所有主机都在等待某个请求的返回,机器收到的其它请求就会被同步(对齐)。这种现象在系统里有很多机器、很多事件的时候就会发生。每发生一次,系统的熵就会减少一些,你必须把这些熵加回去才能保证系统健康运行。
  • 作弊(怎样制造假数据)
    这是很有用的技巧。最快的函数调用是不调用任何函数。如果你有一个单调上升的计数器,比如视频播放次数或者用户页面查看次数,你可以每次访问都更新计数器。或者,你也可以每隔一段时间更新一次,增加一个随机数,只要它是从奇数变成偶数,用户很有可能相信这个数字是真的。这是数据造假的技巧。(原来YouTube数据也会造假……)
  • 模块化的重要性
    在YouTube中的Python程序,会逐渐被RPC隔离开。代码结构的优劣往往依赖于程序员的自我约束,所以必须确立良好的代码规范,但另一方面,如果自我约束的效果不好,总可以用RPC把模块强制隔离开,来确保接口输入输出的清晰、规范。
    不是每个模块的实现都是完美的。模块可能存活一个月或者半年,这很难说。如果严格定义模块边界,你就有很大的灵活性,你可以把模块替换掉(比如用C重写),或者干脆抛弃。
    模块之间交互的数据需要定义严格的数据规范。
  • 效率有时需要让步于可扩展性
    效率经常要让步于扩展性。最高效的程序是用C写,把所有功能塞在一个进程里面,但是这样没法扩展。
    着眼于宏观,看看你的各个组件是如何分离的。是用RPC比较好还是内联调用比较好?可能将来这个问题的答案和今天不一样。
    注重算法。Python并没有花力气优化算法的实现,和C相比很多算法很低效,但是方便使用。可以用C来优化核心算法。
    度量。在Python里面很多度量是违背直觉的,比如垃圾回收的代价。在YouTube的应用中很大一部分时间是用于串行化的,串行化的调优就非常重要。
  • Python实用建议
    尽量写无脑的代码,这样更容易查找,更容易维护。代码越高深莫测,就越难搞清楚它是如何工作的。
    不要用很多OO的特性。我们使用了很多namespace, 也用Class来组织数据,但是几乎不用OO的特性。
    代码树应该什么样?要简单、实用、优雅、正交、易于组合。

参考资料

http://highscalability.com/blog/2012/3/26/7-years-of-youtube-scalability-lessons-in-30-minutes.html

https://www.youtube.com/watch?v=G-lGCC4KKok

http://highscalability.com/youtube-architecture

http://www.slideshare.net/didip/super-sizing-youtube-with-python/25-Questions

https://youtube.googleblog.com

    原文作者:王雷
    原文地址: https://zhuanlan.zhihu.com/p/22339441
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞