# 解剖Twitter 【7】 作为一种进步的不彻底

- 作者：邓侃
- 发布时间：2010-03-13 15:30
- 原文：https://www.ifanr.com/18823

不彻底的工作方式，对于架构设计是一种进步。

当一个来自浏览器的用户请求到达Twitter后台系统的时候，第一个迎接它的，是Apache Web Server。第二个出场的，是Mongrel Rails Server。Mongrel既负责处理上传的请求，也负责处理下载的请求。Mongrel处理上传和下载的业务逻辑非常简洁，但是简洁的表象之下，却蕴含着反常规的设计。这种反常规的设计，当然不是疏忽的结果，事实上，这正是Twitter架构中，最值得注意的亮点。

![](http://docs.google.com/File?id=drfcsw8_446f3596khs_b)

Figure 9. Twitter internal flows   
Courtesy http://farm3.static.flickr.com/2766/4095392354\_66bd4bcc30\_o.png

所谓上传，是指用户写了一个新短信，上传给Twitter以便发表。而下载，是指Twitter更新读者的主页，添加最新发表的短信。 Twitter下载的方式，不是读者主动发出请求的pull的方式，而是Twitter服务器主动把新内容push给读者的方式。先看上传，Mongrel处理上传的逻辑很简洁，分两步。

1\. 当Mongrel收到新短信后，分配一个新的短信ID。然后把新短信的ID，连同作者ID，缓存进Vector MemCached服务器。接着，把短信ID以及正文，缓存进Row MemCached服务器。这两个缓存的内容，由Vector MemCached与Row MemCached在适当的时候，自动存放进MySQL数据库中去。

2\. Mongrel在Kestrel消息队列服务器中，寻找每一个读者及作者的消息队列，如果没有，就创建新的队列。接着，Mongrel把新短信的ID，逐个放进“追”这位作者的所有在线读者的队列，以及作者本人的队列。

品味一下这两个步骤，感觉是Mongrel的工作不彻底。一，把短信及其相关IDs，缓存进Vector MemCached和Row Cached就万事大吉，而不直接负责把这些内容存入MySQL数据库。二，把短信ID扔进Kestrel消息队列，就宣告上传任务结束。Mongrel 没有用任何方式去通知作者，他的短信已经被上传。也不管读者是否能读到新发表的短信。

为什么Twitter采取了这种反常规的不彻底的工作方式？回答这个问题以前，不妨先看一看Mongrel处理下载的逻辑。把上传与下载两段逻辑联系起来，对比一下，有助于理解。Mongrel下载的逻辑也很简单，也分两步。

1\. 分别从作者和读者的Kestrel消息队列中，获得新短信的ID。

2\. 从Row MemCached缓存器那里获得短信正文。以及从Page MemCached那里获得读者以及作者的主页，更新这些主页，也就是添加上新的短信的正文。然后通过Apache，push给读者和作者。

对照Mongrel处理上传和下载的两段逻辑，不难发现每段逻辑都“不彻底”，合在一起才形成一个完整的流程。所谓不彻底的工作方式，反映了 Twitter架构设计的两个“分”的理念。一，把一个完整的业务流程，分割成几段相对独立的工作，每一个工作由同一台机器中不同的进程负责，甚至由不同的机器负责。二，把多个机器之间的协作，细化为数据与控制指令的传递，强调数据流与控制流的分离。

分割业务流程的做法，并不是Twitter的首创。事实上，三段论的架构，宗旨也是分割流程。Web Server负责HTTP的解析，Application Server负责业务逻辑，Database负责数据存储。遵从这一宗旨，Application Server的业务逻辑也可以进一步分割。

1996年，发明TCL语言的前伯克利大学教授John Ousterhout，在Usenix大会上做了一个主题演讲，题目是“为什么在多数情况下，多线程是一个糟糕的设计\[36\]”。2003年，同为伯克利大学教授的Eric Brewer及其学生们，发表了一篇题为“为什么对于高并发服务器来说，事件驱动是一个糟糕的设计\[37\]”。这两个伯克利大学的同事，同室操戈，他们在争论什么？

所谓多线程，简单讲就是由一根线程，从头到尾地负责一个完整的业务流程。打个比方，就像修车行的师傅每个人负责修理一辆车。而所谓事件驱动，指的是把一个完整的业务流程，分割成几个独立工作，每个工作由一个或者几个线程负责。打个比方，就像汽车制造厂里的流水线，有多个工位组成，每个工位由一位或者几位工人负责。

很显然，Twitter的做法，属于事件驱动一派。事件驱动的好处在于动态调用资源。当某一个工作的负担繁重，成为整个流程中的瓶颈的时候，事件驱动的架构可以很方便地调集更多资源，来化解压力。对于单个机器而言，多线程和事件驱动的两类设计，在性能方面的差异，并不是非常明显。但是对于分布式系统而言，事件驱动的优势发挥得更为淋漓尽致。

Twitter把业务流程做了两次分割。一，分离了Mongrel与MySQL数据库，Mongrel不直接插手MySQL数据库的操作，而是委托MemCached全权负责。二，分离了上传和下载两段逻辑，两段逻辑之间通过Kestrel队列来传递控制指令。

在John Ousterhout和Eric Brewer两位教授的争论中，并没有明确提出数据流与控制流分离的问题。所谓事件，既包括控制信号，也包括数据本身。考虑到通常数据的尺寸大，传输成本高，而控制信号的尺寸小，传输简便。把数据流与控制流分离，可以进一步提高系统效率。

在Twitter系统中，Kestrel消息队列专门用来传输控制信号，所谓控制信号，实际上就是IDs。而数据是短信正文，存放在Row MemCached中。谁去处理这则短信正文，由Kestrel去通知。

Twitter完成整个业务流程的平均时间是500ms，甚至能够提高到200-300ms，说明在Twitter分布式系统中，事件驱动的设计是成功。

Kestrel消息队列，是Twitter自行开发的。消息队列的开源实现很多，Twitter为什么不用现成的免费工具，而去费神自己研发呢？

Reference,

\[36\] Why threads are a bad idea (for most purposes), 1996.   
(http://www.stanford.edu/class/cs240/readings/threads-bad-usenix96.pdf)   
\[37\] Why events are a bad idea (for high-concurrency servers), 2003.   
(http://www.cs.berkeley.edu/~brewer/papers/threads-hotos-2003.pdf)
