解剖Twitter 【2】三段论

Sina WeiboBaiduLinkedInQQGoogle+RedditEvernote分享




【2】三段论

网站的架构设计,传统的做法是三段论。所谓“传统的”,并不等同于“过时的”。大型网站的架构设计,强调实用。新潮的设计,固然吸引人,但是技术可能不成熟,风险高。所以,很多大型网站,走的是稳妥的传统的路子。

2006年5月Twitter刚上线的时候,为了简化网站的开发,他们使用了Ruby-On-Rails工具,而Ruby-On-Rails的设计思想,就是三段论。

1. 前段,即表述层(Presentation Tier) 用的工具是Apache Web Server,主要任务是解析HTTP协议,把来自不同用户的,不同类型的请求,分发给逻辑层。

2. 中段,即逻辑层 (Logic Tier)用的工具是Mongrel Rails Server,利用Rails现成的模块,降低开发的工作量。

3. 后段,即数据层 (Data Tier) 用的工具是MySQL 数据库。

先说后段,数据层。

Twitter 的服务,可以概括为两个核心,1. 用户,2. 短信。用户与用户之间的关系,是追与被追的关系,也就是Following和Be followed。对于一个用户来说,他只读自己“追”的那些人写的短信。而他自己写的短信,只有那些“追”自己的人才会读。抓住这两个核心,就不难理解 Twitter的其它功能是如何实现的[7]。

围绕这两个核心,就可以着手设计Data Schema,也就是存放在数据层(Data Tier)中的数据的组织方式。不妨设置三个表[8],

1. 用户表:用户ID,姓名,登录名和密码,状态(在线与否)。

2. 短信表:短信ID,作者ID,正文(定长,140字),时间戳。

3. 用户关系表,记录追与被追的关系:用户ID,他追的用户IDs (Following),追他的用户IDs (Be followed)。

再说中段,逻辑层。

当用户发表一条短信的时候,执行以下五个步骤,

1. 把该短信记录到“短信表” 中去。

2. 从“用户关系表”中取出追他的用户的IDs。

3. 有些追他的用户目前在线,另一些可能离线。在线与否的状态,可以在“用户表”中查到。过滤掉那些离线的用户的IDs。

4. 把那些追他的并且目前在线的用户的IDs,逐个推进一个队列(Queue)中去。

5. 从这个队列中,逐个取出那些追他的并且目前在线的用户的IDs,并且更新这些人的主页,也就是添加最新发表的这条短信。

以上这五个步骤,都由逻辑层(Logic Tier)负责。前三步容易解决,都是简单的数据库操作。最后两步,需要用到一个辅助工具,队列。队列的意义在于,分离了任务的产生与任务的执行。

队列的实现方式有多种,例如Apache Mina[9]就可以用来做队列。但是Twitter团队自己动手实现了一个队列,Kestrel [10,11]。Mina与Kestrel,各自有什么优缺点,似乎还没人做过详细比较。

不管是Kestrel还是Mina,看起来都很复杂。或许有人问,为什么不用简单的数据结构来实现队列,例如动态链表,甚至静态数组?如果逻辑层只在一台服务器上运行,那么对动态链表和静态数组这样的简单的数据结构,稍加改造,的确可以当作队列使用。Kestrel和Mina这些“重量级”的队列,意义在于支持联络多台机器的,分布式的队列。在本系列以后的篇幅中,将会重点介绍。

最后说说前段,表述层。

表述层的主要职能有两 个,1. HTTP协议处理器(HTTP Processor),包括拆解接收到的用户请求,以及封装需要发出的结果。2. 分发器(Dispatcher),把接收到的用户请求,分发给逻辑层的机器处理。如果逻辑层只有一台机器,那么分发器无意义。但是如果逻辑层由多台机器组成,什么样的请求,发给逻辑层里面哪一台机器,就大有讲究了。逻辑层里众多机器,可能各自专门负责特定的功能,而在同功能的机器之间,要分摊工作,使负载均衡。

