Tumblr 架构设计

原文:The Tumblr Architecture Yahoo Bought For A Cool Billion Dollars

据报道,雅虎以11亿美元收购了Tumblr。你可能记得HighScalability曾剖析过Instagram,它同样被Facebook以巨额收购。这是巧合吗?你来判断。

雅虎买到的究竟是什么?这笔交易的商业价值我无法评判,但如果从技术层面进行尽职调查,Tumblr很可能会获得一个大大的好评。欲知详情,请继续阅读……

凭借每月超过150亿的页面浏览量,Tumblr已成为一个异常受欢迎的博客平台。用户可能喜欢它的简洁、美观、对用户体验的极致关注,或其友好且活跃的社区。

但以每月超过30%的速度增长并非没有挑战,其中就包括一些可靠性问题。需要认识到Tumblr的运营规模惊人地庞大:每天5亿页面浏览量,峰值约每秒4万请求,每天约3TB新数据需要存储,所有这些都运行在1000多台服务器上。

成功的初创公司有一个普遍模式,那就是从初创公司跨越到极其成功的初创公司,这个过程充满危险。这意味着在只有四名工程师的情况下,你需要找人、演进基础设施、维护旧基础设施,同时还要处理每月巨大的流量增长,这迫使你必须在工作重点上做出艰难选择。这正是Tumblr曾经面临的情况。现在,拥有二十名工程师的团队有足够精力去解决问题,并开发出一些非常有趣的解决方案。

Tumblr开始时是一个相当典型的大型LAMP应用。他们现在正朝着基于Scala、HBase、Redis、Kafka、Finagle的分布式服务模式发展,并为其Dashboard(仪表盘)设计了一个引人注目的基于单元(Cell)的架构。当前的努力方向是修复PHP应用中的短期问题,剥离功能,并通过服务以正确的方式重构。

Tumblr的主题是在巨大规模下的转型。从LAMP技术栈转向一个有些前沿的技术栈。从一个小型初创团队转变为一个功能齐全、随时待命,能够持续推出新功能和基础设施的开发团队。帮助我们理解Tumblr如何实践这一主题的是资深技术专家、Tumblr分布式系统工程师Blake Matheny。以下是Blake对Tumblr技术架构的介绍:

网站http://www.tumblr.com/


📊 关键数据

  • 每天 5亿 页面浏览量
  • 每月 150亿+ 页面浏览量
  • 20名 工程师
  • 峰值约 每秒4万 请求
  • 每天 1+ TB 数据流入Hadoop集群
  • 每天数TB数据流入 MySQL/HBase/Redis/Memcache
  • 每月增长率超过 30%
  • 生产环境约有 1000个 硬件节点
  • 平均每名工程师每月负责处理数十亿页面访问
  • 每日新增帖子约 50GB。每日关注者列表更新约 2.7TB
  • Dashboard(仪表盘)每秒处理 百万级 写入、5万次 读取,且仍在增长。

🛠️ 软件栈

  • 开发环境:OS X;生产环境:Linux (CentOS, Scientific)
  • Web服务器:Apache
  • 编程语言:PHP, Scala, Ruby
  • 数据库/存储:Redis, HBase, MySQL
  • 缓存/代理/队列:Varnish, HA-Proxy, nginx, Memcache, Gearman, Kafka, Kestrel, Finagle
  • 通信协议:Thrift, HTTP
  • 运维工具Func(安全的可脚本化远程控制框架)、Git, Capistrano, Puppet, Jenkins

🖥️ 硬件规模

  • 500台 Web服务器
  • 200台 数据库服务器(其中许多是备用池,用于故障替换)
    • 47个数据库池
    • 30个数据库分片
  • 30台 Memcache 服务器
  • 22台 Redis 服务器
  • 15台 Varnish 服务器
  • 25台 HAProxy 节点
  • 8台 nginx 服务器
  • 14台 任务队列服务器 (Kestrel + Gearman)

