一:硬架構(gòu)
1
:機房的選擇:
在
選擇機房的時候,根據(jù)網(wǎng)站用戶的地域分布,可以選擇網(wǎng)通或電信機房,但更多時候,可能雙線機房才是合適的。越大的城市,機房價格越貴,從成本的角度看可以
在一些中小城市托管服務(wù)器,比如說廣州的公司可以考慮把服務(wù)器托管在東莞,佛山等地,不是特別遠(yuǎn),但是價格會便宜很多。
2
:帶寬的大小:
通常老板花錢請我們架構(gòu)網(wǎng)站的時候,會給我們提出一些目標(biāo),諸如網(wǎng)站每天要能承受100
萬PV
的訪問量等等。這時我們要預(yù)算一下大概需要多大的帶寬,計算帶寬大小主要涉及兩個指標(biāo)(峰值流量和頁面大小),我們不妨在計算前先做出必要的假設(shè):
第一:假設(shè)峰值流量是平均流量的5
倍。
第二:假設(shè)每次訪問平均的頁面大小是100K
字節(jié)左右。
如果100
萬PV
的訪問量在一天內(nèi)平均分布的話,折合到每秒大約12
次訪問,如果按平均每次訪問頁面的大小是100K
字節(jié)左右計算的話,這12
次訪 問總計大約就是1200K
字節(jié),字節(jié)的單位是Byte
,而帶寬的單位是bit
,它們之間的關(guān)系是1Byte = 8bit
,所以1200K Byte
大致就相當(dāng)于9600K bit
,也就是9Mbps
的樣子,實際情況中,我們的網(wǎng)站必須能在峰值流量時保持正常訪問,所以按照假設(shè)的峰值流量算,真實帶寬的需求應(yīng)該在45Mbps
左右。
當(dāng)然,這個結(jié)論是建立在前面提到的兩點假設(shè)的基礎(chǔ)上,如果你的實際情況和這兩點假設(shè)有出入,那么結(jié)果也會有差別。
3
:服務(wù)器的劃分:
先看我們都需要哪些服務(wù)器:圖片服務(wù)器,頁面服務(wù)器,數(shù)據(jù)庫服務(wù)器,應(yīng)用服務(wù)器,日志服務(wù)器等等。
對于訪問量大點的網(wǎng)站而言,分離單獨的圖片服務(wù)器和頁面服務(wù)器相當(dāng)必要,我們可以用lighttpd
來跑圖片服務(wù)器,用apache
來跑頁面服務(wù) 器,當(dāng)然也可以選擇別的,甚至,我們可以擴展成很多臺圖片服務(wù)器和很多臺頁面服務(wù)器,并設(shè)置相關(guān)域名,如img.domain.com
和 www.domain.com
,頁面里的圖片路徑都使用絕對路徑,如<img src="http://img.domain.com/abc.gif" />
,然后設(shè)置DNS
輪循,達(dá)到最初級的負(fù)載均衡。當(dāng)然,服務(wù)器多了就不可避免的涉及一個同步的問題,這個可以使用rsync
軟件來搞定。
數(shù)據(jù)庫服務(wù)器是重中之重,因為網(wǎng)站的瓶頸問題十有八九是出在數(shù)據(jù)庫身上。現(xiàn)在一般的中小網(wǎng)站多使用MySQL
數(shù)據(jù)庫,不過它的集群功能似乎還沒有達(dá) 到stable
的階段,所以這里不做評價。一般而言,使用MySQL
數(shù)據(jù)庫的時候,我們應(yīng)該搞一個主從(一主多從)結(jié)構(gòu),主數(shù)據(jù)庫服務(wù)器使用innodb
表結(jié)構(gòu),從數(shù)據(jù)服務(wù)器使用myisam
表結(jié)構(gòu),充分發(fā)揮它們各自的優(yōu)勢,而且這樣的主從結(jié)構(gòu)分離了讀寫操作,降低了讀操作的壓力,甚至我們還可以設(shè)定一個專門的從服務(wù)器做備份服務(wù)器,方便備份。不然如果你只有一臺主服務(wù)器,在大數(shù)據(jù)量的情況下,mysqldump
基本就沒戲了,直接拷貝數(shù)據(jù)文件的話,還得先停止數(shù)據(jù)庫服務(wù)再拷貝,否則備份文件會出錯。但對于很多網(wǎng)站而言,即使數(shù)據(jù)庫服務(wù)僅停止了一秒也是不可接受的。如果你有了一臺從數(shù)據(jù)庫服務(wù)器,在備份數(shù) 據(jù)的時候,可以先停止服務(wù)(slave stop
)再備份,再啟動服務(wù)(slave start
)后從服務(wù)器會自動從主服務(wù)器同步數(shù)據(jù),一切都沒有影響。但是主從結(jié)構(gòu)也是有致命缺點的,那就是主從結(jié)構(gòu)只是降低了讀操作的壓力,卻不能降低寫操作的壓力。為了適應(yīng)更大的規(guī)模,可能只剩下最后這招了:橫向/
縱向分割數(shù)據(jù)庫。所謂橫向分割數(shù)據(jù)庫,就是把不同的表保存到不同的數(shù)據(jù)庫服務(wù)器上,比如說用戶表保存在A
數(shù)據(jù)庫服務(wù)器上,文章表保存在B
數(shù)據(jù)庫服務(wù)器上,當(dāng)然這樣的分割是有代價的,最基本的就是你沒法進(jìn)行LEFT JOIN
之類的操作了。所謂縱向分割數(shù)據(jù)庫,一般是指按照用戶標(biāo)識(user_id
)等來劃分?jǐn)?shù)據(jù)存儲的服務(wù)器,比如說:我們有5
臺數(shù)據(jù)庫服務(wù)器,那么 “user_id % 5 + 1”
等于1
的就保存到1
號服務(wù)器,等于2
的就保存到2
好服務(wù)器,以此類推,縱向分隔的原則有很多種,可以視情況選擇。不過和橫向分割數(shù)據(jù)庫一樣,縱向分割 數(shù)據(jù)庫也是有代價的,最基本的就是我們在進(jìn)行如COUNT, SUM
等匯總操作的時候會麻煩很多。綜上所述,數(shù)據(jù)庫服務(wù)器的解決方案一般視情況往往是一個混合的方案,以其發(fā)揮各種方案的優(yōu)勢,有時候還需要借助 memcached
之類的第三方軟件,以便適應(yīng)更大訪問量的要求。
如果有專門的應(yīng)用服務(wù)器來跑PHP
腳本是最合適不過的了,那樣我們的頁面服務(wù)器只保存靜態(tài)頁面就可以了,可以給應(yīng)用服務(wù)器設(shè)置一些諸如 app.domain.com
之類的域名來和頁面服務(wù)器加以區(qū)別。對于應(yīng)用服務(wù)器,我還是更傾向于使用prefork
模式的apache
,配上必要的 xcache
之類的PHP
緩存軟件,加載模塊要越少越好,除了mod_rewrite
等必要的模塊,不必要的東西統(tǒng)統(tǒng)舍棄,盡量減少httpd
進(jìn)程的內(nèi)存消耗,而那些圖片服務(wù)器,頁面服務(wù)器等靜態(tài)內(nèi)容就可以使用lighttpd
或者tux
來搞,充分發(fā)揮各種服務(wù)器的特點。
如果條件允許,獨立的日志服務(wù)器也是必要的,一般小網(wǎng)站的做法都是把頁面服務(wù)器和日志服務(wù)器合二為一了,在凌晨訪問量不大的時候cron
運行前一天 的日志計算,不過如果你使用awstats
之類的日志分析軟件,對于百萬級訪問量而言,即使按天歸檔,也會消耗很多時間和服務(wù)器資源去計算,所以分離單獨的日志服務(wù)器還是有好處的,這樣不會影響正式服務(wù)器的工作狀態(tài)。
二:軟架構(gòu)
1
:框架的選擇:
現(xiàn)在的PHP
框架有很多選擇,比如:CakePHP
,Symfony
,Zend Framework
等等,至于應(yīng)該使用哪一個并沒有唯一的答案,要根據(jù)Team
里團(tuán)隊成員對各個框架的了解程度而定。很多時候,即使沒有使用框架,一樣能寫出好的程序來,比如Flickr
據(jù)說就是用Pear+Smarty
這樣的類庫寫出來的,所以是否用框架,用什么框架,一般不是最重要,重要的是我們的編程思想里要有框架的意識。
現(xiàn)在的.NET
框架有很多選擇,比如:cnForums
,.text
,cs, Castle,
等等
2
:邏輯的分層:
網(wǎng)站規(guī)模到了一定的程度之后,代碼里各種邏輯糾纏在一起,會給維護(hù)和擴展帶來巨大的障礙,這時我們的解決方式其實很簡單,那就是重構(gòu),將邏輯進(jìn)行分層。通常,自上而下可以分為表現(xiàn)層,應(yīng)用層,領(lǐng)域?qū)樱志脤印?/font>
所
謂表現(xiàn)層,并不僅僅就指模板,它的范圍要更廣一些,所有和表現(xiàn)相關(guān)的邏輯都應(yīng)該被納入表現(xiàn)層的范疇。比如說某處的字體要顯示為紅色,某處的開頭要空兩格,
這些都屬于表現(xiàn)層。很多時候,我們?nèi)菀追傅腻e誤就是把本屬于表現(xiàn)層的邏輯放到了其他層面去完成,這里說一個很常見的例子:我們在列表頁顯示文章標(biāo)題的時
候,都會設(shè)定一個最大字?jǐn)?shù),一旦標(biāo)題長度超過了這個限制,就截斷,并在后面顯示“..”
,這就是最典型的表現(xiàn)層邏輯,但是實際情況,有很多程序員都是在非表現(xiàn)層代碼里完成數(shù)據(jù)的獲取和截斷,然后賦值給表現(xiàn)層模板,這樣的代碼最直接的缺點就是同樣一段數(shù)據(jù),在這個頁面我可能想顯示前10
個字,再另一個 頁面我可能想顯示前15
個字,而一旦我們在程序里固化了這個字?jǐn)?shù),也就喪失了可移植性。正確的做法是應(yīng)該做一個視圖助手之類的程序來專門處理此類邏輯,比如說:Smarty
里的truncate
就屬于這樣的視圖助手(不過它那個實現(xiàn)不適合中文)。
所謂應(yīng)用層,它的主要作用是定義用戶可以做什么,并把操作結(jié)果反饋給表現(xiàn)層。至于如何做,通常不是它的職責(zé)范圍(而是領(lǐng)域?qū)拥穆氊?zé)范圍),它會通過委派把如何做的工作交給領(lǐng)域?qū)尤ヌ幚怼T谑褂肕VC
架構(gòu)的網(wǎng)站中,我們可以看到類似下面這樣的URL
:domain.com/articles/view/123
,其內(nèi)部編碼實現(xiàn),一般就是一個Articles
控制器類,里面有一個view
方法,這就是一 個典型的應(yīng)用層操作,因為它定義了用戶可以做一個查看的動作。在MVC
架構(gòu)中,有一個準(zhǔn)則是這么說的:Rich Model Is Good
。言外之意,就是Controller
要保持“
瘦”
一些比較好,進(jìn)而說明應(yīng)用層要盡量簡單,不要包括涉及領(lǐng)域內(nèi)容的邏輯。
所謂領(lǐng)域?qū)樱钪苯拥慕忉尵褪前I(lǐng)域邏輯的層。它是一個軟件的靈魂所在。先來看看什么叫領(lǐng)域邏輯,簡單的說,具有明確的領(lǐng)域概念的邏輯就是領(lǐng)域邏輯,比如我們在ATM
機上取錢,過程大致是這樣的:插入銀聯(lián)卡,輸入密碼,輸入取款金額,確定,拿錢,然后ATM
吐出一個交易憑條。在這個過程中,銀聯(lián)卡 在ATM
機器里完成錢從帳戶上劃撥的過程就是一個領(lǐng)域邏輯,因為取錢在銀行中是一個明確的領(lǐng)域概念,而ATM
機吐出一個交易憑條則不是領(lǐng)域邏輯,而僅是一
個應(yīng)用邏輯,因為吐出交易憑條并不是銀行中一個明確的領(lǐng)域概念,只是一種技術(shù)手段,對應(yīng)的,我們?nèi)″X后不吐交易憑條,而發(fā)送一條提醒短信也是可能的,但并
不是一定如此,如果在實際情況中,我們要求取款后必須吐出交易憑條,也就是說吐出交易憑條已經(jīng)和取款緊密結(jié)合,那么你也可以把吐出交易憑條看作是領(lǐng)域邏輯
的一部分,一切都以問題的具體情況而定。在Eric
那本經(jīng)典的領(lǐng)域驅(qū)動設(shè)計中,把領(lǐng)域?qū)臃譃榱宋宸N基本元素:實體,值對象,服務(wù),工廠,倉儲。具體可以參 閱書中的介紹。領(lǐng)域?qū)幼畛7傅腻e誤就是把本應(yīng)屬于領(lǐng)域?qū)拥倪壿嬓孤兜搅似渌麑哟危热缯f在一個CMS
系統(tǒng),對熱門文章的定義是這樣的:每天被瀏覽的次數(shù)多 于1000
次,被評論的次數(shù)多于100
次,這樣的文章就是熱門文章。對于一個CMS
來說,熱門文章這個詞無疑是一個重要的領(lǐng)域概念,那么我們?nèi)绾螌崿F(xiàn)這個邏輯的設(shè)計的?你可能會給出類似下面的代碼:“SELECT ... FROM ... WHERE
瀏覽 > 1000 AND
評論 > 100”
,沒錯,這是最簡單的實現(xiàn)方式,但是這里需要注意的是“
每天被瀏覽的次數(shù)多于1000
次,被評論的次數(shù)多于100
次”
這個重要的領(lǐng)域邏輯被隱藏到了SQL
語句中,SQL
語句顯然不屬于領(lǐng)域?qū)拥姆懂牐簿褪钦f,我們的領(lǐng)域邏輯泄露了。
所謂持久層,就是指把我們的領(lǐng)域模型保存到數(shù)據(jù)庫中。因為我們的程序代碼是面向?qū)ο箫L(fēng)格的,而數(shù)據(jù)庫一般是關(guān)系型的數(shù)據(jù)庫,所以我們需要把領(lǐng)域模型 碾平,才能保存到數(shù)據(jù)庫中,但是在
PHP
里,直到目前還沒有非常好的
ORM
出現(xiàn),所以這方面的解決方案不是特別多,參考
Martin
的企業(yè)應(yīng)用架構(gòu)模式一
書,大致可以使用的方法有行數(shù)據(jù)入口(
Row Data Gateway
)或者表數(shù)據(jù)入口(
Table Data Gateway),或者把領(lǐng)域?qū)雍统志脤雍隙橐蛔兂苫顒佑涗洠ˋctive Record)的方式。