访问Twitter网站的,不仅仅是浏览器,而且还有手机,还有像QQ那样的电脑桌面工具,另外还有各式各样的网站插件,以便把其它网站联系到Twitter.com上来[12]。因此,Twitter的访问者与Twitter网站之间的通讯协议,不一定是HTTP,也存在其它协议。

三段论的Twitter架构,主要是针对HTTP协议的终端。但是对于其它协议的终端,Twitter的架构没有明显地划分成三段,而是把表述层和逻辑层合二为一,在Twitter的文献中,这二合一经常被称为“API”。

综上所述,一个能够完成Twitter基本功能的,简单的架构如Figure 1 所示。或许大家会觉得疑惑,这么出名的网站,架构就这么简单?Yes and No,2006年5月Twitter刚上线的时候,Twitter架构与Figure 1差距不大,不一样的地方在于加了一些简单的缓存(Cache)。即便到了现在,Twitter的架构依然可以清晰地看到Figure 1 的轮廓。

解剖Twitter 【2】三段论

Figure 1. The essential 3-tier of Twitter architecture
Courtesy http://farm3.static.flickr.com/2683/4051785892_e677ae9d33_o.png

Reference,

[7] Tweets中常用的工具 (http://www.ccthere.com/article/2383833)
[8] 构建基于PHP的微博客服务 (http://webservices.ctocio.com.cn/188/9092188.shtml)
[9] Apache Mina Homepage (http://mina.apache.org/)
[10] Kestrel Readme (http://github.com/robey/kestrel)
[11] A Working Guide to Kestrel. (http://github.com/robey/kestrel/blob/master/docs/guide.md)
[12] Alphabetical List of Twitter Services and Applications (http://en.wikipedia.org/wiki/List_of_Twitter_services_and_applications)

(5个打分, 平均:4.40 / 5)

雁过留声

“解剖Twitter 【2】三段论”有9个回复

  1. wenlujon 于 2010-03-08 6:08 下午

    受益匪浅。有一个问题就是,如果离线的人上线的话,如何去更新到发送的短信?

  2. 邓侃 于 2010-03-08 8:12 下午

    “如何去更新到发送的短信?”

    你是说,如何更新已经发出的短信?

    一旦发出,估计是改不了了。如果改的话,无异于发了一条新的短信。

  3. wenlujon 于 2010-03-08 8:45 下午

    哦,不是这个意思,我是说如果某一个人发了一条短信(短信是否就是发布的内容?),而在发送的时候我(我follow了发布人)恰好不在线(这个在线是否是指通过帐号登录到twitter?),内部是怎样去收取之前发送的短信?

  4. 邓侃 于 2010-03-08 10:02 下午

    这个问题问得好,后续章节中有详细解答。呵呵

  5. fonix 于 2010-03-09 12:00 上午

    其实 解剖Twitter 【1】万事开头易 里面回答过wenlujon的问题:

    当用户打开自己的个人空间时,Twitter先查阅Following表,找到所有following的作者的ID。然后去数据库读取每一位作者最近写的短信。汇总后按时间顺序显示在用户的个人主页上。

  6. fonix 于 2010-03-09 12:05 上午

    其中有个词“最近”,还需要进一步解释:

    就是从我所有following的人的短信表里,查找所有创建时间>=我上次在线时,server最后一次为我更新个人主页的时间

  7. sarlmolapple 于 2010-03-10 7:30 上午

    应该还有个字段isread吧?

  8. 邓侃 于 2010-03-10 4:00 下午

    回复Sarlmolapple,

    不一定需要isread字段。

    设想一个读者,following几个作者。这个读者经常上网看twitter,而他follow的作者不经常写。

    这样,假如T1时间,这个读者上网后,看到几条新帖子。T2时间,他再次上网,假如所有的帖子他都已经看过,那么是不是在他的主页上,就应该不显示任何帖子呢?显然不是。

    所以设置isread,不一定有意义。

  9. pandonix 于 2010-03-14 1:58 上午

    首先赞一个,文章分析的很到位,但对于”例如Apache Mina[9]就可以用来做队列”,不敢苟同。Mina只是一个基于Java Nio的异步网络框架,并非一个队列服务。Apache的开源的队列是ActiveMQ