Pinterest 架构:两年内月 PV 从零到百亿【翻译】

原文地址html

这篇文章,采用 Markdown 方式,写的仍是比较实在的,要是有架构图就行了。算法

Pinterest 是图片版的 Twitter,用户把本身感兴趣的东西用图钉(Pins)钉在钉板(PinBoard)上,采用 Pinterest 瀑布流的形式展示图片内容,用户无需翻页,新图片不断地自动加载到页面底端,让用户发现新的图片。截至2013年9月,该软件已进入全球最热门社交网站前十名。在移动互联网时代,网民在移动设备上更喜欢看图片。Pinterest、Snapchat、Instagram 等图片社交平台受到用户热捧,目前市场估值也明显高于其余“文本”社交网络。sql

Pinterest 正经历了指数级的增加,每个半月就翻番。在这两年里,Pinterest,从 每个月PV量0增加到100亿,从两名创始人和一个工程师成长为四十个工程师,从只有一台MySQL 服务器,发展到180台Web 服务器(Web Engine)、240台接口服务器(API Engine)、88 台 MySQL 数据库 (AWS cc2.8xlarge, a Cluster Compute 8XL) ,而且每台DB有一个备份服务器,110台Redis 实例服务(Redis Instance)、200台 Memcache 实例服务(Memcache  Instance)。数据库

clipboard

这种增加的确是使人叹为观止。咱们请来了 Pinterest 的两位创始人Yashwanth NelapatiMarty Weiner,他们将以  Scaling Pinterest 为题讲述 Pinterest 架构充满戏剧化的传奇故事。他们说,若是能在一年半前飞速发展时,能看到有人作相似题材的演讲的话,他们就会有更多的选择,以避免本身在这一年半里作出不少错误的决定。后端

这是一个很不错的演讲,充满了使人惊讶的细节。同时这个演讲也很务实,归根结底,它带来了可以让你们选择的策略。缓存

这篇演讲中有两个我最为看重的经验:安全

  1. 强大的架构在处理增加时经过简单增长相同的东西(服务器)来应对,同时还能保证系统的正确性。当遇到性能问题时,经过砸钱,简单增长服务器来扩容。若是你的架构可以作到这一点,那它就如金子般强大而珍贵!
  2. 当性能快到极限时,大多数技术其实都会失败,以它们本身的方式。这使得他们在审核工具时要考虑如下特性:成熟且简单,用得人多,良好的支持,持续的优异性能,不多失败,开源。按照这样的标准,他们选择了:MySQL, Solr、Memcache 和 Redis,放弃了 Cassandra 、Mongo。

这两点经验是相互联系的。遵循(2)中提到的能够在扩容时简单增长服务器便可。当负载增长了,成熟的产品会更少有问题。当你遇到问题时,至少但愿可以在社区中解决。当你使用的工具过于技巧化和讲究时,会发现,你遇到一堵没法逾越的墙。服务器

在这段演讲里,碎片化(sharding)优于集群(clusterting)的观点是我认为最好的一部分。所以,他们选择以 sharding 方式增加,而不是 clustering。关于他们为何选择 sharding 和如何作 sharding 是颇有趣的事,这极可能触及到你之前未考虑过的场景。网络

如今,让咱们看看 Pinterest 如何扩容。数据结构

启动于2010年3月——自我发现时期

此时此刻,你甚至不知道你在作的这个产品将要作什么。你有想法,迭代开发更新产品的频率很高。

早期的一些数字:

  • 2个创始人
  • 1个工程师
  • Rackspace(全球三大云计算中心之一,是一家全球领先的托管服务器及云计算提供商,产品包括专用服务器,电子邮件,SharePoint,云服务器,云存储,云网站等。在服务架构上提供专用托管,公有云,私有云及混合云)
  • 1个小型 Web 引擎
  • 1个小型 MySQL DB

2011年1月

