前言
Pinterest已经可以驾驭每2.5个月流量就翻一倍的指数增长,他们实现了在2年内每月pv从0到10亿的结果。从2个创始人加1位工程师到超过40名工程师,从只有1台MySQL服务器到180台web服务器、240 API引擎、88个MYSQL DB和1个从库、110个Redis实例、200个Memcache实例。
令人吃惊的增长!他们是如何做到的呢?1年半年Yashwanth Nelapati 和 Marty Weiner演讲过这个富有戏剧性的架构进化故事,当初为求快,选择又多,他们也做了很多错误的选择(接下来是教训与经验总结)。
这是一个伟大的演讲,非常详情,但同时很接地气,架构的策略适合每个普通的你我,强烈推荐!
我从演讲中主要学到两条原则:
1.架构是当业务增长时可以方便的添加同样的东西就搞定。如果你认为扩展架构是只要扔更多的钱、加更多服务器(Boxs)的话,但如果你的架构能做到,那就是金钱。
2.当到达极限时所有的技术都会以自已特有的方式失败,这就引出我们选型时的原则:成熟的、简单实用的、广为人知和喜欢的、社区支持友好的、性能向来表现优异的、尽可能不会出现失效且是免费的。根据这些原则,他们选择了:MySQL、Solr、Memcache和Redis。Cassandra和MongoDB丢掉了。
这两条原则是互相关联的。依照原则2工具可以添加更多空间实现扩展,负载增长时成熟的工具将会有较少的问题,即使有问题也有社区帮助一起解决,如果你选的工具太稀少人用那就你可能就会撞到南墙了。
这也中我认为整个演讲中最好的之一,关于分片(sharding)比集群更好的讨论,体现了业务增长时只要添加更多资源、较少失效、成熟、简单、好的支持等特征。注意下所有的工具他们都选择可分片而不是集群,关于这部分的讨论很有趣可能很多你之前也没考虑过。
好了,是不是迫不及待了,Let's go:)
术语表
喜欢的图片(pins):是一张带信息图片,包含了描述、重要性、跟踪链接等。
Pinterest:是图片SNS,包括关注的人和板块功能
数据库Database:包括用户库-用户的板块和板块的大头针;关注和转贴(repin)关系、认证信息等。
2010年3月份项目启动 - 寻找自我
这时你甚至不知要建设一个什么样的产品,你有很多想法,但经常很快改变,最后只留下一些很奇怪的MySQL查询。
一些早期数据:
2个创始人
一个工程师
托管在Rackspace
一台小的web服务引擎
一台小的MySQL数据库
2011年1月
仍在秘密开发,根据一些用户的反馈进行迭代。一些数据:
Amazon EC2 + S3 + CloudFront
1 NGinX, 4 Web Engines (用来冗余, 并未在线使用)
1 MySQL DB + 1只读库(防止主库DOWN掉)
1 任务队列 + 2任务处理器
1 MongoDB (用来做计数器)
2 个工程师
直到2011年9月 - 试验阶段
疯狂的增长,每2个半月翻一倍。
当你达到这个增长速度时,所有东东每个晚上、每周都会掉链子
这时,网上查了很多架构方案都只会说加服务器吧,加就加吧。同时又加了很多其它技术,但全部失效了。
同时你看到你组更复杂的数据:
Amazon EC2 + S3 + CloudFront
2 NGinX, 16 Web 引擎 + 2 API 引擎
5 功能性 分片 MySQL DB + 9 只读从库
4 Cassandra 节点
15 Membase 节点 (3 独立集群)
8 Memcache 节点
10 Redis 节点
3 任务路由 + 4 任务处理器
4 Elastic Search 节点
3 Mongo 集群
3 工程师
5个主要数据库单独存放自己的数据
因增长太快MySQL也快顶不住了,所有技术都用到了极限
当所有技术用到极限时都以自己的方式失效了
开始摈弃一些技术,问问自己到底我们要什么。然后是重构。。。重构。。。
2012年1月 - 成熟期
全部重构后的系统是这样的:
Amazon EC2 + S3 + Akamai, ELB
90 Web 引擎 + 50 API 引擎
66 MySQL DBs (m1.xlarge) + 每台 1 从库
59 Redis 实例
51 Memcache 实例
1 Redis 任务管理器 + 25 任务处理器
分片的 Solr
6 工程师
现在都运行在分片的MySQL、Redis、Memcache和Solr上,就是这样,好处是技术简单又成熟。
网站流量以同样的速度在增长,同是,iPhone的流量也开始增长了。
2012年10月12日 - 历史轮回
大概是1月份的4倍,数据是这样的:
Amazon EC2 + S3 + Edge Cast,Akamai, Level 3
180 Web 引擎 + 240 API 引擎
88 MySQL DBs (cc2.8xlarge) + 每个配1 从库
110 Redis 实例
200 Memcache 实例
4 Redis 任务管理器 + 80 任务处理器
分片的 Solr
40 工程师 (还在增长)
注意到没有,架构很好用,扩展时只需要添加一样的东西,你想砸更多钱来扩展,还是仅仅是添加空间容量?
硬盘用SSD了
为何选用Amazon EC2/S3?
稳定性好。数据中虽也出过问题,添加了些风险,但整体还不错;
很好的报告和支持。他们很懂架构知识问题在哪。
很好的外围支持。当你快速增长时,你可以快速组织起缓存、负载均衡、map reduce、数据库等等你想要的,不必什么都自己写,可以快速用他们的服务起动起来,当你有工程师再跟自已玩;
新的实例只要几秒就可启动起来。这是云服务的力量。特别是当你只有两个工程师时,你不必当心流量增长计划而耗费2周时间,10台缓存服务只要几分钟就搞定!
弊端:选择有限。只到最近才可用SSD,还没有大内存可配置;
优势:选择有限。你不必终结于不同服务器不同的配置上
*国内貌似阿里云是个不错的选择(译者)。
为何选用MySQL?
真的很成熟。
很坚固。从没当过机和丢失过数据。
很多人可招募。
响应时间和请求数是线性增长的。有些技术路线并不是这样增长。
很好的软件支持。XtraBackup,innotop,maatkit
很好的社区支持。获得问题回答很容易。
获得公司的支持也很容易,如 percona
免费。刚开始时如果没有投资就很重要。
为何选用Memcache?
很成熟。
很简单。它类似一个带socket的哈希表。
性能一贯很好
广为人知且喜欢
不会崩溃
免费
为何选用Redis?
虽还不成熟,但很好用且简单。
提供了多样的数据结构。
有持久层和可复制,且可以选择如何实现。如你想要像MySQL一样保存也可以,如果你不想保存也可以,如果你想只保存3个小时也可以。说明:1.Redis的根种子(home seed)是每隔3小时保存一次,并没有3小时复制功能,它只是每3时备份一次;2.如果你的服务器(box)保存不下数据了,那它只会备份几小时。这不是十分可靠,但还算简单。不必把持久化和复制搞复杂了,这只是更简单和很廉价的架构方法。
广为人知和喜欢。
一贯性能良好。
很少失效。只有小数几个微妙的失效需要了解一下。这也是它成熟的原因之一,只要学会了就可以搞定。
为何是Solr?
崛起中的伟大产品。安装只要几分钟,你就有一个搜索引擎了。
不能跨服务器扩展(至少目前为止)。
试过Elastic search搜索引擎,但扩展上有问题,特别是处理小文档上,而且查询太多。
现在用的是Websolr,Pinterest有个搜索团队在支撑。
集群与分片到底哪个好?
随着业务的快速增长他们意识到需要把数据分散在不同的区域保障未来的增长压力可控;
关于集群好还是分片好有一大堆讨论
集群 - 所有数据都是原子态的
选型结果:太可怕了,可能将来会用,但不是现在,他们太复杂,而且太多失效的情况。
特性:数据自动分布式;数据可移动;重新均衡可分布负载;各节点可互相通讯,很多交叉通讯、协调。
优劣:
自动扩展数据库,至少白皮收是这样说的。
容易安装。
地理分布数据,你的数据中心可以在不同地区。
高可用性。
负载均衡。
无单点失效。
劣势(来自第一手经验):
仍十分年轻。
基本上很复杂。一堆节点要协调,在生产环境很难排错。
社区很少可以得到支持。因产品分割每个阵营只有少量支持。
困难和可怕的升级机制。
集群管理的算法是:SPOF,如果某个节点挂了会影响所有节点,曾经挂过4次。
集群管理复杂的代码透过所有节点,可能有以下几种失效的情况:1.数据再均衡中断。新装一个节点开始同步时卡住了怎么办?没人告诉你,就卡那了,最后回去用MySQL了。2.破坏的数据传染到所有节点。3.不正确的平衡也不容易修正。4.数据授权失败。
分片shard - 全手工
选型结果:分片方案获胜!说明下,分片用得太普遍了,如 Flickr 的架构。
特性:去掉了所有你不喜欢的集群的特性;数据分布是手工设的;数据不会迁移,虽然有人这么做,但争议很大。分割数据来分散负载;各节点之间不需要沟通,主节点控制了一切。
优势:
可以分割数据库来增加容量
可地理分布与协调数据
负载均衡
数据的算法很简单。主要原因,虽然也用了SPOF,但比复杂的集群方案它只有一半复杂,第1次用上就知道它是能工作还是不能工作。
ID生成很简单。
劣势:
不能执行大多数的join操作
不支持事务。写A库可能成功,写B库可能失败。
很多约束限制要移到应用去实现。
(schema表)结构修改要更慎重
查询报告需要在所有分片上运行,然后手工聚合
聚合是在应用层完成的
你的应用必须搞定上述这些问题:D
何时可以进行分片shard?
如果你的项目只有几TB数据时,赶紧开始分片;
当主要业务表行数达到几亿行时,建索引都内存溢出,或用上了交换分区时;
需要把最大的表单独拿出来放到独立的数据库时;
单个库已经把你的空间都用光时;
那,就要开始享受分片的好时光了:)
开始分片shard
开始分片时先不要上新功能;
再确定如何分片。原则是,用最少些里查询及最小遍历几个数据库即可生成一个页面;
去掉所有的Join操作。因表可能从不同分区返回,join将无法工作;
加了非常多的缓存。基本上每个查询都有缓存。
步骤就像:
1 DB + Foreign Keys + Joins
1 DB + 反范式Denormalized + 缓存
1 DB + 只读从库 + 缓存
几个分片库 +只读从库 + 缓存
ID 分片的数据库 + 备份从库 + 缓存
提早从只读库获取数据可难会因为延时导致问题,一个对从库的读请求可能主库还没完成复制,看起来就像数据丢失了,绕开这个问题要用到缓存。
用户表没有分片。只用到了一个超大的数据库并且name是唯一的,重复用户名将导致失败。大多写请求都写到分片的数据库里。
如何分片Shard?
看看Cassandra的环模式(Ring Model),还有Membase和Twitter的Gizzard(已经废弃 -译者)。
策略:最少的数据漫延等于最大的稳定性。
Cassadra有个数据均衡和授权问题:它并不知道谁拥有哪块数据。Pinterest的做法是在应用层决定数据去向何方,所以这永远不是一个问题。
对将来5次分片已经计划好了。
一开始就创建了很多虚拟分片。8台服务器、每台有512个数据库,每个数据库拥有所有的表。
为了高可用性,他们架构了多主库复制模式。每个主库分配到不同地区,一个失效可以切换到全新的替换库上。
当数据库压力增长时:
查看提交的新代码是否有新功能、缓存问题或其它问题发生。
如果只是负载增长,他们只是分割数据然后告诉应用层去一个新的数据中心获取数据。
分割数据前,先启动这些主库的从库,然后切换应用层代码访问新库,有几分钟的时间数据仍写到旧库(同时会复制到新的从库了),最后撤掉旧库(应用层代码此时只访问新的从库--这时变成主库了)。
ID的结构
64位
分片ID:16位
类型:10位-大头针(pin)、版块、用户和其它对象
本地ID:其它 位 用于本地表自增id。
Twitter用了一个映射表来映射ID到物理主机,这就需要一次查询,因为Pinterest运行在AWS上,MySQL查询一次需要3为毫秒,他们这种额外间接的开销接受不了,所有把位置(库、表所在的服务器地址信息)也定义到ID里了。
新注册的用户是随机分配了分片的服务器上。
所有的数据(包括Pin、版本等)都分配到同一个分片上。这点带来极大的好处,例如生成一个用户信息页,就不需要跨越多个不同的分片,这样更快。
ID的设计足够用到65536个分片使用。但开始只用到了4096,以后还可以水平扩展,当用户库快满了,再开放更多的分片,然后新用户就会分配到新的分片上。
查询
假设如果有50个查询,Pinterest会根据id进行分割,所有并发的查询,延时即是最长一个的等待时间。
每个应用都有一个配置文件,映射分片区间到一个物理主机
“sharddb001a”: : (1, 512)(译者:貌似他们是按字节来计算,一个字节8位最大值是256,两字节x2=512 ;也有可能是写死映射关系的)
“sharddb001b”: : (513, 1024) - 备份热主库
如果要查的某用户ID在sharddb003a:
分解ID
在分片映射中查询
连接分片所在的服务器,根据类型选择数据库,用本地ID再查正确的用户,返回序列化的数据
对象和映射
所有的数据或者是一个对象(Pin、版块、用户、评论)或是一个映射(用户的版块、喜欢的图片Pin)
是对象的话,一个本地ID映射到MySQL的blob(译者:或Longtext),blob块格式是json但返回昌序列化的thrift(参考Fackbook thrift)。
如果是映射,就是一个映射表,可以查询用户的版块,ID包含了时间戳,可以据此按事件顺序排序。
Pinterest还维护了一个反射映射表,多对多的表,用来回答这类型的查询问题:关注我的用户。
表名是这样纸滴:名词动词名词,如userlikepings,pinslikeuser。
查询都是按主键或索引键查询(没有join)
数据不会像集群一样到处飞。一旦一个用户分配到第20个分片,那他所有的数据都会在这台分片上,永远不会飞到别的地方去。64位ID包括了分片id,所有不会变化了。你可以移动物理数据到另外一台数据库,但他们仍然关连着同一个分片。
所有的表在所有分片都一样(数据是不同的)。没有特殊的分片。不会遇到像用户表这个大表检测用户名冲突的问题。
不需要改表结构和新索引(最神奇的地方!看他们是怎么做到的)
因为表结构大多是key=>value形式,value只是一个blob,可以随意添加字段而不用改表结构了。还有个version字段,表明blob的版本,这样应用层可以检测是否要修改blob的结构,所有的数据不需要立即更新,会在用户查询时自动升级。
因为修改大表的结构有时会花费数小时甚至是整天,这样的表结构很有优势。如果你想加个新的索引,你只要创建一个新表,然后迁移数据,当你不要时时人干掉它就行了(没提到这种更新是否事务安全)。
实战举例-生成一个用户个人信息页面
从ULR得到用户名,从巨大的单个用户表查表用户id
分析(分割)用户ID
选择分片,连接分片
SELECT body from users WHERE id = userid>
SELECT boardid FROM userhasboards WHERE userid=id>
SELECT body FROM boards WHERE id IN (ids>)
SELECT pinid FROM boardhaspins WHERE boardid=id>
SELECT body FROM pins WHERE id IN (pinids)
大多请求是从缓存返回 (memcache or redis), 实践中没有多少流量去掉数据库.
割接过程及脚本
当迁移到一个分片的架构时,你此时有新、旧两套架构,脚本是用来迁移到新架构的代码
Pinterest迁移过5亿条Pins和1.6亿关注等
低估了迁移的成本,开始他们认为需要2个月,事实上花了4-5个月,注意哦,是在冻结新功能上线的时候。
应用层必须同时往新、旧两套架构写数据
一旦确认所有的数据已经转到新架构了,然后就逐步切割请求到新的架构,逐步增加同时测试后台
多搞一些脚本,多投些人力来完成切割。
搞个Pyres,一个用Python写的访问Github的Resque队列的工具,一个建立在Redis的队列服务,支持优先及和重试,比起Celery和RabbitMQ好多了。
割接中犯了很多错误。像用户没有了版块,只有重复搞了几遍确保没错。
开发
开始尝试给每位开始配了一个系统的基本环境,都有独立的MySQL,但业务发展太快,没有作用;
学Facebook的方法,都在生产环境开发,但必须非常小心;
未来的方向
SOA
他们注意到,当数据库负载升高时,如果加应用服务和一堆服务器,所有的服务都连着MySQL和Memcache,意味着3万个连接到memcache时内存将达到上G,这时memcache会使用交换分区。
转到SOA架构是一个办法。如,搞一个“关注”服务,只做“关注”的查询,这样可以隔离连接到30个,这样压力就可以接受。
SOA还可以帮助隔离功能。根据服务配置项目组及支持组和安全组。
学到的知识点
什么事都可能会失败。一定要注意简单。
让事情很有趣。
用到极限什么东东都会挂掉。
构架是只要添加复制一样的东东就能扩展容量。好的架构也是金钱。
集群管理算法是SPOF,如果有一个BUG将与每个节点冲突,就这,挂过4次。
分散数据来分散负载
越少分散数据你的架构就越稳定,这也是为何pinterest选择分片而不是集群的原因。
SOA的守则:功能隔离。可以帮助减少连结、组织团队、组织支持、提升安全。
问问自已到底要什么?不要管技术也要符合你的有所愿景(或叫商业模式),即使你要全部重构。
不要害怕丢掉一点点数据。pinterest保存用户数据在内存然后周期性的写到数据库,丢失只意味了几个小时的数据没了,但系统会很简单高效,这才是更重要的。
相关文章
英文原文 http://highscalability.com/blog/2013/4/15/scaling-pinterest-from-0-to-10s-of-billions-of-page-views-a.html
Pinterest Architecture Update - 18 Million Visitors, 10x Growth,12 Employees, 410 TB Of Data
A Short On The Pinterest Stack For Handling 3+ Million Users
Pinterest Cut Costs From $54 To $20 Per Hour By Automatically Shutting Down Systems
7 Scaling Strategies Facebook Used To Grow To 500 Million Users and The Four Meta Secrets Of Scaling At Facebook - I think you'll find some similarities in their approaches.
<翻译:朱淦 350050183@qq.com 2015.7.27>
来源:oschina
链接:https://my.oschina.net/u/1263162/blog/484475