🏗️ 架构概览

Tumblr 独特的使用模式

  1. 广泛的平均影响力:每天发布5000多万个帖子,平均每个帖子会送达数百人。不仅仅是少数用户拥有数百万粉丝。Tumblr用户的关注图通常有数百个关注者。这与其他社交网络不同,也是Tumblr难以扩展的主要原因。
  2. 用户停留时间长:用户花在Tumblr上的时间在所有社交网络中排名第二。内容是图片和视频,引人入胜。帖子不全是短文,用户会发布深度内容,值得阅读,因此用户会停留数小时。
  3. 深度阅读习惯:用户与其他用户建立联系后,会翻阅Dashboard中数百页之前的内容来阅读。而其他社交网络更像是一个你只抽样浏览的信息流。
  4. 海量更新处理:综合用户数量、用户的平均影响力以及用户的高发布活跃度,需要处理的更新量极其巨大。

两大平台组件

Tumblr 作为一个平台有两个主要组成部分:

  1. 公开 Tumblelog(博客):公众所见的博客页面。由于其动态性不强,易于缓存。
  2. Dashboard(仪表盘):类似于Twitter的时间线。用户实时接收所有关注者的更新。
    • 与博客相比,其扩展特性截然不同。缓存作用有限,因为每个请求(尤其对于活跃关注者)都不同。
    • 需要实时且一致,不应显示陈旧数据。需要处理的数据量巨大。每日帖子数据约50GB,而每日关注者列表更新达2.7TB。所有媒体文件都存储在S3上。
    • 大多数用户将Tumblr用作内容消费工具。在每日5亿+的页面浏览量中,70% 来自Dashboard。
    • Dashboard的可用性一直很好。Tumblelog则稍差,因为其遗留基础设施难以迁移。团队规模小,只能优先解决关键扩展问题。

🏛️ 旧 Tumblr 架构

  • 遗留问题:早期在Rackspace时,为每个自定义域名博客设置了一个A记录。当规模超出Rackspace时,用户过多已无法迁移(2007年)。至今仍有自定义域名托管在Rackspace,需通过HAProxy和Varnish路由回其托管机房。
  • 典型的LAMP演进路径
    • 历史上主要使用PHP开发,几乎每位工程师都编写PHP。
    • 从一台Web服务器、一台数据库服务器和一个PHP应用起步。
    • 为扩展,先后引入了Memcache、前端缓存、HAProxy和MySQL分片。其中,MySQL分片起到了巨大作用。
    • 采用“榨干单台服务器性能”的策略。过去一年里,他们用C语言开发了几个后端服务:一个ID生成器Staircar(使用Redis驱动Dashboard通知)。
  • Dashboard 的 Scatter-Gather 模式:当用户访问Dashboard时,系统会拉取并显示其所有关注者的事件。该模式预计还能支撑约6个月。由于数据是按时间排序的,分片方案效果不佳。

🚀 新 Tumblr 架构方向

向 JVM 中心化转型

出于招聘和开发速度的考虑,转向以JVM为中心的技术栈。

  • 目标:将所有逻辑从PHP应用中剥离,迁移到服务中,使应用成为一层薄薄的服务代理,仅负责请求认证、表示层等。
  • 为何选择 Scala & Finagle
    • 内部团队多具有Ruby和PHP经验,Scala具有吸引力。
    • Finagle(来自Twitter) 是选择Scala的关键因素。它处理了大多数分布式问题,如分布式追踪、服务发现、服务注册,无需重复造轮子。
    • 在JVM上,Finagle提供了所需的所有基础模块(Thrift, ZooKeeper等)。
    • 选择Scala而非Node.js,是因为基于JVM更容易扩展团队,有大量经过测试的Java代码库可用,且生态系统更成熟。