仍在潜伏前进中,产品获得了一些用户反馈。此时架构图:

  • Amazon EC2 + S3 + CloudFront 云服务
  • 1台 Nginx,4台 Web 引擎(做为冗余,不是为了负载)
  • 1台 MySQL DB + 1台读 Slave MySQL 服务器(防止 Master 服务器宕机)
  • 1个任务队列  + 2个任务处理
  • 1台 MongoDB(为了统计计数)
  • 2个工程师

至2011年9月——尝试阶段

每个半月翻翻的疯狂增加阶段——毫无理性的增加。

  • 当快速发展时,全部的一切都会在每晚每星期崩溃。
  • 此时,你阅读大量白皮书,它们告诉你添加服务器,你也这么作了。所以,他们(Yashwanth Nelapati 和 Marty Weiner)开始添加不少技术。毫无例外都失败了。
  • 结果,你获得一个极为复杂的架构图。
    • Amazon EC2 + S3 + CloudFront
    • 2 Nginx、16 Web Engines + 2 API Engines
    • 5 Functionally Sharged MySQL DB + 9 Slaves
    • 4 Cassandra 节点
    • 15 Membase 节点(分红三个单独的集群)
    • 8 Memcache 节点
    • 10 Redis 节点
    • 3 任务路由(Task Routers)+ 4 Task Processors
    • 4 Elastic Search 节点
    • 3 Mongo 集群
    • 3 工程师
  • 5种主要的数据库技术只为了应付他们本身的数据。
  • 增加极快,以致MySQL负载很高,而其余全部技术都快到达极限了。
  • 当你把某个技术推至极限时,它们会以本身的方式崩溃。
  • 他们开始放弃一些技术,并问本身他们到底须要什么。因而,大规模重构架构。

2012年1月——成熟阶段

  • 从新设计架构后,系统就变成了下面的样子。
    • Amazon EC2 + S3 + Akamai, ELB
    • 90 Web Engines + 50 API Engines
    • 66 MySQL DBs(m1.xlarge(AWS 提供 8 CPU,32G 内存)) + 1 slave each
    • 59 Redis Instances
    • 51 Memcache Instances
    • 1 Redis Task Manager + 25 Task Processors
    • Sharded Solr
    • 6 Engineers
  • 使用 Mysql、Redis、Memcache 和 Solr,它们的优点是,至关简单且成熟的技术。
  • Web 流量以某个速度保持增长,而且 iPhone的流量开始增长。

2012年10月12日——稳按期

一月份后,大概就有4倍的增加。

  • 系统架构数据以下:
    • Amazon EC2 + S3 + Edge Cast,Akamai, Level 3
    • 180 Web Engines + 240 API Engines
    • 88 MySQL DBs (cc2.8xlarge) + 1 slave each
    • 110 Redis Instances
    • 200 Memcache Instances
    • 4 Redis Task Manager + 80 Task Processors
    • Sharded Solr
    • 40 Engineers (and growing)
  • 注意,此时的架构才是合理的。增加是经过增长更多的服务器。出问题时,你可以 经过购买服务器来进行扩展。
  • 如今,迁移到 SSDs。

为何是 Amazon EC2/S3

  • 可靠性至关好。数据中心也会宕机, 多租户(Multitenancy)会增长一些风险,但还不算太坏。
  • 良好的报告和支持。他们具备至关好的架构,并知道问题出在哪里。
  • 良好的额外服务支持,特别是当你的应用处于增加期时。你不用本身去实现,就能得到缓存、负载均衡、映射化简、数据库和其余全部方面。Amazon 服务特别适合起步阶段,以后你能够招聘工程师来优化。
  • 分分钟得到新的服务实例。这是云服务的威力。特别是当你只有两名工程师,不用担忧容量规划,或为了获得 memcache 而等上两周时间:添加 10 台 memcache 服务器也就是几分钟的事。
  • 反对的理由:有限的选择。直到最近,你能够获得SSD,但不能得到大容量的RAM配置。
  • 同意的理由:仍是有限的选择。你不须要面对一大堆配置迥异的服务器。

