Flickr的架构学习

jopen 9年前

Flickr.com 是最初由Ludicorp公司开发设计并于2004年2月正式发布的,2007年11月,Flickr迎来了第20亿张照片,一年后,这个数字就达到了 30亿,并且还 在以加速度增长。 2005年3月,雅虎公司以3千500万美元收购了Ludicorp公司和Flickr.com。

在讨论Flickr 网站架构之前,让我们先来看一组统计数据(数据来源:April 2007 MySQL Conf and Expo和Flickr网站)

  • 每天多达40亿次的查询请求
  • squid总计约有3500万张照片(硬盘+内存)
  • squid内存中约有200万张照片
  • 总计有大约4亿7000万张照片,每张图片又生成不同尺寸大小的4-5份图片
  • 每秒38000次Memcached请求 (Memcached总共存储了1200万对象)
  • 超过2PB 存储,其中数据库12TB(星期天要消费~1.5TB)
  • 每天新增图片超过 40万(周日峰值超过200万,约1.5TB)
  • 超过8百50万注册用户
  • 超过1千万的唯一标签(tags)
  • 响应4万个照片访问请求
  • 处理10万个缓存操作
  • 运行13万个数据库查询

2009年Flickr 的数据相比2007的时候又有了显著的增长:

  • 24 TB 的 MySQL 数据
  • 每秒钟 MySQL 有 3.2 万次写操作
  • 每秒钟 MySQL 有 12万次读操作
  • 图片容量 6PB
  • 每天要用掉 10TB 存储
  • 超过 15000 个服务监控点

以下内容是网上信息的拼凑,由于相关的信息比较早,所以有些信息可能已经过期了。

Flickr用到的技术:

  • PHP App Servers
    - 运行REDHAT LINUX、Apache上的PHP应用,Flickr网站的主体是大约6万行PHP代码
    - 没有使用PHP session, 应用是stateless,便于扩展,并避免PHP Server故障所带来的Session失效。
    - 每个页面有大约27~35个查询
    - 另有专门的Apache Web Farm 服务于静态文件(HTML和照片)的访问
  • MySQL,Master- Master结构,mysql的常见的master-slave结构,大家都知道存在”single point of failure”(单点故障的问题),且只对读操作有好处,对于写频繁的网站却不是一个好的解决方案,Flickr的双master方案据我推测用的就是这个http://code.google.com/p/mysql-master-master/,原理就是master轮询,保证同时只有一个master负责写,解决了单点故障的问题。
  • Shards
  • Memcached 作为中间缓存层
  • Squid 作反向代理服务器(reverse-proxy for html and images).
  • Smarty 作为模板解析
  • Perl 估计用perl做一些系统层面的东西吧,比如日志处理(猜测)
  • PEAR 做XML和Email解析
  • 前期使用的是ImageMagick 进行图像处理,在 2004  转移到 GraphicsMagick, 处理速度提升了 15%。
  • Java 作为节点服务
  • Apache
  • SystemImager 作为服务器部署
  • Ganglia 分布式系统监控
  • Subcon 用SVN维护服务器配置文件并且可以部署不同的配置文件到服务器集群中去
  • Cvsup 用做文件分发、更新
  • Wackamole前端负载均衡
  • Pair of ServerIron’s 做负载均衡方案
  • Squid Caches 代理,用于缓存静态的HTML和照片
  • Dual Tree Central Database
    - MySQL 数据库,存放用户表,记录的信息是用户主键以及此用户对以的数据库Shard区,从中心用户表中查出用户数据所在位置,然后直接从目标Shard中取出数据。
    - “Dual Tree”架构是”Master-Master”和“Master-Slave”的有效结合,双Master 避免了“单点故障”,Master-Slave又提高了读取速度,因为用户表的操作90%以上是读。
  • Master-master shards
    - MySQL 数据库,存储实际的用户数据和照片的元数据(Meta Data),每个Shard 大约40万个用户,120GB 数据。每个用户的所有数据存放在同一个shard中。
    - Shard中的每一个server的负载只是其可最大负载的50%,这样在需要的时候可以Online停掉一半的server进行升级或维护而不影响系统性能。
    - 为了避免跨Shard查询所带来的性能影响,一些数据有在不同的Shard有两份拷贝,比如用户对照片的评论,通过事务来保证其同步。
  • Memcached Cluster 中间层缓存服务器,用于缓存数据库的SQL查询结果等。
  • Big Search Engine
    – 复制部分Shard数据(Owner’s single tag)到Search Engine Farm以响应实时的全文检索。
    – 其他全文检索请求利用Yahoo的搜索引擎处理
  • Storage Manager 运行私有的,适用于海量文件存储的Flickr File System
  • Net App公司的Filer, NAS存储设备,用于存储照片
  • 服务器的硬件配置:
    – Intel或AMD 64位CPU,16GB RAM
    – 6-disk 15K RPM RAID-10.
    – 2U boxes.
  • 服务器数量:(数据来源:April 2008 MySQL Conference & Expo)
    166 DB servers, 244 web servers(不知道是否包括 squid server?), 14 Memcached servers
  • 使用SystemImager/SystemConfigurator 自动化安装、软件分发,
  • Subcon作为配置管理工具提高部署效率。
  • Ganglia 来进行容量数据的展现。Ganglia 最主要的优点还是管理的方便性: Client/Server 结构, 各自跑 Demon 进行数据交互(XML形式)。相比 Cacti + Collectd 需要进行很多手工配置,更加方便。John Allspaw很强调测量(measurement)的重要性;他也很鄙视benchmark,无论是对开源软件比如Cassandra的benchmark,或是自己开发的进程的性能测试,都与上线后运营的负载差异太大,以致对容量规划几乎没帮助。基本上需要在灰度发布后根据实际应用负载才能做比较靠谱的规划。

