需要特别感谢一下工业酒精同学,让我能够流畅地访问Facebook, Youtube和Blogger。这样我总算经历了一段只在优酷上看动画片的井底之蛙时期后,又可以去看看Facebook Techtalks发布了什么新内容,或者看看Blogger上一些技术博客的文章了。
以下内容就来自于Google到的关于Facebook Seattle Engineering Road Show上的一些信息,希望之后能在Facebook上有发布Mike Schreopfer & Greg Badros相应的演讲视频。
从比较高的层次来看,Facebook的整体架构其实和许多大型网站也一样,就如下面这张图。
从比较高的层次看,facebook的基本结构就像下面这个从演讲中截下来的图一般:

一般来说,可能区分中型Web App和大型Web App的一个特点就是看是否有大量的Dedicated Web Service,我不知道这到底该怎么称呼,也许淘宝的消息中心也就是属于这种Web Service的范畴之内。而且从这张图,尤其是Dedicated Web Service这个名字上来看,位于前端的Web Server仍然是与数据库和缓存有直接联系的,并非完全被Web Service隔断。
前端的Web Server是一些PHP程序(许多国内公司还分不清楚Front-End和Back-End的范畴,真是茶几),采用PHP的原因是简单易学,开发效率高。而缺点是性能确实不足,据说从他们的benchmark来看这比用C#和Java来写要慢了十倍,这也算解答了我一个疑惑,从理论角度上来说脚本语言本身总是不会比成熟的托管代码快的,也许只有Lua是比较接近和特别的,但Lua也才刚刚开始接触一下,还说不太清楚。不过Facebook也做了很多工作去优化PHP,只是不知道这些东西有没有回馈出来了。
对于Dedicated Web Service而言,Facebook让开发人员采用他们认为适合的语言开发。似乎有C++, Erlang, Python和Java。比如说Facebook Chat就是用Erlang编写的,Erlang似乎很适合做这类消息系统,性能方面我没有深入的研究,但对于处理并发来说,这个语言从设计上巧妙地回避了锁的问题,给开发提供了很大的便利。37signals不也用Erlang编写了Campfire里的PollerService吗,这至少有两个很好的Erlang使用案例了。
对于多语言混搭的系统来说,如何集成和RPC调用是一定要解决的问题。Facebook倒是开源了一个解决这个问题的框架
Thrift,这个框架里也包含一个code generation engine,使用起来应该很便利。这方面是迟早需要做些研究和准备的课题吧。我倒很好奇国内一些站点对于此问题是如何解决的,一直没看到相关的资料。
另一个比较有趣的东西是Scribe,对Facebook来说,典型的应用就是MultiFeed。MultiFeed将用户好友数以千记的Feed信息聚合起来,然后筛选出45条,用于用户登录后首页的显示。当一个用户有更新时,信息将写入MySQL和缓存当中,缓存中对每个用户储存了55个最新的更新信息,当响应访问的时候,聚合器会根据用户的好友列表对多个子结点访问这些用户的更新,同时这些子结点本身就会做一定的过滤,聚合器也会再做过滤,然后将最后的StoryID返回给应用,应用再根据这些ID去memcache集群里获取详细信息。这里面有趣的有两点,第一个在于对于一个类似的Feed系统,关键问题在于存储的设计,Scribe应该是用来实现了用于聚合的Server,这样Facebook只用对每个用户的Feed进行流水记录就可以了,当然在做数据的拆分时应该还是根据用户的好友数及活跃程度有更细致的技巧。第二个有趣的地方在于其底层本质是一个C++实现的non-blocking server,似乎一般做Web App的对non-blocking server可能没多少概念,但在网游业界里应该都是必备的,也许在Steven Xu看来,这应该是基础吧。而我也只研究了下Python实现的Tornado,有空还该再仔细看下Scribe的源代码。

Facebook大量使用了memcached,也对这个项目回馈了很多东西,似乎淘宝早期也发现了memcached的问题,那就是在能支撑的最大连接数上还是有瓶颈。Facebook也对memcached做过许多优化,比如64-bit的版本,将TCP协议换为UDP,优化了network stack,提供了multithreading的支持。memcached作为现在Web Application的家居必备,Facebook对该项目的回馈功不可没。
不过比较值得注意的细节是在使用Batch Load的时候,将获取的item控制在一定数量是有好处的,比如首页并非一次就获取了所有的45条Feed信息,而是在滚动到页面最底端时才获取了剩余的部分。同时保证一次要访问的memcache服务器数量尽可能少一些也是对性能有帮助的,因此对缓存的数据最终也会需要根据Object Type做垂直地切分。这带来的一个问题是有时需要对数据进行复制以应对单点的失效,因此引出了数据一致性上维护的问题,而Facebook的应对策略似乎是尽量少用这种切分,数据不一致也就忽略不管了。确实,对于有些应用形式来说,一定量的数据不一致真的没有什么关系,追求逻辑上的完美,有时候是挖坑给自己跳而已。
数据库方面,有一点也值得一提,Facebook有时候会允许开发人员往数据库里增加新的数据类型,但不改变Schema,这种情况估计做Web Application的团队都有,缺陷就在于对这些字段往往就很难便捷的查询和索引。对于此,How FriendFeed uses MySQL to store schema-less data这篇文章提供了不少经验之谈。
对于图片存储,Facebook现在用的项目应该是Haystack,链接的文章里有比较详细的说明和介绍,就不重复了,这个项目应该未来也会开源。而对于优化访问性能方面,除了CDN似乎也没什么更好的途径,区别在于是找比较成熟的CDN服务商做,还是自己来做。
Facebook目前正在面对的问题是异地的数据同步,目前他们在西海岸和东海岸都有数据中心,但东海岸的数据是只读的,虽然他们对MySQL的复制已经做了一些hack,但这些hack似乎也带来一些tricky的问题。如果他们以后在美国之外建数据中心,那么解决地理位置上不同数据中心的数据同步问题会越来越重要。Yahoo!似乎对此有一套解决方案,这个改次再写。淘宝表示Oracle在这方面的表现好像非常出色。
写这样的记录文章真的挺累,也发现其实还有很多的东西值得更深入地研究和学习,很多东西还是要通过实践才能真正明白。同样的问题,甚至架构思路,在实际解决途径上都可能会有很多差别。光看这些资料,也是盲人摸象而已。
最后记录三个Facebook的工程师文化要点吧。
1. Move Fast and Break Things.
2. Huge Impact with Small Teams
3. Be Bold and Innovative
好文!
Comment by pumaboyd — November 14, 2009 @ 7:45 pm