为何是 MySQL?

  • 至关成熟。
  • 很是耐用。不会宕机,也不会丢失数据。
  • 招聘方便。大堆工程师懂 MySQL。
  • 响应时间随请求数量线性增加。当请求数量飙升时,有些技术不能很好的响应。
  • 很好的软件支持——XtraBackup、Innotop、Maatkit。
  • 很好的社区,问题总能轻易获得答案。
  • 很好的厂商支持,像 Percona。
  • 开源免费。这一点很重要,特别是你刚开始没有不少资金支持时。

为何选择 Memcache?

  • 很是成熟。
  • 至关简单。就是一个 socket 的哈希表。
  • 保持良好性能。
  • 不少人知道并喜欢。
  • 从不崩溃。
  • 开源免费。

为何选择 Redis?

  • 还不成熟,但很是好且至关简单。
  • 提供了各类的数据结构。
  • 持久化和复制,而且能够选择如何实现它们。你能够用MySQL风格持久化,或者不要持久化,或者只要3小时的持久化。
  • 不少人知道并喜欢。
  • 保持良好性能。
  • 不多故障。你须要了解一些小故障,学习并解决它们,使它愈来愈成熟。
  • 开源免费。

注意,Memcache 和 Redis 的区别和适应场景。

Solr

  • 只须要几分钟的安装时间,就能够投入使用。
  • 不能扩展(最新版本并不是如此)。
  • 尝试弹性搜索,可是以Pinterest的规模来讲,可能会由于零碎文件和查询太多而产生问题。
  • 如今使用 Websolr,可是 Pinterest 拥有搜索团队,未来可能会开发本身的版本。

Solr 是一个高性能,采用Java开发,基于Lucene的全文搜索服务器。同时对其进行了扩展,提供了比Lucene更为丰富的查询语言,同时实现了可配置、可扩展并对查询性能进行了优化,而且提供了一个完善的功能管理界面,是一款很是优秀的全文搜索引擎。

集群 vs. 分片

  • 在迅速扩展的过程当中,Pinterest 认识到每次负载的增长,都须要均匀的传播他们的数据。
  • 针对问题先肯定解决方案的范围,他们选择的范围是集群和分片之间的一系列解决方案。

集群 —— 全部事情都是自动化的

  • 示例:Cassandra、MemBase、HBase
  • 结论: 太可怕了,不是在如今,可能在未来,但如今太复杂了,有很是多的故障点。
  • 属性:
    • 自动化数据分布。
    • 数据可移动。
    • 可从新进行分布均衡。
    • 节点间可相互通信。大量的通讯。
  • 优势:
    • 自动伸缩数据存储,至少白皮书上是这么说的。
    • 安装简单。
    • 在空间中分布存储你的数据,可在不一样区域有数据中心。
    • 高可用性。
    • 负载均衡。
    • 没有单点故障。
  • 缺点(来自第一手的经验):
    • 仍是至关年轻不成熟。
    • 仍是太复杂,一大堆节点必须对称的协议,这是一个在生产环境中难以解决的问题。
    • 不多的社区支持,有一个沿着不一样产品线的分裂社区会减小每一个阵营的支持。
    • 不多工程师有相关的知识,多是不少工程师都没用过 Cassandra。
    • 复杂和和可怕的升级机制
    • 集群管理算法是一个 SPOF 单点故障,若是有个 bug 影响每一个节点,这可能会宕机 4 次。
    • 集群管理器编码复杂,有以下一些失败的模式:
      • 数据从新均衡中断:当一个新机器加入而后数据开始复制,它被卡住了。你作什么工做?没有工具来找出到底发生了什么。没有社会的帮助,因此他们被困。他们又回到了MySQL。
      • 全部节点的数据损坏. What if there’s a bug that sprays badness into the write log across all of them and compaction or some other mechanism stops? Your read latencies increase. All your data is screwed and the data is gone.
      • 均衡不当并且很难修复. 很是常见,若是你有10个节点,你会注意到全部节点都在一个节点上,有一个手工处理方式,但会将全部负载分布到一个单节点上
      • 权威数据失效. 集群方案是很智能的。In one case they bring in a new secondary. At about 80% the secondary says it’s primary and the primary goes to secondary and you’ve lost 20% of the data. Losing 20% of the data is worse than losing all of it because you don’t know what you’ve lost.

