Linux:Yupoo(又拍网)的系统架构
July 10, 2015
Yupoo!(又拍网) 是目前国内最大的图片服务提供商,整个网站构建于大量的开源软件之上。以下为其使用到的开源软件信息:
- 操作系统:CentOS、MacOSX、Ubuntu
- 服务器:Apache、Nginx、Squid
- 数据库:MySQLmochiweb、MySQLdb
- 服务器监控:Cacti、Nagios、
- 开发语言:PHP、Python、Erlang、Java、Lua
- 分布式计算:Hadoop、Mogilefs、
- 日志分析:AWStats
- 任务管理:Redmine
- 消息系统:RabbitMQ、php-amqp
- 前端框架:Mootools
- 缓存系统:Memcached、php-memcached、libmemcached、pylibmc、XCache、RedisRiak、Predis
- 图片处理:GraphicsMagick、gmagick
- FTP工具:vsftpd
- 开发工具:VIM、Readline
- 调试工具:Firebug、Xdebug
- 版本控制:Mercurial
- 搜索服务:Solr
- 邮件服务:Postfix
- 网络编程:Twisted、cURL、libevent、Net-SNMP、NTP
- 可用性测试:ibrowse
- 集群系统:Heartbeat
- 并行开发:gevent
- 负载均衡:IPVS
- Python框架:bottle
- 虚拟通道:OpenVPN
(信息来源:http://www.yupoo.com/info/about/)。
一、Yupoo的整体架构
二、程序语言的选择
Yupoo的服务器端开发语言主要是PHP和Python,其中PHP用于编写Web逻辑(通过HTTP和用户直接打交道), 而Python则主要用于开发内部服务和后台任务。在客户端则使用了大量基于MooTools框架的Javascript。 另外,Yupoo把图片处理过程从PHP进程里独立出来变成一个服务。这个服务基于nginx,作为nginx的一个模块开放REST API。
三、服务器的选用
选用Squid的原因是“目前暂时还没找到效率比 Squid 高的缓存系统,原来命中率的确很差,后来在 Squid 前又装了层 Lighttpd, 基于 url 做 hash, 同一个图片始终会到同一台 squid 去,所以命中率彻底提高了。”
同时Yupoo也使用Python开发了YPWS/YPFS:
- YPWS–Yupoo Web Server 是用 Python开发的一个小型 Web 服务器,提供基本的 Web 服务外,可以增加针对用户、图片、外链网站显示的逻辑判断,可以安装于任何有空闲资源的服务器中,遇到性能瓶颈时方便横向扩展。
- YPFS–Yupoo File System 与 YPWS 类似,也是基于这个 Web 服务器上开发的图片上传服务器。
有网友留言质疑 Python 的效率,Yupoo 老大刘平阳在 del.icio.us 上写到 “YPWS用Python自己写的,每台机器每秒可以处理294个请求, 现在压力几乎都在10%以下”
四、Yupoo的消息系统
由于PHP的单线程模型,Yupoo把耗时较久的运算和I/O操作从HTTP请求周期中分离出来, 交给由Python实现的任务进程来完成,以保证请求响应速度。这些任务主要包括:邮件发送、数据索引、数据聚合和好友动态推送等等。PHP通过消息队列 (Yupoo用的是RabbitMQ)来触发任务执行。这些任务的主要特点为:
- 由用户或者定时触发的
- 耗时比较长的
- 需要异步执行的
整个任务系统主要分为以消息分发、进程管理和工作进程组成。
五、数据库的设计
数据库一向是网站架构中最具挑战性的,瓶颈通常出现在这里。又拍网的照片数据量很大,数据库也几度出现严重的压力问题。和很多使用MySQL的 2.0站点一样,又拍网的MySQL集群经历了从最初的一个主库一个从库、到一个主库多个从库、 然后到多个主库多个从库的一个发展过程。
最初是由一台主库和一台从库组成,当时从库只用作备份和容灾,当主库出现故障时,从库就手动变成主库,一般情况下,从库 不作读写操作(同步除外)。随着压力的增加,加上了memcached,当时只用其缓存单行数据。 但是,单行数据的缓存并不能很好地解决压力问题,因为单行数据的查询通常很快。所以把一些实时性要求不高的Query放到从库去执行。后面又通过添加多个 从库来分流查询压力,不过随着数据量的增加,主库的写压力也越来越大。在参考了一些相关产品和其它网站的做法后,进了行数据库拆分。也就是将数据存放到不 同的数据库服务器中。
如何进行数据库拆分?
- 垂直拆分:是指按功能模块拆分,比如可以将群组相关表和照片相关表存放在不同的数据库中,这种方式多个数据库之间的表结构不同。
- 水平拆分:而水平拆分是将同一个表的数据进行分块保存到不同的数据库中,这些数据库中的表结构完全相同。
一般都会先进行垂直拆分,因为这种方式拆分方式实现起来比较简单,根据表名访问不同的数据库就可以了。但是垂直拆分方式并不能彻底解决所有压力问 题,另外,也要看应用类型是否合适这种拆分方式。如果合适的话,也能很好的起到分散数据库压力的作用。比如对于豆瓣我比较适合采用垂直拆分, 因为豆瓣的各核心业务/模块(书籍、电影、音乐)相对独立,数据的增加速度也比较平稳。不同的是,又拍网的核心业务对象是用户上传的照片,而照片数据的增 加速度随着用户量的增加越来越快。压力基本上都在照片表上,显然垂直拆分并不能从根本上解决我们的问题,所以,Yupoo采用水平拆分的方式。
水平拆分实现起来相对复杂,我们要先确定一个拆分规则,也就是按什么条件将数据进行切分。 一般2.0网站都以用户为中心,数据基本都跟随用户,比如用户的照片、朋友和评论等等。因此一个比较自然的选择是根据用户来切分。每个用户都对应一个数据 库,访问某个用户的数据时, 要先确定他/她所对应的数据库,然后连接到该数据库进行实际的数据读写。那么,怎么样对应用户和数据库呢?Yupoo有这些选择:
1、按算法对应
最简单的算法是按用户ID的奇偶性来对应,将奇数ID的用户对应到数据库A,而偶数ID的用户则对应到数据库B。这个方法的最大问题是,只能分成两 个库。另一个算法是按用户ID所在区间对应,比如ID在0-10000之间的用户对应到数据库A, ID在10000-20000这个范围的对应到数据库B,以此类推。按算法分实现起来比较方便,也比较高效,但是不能满足后续的伸缩性要求,如果需要增加 数据库节点,必需调整算法或移动很大的数据集, 比较难做到在不停止服务的前提下进行扩充数据库节点。
2、按索引/映射表对应
这种方法是指建立一个索引表,保存每个用户的ID和数据库ID的对应关系,每次读写用户数据时先从这个表获取对应数据库。新用户注册后,在所有可用 的数据库中随机挑选一个为其建立索引。这种方法比较灵活,有很好的伸缩性。一个缺点是增加了一次数据库访问,所以性能上没有按算法对应好。
比较之后,Yupoo采用的是索引表的方式,我们愿意为其灵活性损失一些性能,更何况我们还有memcached, 因为索引数据基本不会改变的缘故,缓存命中率非常高。所以能很大程度上减少了性能损失。
索引表的方式能够比较方便地添加数据库节点,在增加节点时,只要将其添加到可用数据库列表里即可。 当然如果需要平衡各个节点的压力的话,还是需要进行数据的迁移,但是这个时候的迁移是少量的,可以逐步进行。要迁移用户A的数据,首先要将其状态置为迁移 数据中,这个状态的用户不能进行写操作,并在页面上进行提示。 然后将用户A的数据全部复制到新增加的节点上后,更新映射表,然后将用户A的状态置为正常,最后将原来对应的数据库上的数据删除。这个过程通常会在临晨进 行,所以,所以很少会有用户碰到迁移数据中的情况。当然,有些数据是不属于某个用户的,比如系统消息、配置等等,把这些数据保存在一个全局库中。
0 Comments