基础设施演进

  • 服务化:内部服务正从C/libevent基础转向Scala/Finagle基础。
  • 数据存储:虽然大量数据仍存储在深度分片的MySQL架构中,但已开始使用更新的非关系型数据存储如HBase和Redis。
  • HBase 应用:用于支撑其URL缩短服务,存储数十亿URL及历史数据和分析。对于高写入场景(如Dashboard替代方案的每秒百万级写入)表现出色。之所以没有用HBase全面替代MySQL,是因为当时团队尚不能将核心业务押注于HBase,故先在小型非关键路径项目中积累经验。
  • 通用服务框架
    • 投入大量时间预先解决如何管理分布式系统的运维问题。
    • 为内部服务构建了类似Rails脚手架的工具模板,所有服务从运维角度看都保持一致,便于监控和管理。
    • 使用SBT(Scala构建工具)及其插件处理构建过程中的通用活动。

运维与扩展

  • 前端架构:使用HAProxy作为前端负载均衡。公开博客可能经过Varnish缓存。
  • 高可用性:由于使用商用硬件,平均无故障时间较低,因此准备了大量备用数据库服务器以应对故障。
  • 服务团队:有专门团队负责后端服务开发,每2-3周会推出一个新服务,包括Dashboard通知、Dashboard二级索引、URL缩短器等。
  • 任务队列:使用Gearman处理长时间运行的“触发即忘”型任务。

🔥 内部 Firehose(数据流)

  • 背景:内部应用需要实时访问用户活动流(如创建/删除帖子、点赞/取消点赞等)。挑战在于实时分发如此巨量的数据。
  • 解决方案:创建了一个中心化的内部消息总线——Firehose。服务和应用通过Thrift协议与之通信。
  • 技术选型:使用LinkedIn的Kafka存储消息。内部消费者通过HTTP流从Firehose读取数据。
  • 特点
    • 可回溯:不像Twitter的Firehose(数据可能丢失),Tumblr的Firehose保留一周数据,客户端可指定从某个时间点开始读取。
    • 无重复消费:基于Kafka的“消费者组”概念,每个消费者组内的客户端不会看到重复数据,允许多客户端并行独立处理数据。

📬 基于 Cell 架构的 Dashboard 收件箱设计

为何需要改变?

当前的Scatter-Gather模式即将达到极限,需要转向一种类似Facebook Messages的“收件箱模型”,并采用基于Cell的架构。

收件箱模型

  • 概念:将用户的Dashboard(包含关注者的帖子和其他用户的操作)按时间顺序逻辑地存储在一起,称为“收件箱”。
  • 优势:解决了Scatter-Gather问题,只需查询收件箱内容,成本远低于查询每个关注者。此模型将具备更长的扩展空间。

Cell(单元)架构详解

  • 定义:一个Cell是一个自包含的单元,包含一部分用户所需的所有数据。渲染用户Dashboard所需的所有数据都在其归属的Cell内。
  • 构成:每个Cell包含一个HBase集群、一个服务集群和一个Redis缓存集群。
  • 工作原理
    1. 用户被映射到特定的Cell。
    2. 所有Cell通过Firehose消费所有新帖子。
    3. 当用户访问Dashboard时,请求被路由到其归属的Cell,服务节点从HBase中读取其Dashboard数据并返回。
  • 请求流程:用户发布帖子 → 帖子写入Firehose → 所有Cell消费该帖子并写入Cell内的帖子数据库 → Cell查找该帖子发布者的关注者是否在本Cell内 → 如果是,则用帖子ID更新这些关注者的收件箱。

关键设计思想:数据全复制

  • 核心所有帖子被复制到所有Cell中。每个Cell都存储了所有帖子的一个副本
  • 存储设计:使用两个HBase表:
    1. 帖子内容表:存储每个帖子的完整内容(每日增长约50GB/Cell)。
    2. 用户收件箱映射表:存储本Cell内每个用户收件箱中的帖子ID列表(每日增长约2.7TB/Cell,是主要增长来源)。
  • 优势
    • 强隔离与并行化:每个Cell都是独立的并行单元,便于水平扩展。一个Cell的故障不会影响其他Cell。
    • 无跨Cell通信:处理用户Dashboard请求无需与其他Cell通信,性能好,复杂度低。
    • 状态管理:易于记录用户已读状态。
    • 滚动升级:可在不同Cell上测试新版本软件。
  • 权衡:以存储空间的冗余换取极高的读取性能、可用性和可维护性