Flickr的数据库从“Master-Slave”的复制模式到Shard架构

也许有人不相信,Flickr是从一台服务器起步的,即Apache/PHP和MySQL是运行在同一台服务器上的,很快MySQL服务器就独立 了出来,成了双服务器架构。随着用户和访问量的快速增长,MySQL数据库开始承受越来越大的压力,成为应用瓶颈,导致网站应用响应速度变慢。一般来说,数据库的扩展无外是两条路,Scale-Up和Scale-Out。Scale-Up,简单的说就是在同一台机器内增加CPU、内存等硬件来增加数据库系统的处理能力,一般不需要修改应用程序;Scale-Out,就是我们通常所说的数据库集群方式, 即通过增加运行数据库服务器的数量来提高系统整体的能力,而应用程序则一般需要进行相应的修改。Flickr的技术人员发现,查询即SELECT语句的数量要远远大于添加,更新和删除的数量,比例达到了大约13:1甚至更多,所以他们采用了“Master-Slave”的复制模式,即所有的“写”操作都在发生在“Master”, 然后”异步“复制到一台或多台“Slave”上,而所有的”读“操作都转到”Slave”上运行,这样随着“读”交易量的增加,只需增加Slave服务器就可以了。

所以Frickr最初的数据库架构为:应用程序能够在多个”Slave“上进行负载均分;2)当一个或多个”slave”出现故障时,应用程序能自动尝试下一个“slave”,如果全部“Slave”失效,则返回错误。 Flickr曾经考虑过的方案是在Web应用和”Slave“群之间加入一个硬件或软件的”Load Balancer“,这样的好处是应用所需的改动最小,因为对于应用来说,所有的读操作都是通过一个虚拟的Slave来进行,添加和删除“Slave”服务器对应用透明,Load Balancer 实现对各个Slave服务器状态的监控并将出现故障的Slave从可用节点列表里删除,并可以实现一些复杂的负载分担策略,比如新买的服务器处理能力要高 过Slave群中其他的老机器,那么我们可以给这个机器多分配一些负载以最有效的利用资源。

