上一篇我们花了大部分的口水去说MQ,现在我们要言归正传说说twitter的架构了。
我们说到我们要应付的事情出了大量的消息要转发要处理,还有突然爆发的流量等等。解决这样的问题,思路应该是要提高服务器每秒对请求的响应数。通常我们是将Memcached作为Fragment Cache放在我们前端服务器上。当收到用户请求的时候我们的Router会去判断这样的Fragment Cache是否存在,如果存在的话则直接返回。这样的做法是有效率的,而且就memcahced而言,命中率很高,我们很难去确定请求的来源位置,所以我们不如把预先计算好的数据放在网络前端的RAM上面。
当然在这个之后我们还会有很多的问题,并且在twitter向我们展示的宏大架构中我们找到了一些有用的东西。
首先我们看看twitter用到的这些中间件。

memcached,这个就不说了。
Varnish,是取代Squid的一个HTTP缓存,也就是一个Page Cache,据说性能至少是Squid的四倍。这个数据来源也是据说挪威的在线报纸vg.no,用三台Varnish替代了12台Squid,效果还比之前要好。
Kestrel,是twitter团队开发的一个开源的MQ,持久存储效果不错,听说代码写得也很漂亮1200来行,Kestrel是基于Memcached的文本协议,并对这些协议有所优化和完善。这个东西最初是用Ruby搞的后来又在Scala上实现了一遍。Ruby下的那个版本伸缩性不强,特别是Ruby的GC(垃圾处理)不是分代的,这意味这GC在处理完一个队列之后就会把自己给kill掉,然后MQ就崩溃了。后来移植到Scala上,它有成熟的JVM GC这个机制,而且代码还可以更简要。
Comet,一种服务器push的技术,之前facebook貌似也在用。简单的说,就是Client发送一条请求,然后Server接收到之后进入一个无限循环,将Client需要的数据push到一个response里面并且刷新它,这个response并不会被关闭,而是不断的被push数据然后刷新。知道Client断开连接之后才跳出这个循环。正是这样,我们可以说,Ajax解决了单用户的响应,而Comet则在保障性能的前提下,解决了协同多用户的响应。Comet的优点是它可以随时向客户端发送数据而不是仅仅响应用户的输入请求。而且这个数据是在现有的一个单连接上面进行,所以大大降低了发送数据的延迟时间(建立链接的开销和用户发送请求的开销)。

按照twitter自己的说法,最初他cache的策略简单到无敌,有点让人蛋疼,除了在API那里有一个Page Cache之外,然后啥都没有了。不过还好他80%的流量是来自API的。后来随着发展,twitter进行了强有力的架构的建设,其中cache策略的发展也是飞速。
一、Vector Cache,直接放在Data Pool的上面。存放了tweetID和tweetID之间的关系,tweetID是序列化的64位整数。而直写式的设计保证了极高的命中率,使得程序在Data Pool中工作的效率大大的提高,并且极大程度上加大了Data Pool和上层Cache的关联。
二、再往上是一个Row Cache,存放了数据库信息,主要是用户和tweet详细信息的相互对应。同样是直写式,并且使用了一个叫做Cache-money的程序,这是一个针对rail的ActiveRecord的直写式缓存实现。Rail的ActiveRecord具有很强的灵活性,完全不亚于hibernate,而且使用简单,效率也很高。
三、接着往上依次便是用memcached做的Fragment Cache和用Varnish做的Page Cache。其中Fragment Cache是直接消费Row甚至是Vector中的数据,打包成JSON或者XML。虽然这是一个直读式的Cache,但是由于特殊的层级关系和工作特性,他的命中率依然能达到95%以上。
这样的缓存策略下,数据库的大部分压力都被转到了后端程序中,而后端程序对资源的占用较为平滑,可预估性也比较强。
另外twitter还提到要为Page Cache提供一个独立的池,理由很简单,他们希望能够为Page Cache提供一个永远不会自毁的Cache的,而是一种按时间来划分的键值模式,以致于可以根据HTTP的协议头来对数据进行切片。我不喜欢这样的方式,虽然它应该会很有用,但是当所有的流过的页面都要在留下痕迹时,情况也许就会变得不太可控。
这样的Cache的架构也有他的问题,因为底层的两个直写式的Cache总是要去修补上面直读式Cache中的数据,这必然会造成MQ的压力。但毕竟有舍才有得,或许这才是架构的魅力吧。