一:硬架构
1 :机房的选择:
在选择机房的时候,根据网站用户的地域分布,可以选择网通或电信机房,但更多时候,可能双线机房才是合适的。越大的城市,机房价格越贵,从成本的角度看可以在一些中小城市托管服务器,比如说广州的公司可以考虑把服务器托管在东莞,佛山等地,不是特别远,但是价格会便宜很多。
2 :带宽的大小:
通常老板花钱请我们架构网站的时候,会给我们提出一些目标,诸如网站每天要能承受100 万PV 的访问量等等。这时我们要预算一下大概需要多大的带宽,计算带宽大小主要涉及两个指标(峰值流量和页面大小),我们不妨在计算前先做出必要的假设:
第一:假设峰值流量是平均流量的5 倍。 第二:假设每次访问平均的页面大小是100K 字节左右。
如果100 万PV 的访问量在一天内平均分布的话,折合到每秒大约12 次访问,如果按平均每次访问页面的大小是100K 字节左右计算的话,这12 次访 问总计大约就是1200K 字节,字节的单位是Byte ,而带宽的单位是bit ,它们之间的关系是1Byte = 8bit ,所以1200K Byte 大致就相当于9600K bit ,也就是9Mbps 的样子,实际情况中,我们的网站必须能在峰值流量时保持正常访问,所以按照假设的峰值流量算,真实带宽的需求应该在45Mbps 左右。
当然,这个结论是建立在前面提到的两点假设的基础上,如果你的实际情况和这两点假设有出入,那么结果也会有差别。
3 :服务器的划分:
先看我们都需要哪些服务器:图片服务器,页面服务器,数据库服务器,应用服务器,日志服务器等等。
对于访问量大点的网站而言,分离单独的图片服务器和页面服务器相当必要,我们可以用lighttpd 来跑图片服务器,用apache 来跑页面服务 器,当然也可以选择别的,甚至,我们可以扩展成很多台图片服务器和很多台页面服务器,并设置相关域名,如img.domain.com 和 www.domain.com ,页面里的图片路径都使用绝对路径,如<img src=\'#\'" //img.domain.com/abc.gif" /> ,然后设置DNS 轮循,达到最初级的负载均衡。当然,服务器多了就不可避免的涉及一个同步的问题,这个可以使用rsync 软件来搞定。
数据库服务器是重中之重,因为网站的瓶颈问题十有八九是出在数据库身上。现在一般的中小网站多使用MySQL 数据库,不过它的集群功能似乎还没有达 到stable 的阶段,所以这里不做评价。一般而言,使用MySQL 数据库的时候,我们应该搞一个主从(一主多从)结构,主数据库服务器使用innodb 表结构,从数据服务器使用myisam 表结构,充分发挥它们各自的优势,而且这样的主从结构分离了读写操作,降低了读操作的压力,甚至我们还可以设定一个专门的从服务器做备份服务器,方便备份。不然如果你只有一台主服务器,在大数据量的情况下,mysqldump 基本就没戏了,直接拷贝数据文件的话,还得先停止数据库服务再拷贝,否则备份文件会出错。但对于很多网站而言,即使数据库服务仅停止了一秒也是不可接受的。如果你有了一台从数据库服务器,在备份数 据的时候,可以先停止服务(slave stop )再备份,再启动服务(slave start )后从服务器会自动从主服务器同步数据,一切都没有影响。但是主从结构也是有致命缺点的,那就是主从结构只是降低了读操作的压力,却不能降低写操作的压力。为了适应更大的规模,可能只剩下最后这招了:横向/ 纵向分割数据库。所谓横向分割数据库,就是把不同的表保存到不同的数据库服务器上,比如说用户表保存在A 数据库服务器上,文章表保存在B 数据库服务器上,当然这样的分割是有代价的,最基本的就是你没法进行LEFT JOIN 之类的操作了。所谓纵向分割数据库,一般是指按照用户标识(user_id )等来划分数据存储的服务器,比如说:我们有5 台数据库服务器,那么 “user_id % 5 + 1” 等于1 的就保存到1 号服务器,等于2 的就保存到2 好服务器,以此类推,纵向分隔的原则有很多种,可以视情况选择。不过和横向分割数据库一样,纵向分割 数据库也是有代价的,最基本的就是我们在进行如COUNT, SUM 等汇总操作的时候会麻烦很多。综上所述,数据库服务器的解决方案一般视情况往往是一个混合的方案,以其发挥各种方案的优势,有时候还需要借助 memcached 之类的第三方软件,以便适应更大访问量的要求。
如果有专门的应用服务器来跑PHP 脚本是最合适不过的了,那样我们的页面服务器只保存静态页面就可以了,可以给应用服务器设置一些诸如 app.domain.com 之类的域名来和页面服务器加以区别。对于应用服务器,我还是更倾向于使用prefork 模式的apache ,配上必要的 xcache 之类的PHP 缓存软件,加载模块要越少越好,除了mod_rewrite 等必要的模块,不必要的东西统统舍弃,尽量减少httpd 进程的内存消耗,而那些图片服务器,页面服务器等静态内容就可以使用lighttpd 或者tux 来搞,充分发挥各种服务器的特点。
如果条件允许,独立的日志服务器也是必要的,一般小网站的做法都是把页面服务器和日志服务器合二为一了,在凌晨访问量不大的时候cron 运行前一天 的日志计算,不过如果你使用awstats 之类的日志分析软件,对于百万级访问量而言,即使按天归档,也会消耗很多时间和服务器资源去计算,所以分离单独的日志服务器还是有好处的,这样不会影响正式服务器的工作状态。
二:软架构
1 :框架的选择:
现在的PHP 框架有很多选择,比如:CakePHP ,Symfony ,Zend Framework 等等,至于应该使用哪一个并没有唯一的答案,要根据Team 里团队成员对各个框架的了解程度而定。很多时候,即使没有使用框架,一样能写出好的程序来,比如Flickr 据说就是用Pear+Smarty 这样的类库写出来的,所以是否用框架,用什么框架,一般不是最重要,重要的是我们的编程思想里要有框架的意识。 现在的.NET 框架有很多选择,比如:cnForums ,.text ,cs, Castle, 等等
2 :逻辑的分层:
网站规模到了一定的程度之后,代码里各种逻辑纠缠在一起,会给维护和扩展带来巨大的障碍,这时我们的解决方式其实很简单,那就是重构,将逻辑进行分层。通常,自上而下可以分为表现层,应用层,领域层,持久层。
所谓表现层,并不仅仅就指模板,它的范围要更广一些,所有和表现相关的逻辑都应该被纳入表现层的范畴。比如说某处的字体要显示为红色,某处的开头要空两格,这些都属于表现层。很多时候,我们容易犯的错误就是把本属于表现层的逻辑放到了其他层面去完成,这里说一个很常见的例子:我们在列表页显示文章标题的时候,都会设定一个最大字数,一旦标题长度超过了这个限制,就截断,并在后面显示“..” ,这就是最典型的表现层逻辑,但是实际情况,有很多程序员都是在非表现层代码里完成数据的获取和截断,然后赋值给表现层模板,这样的代码最直接的缺点就是同样一段数据,在这个页面我可能想显示前10 个字,再另一个 页面我可能想显示前15 个字,而一旦我们在程序里固化了这个字数,也就丧失了可移植性。正确的做法是应该做一个视图助手之类的程序来专门处理此类逻辑,比如说:Smarty 里的truncate 就属于这样的视图助手(不过它那个实现不适合中文)。
所谓应用层,它的主要作用是定义用户可以做什么,并把操作结果反馈给表现层。至于如何做,通常不是它的职责范围(而是领域层的职责范围),它会通过委派把如何做的工作交给领域层去处理。在使用MVC 架构的网站中,我们可以看到类似下面这样的URL :domain.com/articles/view/123 ,其内部编码实现,一般就是一个Articles 控制器类,里面有一个view 方法,这就是一 个典型的应用层操作,因为它定义了用户可以做一个查看的动作。在MVC 架构中,有一个准则是这么说的:Rich Model Is Good 。言外之意,就是Controller 要保持“ 瘦” 一些比较好,进而说明应用层要尽量简单,不要包括涉及领域内容的逻辑。
所谓领域层,最直接的解释就是包含领域逻辑的层。它是一个软件的灵魂所在。先来看看什么叫领域逻辑,简单的说,具有明确的领域概念的逻辑就是领域逻辑,比如我们在ATM 机上取钱,过程大致是这样的:插入银联卡,输入密码,输入取款金额,确定,拿钱,然后ATM 吐出一个交易凭条。在这个过程中,银联卡 在ATM 机器里完成钱从帐户上划拨的过程就是一个领域逻辑,因为取钱在银行中是一个明确的领域概念,而ATM 机吐出一个交易凭条则不是领域逻辑,而仅是一 个应用逻辑,因为吐出交易凭条并不是银行中一个明确的领域概念,只是一种技术手段,对应的,我们取钱后不吐交易凭条,而发送一条提醒短信也是可能的,但并不是一定如此,如果在实际情况中,我们要求取款后必须吐出交易凭条,也就是说吐出交易凭条已经和取款紧密结合,那么你也可以把吐出交易凭条看作是领域逻辑 的一部分,一切都以问题的具体情况而定。在Eric 那本经典的领域驱动设计中,把领域层分为了五种基本元素:实体,值对象,服务,工厂,仓储。具体可以参 阅书中的介绍。领域层最常犯的错误就是把本应属于领域层的逻辑泄露到了其他层次,比如说在一个CMS 系统,对热门文章的定义是这样的:每天被浏览的次数多 于1000 次,被评论的次数多于100 次,这样的文章就是热门文章。对于一个CMS 来说,热门文章这个词无疑是一个重要的领域概念,那么我们如何实现这个逻辑的设计的?你可能会给出类似下面的代码:“SELECT ... FROM ... WHERE 浏览 > 1000 AND 评论 > 100” ,没错,这是最简单的实现方式,但是这里需要注意的是“ 每天被浏览的次数多于1000 次,被评论的次数多于100 次”这个重要的领域逻辑被隐藏到了SQL 语句中,SQL 语句显然不属于领域层的范畴,也就是说,我们的领域逻辑泄露了。
所谓持久层,就是指把我们的领域模型保存到数据库中。因为我们的程序代码是面向对象风格的,而数据库一般是关系型的数据库,所以我们需要把领域模型 碾平,才能保存到数据库中,但是在 PHP 里,直到目前还没有非常好的ORM 出现,所以这方面的解决方案不是特别多,参考 Martin 的企业应用架构模式一 书,大致可以使用的方法有行数据入口( Row Data Gateway )或者表数据入口( Table Data Gateway),或者把领域层和持久层合二为一变成活动记录(Active Record)的方式。