“Master”-“Slave”模式的缺点是它并没有对于“写’操作提供扩展能力,而且存在单点故障,即一旦Master故障,整个网站将丧失 “更新” 的能力。解决的办法采用“Master”-“Master”模式,即两台服务器互为”Master“-“Slave”,这样不仅”读/写“能力扩展了一 倍,而且有效避免了”单点故障“,结合已有的“Master”-“Slave”,整个数据库的架构就变成了下面的”双树“结构。“双树”架构并没有支撑太久的时间,大概6个月后,随着用户的激增,系统再一次达到了极限,不仅”写”操作成为了瓶颈,而且“异步复制”也由于 ”Slave“服务器过于繁忙而出现了严重的滞后而造成读数据的不一致。那么,能不能在现有架构加以解决,比如说增加新的”Master“服务器和考虑采用”同步复制“呢?答案是否定的,在Master超过两台的设置中,只能采用”闭环链“的方式进行复制,在大数据量的生产环境中,很容易造成在任意时刻没 有一个Master或Slave节点是具有全部最新数据的,这样很难保障数据的一致性,而且一旦其中一个Master出现故障,将中断整个复制链;而对于”同步复制“,当然这是消除”复制滞后“的最好办法,不过在当时MySQL的同步复制还远没有成熟到可以运用在投产环境中。Flickr网站的架构,需要一次大的变化来解决长期持续扩展的问题。

2005年7月Dathan Pattishall(MySQL 2005、2006年度 “Application of the Year Award”获得者)加入了Flickr团队。一个星期之内,Dathan解决了Flickr数据库40%的问题,更重要的是,他为Flickr引进了 Shard架构,从而使Flickr网站具备了真正“线性”Scale-Out的增长能力,并一直沿用至今,取得了巨大的成功。 Shard主要是为了解决传统数据库Master/Slave模式下单一Master数据库的“写”瓶颈而出现的,简单的说Shard就是将一个大表分割成多个小表,每个小表存储在不同机器的数据库上,从而将负载分散到多个机器并行处理而极大的提高整个系统的“写”扩展能力。相比传统方式,由于每个数据库都相对较小,不仅读写操作更快,甚至可以将整个小数据库缓存到内存中,而且每个小数据库的备份,恢复也变得相对容易,同时由于分散了风险,单个小数据库 的故障不会影响其他的数据库,使整个系统的可靠性也得到了显著的提高。

对于大多数网站来说,以用户为单位进行Shard分割是最合适不过的,常见的分割方法有按地域(比如邮编),按Key值(比如Hash用户ID), 这些 方法可以简单的通过应用配置文件或算法来实现,一般不需要另外的数据库,缺点是一旦业务增加,需要再次分割Shard时要修改现有的应用算法和重新计算所 有的Shard KEY值;而最为灵活的做法是以“目录”服务为基础的分割,即在Shard之前加一个中央数据库(Global Lookup Cluster),应用要先根据用户主键值查询中央数据库,获得用户数据所在的Shard,随后的操作再转向Shard所在数据库,Dual Tree Central Database就是中央数据库,存放用户表,记录的信息是用户主键以及此用户对以的数据库Shard区;而Master-Master Shards就是一个个的Shard用户数据库,存储实际的用户数据和照片的元数据(Meta Data)。

Shard架构的一些问题和Flickr的解决办法:

1)Shard只适用于不需要 join操作的表,因为跨Shard join操作的开销太大。

解决的办法是将一个用户的所有数据全部存放在同一个Shard里,对于一些传统方式下需要 跨Shard查询的数据,只能采取冗余的方法,比如Shard1的用户A对Shard2的用户B的照片进行了评论,那么这条评论将同时存放在Shard1 和Shard2中。这样就存在一个数据一致性的问题,常规的做法是用数据库事务(Transaction)、”两阶段提交“(2 phase commit)来解决,但做过两阶段提交(2PC)应用的都知道,2PC的效率相对较差,而且实际上也不能100%保证数据的完整性和一致性;另外,一旦由于其中一个Shard故障而提交失败回滚,用户只能放弃或再试一遍,用户体验较差。Flickr对于数据一致性的解决方案是Queue(Flickr用 PHP开发了一个强大的Queue系统,将所有可以异步的任务都用Queue来实现,每天处理高达1千万以上的任务。),事实上当用户A对用户B的照片进行评论时,他并不关心这条评论什么时候出现在用户B的界面上,即将这条评论添加到用户B的交易是可以异步的,允许一定的迟延,通过Queue处理,既保证了数据的一致性,又缩短了用户端的相应时间,提高了系统性能。