分片(sharding)——全凭人手

  • 分片是赢家。我以为他们分片的方案与Flicker架构很是类似。
  • 特色:
    • 若是去掉集群方式下全部很差的特色,就获得了分片。
    • 人工对数据进行分布。
    • 不移动数据。
    • 经过切分数据来分担负荷。
    • 节点不知道其它节点的存在。某些主节点控制一切。
  • 优势:
    • 能够经过切分数据库来扩大容量。
    • 在空间上分布数据。
    • 高可用。
    • 负载均衡。
    • 放置数据的算法十分简单。这是最主要的缘由。虽然存在单点(SPOF),但只是很小的一段代码,而不是复杂到爆的集群管理器。过了第一天就知道有没有问题。
    • ID的生成很简单。
  • 缺点:
    • 没法执行大多数链接。
    • 没有事务功能。可能会出现写入某个数据库失败、而写入其它库成功的状况。
    • 许多约束只能转移到应用层实现。
    • schema的修改须要更多的规划。
    • 若是要出报表,必须在全部分片上分别执行查询,而后本身把结果合起来。
    • 链接只能转移到应用层实现。
    • 应用必须应付以上全部的问题。

什么时候分片?

  • 当有几TB的数据时,应该尽快分片。
  • 当Pin表行数达到几十亿,索引超出内存容量,被交换到磁盘时。
  • 他们选出一个最大的表,放入单独的数据库。
  • 单个数据库耗尽了空间。
  • 而后,只能分片。

分片过渡

  • 过渡从一个特性的冻结开始。
  • 确认分片该达到什么样的效果——但愿尽少的执行查询以及最少数量的数据库去呈现一个页面。
  • 剔除全部的MySQL join,将要作join的表格加载到一个单独的分片去作查询。
  • 添加大量的缓存,基本上每一个查询都须要被缓存。
  • 这个步骤看起来像:
    • 1 DB + Foreign Keys + Joins
    • 1 DB + Denormalized + Cache
    • 1 DB + Read Slaves + Cache
    • Several functionally sharded DBs+Read Slaves+Cache
    • ID sharded DBs + Backup slaves + cache

  • 早期的只读奴节点一直都存在问题,由于存在slave lag。读任务分配给了奴节点,然而主节点并无作任何的备份记录,这样就像一条记录丢失。以后Pinterest使用缓存解决了这个问题。
  • Pinterest拥有后台脚本,数据库使用它来作备份。检查完整性约束、引用。
  • 用户表并不进行分片。Pinterest只是使用了一个大型的数据库,并在电子邮件和用户名上作了相关的一致性约束。若是插入重复用户,会返回失败。而后他们对分片的数据库作大量的写操做。

如何分片?

  • 能够参考Cassandra的ring模型、Membase以及Twitter的Gizzard。
  • 节点间数据传输的越少,你的架构越稳定。
  • Cassandra存在数据平衡和全部权问题,由于节点们不知道哪一个节点保存了另外一部分数据。Pinterest认为应用程序须要决定数据该分配到哪一个节点,那么将永远不会存在问题。
  • 预计5年内的增加,而且对其进行预分片思考。
  • 初期能够创建一些虚拟分片。8个物理服务器,每一个512DB。全部的数据库都装满表格。
  • 为了高有效性,他们一直都运行着多主节点冗余模式。每一个主节点都会分配给一个不一样的可用性区域。在故障时,该主节点上的任务会分配给其它的主节点,而且从新部署一个主节点用以代替。
  • 当数据库上的负载加剧时:
    • 先着眼节点的任务交付速度,能够清楚是否有问题发生,好比:新特性,缓存等带来的问题。
    • 若是属于单纯的负载增长,Pinterest会分割数据库,并告诉应用程序该在何处寻找新的节点。
    • 在分割数据库以前,Pinterest会给这些主节点加入一些奴节点。而后置换应用程序代码以匹配新的数据库,在过渡的几分钟以内,数据会同时写入到新旧节点,过渡结束后将切断节点之间的通道。

