【导语】这是3月14日格灵深瞳Muse组主办的Meetup——实时数据分析的讨论中,我们的软件“攻城狮”王超所做的精彩分享。现在将主要内容整理出来,以供大家参考,具体内容可点击文末视频进行观看。如果您也有观点想要分享,欢迎在线留言。
正文分割线
大家好,今天分享的主题是Twitter的timeline架构及其架构在演进过程中学到的一些经验。
在 Twitter 架构的演进过程中,主要有三点经验:
第一个经验是,服务架构是递增改进的。
Twitter 最开始的架构,并不怎么美观,是典型的 Monolithic 服务:
并不是说这个架构不好。每个架构都有其适用的特定历史时期。在 Twitter 团队早期,这个架构是相当合适的,当时 Twitter 的功能也简单,这个架构适合小团队快速迭代。但是随着用户量的增长和内部服务的复杂化,这个架构就臃肿了。存储结构不可扩展,每次部署都要涉及所有的服务器,并行化不高,并且内部服务紧耦合等等一系列缺点慢慢浮现。那么解决方法是怎么呢?
动手术!化大为小。最显然的是先把持久层拆分,避免所有服务的读写瓶颈:
其中, Tweets,Users,SocialGraph 三个服务是基于 Gizzard 的。通俗来讲,Gizzard 是个分布式数据库的前端。这三个服务的数据都必须做持久化的。而 Timelines 数据是存在 Redis 缓存里的。它没必要做持久化。后面会详细说到。
Monorail(Monolithic + Ruby on Rails)这个服务也可以拆分如下:
那么,当你发送一条 tweet 的时候,整个系统发生了什么呢?
首先,tweet 经过 Front End,到达一个 Write API 服务。这个服务负责写操作。它把 tweet 分发给一个 Fanout 服务。Fanout 服务负责分发 tweet。它把你的 tweet 发给你的 followers 的主页 timeline 缓存内。这些 timeline 缓存在 Redis 集群里,每份有 3 个 replica。可以把你 follower 的 timeline 缓存想象成一个邮箱,你每发一条 tweet 都是给这些 follower 发一封邮件。那么,如果你有 N 个 follower,这个写操作是 O(n) 的。
当你的 follower 打开 Twitter 主页时,如果你的 follower 是活跃用户,最近30天登陆过,那么 timeline 服务直接从 Redis 集群里请求缓存的 timeline 信息。因为每个人只有一个 timeline,所以这个操作是 O(1) 的。如果这个 follower 不是活跃用户,这时就要经过一个 reconstruction 的过程。timeline 服务会请求一个 social graph 服务,拿到所有 following 的人的信息,再从这些人的用户 timeline 组合出这个 follower 的主页 timeline。这个过程可能会有几秒的延迟,但是对于人来讲,这也算是实时的了。
当发一条 tweet 时,还会经历搜索服务器,见下图左面的路径:
首先,这条 tweet 会经过一个 Ingester 服务,这个服务把 tweet 里所有能索引的东西提取出来,例如分词,地理位置,短链接扩展等等。然后,这些信息会放进 Earlybird 服务,这个过程是 O(1)的。这个服务是一个修改版的 Lucene。当收到一个搜索请求时,Blender 服务会查询所有的 Earlybird 服务器,把所有符合要求的结果整合起来,这个过程是 O(N) 的。可以看到,复杂度上,左边的路径和右边的路径正好相反:
那么问题来了,像这样一些“大V”发推时,会发生什么事情呢?
@ladygaga 发一条 tweet,在 Fanout 服务分发时,要发给几千万个人,这些人即便有十分之一的活跃用户,那么就要把 tweet 发给几百万人的 timeline 缓存里。这时会有一个有趣的现象,有的用户可能先收到 ladygaga 这条 tweet 的评论,后收到原 tweet。虽然组合 timeline 的过程会有一些 re-ranking来解决这个问题,但是怎么从本质上解决这个分发过程的低效率呢?
答案就是把上面讲的两条路径的思路混合起来,形成一个 hybrid 的方案:
当“大V”发推时,Fanout 服务并不把这条 tweet 发给所有活跃 follower 的 timeline 缓存。当这些 follower 请求主页 timeline 时,再动态地把这个用户的主页 timeline 缓存(包含所有“非大V”的 tweets)和这个用户 follow 的“大V”的 tweets整合起来。这样整体性能都可以接受。
Twitter 还有一个 push 服务,见下图最右的路径:
这个推送服务里维护了上百万长链接。当一条 tweet 发出时,这个推送服务会把它送给相关的 timeline 或移动消息端。由于维护了长连接,延迟仅在几百毫秒。
下面讲讲 Twitter 架构演进过程中的第二个经验:业务和底层实现分离。
Twitter 有团队实现了基础的框架,如 Twitter-server 和 Finagle。前者是组合服务逻辑的框架,使用 Scala 开发。后者是服务间通讯的框架,全部是开源的。这个扩架的好处是,它的服务组合非常方便:
通过这样方便地组合不同的服务,就实现了下面的流程图:
并且图中 “get tweet” 的服务是自动并行触发的。
基于这样的基础服务,Twitter 的架构可以切分为如下:
基础架构服务的团队只需要关心中部,业务逻辑的团队只需要关心上部,虽然会有一些重叠,但是分工尽量分开后,基础架构的团队可以专注于实时性的优化,而优化的结果也可以在所有业务逻辑的团队共享。这样开发更利于实现全局的实时性。
最后一个经验是,要有有效的延迟监测手段和分析框架。
Twitter 内部有两个服务,一个是分析垂直服务栈上的延迟框架:
另一个是分析水平不同服务间的延迟框架:
只有通过这些清晰明了的延迟分析,才能找出整个系统中的延迟瓶颈,最终对整体延迟做出快速的改进。下图就是 Twitter 内部不同服务间的关系,圆周上每个点都是一个服务,线是它们之间的关系。可以想像到,如果没有上述的延迟监测框架,是无法掌控整体的延迟的。
我的分享就到这里,谢谢大家!
欢迎点击下面的视频进行观看: