Pinterest一直保持着指数增加,每个半月都会翻一翻。在两年内,他们实现了从0到数百亿的月PV;从开始的两个创始人加一个工程师增加到如今超过40个工程师,从一个小型的MySQL服务器增加到180个Web Enigne、240个API Enigne、88个MySQL DB(cc2.8xlarge,每一个DB都会配置一个从属节点)、110个Redis Instance以及200个Mmecache Instance。html
在一个名为 《Scaling Pinterest》 的主题演讲上,Pinterest的Yashwanth Nelapati和 Marty Weiner为咱们讲述了这个戏剧性的过程。固然扩展到当下规模,Pinterest在众多选择中不可避免的走了许多的弯路,而Todd Hoff认为其中最宝贵的经验该归结于如下两点:算法
若是你的架构应对增加所带来的问题时,只须要简单的投入更多的主机,那么你的架构含金量十足。数据库
当你把事物用至极限时,这些技术都会以各自不一样的方式发生故障,这致使他们对工具的选择有着特殊的偏好:成熟、简单、优秀、知名、被更多的用户喜好、更好的支持、稳定且杰出的表现、一般状况下无端障以及免费。使用这些标准,他们选择了MySQL、Solr、Memcache、Redis、Cassandra,同时还抛弃了MongoDB。后端
一样这两个点是有关联的,符合第二个原则的工具就能够经过投入更多的主机进行扩展。即便负载的增长,项目也不会出现不少故障。即便真的出现难以解决的问题,至少有一个社区去寻找问题解决的方案。一旦你选择过于复杂和挑剔的工具,在扩展的道路上将充满荆棘。缓存
须要注意的是全部他们选择的工具都依靠增长分片来进行扩展,而非经过集群。讲话中还阐述了为何分片优于集群以及如何进行分片,这些想法多是以前你闻所未闻的。安全
下面就看一下Pinterest扩展的阶段性时间轴:服务器
项目背景网络
Pins是由其它零零碎碎信息集合成的图片,显示了对客户重要的信息,而且连接到它所在的位置。数据结构
Pinterest是一个社交网络,你能够follow(关注)其余人以及board。架构
数据库:Pinterest的用户拥有board,而每一个board都包含pin;follow及repin人际关系、验证信息。
1. 2010年3月发布——寻找真个人时代
在那时候,你甚至不知道须要创建一个什么样的产品。你有想法,因此你快速的迭代以及演变。而最终你将获得一些很小的MySQL查询,而这些查询在现实生活中你从未进行过。
Pinterest初期阶段的一些数字:
2个创始人
1个工程师
Rackspace
1个小的网络引擎
1个小的MySQL数据库
2011年11月
仍然是小规模,产品经过用户反馈进行演变后的数字是:
Amazon EC2 + S3 + CloudFront
1 NGinX, 4 Web Engines (用于冗余,不全是负载)
1 MySQL DB + 1 Read Slave (用于主节点故障状况)
1 Task Queue + 2 Task Processors
1 MongoDB (用于计数)
2 Engineers
2. 贯穿2011年——实验的时代
迈上疯狂增加的脚步,基本上每1个半月翻一翻。
当你增加的如此之快,每一天每一星期你可能都须要打破或者抛弃一些东西。
在这个时候,他们阅读大量的论文,这些论文都阐述着只须要添加一台主机问题就会得以解决。他们着手添加许多技术,随后又不得不放弃。
因而出现了一些很奇怪的结果 :
Amazon EC2 + S3 + CloudFront
2NGinX, 16 Web Engines + 2 API Engines
5 Functionally Sharged MySQL DB + 9 read slaves
4 Cassandra Nodes
15 Membase Nodes (3 separate clusters)
8 Memcache Nodes
10 Redis Nodes
3 Task Routers + 4 Task Processors
4 Elastic Search Nodes
3 Mongo Clusters
3个工程师
5个主数据库技术,只为了独立其中的数据。
增加太快以致于MySQL疲于奔命,全部其它的技术也达到了极限。
当你把事物用至极限时,这些技术都会以各自不一样的方式出错。
开始抛弃一些技术,而且自我检讨究竟须要些什么,基本上重作了全部的架构。
3. 2012年2月——成熟的时代
在重作了全部的架构后,系统呈现了以下状态
Amazon EC2 + S3 + Akamai, ELB
90 Web Engines + 50 API Engines
66 MySQL DBs (m1.xlarge) +,每一个数据库都配备了从属节点
59 Redis Instances
51 Memcache Instances
1 Redis Task Manager + 25 Task Processors
Sharded Solr
6个工程师
如今采用的技术是被分片的MySQL、Redis、Memcache和Solr,有点在于这些技术都很简单很成熟。
网络传输增加仍然保持着以往的速度,而iPhone传输开始走高。
4. 2012年10月12日 —— 收获的季节
大约是1月份的4倍
如今的数据是:
Amazon EC2 + S3 + Edge Cast,Akamai, Level 3
180 Web Engines + 240 API Engines
88 MySQL DBs (cc2.8xlarge) ,一样每一个数据库都有一个从属节点
110 Redis Instances
200 Memcache Instances
4 Redis Task Manager + 80 Task Processors
Sharded Solr
40个工程师(仍在增加)
须要注意的是,现在的架构已趋近完美,应对增加只须要投入更多的主机。
当下已开始转移至SSD
下面一览该演讲中的干货,决策的制定:
为何会选择EC2和S3
至关好的可靠性,即便数据中心发生故障。多租户会增长风险,可是也不是太坏。
良好的报告和支持。它们(EC2和S3)有着良好的架构,而且知道问题所在。
完善的周边设施,特别是在你须要快速增加时。你能够从APP Engine处得到maged cache、负载均衡、MapReduce、数据库管理以及其它你不想本身动手编写的组件,这能够加速你应用程序的部署,而在你工程师空闲时,你能够着手编写你须要的一切。
新的实例能够在几秒内就绪,这就是云的力量;特别是在只有两个工程师的初期,不须要去担忧容量规划,更不须要花两个星期去创建本身的Memcache,你能够在数分钟内添加10个Memcached。
缺点:有限的选择。直到最近,才能够选择使用SSD,同时没法得到太大的内存配置。
优势:你不须要给大量的主机进行不一样的配置。
为何会选择MySQL
很是成熟。
很是稳定。不会宕机,而且不会丢失数据。
在招聘上具备优点,市场上有大把的人才。
在请求呈直线上升时,仍能将相应时间控制在必定的范围内,有些数据库技术在面对请求的飙升时表现并非很好。
很是好的周边软件支持——XtraBackup、Innotop、Maatkit。
能够从相似Percona这样的公司获得优秀的技术支持。
开源(免费)——这一点很是重要,特别是在资金缺少的初期
为何使用Memcache
很是成熟。
很是简单。能够当成是一个socket哈希表
杰出稳定的表现
知名并为大量用户喜好
永不崩溃
开源
为何选择Redis
虽然还不够成熟,可是很是简单及优秀
提供了大量的数据结构类型
提供多种的选择进行持久化和备份:你能够备份而非持久化,选择备份的话你还能够选择多久备份一次;一样你还能够选择使用什么方式进行持久化,好比MySQL等。
Home feed被储存在Redis上,每3个小时保存一次;然而并非3个小时持久化一次,只是简单的每3个小时备份一次。
若是你存储数据的主机发生故障,丢失的也只是备份周期内的数据。虽然不是彻底可靠,可是很是简单。避免了复杂的持久化及复制,这样的架构简单且便宜。
知名并为大量用户喜好
稳定且杰出的表现
不多出故障。有一些专有的故障模型,你须要学会解决。这也是成熟的优点,只须要学习就能够解决。
开源
Solr
只须要几分钟的安装时间,就能够投入使用
不能扩展到多于一台的机器上(最新版本并不是如此)
尝试弹性搜索,可是以Pinterest的规模来讲,可能会由于零碎文件和查询太多而产生问题。
选择使用Websolr,可是Pinterest拥有搜索团队,未来可能会开发本身的版本。
集群vs.分片
在迅速扩展的过程当中,Pinterest认识到每次负载的增长,都须要均匀的传播他们的数据。
针对问题先肯定解决方案的范围,他们选择的范围是集群和分片之间的一系列解决方案。
集群——全部的操做都是经过自动化
好比:Cassandra、MemBase、HBase
结论:没有安全感,未来可能会比较成熟,可是当下这个解决方案中还存在太多的复杂性和故障点。
特性:
数据自动分布
节点间转移数据
须要平衡分配
节点间的相互通讯,须要作不少措施用于防止干扰、无效传递及协商。
优势:
自动扩展你的数据存储,最起码论文中是这么说的。
便于安装
数据上的空间分布及机房共置。你能够在不一样区域创建数据中心,数据库会帮你打理好一切。
高有效性
负载平衡
不存在单点故障
缺点:
仍然不成熟。
本质上说还很复杂。一大堆的节点必须对称协议,这一点很是难以解决。
缺乏社区支持。社区的讨论由于产品方向的不一样而不能统一,而在每一个正营中也缺少强有力的支持。
缺少领域内资深工程师,可能大多数的工程师都还未使用过Cassandra。
困难、没有安全感的机制更新。这多是由于这些技术都使用API而且只在本身的领域内通行,这致使了复杂的升级路径。
集群管理算法自己就用于处理SPOF(单点故障),若是存在漏洞的话可能就会影响到每一个节点。
集群管理器代码很是复杂,而且须要在全部节点上重复,这就可能存在如下的故障模式:
数据平衡失控。当给集群中添加新的主机时,可能由于数据的拷贝而致使集群性能降低。那么你该作什么?这里不存在去发现问题所在的工具。没有社区能够用来求助,一样你也被困住了,这也是Pinterest回到MySQL的缘由。
跨节点的数据损坏。若是这里存在一个漏洞,这个漏洞可能会影响节点间的日志系统和压缩等其它组件?你的读延时增长,全部的数据都会陷入麻烦以及丢失。
错误负载平衡很难被修复,这个现象十分广泛。若是你有10个节点,而且你注意到全部的负载都被堆积到一个节点上。虽然能够手动处理,可是以后系统还会将负载都加之一个节点之上。
数据全部权问题,主次节点转换时的数据丢失。集群方案是很是智能的,它们会在特定的状况下完成节点权利的转换,而主次节点切换的过程当中可能会致使数据的部分丢失,而丢失部分数据可能比丢失所有还糟糕,由于你不可能知道你究竟丢失了哪一部分。
分片——全部事情都是手动的
结论:它是获胜者。Todd Hoff还认为他们的分片架构可能与Flickr架构相似。
特性:
分片可让你摆脱集群方案中全部不想要的特性。
数据须要手动的分配。
数据不会移动。Pinterest永远都不会在节点间移动,尽管有些人这么作,这让他们在必定范围内站的更高。
经过分割数据的方式分配负载。
节点并无互相通讯,使用一些主节点控制程序的运行。
优势:
能够分割你的数据库以提升性能。
空间分布及放置数据
高有效性
负载平衡
放置数据的算法很是简单。主要缘由是,用于处理单点故障的代码只有区区的半页,而不是一个复杂的集群管理器。而且通过短暂的测试就知道它是否可以正常工做。
ID生成很是简单
缺点:
不能够执行大多数的join。
失去全部事务的能力。在一个数据库上的插入可能会成功,而在另外一个上会失败。
许多约束必须放到应用程序层。
模式的转变须要从长计议。
报告须要在全部分片上执行查询,而后须要手动的进行聚合。
Join在应用程序层执行。
应用程序必须容忍以上全部问题。
何时进行分片
若是你的项目拥有PB级的数据,那么你须要马上对其进行分片。
Pin表格拥有百万行索引,索引大小已经溢出内存并被存入了磁盘。
Pinterest使用了最大的表格,并将它们(这些索引)放入本身的数据库。
而后果断的超过了单数据库容量。
接着Pinterest必须进行分片。
分片的过渡
过渡从一个特性的冻结开始。
确认分片该达到什么样的效果——但愿尽少的执行查询以及最少数量的数据库去呈现一个页面。
剔除全部的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)或者映射(用户由baord,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),因此在实践中并无太多链接数据库的后端操做。
脚本相关
当你过渡到一个分片架构,你拥有两个不一样的基础设施——没有进行分片的旧系统和进行分片的新系统。脚本成为了新旧系统之间数据传输的桥梁。
移动5亿的pin、16亿的follower行等。
不要轻视项目中的这一部分,Pinterest原认为只须要2个月就能够完成数据的安置,然而他们足足花了4至5个月时间,别忘了期间他们还冻结了一项特性。
应用程序必须同时对两个系统插入数据。
一旦确认全部的数据都在新系统中就位,就能够适当的增长负载来测试新后端。
创建一个脚本农场,雇佣更多的工程师去加速任务的完成。让他们作这些表格的转移工做。
设计一个Pyres副本,一个到GitHub Resque队列的Python的接口,这个队列创建在Redis之上。支持优先级和重试,使用Pyres取代Celery和RabbitMQ更是让他们受益良多。
处理中会产生大量的错误,用户可能会发现相似丢失board的错误;必须重复的运行任务,以保证在数据的处理过程当中不会出现暂时性的错误。
开发相关
开始尝试只给开发者开放系统的一部分——他们每一个人都拥有本身的MySQL服务器等,可是事情改变的太快,以致于这个模式根本没法实行。
转变成Facebook模式,每一个人均可以访问全部东西,因此不得不很是当心。
将来的方向
基于服务的架构
当他们发现大量的数据库负载,他们开始布置大量的应用程序服务器和一些其它的服务器,全部这些服务器都链接至MySQL和Memcache。这意味着在Memcache上将存在3万的链接,这些链接将占用几个G的内存,同时还会产生大量的Memcache守护进程。
为了解决这个问题,将这些工做转移到了一个服务架构。好比:使用一个follower服务,这个服务将专一处理follower查询。这将接下30台左右的主机去链接数据库和缓存,从而减小了链接的数量。
对功能进行隔离,各司其职。让一个服务的开发者不能访问其它的服务,从而杜绝安全隐患。
学到的知识
为了应对将来的问题,让其保持简单。
让其变的有趣。只要应用程序还在使用,就会有不少的工程师加入,过于复杂的系统将会让工做失去乐趣。让架构保持简单就是大的胜利,新的工程师从入职的第一周起就能够对项目有所贡献。
当你把事物用至极限时,这些技术都会以各自不一样的方式发生故障。
若是你的架构应对增加所带来的问题时,只须要简单的投入更多的主机,那么你的架构含金量十足。
集群管理算法自己就用于处理SPOF,若是存在漏洞的话可能就会影响到每一个节点。
为了快速的增加,你须要为每次负载增长的数据进行均匀分配。
在节点间传输的数据越少,你的架构越稳定。这也是他们弃集群而选择分片的缘由。
一个面向服务的架构规则。拆分功能,能够帮助减小链接、组织团队、组织支持以及提高安全性。
搞明白本身究竟须要什么。为了匹配愿景,不要怕丢弃某些技术,甚至是整个系统的重构。
不要惧怕丢失一点数据。将用户数据放入内存,按期的进行持久化。失去的只是几个小时的数据,可是换来的倒是更简单、更强健的系统!
原文连接: Scaling Pinterest - From 0 To 10s Of Billions Of Page Views A Month In Two Years (编译/仲浩 审校/王旭东)