ID 结构

  • 64位
    • 分片ID 为16位
    • Type为10位—— Board、User或者其它对象类型
    • 本地ID——余下的位数用于表中ID,使用MySQL自动递增。
  • Twitter使用一个映射表来为物理主机映射ID,这将须要备份;鉴于Pinterest使用AWS和MySQL查询,这个过程大约须要3毫秒。Pinterest并无让这个额外的中间层参与工做,而是将位置信息构建在ID里。
  • 用户被随机分配在分片中间。
  • 每一个用户的全部数据(pin、board等)都存放在同一个分片中,这将带来巨大的好处,避免了跨分片的查询能够显著的增长查询速度。
  • 每一个board都与用户并列,这样board能够经过一个数据库处理。
  • 分片ID足够65536个分片使用,可是开始Pinterest只使用了4096个,这容许他们轻易的进行横向扩展。一旦用户数据库被填满,他们只须要增长额外的分片,而后让新用户写入新的分片就能够了。

查找

  • 举个例子,若是存在50个查找,他们将ID分割且并行的运行查询,那么延时将达到最低。
  • 每一个应用程序都有一个配置文件,它将给物理主机映射一个分片范围。
    • “sharddb001a”: : (1, 512)
    • “sharddb001b”: : (513, 1024)——主要备份主节点
  • 若是你想查找一个ID坐落在sharddb003a上的用户:
    • 将ID进行分解
    • 在分片映射中执行查找
    • 链接分片,在数据库中搜寻类型。并使用本地ID去寻找这个用户,而后返回序列化数据。

对象和映射

  • 全部数据都是对象(pin、board、user、comment)或者映射(用户由borad,pin有like)。
  • 针对对象,每一个本地ID都映射成MySQL Blob。开始时Blob使用的是JSON格式,以后会给转换成序列化的Thrift。
  • 对于映射来讲,这里有一个映射表。你能够为用户读取board,ID包含了是时间戳,这样就能够体现事件的顺序。
    • 一样还存在反向映射,多表对多表,用于查询有哪些用户喜欢某个pin这样的操做。
    • 模式的命名方案是:noun_verb_noun: user_likes_pins, pins_like_user。
  • 只能使用主键或者是索引查找(没有join)。
  • 数据不会向集群中那样跨数据的移动,举个例子:若是某个用户坐落在20分片上,全部他数据都会并列存储,永远不会移动。64位ID包含了分片ID,因此它不可能被移动。你能够移动物理数据到另外一个数据库,可是它仍然与相同分片关联。
  • 全部的表都存放在分片上,没有特殊的分片,固然用于检测用户名冲突的巨型表除外。
  • 不须要改变模式,一个新的索引须要一个新的表。
    • 由于键对应的值是blob,因此你不须要破坏模式就能够添加字段。由于blob有不一样的版本,因此应用程序将检测它的版本号而且将新记录转换成相应的格式,而后写入。全部的数据不须要马上的作格式改变,能够在读的时候进行更新。
    • 巨大的胜利,由于改变表格须要在上面加几个小时甚至是几天的锁。若是你须要一个新的索引,你只须要创建一张新的表格,并填入内容;在不须要的时候,丢弃就好。