2)Shard的另一个主要问题Rebalancing,既当现有Shard的负载达到一定的阀值,如何将现有数据再次分割。

Flickr目前的方式依然是手工的,既人工来确定哪些用户需要迁移,然后运行一个后台程序进行数据迁移,迁移的过程用户账户将被锁住。

Memcached在Flickr的应用

Flickr为中央数据库配置了Memcached,但是没有给Shard配置,原因是什么?Memecached的主要目的是将经常读取的对象放入内存以提高整个系统,尤其是数据库的扩展能力。Memcached的主要结构是两个Hash Table,Server端的HashTable以key-value pair的方式存放对象值,而Client端的HashTable的则决定某一对象存放在哪一个Memcached Server.举个例子说,后台有3个Memecached Server,A、B、C,Client1需要将一个对象名为”userid123456“,值为“张三”的存入,经过Client1的Hash计 算,”userid123456″的值应该放入Memcached ServerB, 而这之后,Client2需要读取”userid123456″的值,经过同样的Hash计算,得出”userid123456″的值如果存在的话应该在 Memcached Server B,并从中取出。最妙的是Server之间彼此是完全独立的,完全不知道对方的存在,没有一个类似与Master或Admin Server的存在,增加和减少Server只需在Client端”注册”并重新Hash就可以了。

Memcached作为数据库缓存的作用主要在于减轻甚至消除高负载数据库情况下频繁读取所带来的Disk I/O瓶颈,相对于数据库自身的缓存来说,具有以下优点:

  1. Memecached的缓存是分布式的,而数据库的缓存只限于本机;
  2. Memcached 缓存的是对象,可以是经过复杂运算和查询的最终结果,并且不限于数据,可以是任何小于1MB的对象,比如html文件等;而数据库缓存是以”row”为单 位的,一旦”row”中的任何数据更新,整个“row”将进行可能是对应用来说不必要的更新;
  3. Memcached的存取是轻量的,而数据库的则相对较 重,在低负载的情况下,一对一的比较,Memcached的性能未必能超过数据库,而在高负载的情况下则优势明显。

Memcached并不适用于更新频繁的数据,因为频繁更新的数据导致大量的Memcached更新和较低的缓冲命中率,这可能也是为什么 Shard没有集成它的原因;Memcached更多的是扩展了数据库的”读“操作,这一点上它和Slave的作用有重叠,以至于有人争论说应该 让”Relication”回到它最初的目的”Online Backup”数据库上,而通过Memcached来提供数据库的“读”扩展。然而,在体系架构中增加Memecached并不是没有代价的,现有的应用要做适当的修改来同步Memcached和数据库中的数据,同时Memcached不提供任何冗余和“failover”功能,这些复杂的控制都需要应用来实现。

我们看到在每一次数据更新都需要更新Memcached,而且数据库或Memcached任何一点写错误应用就可能取得“过期”的数据而得到错误的结果,如何保证数据库和Memcached的同步呢?我们知道复制滞后的主要原因是数据库负载过大而造成异步复制的延迟,Shard架构有效的分散了系统负载,从而大大减轻了这一现象,但是并不能从根本上消除,解决这一问题还是要靠良好的应用设计。当用户访问并更新Shard数据时,Flickr采用了将用户“粘”到某一机器的做法,即同一用户每次登录的所有操作其实都是在Shard中的一个Master上运行的,这样即使复制到Slave,也就是另一台 Master的时候有延时,也不会对用户有影响,除非是用户刚刚更新,尚未复制而这台Master就出现故障了。

对于Central Database的复制滞后和同步问题,Flickr采用了一种复杂的“Write Through Cache”的机制来处理:”Write Through Cache”就是将所有的数据库”写“操作都先写入”Cache”,然后由Cache统一去更新数据库的各个Node,“Write Through Cache”维护每一个Node的更新状态,当有读请求时,即将请求转向状态为”已同步“的Node,这样即避免了复制滞后和Memcached的同步题,但缺点是其实现极为复杂,“Write Throug Cache”层的代码需要考虑和实现所有”journal”,”Transaction“,“failover”,和“recovery”这些数据库已经实现的功能,另外还要考虑自身的”failover”问题。

