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技术架构的介绍:
📊 关键数据
- 每天 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 独特的使用模式
- 广泛的平均影响力:每天发布5000多万个帖子,平均每个帖子会送达数百人。不仅仅是少数用户拥有数百万粉丝。Tumblr用户的关注图通常有数百个关注者。这与其他社交网络不同,也是Tumblr难以扩展的主要原因。
- 用户停留时间长:用户花在Tumblr上的时间在所有社交网络中排名第二。内容是图片和视频,引人入胜。帖子不全是短文,用户会发布深度内容,值得阅读,因此用户会停留数小时。
- 深度阅读习惯:用户与其他用户建立联系后,会翻阅Dashboard中数百页之前的内容来阅读。而其他社交网络更像是一个你只抽样浏览的信息流。
- 海量更新处理:综合用户数量、用户的平均影响力以及用户的高发布活跃度,需要处理的更新量极其巨大。
两大平台组件
Tumblr 作为一个平台有两个主要组成部分:
- 公开 Tumblelog(博客):公众所见的博客页面。由于其动态性不强,易于缓存。
- Dashboard(仪表盘):类似于Twitter的时间线。用户实时接收所有关注者的更新。
- 与博客相比,其扩展特性截然不同。缓存作用有限,因为每个请求(尤其对于活跃关注者)都不同。
- 需要实时且一致,不应显示陈旧数据。需要处理的数据量巨大。每日帖子数据约50GB,而每日关注者列表更新达2.7TB。所有媒体文件都存储在S3上。
- 大多数用户将Tumblr用作内容消费工具。在每日5亿+的页面浏览量中,70% 来自Dashboard。
- Dashboard的可用性一直很好。Tumblelog则稍差,因为其遗留基础设施难以迁移。团队规模小,只能优先解决关键扩展问题。
🏛️ 旧 Tumblr 架构
- 遗留问题:早期在Rackspace时,为每个自定义域名博客设置了一个A记录。当规模超出Rackspace时,用户过多已无法迁移(2007年)。至今仍有自定义域名托管在Rackspace,需通过HAProxy和Varnish路由回其托管机房。
- 典型的LAMP演进路径:
- 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缓存集群。
- 工作原理:
- 用户被映射到特定的Cell。
- 所有Cell通过Firehose消费所有新帖子。
- 当用户访问Dashboard时,请求被路由到其归属的Cell,服务节点从HBase中读取其Dashboard数据并返回。
- 请求流程:用户发布帖子 → 帖子写入Firehose → 所有Cell消费该帖子并写入Cell内的帖子数据库 → Cell查找该帖子发布者的关注者是否在本Cell内 → 如果是,则用帖子ID更新这些关注者的收件箱。
关键设计思想:数据全复制
- 核心:所有帖子被复制到所有Cell中。每个Cell都存储了所有帖子的一个副本。
- 存储设计:使用两个HBase表:
- 帖子内容表:存储每个帖子的完整内容(每日增长约50GB/Cell)。
- 用户收件箱映射表:存储本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接受采访并提供宝贵见解。)
🔗 相关资源
- Tumblr Engineering Blog
- Building Network Services with Finagle and Ostrich
- Ostrich - Scala服务器的统计收集和报告工具
- ID Generation at Scale - Blake Matheny
- Finagle - JVM网络栈,用于构建异步RPC客户端和服务器
- Massively Sharded MySQL at Tumblr - 关于MySQL分片的优秀演示文稿
- Staircar: Redis-powered notifications - Blake Matheny