呈现一个用户我的资料页面

  • 从URL中取得用户名,而后到单独的巨型数据库中查询用户的ID。
  • 获取用户ID,并进行拆分
  • 选择分片,并进入
  • SELECT body from users WHERE id = <local_user_id>
  • SELECT board_id FROM user_has_boards WHERE user_id=<user_id>
  • SELECT body FROM boards WHERE id IN (<boards_ids>)
  • SELECT pin_id FROM board_has_pins WHERE board_id=<board_id>
  • SELECT body FROM pins WHERE id IN (pin_ids)
  • 全部调用都在缓存中进行(Memcache或者Redis),因此在实践中并无太多链接数据库的后端操做。

开发

  • Initially tried to give developers a slice of the system. Each having their own MySQL server, etc, but things changed so fast this didn’t work.

  • Went to Facebook’s model where everyone has access to everything. So you have to be really careful.

脚本化

  • 当你过渡到一个分片架构,你拥有两个不一样的基础设施——没有进行分片的旧系统和进行分片的新系统。脚本成为了新旧系统之间数据传输的桥梁。
  • 移动5亿的pin、16亿的follower行等。
  • 不要轻视项目中的这一部分,Pinterest原认为只须要2个月就能够完成数据的安置,然而他们足足花了4至5个月时间,别忘了期间他们还冻结了一项特性。
  • 应用程序必须同时对两个系统插入数据。
  • 一旦确认全部的数据都在新系统中就位,就能够适当的增长负载来测试新后端。
  • 创建一个脚本农场,雇佣更多的工程师去加速任务的完成。让他们作这些表格的转移工做。
  • 设计一个Pyres副本,一个到GitHub Resque队列的Python的接口,这个队列创建在Redis之上。支持优先级和重试,使用Pyres取代Celery和RabbitMQ更是让他们受益良多。
  • 处理中会产生大量的错误,用户可能会发现相似丢失board的错误;必须重复的运行任务,以保证在数据的处理过程当中不会出现暂时性的错误。

将来方向

  • 基于服务的架构。
    • 当他们开始看到了不少的数据库负载,便像产卵同样,致使了不少的应用服务器和其余服务器堆在一块儿。全部这些服务器链接到MySQL和Memcache。这意味着有30K上的memcache链接了一对夫妇演出的RAM引发的memcache守护进程交换。
    • 做为一个修补程序,这些都是移动的服务架构。有一个跟随服务,例如,将只回答跟随查询。此隔离的机器数目至30访问数据库和高速缓存,从而减小了链接。
    • 帮助隔离功能。帮助组织,解决和支持这些服务的团队。帮助开发人员,为了安全开发人员不能访问其余服务。

获得教训

  • 为了应对将来的问题,让其保持简单。
  • 让其变的有趣。只要应用程序还在使用,就会有不少的工程师加入,过于复杂的系统将会让工做失去乐趣。让架构保持简单就是大的胜利,新的工程师从入职的第一周起就能够对项目有所贡献。
  • 当你把事物用至极限时,这些技术都会以各自不一样的方式发生故障。
  • 若是你的架构应对增加所带来的问题时,只须要简单的投入更多的主机,那么你的架构含金量十足。
  • 集群管理算法自己就用于处理SPOF,若是存在漏洞的话可能就会影响到每一个节点。
  • 为了快速的增加,你须要为每次负载增长的数据进行均匀分配。
  • 在节点间传输的数据越少,你的架构越稳定。这也是他们弃集群而选择分片的缘由
  • 一个面向服务的架构规则。拆分功能,能够帮助减小链接、组织团队、组织支持以及提高安全性。
  • 搞明白本身究竟须要什么。为了匹配愿景,不要怕丢弃某些技术,甚至是整个系统的重构。
  • 不要惧怕丢失一点数据。将用户数据放入内存,按期的进行持久化。失去的只是几个小时的数据,可是换来的倒是更简单、更强健的系统!

 

更多信息,查看 high scalability

相关文章
相关标签/搜索