另外Flickr也用了Redis,在他的那个offline tasks system,对Redis评价很高。

对于PHP session 的弃用

除图片外,Flickr所有的数据都存在数据库中。

php的session是存储在服务器文件系统的,而且默认没有做hash目录,这就意味着如果你的网站访问量大,比如有10万个人在线,你的 session目录下就有10万个文件,如果你的文件格式是NTFS(windows)或者Ext3(Linux),你要定位到某个文件,系统基本上会假死。使用默认的php session还有另外一个问题:服务器session同步,用户在A服务器登录后,session存储在A服务器上,然后应用跳转到B服务器,B服务器上的session没有同步就出问题。

Flickr的架构不能说是完美的,没有完美的架构,ebay对于扩展有以下建议:

  • 不要预先去为性能扩展,出现问题之后找到问题再寻扩展;
  • 不要想寻找到一个一劳永逸的方案,因为你不知道下一个瓶颈在哪里;
  • 访问量大了,出了问题,修改架构,稳定运行,访问量再大了,又出问题了,再修改,这个是解决问题的唯一方案。

Todd Hoff总结的经验:

  • 不要把你的应用简单的看成一个Web应用,可能会有REST APIs, SOAP APIs, RSS feeds, Atom feeds等等的应用
  • “无界限”设计,不要把你的用户死死的绑定在某个服务器上
  •  产品设计时需要做扩容的计划以及预算
  • 慢慢来,不要一开始就买一堆服务器
  • 实地考察,不要臆想,获得实际数据之后再做决定
  • 内建日志系统,记录服务器和应用日志
  • Cache,缓存是必不可少的
  • 抽象层,由于你的架构随时可能变,架构的变化必定要带来底层的变化,这就需要你在底层的基础上根据业务封装一层中间层,这样底层的改动不至于影响业务(这个太重要了,不要因为扩展把原来的程序推倒重来)
  • 迭代开发,随时改进
  • 忘记那些调优的小技巧吧,比如很多人对与PHP里面的require和require_once的性能差别,这些性能的差异和架构上的短板比起来根本不足为道
  • 在线上测试你的效果
  • 忘记用工具测试出来的结果,这些结果只能给你一个大概的印象而已
  • 找出你的系统短板,一台服务器的最大处理能力是多少?现在离最大负载还有多远?mysql的瓶颈在哪里?是不是磁盘IO?memcache的瓶颈在哪里?CPU还是网络传输?
  • 注意你的用户使用规律,比如Flickr发现每年的第一个工作日比平时多20%~40%的上传量,周日的访问量比平时要多40%~50%
  • 要注意指数型的增长
  • 你的计划是为你访问的峰值设计的

除了技术手段的优化,Flickr 充分利用硬件本身的更新换代带来的好处,曾经用 18 台新机器替换掉原来的 67 台 Web 服务器,用 8 台新机器替换掉原来的 23 台图片处理的机器。无论从机架占用还是电力使用都节省了很多,而整理处理能力并没有削弱。

Flickr 技术团队随着网站的快速发展并没有增加大量人手,个人生产力的产出是相当的高。如何做到的呢?给出了四个非常有趣的原则:

  • 使得机器自动构建 (Teach machines to build themselves)
  • 使得机器自监控(Teach machines to watch themselves)
  • 使得机器自修复(Teach machines to fix themselves)
  • 通过流程减少 MTTR (Reduce MTTR by streamlining)

自动购建上,Flickr 使用了 OpsCode 、Puppet 以及 System Imager/Configurator 等。或许这几个工具值得我们关注一下。

Flickr 团队内部沟通工具也挺有意思,除了内部的 IRC 用于讨论之外,还利用 Yahoo! Messenger 的IM Bot 记录更多的系统变化,并且,重要的是,将这些信息弄到搜索引擎里面 … “信息查找”,是国内多数团队交流工具忽视的地方。

参考信息: