坊间传闻, 在2010年的春季, Twitter的搜索服务团队在越来越大的流量压力下, 同时也为了搜索功能的增强, 放弃了原来MySQL+Ruby的方案,采用了Lucene+Blender的解决方案. 最近得到的结果是喜人的, 目前Twitter的搜索服务速度提高了3倍, 同时也为未来的继续增强打下了良好的技术基础,原文在这里:http://engineering.twitter.com/2011/04/twitter-search-is-now-3x-faster_1656.html
让我们先看看数据
Twitter搜索目前日服务过10亿次查询. 这里以这次的日本海啸#tsunami为关键词做比较, 启动Blender后, 其中95%的延时从800ms降低到了250ms,同时CPU负荷也降低了一半. 伴随而来, 每台服务器的吞吐量提高了10倍. 相当可喜. 看下图:
Twitter目前的搜索架构
让我们看看这变化背后的架构. 在入正题之前, 让我们谈谈之前基于RoR的架构的不足. 我们知道每个App Server都跑着固定数目的单线程rails工作进程(Passenger),每个进程都做这样的事:
- 解析查询
- 查询索引服务器, 同步的!
- 聚合结果, 渲染.
众所周知, 这种同步的处理机制并不能充分发挥CPU的性能. 同时, 这种基于Ruby的机制, 让我们很难去添加新功能, 提高搜索的可靠性. 所以, 我们最后转向了Blender,它是这样工作的:
- 异步的内容聚合服务. 没有线程会等着网络I/O结束.
- 从后台服务器聚合结果, 比如, 实时tweet索引, 最流行tweet索引和地理tweet索引.
- 优雅地处理各个不同服务之间的依赖性.
下图就是Twitter搜索引擎的简要架构图. 网络, API或者客户端过来的请求会被Blender处理然后发给后台服务, 由workflow智能地处理其中的依赖性. 最后, 这些服务返回的数据被聚合起来渲染给这些客户端.
Blender简要介绍
Blender是在Netty的基础上开发的Thrift和HTTP服务. Netty是一个异步的,事件驱动的网络编程框架和工具,使用Netty 可以快速开发出可维护的,高性能、高扩展能力的协议服务及其客户端应用。我们之所以用Netty而不是Mina, Jetty, 是因为Netty有更干净的API, 更好的文档, 同时Twitter有一些其他的项目也在使用它. 我们写了一个简单的Thrift编解码器来解码从Netty的channel buffer进来的Thrift请求, 然后返回编码后的Thrift响应. Channel是Netty里的一个关键概念, 它封装了socket连接, 并提供了接口做I/O操作, 如读,写,连接和绑定.所有的Channel的I/O操作都是原生异步的, 也就是每个I/O操作都会立刻返回ChannelFuture的实例, 当操作成功或失败或取消后就会被通知到. 当一个Netty服务器接受了一个新的连接, 它创建了一个新的channel pipeline去处理这个连接. Channel pipeline包含了一系列的business逻辑相关的handlers. 也就是Blender需要实现的.
Workflow框架
Blender里的workflow就是一系列的有依赖关系的后台服务. Blender自动解决了服务之间的依赖关系. 比如Service A依赖于Service B, 那么A会先被处理, 然后把结果传给B. 下面这个图更直观的表现了workflows是什么, 即有向无环图.
在上图中, 我们有6个服务{s1, s2, s3, s4, s5, s6}. 一条有向线表示依赖关系, 比如s3和s1表示在请求s1前必须先请求s3, 因为s1需要s3返回的结果.基于此, Blender执行了拓扑排序得到了执行顺序应该是{(s3, s4), (s1, s5, s6), (s2)}. 这意味着s3和s4可以并行的执行, 而一旦有结果后, s1, s5和s6也可以并行的执行, 最后执行s2. 而这执行的顺序, 就会映射到我们上面讲到的Netty pipeline.
多路传输请求
因为workflow会被映射到Netty pipeline, 我们需要把客户端请求发送给合适的pipeline. 所以我们开发了一个代理层, 用来多路传输分发客户端请求给pipeline:
- 当一个远程的Thrift客户端创建了一条到Blender的长链接, 代理层创建一个到每个本地workflow服务器的本地客户端映射. 记住每个本地的workflow客户端都是跑在Blender的JVM进程里并且在Blender进程启动时就实例化好了.
- 代理层分析请求, 找出被请求的workflow, 然后发送给相应的workflow服务器
- 类似的, 当workflow服务器返回结果后, 代理层会写回给远程的客户端.
这里使用了Netty的事件驱动模式, 所以所有的操作都是异步的.
未来
目前我们还是用Rails来实现这个代理层, 因为考虑到用户体验一致性并做平滑的过度. 下面我们的计划是完全淘汰RoR, 全面迁向Blender. 我想这会对性能带来更大的提高.
下面是工作在Blender上的Twitter开发人员: : Abhi Khune, Aneesh Sharma, Brian Larson, Frost Li, Gilad Mishne, Krishna Gade, Michael Busch, Mike Hayes, Patrick Lok, Raghavendra Prabhu, Sam Luckenbill, Tian Wang, Yi Zhuang, Zhenghua Li.