处理热门用户

通过选择性物化(Selective Materialization) 处理拥有数百万粉丝的极度活跃用户:

  • 两种分发模式:为热门用户和普通用户设计不同的数据分发模式。
  • 数据差异化处理:对于热门用户的帖子,不会立即推送到所有关注者的收件箱,而是根据访问模式进行物化。

🏙️ 在纽约的初创体验

  • 纽约环境不同,金融和广告行业发达,但有初创公司经验的人才相对较少。
  • 近年来,纽约开始注重扶持初创企业。纽约大学和哥伦比亚大学有项目引导学生到初创公司实习。前市长布隆伯格也在推动建立专注于科技的本地校区。

👥 团队结构

  • 基础设施团队:负责网络层(L5及以下)、IP地址、DNS、硬件配置。
  • 平台团队:负责核心应用开发、SQL分片、服务、Web运维。
  • SRE团队:介于服务团队和Web运维团队之间,专注于可靠性和扩展性的即时需求。
  • 服务团队:专注于更具战略性的、未来1-2个月的需求。
  • Web运维团队:负责问题检测、响应和性能调优。

🚢 软件部署

  • 演进过程:rsync脚本 → Capistrano → 基于Func的协调系统。
  • 当前方案:使用RedHat的Func作为轻量级API,向一组主机下发部署命令,避免了SSH连接的瓶颈。部署命令本身仍通过Capistrano执行,支持基于目录的版本管理。
  • 特性:部署期间连接会安全排干;新功能上线前会先以“暗模式”运行。

💻 开发实践

  • 标准化工具栈:早期允许自由选择工具,但随着团队扩大,为便于协作和快速解决问题,现已标准化工具栈。
  • 流程:采用轻量级的类Scrum流程。每位开发者都有通过Puppet更新的预配置开发机。
  • 测试:PHP应用主要通过代码审查;服务端则有提交钩子、Jenkins持续集成和构建通知。

🧑‍💻 招聘流程

  • 面试重点:避免数学谜题和脑筋急转弯,专注于候选人实际将要做的工作。评估其是否聪明、能否完成任务。更看重寻找优秀人才而非设置高门槛。
  • 实践方式:注重编码,要求提供代码样本,电话面试中使用Collabedit进行实时协作编码。鼓励候选人使用Google等工具,模拟真实工作场景。
  • 挑战:找到具有应对Tumblr这种流量级别扩展经验的人才非常困难。
  • 文化:团队有极客文化,曾在工程博客上发文纪念丹尼斯·里奇和约翰·麦卡锡的逝世。

🎓 经验教训总结

  • 全面自动化
  • MySQL(加分片)能扩展,但应用本身不能成为瓶颈
  • Redis 非常出色
  • Scala 应用性能卓越
  • 对不确定能否成功的项目,要果断放弃
  • 招聘:不要基于无用的技术难关来筛选人,要看他们是否适合团队并能胜任工作。选择有助于招聘所需人才的技术栈,围绕团队技能进行建设。
  • 学习与交流:多阅读论文和博客(如单元架构、选择性物化等关键设计思想都来自外部)。积极向同行(如Facebook、Twitter、LinkedIn的工程师)请教经验。
  • 技术选型涉水前行,而非跳跃。在将HBase、Redis等技术投入生产前,先通过试点项目或在损害可控的角色中使用,积累经验。

(感谢Blake Matheny接受采访并提供宝贵见解。)

🔗 相关资源

发布于: 2013-05-29