实践:服务器编写/系统架构/cache
從基本HTTP協(xié)議,服務(wù)器編寫(只講思路),到完整系統(tǒng)搭建(包括負(fù)載均衡LVS,IDC分布,DNS解析),到瀏覽器緩存的使用(重點(diǎn)講述),結(jié)合線上實(shí)例圖文講解如何以最低廉的成本構(gòu)建快速,高并發(fā),高可用,可擴(kuò)展的Web服務(wù)。最后將拿本公司一些線上產(chǎn)品做實(shí)例分析。如果能靈活應(yīng)用這些方法,你也許會(huì)發(fā)現(xiàn)對(duì)于某些網(wǎng)站來(lái)說(shuō)節(jié)約10倍成本,其實(shí)是個(gè)很保守的說(shuō)法。
- 一、服務(wù)器編寫篇
- 二、系統(tǒng)架構(gòu)篇
- 三、Cache為王篇
- 四、實(shí)例分析篇
- 五、動(dòng)態(tài)應(yīng)用篇
-----------------------------------------------------------------------------------------
服務(wù)器編寫
一,如何節(jié)約CPU
二,怎樣使用內(nèi)存
三,減少磁盤I/O
四,優(yōu)化你的網(wǎng)卡
五,調(diào)整內(nèi)核參數(shù)
六,衡量Web Server的性能指標(biāo)
七,NBA js直播的發(fā)展歷程
八,新浪財(cái)經(jīng)實(shí)時(shí)行情系統(tǒng)的歷史遺留問題 (7 byte = 10.68w RMB/year)
-----------------------------------------------------------------------------------------
一,如何節(jié)約CPU
1,選擇一個(gè)好的I/O模型(epoll, kqueue)
3年前,我們還關(guān)心c10k問題,隨著硬件性能的提升,那已經(jīng)不成問題,但如果想讓PIII 900服務(wù)器支撐5w+ connections,還是需要些能耐的。
epoll最擅長(zhǎng)的事情是監(jiān)視大量閑散連接,批量返回可用描述符,這讓單機(jī)支撐百萬(wàn)connections成為可能。linux 2.6以上開始支持epoll,freebsd上相應(yīng)的有kqueue,不過(guò)我個(gè)人偏愛linux,不太關(guān)心kqueue。
邊緣觸發(fā)ET 和 水平觸發(fā)LT 的選擇:
早期的文檔說(shuō)ET很高效,但是有些冒進(jìn)。但事實(shí)上LT使用過(guò)程中,我苦惱了將近一個(gè)月有余,一不留神CPU 利用率99%了,可能是我沒處理好。后來(lái)zhongying同學(xué)幫忙把驅(qū)動(dòng)模式改成了ET模式,ET既高效又穩(wěn)定。
簡(jiǎn)單地說(shuō),如果你有數(shù)據(jù)過(guò)來(lái)了,不去取LT會(huì)一直騷擾你,提醒你去取,而ET就告訴你一次,愛取不取,除非有新數(shù)據(jù)到來(lái),否則不再提醒。
重點(diǎn)說(shuō)下ET,非阻塞模式,
man手冊(cè)說(shuō),如果ET提示你有數(shù)據(jù)可讀的時(shí)候,你應(yīng)該連續(xù)的讀一直讀到返回 EAGAIN or EWOULDBLOCK 為止,但是在具體實(shí)現(xiàn)中,我并沒有這樣做,而是根據(jù)我的應(yīng)用做了優(yōu)化。因?yàn)楝F(xiàn)在操作系統(tǒng)絕大多數(shù)實(shí)現(xiàn)都是最大傳輸單元值為1500。??MTU:1500 - ipheader:20 - tcpheader:20 = 1460 byte .??
HTTP header,不帶cookie的話一般只有500+ byte。留512給uri,也基本夠用,還有節(jié)余。
更正:如果只讀前1460個(gè)字節(jié)的header的話,99.999%的用戶都能正常訪問,但是有一兩個(gè)用戶是通過(guò)某http proxy來(lái)上網(wǎng),這種用戶請(qǐng)求的特征是會(huì)帶2k以上的垃圾信息過(guò)來(lái),類似于cookie,但又不是,重要的http?header被放在了最后,導(dǎo)致我認(rèn)為是非法請(qǐng)求,所以現(xiàn)在來(lái)看,為了避免被投訴,還是建議讀取到"\r\n\r\n"作為結(jié)束,性能的影響也是比較有限的.???(最后修改時(shí)間 2010.05.11)
如果請(qǐng)求的header恰巧比這大是2050字節(jié)呢?
會(huì)有兩種情況發(fā)生:1,數(shù)據(jù)緊挨著同時(shí)到達(dá),一次read就搞定。 2,分兩個(gè)ethernet frame先后到達(dá)有一定時(shí)間間隔。
我的方法是,用一個(gè)比較大的buffer比如1M去讀header,如果你很確信你的服務(wù)對(duì)象請(qǐng)求比1460小,讀一次就行。如果請(qǐng)求會(huì)很大分幾個(gè)ethernet frame先后到達(dá),也就是恰巧你剛剛read過(guò),它又來(lái)一個(gè)新數(shù)據(jù)包,ET會(huì)再次返回,再處理下就是了。
更正:?在追蹤通過(guò)http proxy上網(wǎng)的用戶問題中發(fā)現(xiàn),這個(gè)方法并不嚴(yán)謹(jǐn):1,?2050字節(jié)有可能會(huì)分兩個(gè),以及兩個(gè)以上的ethernet frame到達(dá),雖然我測(cè)試還沒遇到3個(gè)的情況,但是現(xiàn)在我斷定它是會(huì)存在的. 2,即使比1460字節(jié)小的數(shù)據(jù),也不敢保證裝載在一個(gè)ethernet frame里,仍然有可能分多次到達(dá).?所以用1M的buffer去讀,返回?cái)?shù)據(jù)比1460小,并不代表數(shù)據(jù)已經(jīng)讀完,只能說(shuō)明,本次讀完了,下次再有數(shù)據(jù)到達(dá)時(shí)會(huì)ET提示你.測(cè)試中發(fā)現(xiàn),對(duì)于大于1460字節(jié)的頭的讀區(qū),如果分多次到達(dá),第一次返回為1460字節(jié)的概率大一些而已. 測(cè)試中甚至還遇到了另外一個(gè)臨界情況,就是用1M的buffer剛讀完數(shù)據(jù),立刻再接著讀,有可能會(huì)讀到數(shù)據(jù),這個(gè)數(shù)據(jù)應(yīng)該是緊接著到達(dá),很小的時(shí)間間隔,如果你不去讀,我認(rèn)為ET應(yīng)該會(huì)再次返回(僅僅出現(xiàn)過(guò)一次,未能驗(yàn)證,有時(shí)間再求證).??(最后修改時(shí)間 2010.05.11)
?
順便再說(shuō)下寫數(shù)據(jù),一般一次可以write十幾K數(shù)據(jù)到內(nèi)核緩沖區(qū)。
所以對(duì)于很多小的數(shù)據(jù)文件服務(wù)來(lái)說(shuō),是沒有必要另外為每個(gè)connections分配發(fā)送緩沖區(qū)。
只有當(dāng)一次發(fā)送不完時(shí)候才分配一塊內(nèi)存,將數(shù)據(jù)暫存,待下次返回可寫時(shí)發(fā)送。
這樣避免了一次內(nèi)存copy,而且節(jié)約了內(nèi)存。
選擇了epoll并不代表就就擁有了一個(gè)好的 I/O模型,用的不好,你還趕不上select,這是實(shí)話。
epoll的問題我就說(shuō)這么多,關(guān)于描述符管理方面的細(xì)節(jié)請(qǐng)參見我早期的一個(gè)帖子,epoll模型的使用及其描述符耗盡問題的探討??大概討論了18頁(yè),我剛才把解決方法放在第一個(gè)帖子里了。如果你對(duì)epoll有感興趣,我這有?一個(gè)簡(jiǎn)單的基于epoll的web server例子?。
另外你要使用多線程,還是多進(jìn)程,這要看你更熟悉哪個(gè),各有好處。
多進(jìn)程模式,單個(gè)進(jìn)程crash了,不影響其他進(jìn)程,而且可以為每個(gè)worker分別幫定不同的cpu,讓某些cpu單獨(dú)空出來(lái)處理中斷和系統(tǒng)事物。多線程,共享數(shù)據(jù)方便,占用資源更少。進(jìn)程或線程的個(gè)數(shù),應(yīng)該固定在(cpu核數(shù)-1) ~ 2倍cpu核數(shù)間為宜,太多了時(shí)間片輪轉(zhuǎn)時(shí)會(huì)頻繁切換,少了,達(dá)不到多核并發(fā)處理的效果。
還有如何accept也是一門學(xué)問,沒有最好,只有更適用,你需要做很多實(shí)驗(yàn),確定對(duì)自己最高效的方式。有了一個(gè)好的I/O框架,你的效率想低也不容易,這是程序?qū)崿F(xiàn)的大局。
關(guān)于更多網(wǎng)絡(luò)I/O模型的討論請(qǐng)見?<Scalable Network Programming >?中文版。
另外,必須強(qiáng)調(diào)的是,代碼和結(jié)構(gòu)應(yīng)該簡(jiǎn)潔高效,一定要具體問題具體分析,沒什么法則是萬(wàn)能的,要根據(jù)你的服務(wù)量身定做。
2,關(guān)閉不必要的標(biāo)準(zhǔn)輸入和標(biāo)準(zhǔn)輸出
close(0);??//stdin
close(1);??//stdout
如果你不小心,有了printf輸出調(diào)試信息,這絕對(duì)是一個(gè)性能殺手。
一個(gè)高性能的服務(wù)器不出錯(cuò)是不應(yīng)該有任何輸出的,免得耽誤干活。
這樣做,至少能為你節(jié)約兩個(gè)描述符資源。
3,避免用鎖 (i++ or ++i )
多線程編程用鎖是普遍現(xiàn)象,貌似已經(jīng)成為習(xí)慣。
但各線程最好是獨(dú)立的,不需要同步機(jī)制的。
鎖會(huì)消耗資源,而且造成排隊(duì),甚至死鎖,盡量想辦法避免。
非用不可時(shí)候,比如,實(shí)時(shí)統(tǒng)計(jì)各線程的負(fù)載情況,多個(gè)線程要對(duì)全局變量進(jìn)行寫操作。
請(qǐng)用 ++i ,因?yàn)樗且粋€(gè)原子操作。
更正:?pinggao,同學(xué)對(duì)++i的原子性提出了質(zhì)疑,并做了個(gè)測(cè)試程序,經(jīng)過(guò)討論得出結(jié)論:++i在多線程的程序中,在單核環(huán)境下,不一定是原子的,在多核環(huán)境下肯定不是原子的,我把這個(gè)當(dāng)作原子操作用在了統(tǒng)計(jì)中,所以我得出的統(tǒng)計(jì)值是要比實(shí)際處理能力小很多的。。。虧大了。。等有新的數(shù)據(jù)再更正過(guò)來(lái)。。想想犯這個(gè)基本錯(cuò)誤的原因,當(dāng)初我們大學(xué)教授在講++i是原子操作的時(shí)候,距離現(xiàn)在計(jì)算機(jī)環(huán)境發(fā)生了巨大變化。?++i原子性討論??(最后修改時(shí)間 2010.10.02)
4,減少系統(tǒng)調(diào)用
系統(tǒng)調(diào)用是很耗的,因?yàn)樗ǔP枰@進(jìn)內(nèi)核再鉆出來(lái)。
我們應(yīng)該避免用戶空間和內(nèi)核空間的切換。
比如我要為每個(gè)請(qǐng)求打個(gè)時(shí)間戳,以計(jì)算超時(shí),我完全可以在返回一批可用描述符前只調(diào)用一次time(),而不用每個(gè)請(qǐng)求都調(diào)用一次。 time()只精確到秒,一批請(qǐng)求處理都是毫秒級(jí),所以也沒必要那么做,再說(shuō)了,計(jì)算超時(shí)誤差那么一秒有什么影響嗎?
5, Connection: close vs??Keep-Alive ?
談httpd實(shí)現(xiàn),就不能不提長(zhǎng)連接Keep-Alive 。
Keep-Alive是http 1.1中加入的,現(xiàn)在的瀏覽器99。99%應(yīng)該都是支持Keep-Alive的。
先說(shuō)下什么是Keep-Alive:
這是基于tcp的connections說(shuō)的,也就是一個(gè)描述符(fd),它并不代表獨(dú)立占用一個(gè)進(jìn)程或線程。一個(gè)線程用非阻塞模式可以保持成千上萬(wàn)個(gè)長(zhǎng)連接。
先說(shuō)一個(gè)完整的HTTP 1.0的請(qǐng)求和響應(yīng):
建立tcp連接 (syn; ack, syn2; ack2; 三個(gè)分組握手完成)
請(qǐng)求
響應(yīng)
關(guān)閉連接 (fin; ack; fin2; ack2??四個(gè)分組關(guān)閉連接)
再說(shuō)HTTP 1.1的請(qǐng)求和響應(yīng):
建立tcp連接 (syn; ack, syn2; ack2; 三個(gè)分組握手完成)
請(qǐng)求
響應(yīng)
...
請(qǐng)求
響應(yīng)
關(guān)閉連接 (fin; ack; fin2; ack2??四個(gè)分組關(guān)閉連接)
如果請(qǐng)求和響應(yīng)都只有一個(gè)分組,那么HTTP 1.0至少要傳輸11個(gè)分組(補(bǔ)充:請(qǐng)求和響應(yīng)數(shù)據(jù)還各需要一個(gè)ack確認(rèn)),才拿到一個(gè)分組的數(shù)據(jù)。而長(zhǎng)連接可以更充分的利用這個(gè)已經(jīng)建立的連接,避免的頻繁的建立和關(guān)閉連接,減少網(wǎng)絡(luò)擁塞。
我做過(guò)一個(gè)測(cè)試,在2cpu*4core服務(wù)器上,不停的accept,然后不做處理,直接close掉。一秒最多可以accept??7w/s,這是極限。那么我要是想每秒處理10w以上的http請(qǐng)求該怎么辦呢?
目前唯一的也是最好的選擇,就是保持長(zhǎng)連接。
比如我們NBA JS直播頁(yè)面,剛打開就會(huì)向我的js服務(wù)器發(fā)出6個(gè)http請(qǐng)求,而且隨后平均每10秒會(huì)產(chǎn)生兩個(gè)請(qǐng)求。再比如,我們很多頁(yè)面都會(huì)嵌幾個(gè)靜態(tài)池的圖片,如果每個(gè)請(qǐng)求都是獨(dú)立的(建立連接然后關(guān)閉),那對(duì)資源絕對(duì)是個(gè)浪費(fèi)。
長(zhǎng)連接是個(gè)好東西,但是選擇 Keep-Alive必須根據(jù)你的應(yīng)用決定。比如NBA JS直播,我肯定10秒內(nèi)會(huì)產(chǎn)生一個(gè)請(qǐng)求,所以超時(shí)設(shè)置為15秒,15秒還沒活動(dòng),估計(jì)是去打醬油了,資源就得被我回收。超時(shí)設(shè)置過(guò)長(zhǎng),光連接都能把你的服務(wù)器堆死。
為什么有些apache服務(wù)器,負(fù)載很高,把Keep-Alive關(guān)掉負(fù)載就減輕了呢?
apache 有兩種工作模式,prefork和worker。apache 1.x只有,prefork。
prefork比較典型,就是個(gè)進(jìn)程池,每次創(chuàng)建一批進(jìn)程,還有apache是基于select實(shí)現(xiàn)的。在用戶不是太多的時(shí)候,長(zhǎng)連接還是很有用的,可以節(jié)約分組,提升響應(yīng)速度,但是一旦超出某個(gè)平衡點(diǎn),由于為了保持很多長(zhǎng)連接,創(chuàng)建了太多的進(jìn)程,導(dǎo)致系統(tǒng)不堪重負(fù),內(nèi)存不夠了,開始換入換出,cpu也被很多進(jìn)程吃光了,load上去了。這種情況下,對(duì)apache來(lái)說(shuō),每次請(qǐng)求重新建立連接要比保持這么多長(zhǎng)連接和進(jìn)程更劃算。
6,預(yù)處理 (預(yù)壓縮,預(yù)取lastmodify,mimetype)
預(yù)處理,原則就是,能預(yù)先知道的結(jié)果,我們絕不計(jì)算第二次。
預(yù)壓縮:我們?cè)趦扇昵熬烷_始使用預(yù)壓縮技術(shù),以節(jié)約CPU,偉大的微軟公司在現(xiàn)在的IIS 7中也開始使用了。所謂的預(yù)壓縮就是,從數(shù)據(jù)源頭提供的就是預(yù)先壓縮好的數(shù)據(jù),IDC同步傳輸中是壓縮狀態(tài),直到最后web server輸出都是壓縮狀態(tài),最終被用戶瀏覽器端自動(dòng)解壓。
預(yù)取lastmodify:??文件的lastmodify時(shí)間,如果不更新,我們不應(yīng)該取第二次,別忘記了fsat這個(gè)系統(tǒng)調(diào)用是很耗的。
預(yù)取mimetype: mimetype,如果你的文件類型不超過(guò)256種,一個(gè)字節(jié)就可以標(biāo)識(shí)它,然后用數(shù)組下標(biāo)直接輸出,而且不是看到一個(gè)js文件,然后strcmp()了近百種后綴名后,才知道應(yīng)該輸出Content-Type: application/x-javascript,而且這種方法會(huì)隨文件類型增加而耗費(fèi)更多cpu資源。當(dāng)然也可以寫個(gè)hash函數(shù)來(lái)做這事,那也至少需要一次函數(shù)調(diào)用,做些求值運(yùn)算,和分配比實(shí)際數(shù)據(jù)大幾倍的hash表。
如何更好的使用cpu一級(jí)緩存
數(shù)據(jù)分解
CPU硬親和力的設(shè)置
待補(bǔ)充。。。。
二,怎樣使用內(nèi)存
1,避免內(nèi)存copy (strcpy,memcpy)
雖然內(nèi)存速度很快,但是執(zhí)行頻率比較高的核心部分能避免copy的就盡量別使用。如果必須要copy,盡量使用memcpy替代sprintf,strcpy,因?yàn)樗魂P(guān)心你是否遇到'\0'; 內(nèi)存拷貝和http響應(yīng)又涉及到字符串長(zhǎng)度計(jì)算。如果能預(yù)先知道這個(gè)長(zhǎng)度最好用中間變量保留,增加多少直接加上去,不要用strlen()去計(jì)算,因?yàn)樗鼤?huì)數(shù)數(shù)直到遇見'\0'。能用sizeof()的地方就不要用strlen,因?yàn)樗莻€(gè)運(yùn)算符,在預(yù)編的時(shí)被替換為具體數(shù)字,而非執(zhí)行時(shí)計(jì)算。
2,避免內(nèi)核空間和用戶進(jìn)程空間內(nèi)存copy (sendfile, splice and tee)
sendfile: 它的威力在于,它為大家提供了一種訪問當(dāng)前不斷膨脹的Linux網(wǎng)絡(luò)堆棧的機(jī)制。這種機(jī)制叫做“零拷貝(zero-copy)”,這種機(jī)制可以把“傳輸控制協(xié)議(TCP)”框架直接的從主機(jī)存儲(chǔ)器中傳送到網(wǎng)卡的緩存塊(network card buffers)中去,避免了兩次上下文切換。詳細(xì)參見?<使用sendfile()讓數(shù)據(jù)傳輸?shù)玫阶顑?yōu)化>?。據(jù)同事測(cè)試說(shuō)固態(tài)硬盤SSD對(duì)于小文件的隨機(jī)讀效率很高,對(duì)于更新不是很頻繁的圖片服務(wù),讀卻很多,每個(gè)文件都不是很大的話,sendfile+SSD應(yīng)該是絕配。
splice and tee: splice背后的真正概念是暴露給用戶空間的“隨機(jī)內(nèi)核緩沖區(qū)”的概念。“也就是說(shuō),splice和tee運(yùn)行在用戶控制的內(nèi)核緩沖區(qū)上,在這個(gè)緩沖區(qū)中,splice將來(lái)自任意文件描述符的數(shù)據(jù)傳送到緩沖區(qū)中(或從緩沖區(qū)傳送到文件描述符),而tee將一個(gè)緩沖區(qū)中的數(shù)據(jù)復(fù)制到另一個(gè)緩沖區(qū)中。因此,從一個(gè)很真實(shí)(而抽象)的意義上講,splice相當(dāng)于內(nèi)核緩沖區(qū)的read/write,而tee相當(dāng)于從內(nèi)核緩沖區(qū)到另一個(gè)內(nèi)核緩沖區(qū)的memcpy。”。本人覺得這個(gè)技術(shù)用來(lái)做代理,很合適。因?yàn)閿?shù)據(jù)可以直接從一個(gè)soket到另一個(gè)soket,不需要經(jīng)用戶和內(nèi)核空間的切換。這是sendfile不支持的。詳細(xì)參見?<linux2.6.17以上內(nèi)核中的 splice and tee>?,具體實(shí)例請(qǐng)參見??man 2??tee ,里面有個(gè)完整的程序。
3,如何清空一塊內(nèi)存(memset ?)
比如有一個(gè)buffer[1024*1024],我們需要把它清空然后strcat(很多情況下可以通過(guò)記錄寫的起始位置+memcpy來(lái)代替)追加填充字符串。
其實(shí)我們沒有必要用memset(buffer,0x00,sizeof(buffer))來(lái)清空整個(gè)buffer, memset(buffer,0x00,1)就能達(dá)到目的。 我平時(shí)更喜歡用buffer[0]='\0'; 來(lái)替代,省了一次函數(shù)調(diào)用的開銷。
4,內(nèi)存復(fù)用??(有必要為每個(gè)響應(yīng)分配內(nèi)存 ?)
對(duì)于NBA JS服務(wù)來(lái)說(shuō),我們返回的都是壓縮數(shù)據(jù),99%都不超過(guò)15k,基本一次write就全部出去了,是沒有必要為每個(gè)響應(yīng)分配內(nèi)存的,公用一個(gè)buffer就夠了。如果真的遇到大數(shù)據(jù),我先write一次,剩下的再暫存在內(nèi)存里,等待下次發(fā)送。
5,避免頻繁動(dòng)態(tài)申請(qǐng)/釋放內(nèi)存(malloc)
這個(gè)似乎不用多說(shuō),要想一個(gè)Server啟動(dòng)后成年累月的跑,就不應(yīng)該頻繁地去動(dòng)態(tài)申請(qǐng)和釋放內(nèi)存。原因很簡(jiǎn)單一,避免內(nèi)存泄露。二,避免碎片過(guò)多。三,影響效率。一般來(lái)說(shuō),都是一次申請(qǐng)一大塊內(nèi)存,然后自己寫內(nèi)存分配算法。為http用戶分配的緩沖區(qū)生命期的特點(diǎn)是,可以隨著fd的關(guān)閉,而回收,避免漏網(wǎng)。還有Server的編寫者應(yīng)該對(duì)自己設(shè)計(jì)的程序達(dá)到最高支撐量的時(shí)候所消耗的內(nèi)存心中有數(shù)。
6,字節(jié)對(duì)齊
先看下面的兩個(gè)結(jié)構(gòu)體有什么不同:
struct A {
????????short size;?
????????char *ptr;
????????int left;
} a ;
struct B {
????????char *ptr;
????????short size;?
????????int left;
} b ;
僅僅是一個(gè)順序的變化,結(jié)構(gòu)體B順序是合理的:
在32bit linux系統(tǒng)上,是按照32/8bit=4byte來(lái)對(duì)齊的, sizeof(a)=12 ,sizeof(b)=12 。
在64bit linux系統(tǒng)上,是按照64/8bit=8byte來(lái)對(duì)齊的, sizeof(a)=24 ,sizeof(b)=16 。
32bit機(jī)上看到的A和B結(jié)果大小是一樣的,但是如果把int改成short效果就不一樣了。
如果我想強(qiáng)制以2byte對(duì)齊,可以這樣:
#pragma pack(2)
struct A {
????????short size;?
????????char *ptr;
????????int left;
} a ;
#pragma pack()
注意pack()里的參數(shù),只能指定比本機(jī)支持的字節(jié)對(duì)齊標(biāo)準(zhǔn)小,而不能更大。
7,內(nèi)存安全問題
先舉個(gè)好玩的例子,不使用a,而給a賦上值:
int main()
{
????????char a[8];
????????char b[8];
????????memcpy(b,"1234567890\0",10);
????????printf("a=%s\n",a);
????????return 0;
}
程序輸出??a=90 。
這就是典型的溢出,如果是空閑的內(nèi)存,用點(diǎn)也就罷了,可是把別人地盤上的數(shù)據(jù)覆蓋了,就不好了。
接收的用戶數(shù)據(jù)一定要嚴(yán)格判斷,確定不會(huì)越界,不是每個(gè)人都按規(guī)矩辦事的,搞不好就掛了。
8,云風(fēng)的內(nèi)存管理理論 (sd2c大會(huì)所獲?blog & ppt)
沒有永遠(yuǎn)不變的原則
大原則變化的慢
沒有一勞永逸的解決方案
內(nèi)存訪問很廉價(jià)但有代價(jià)
減少內(nèi)存訪問的次數(shù)是很有意義的
隨機(jī)訪問內(nèi)存慢于順序訪問內(nèi)存
請(qǐng)讓數(shù)據(jù)物理上連續(xù)
集中內(nèi)存訪問優(yōu)于分散訪問
盡可能的將數(shù)據(jù)緊密的存放在一起
無(wú)關(guān)性內(nèi)存訪問優(yōu)于相關(guān)性內(nèi)存訪問
請(qǐng)考慮并行的可能性、即使你的程序本身沒有使用并行機(jī)制
控制周期性密集訪問的數(shù)據(jù)大小
必要時(shí)采用時(shí)間換空間的方法
讀內(nèi)存快于寫內(nèi)存
代碼也會(huì)占用內(nèi)存,所以、保持代碼的簡(jiǎn)潔
物理法則
晶體管的排列
批量回收內(nèi)存
不釋放內(nèi)存,留給系統(tǒng)去做
list map??vector (100次調(diào)用產(chǎn)生13次內(nèi)存分配和釋放)
長(zhǎng)用字符串做成hash,使用指針訪問
直接內(nèi)存頁(yè)處理控制
三,減少磁盤I/O
這個(gè)其實(shí)就是通過(guò)盡可能的使用內(nèi)存達(dá)到性能提高和i/o減少。從系統(tǒng)的讀寫buffer到用戶空間自己的cache,都是可以有效減少磁盤i/o的方法。用戶可以把數(shù)據(jù)暫存在自己的緩沖區(qū)里,批量讀寫大塊數(shù)據(jù)。cache的使用是很必要的,可以自己用共享內(nèi)存的方法實(shí)現(xiàn),也可以用現(xiàn)成的BDB來(lái)實(shí)現(xiàn)。歡迎訪問我的公益站點(diǎn)berkeleydb.net?,不過(guò)我不太歡迎那種問了問題就跑的人。BDB默認(rèn)的cache只有256K,可以調(diào)大這個(gè)數(shù)字,也可以純粹使用Mem Only方法。對(duì)于預(yù)先知道的結(jié)果,爭(zhēng)取不從磁盤取第二次,這樣磁盤基本就被解放出來(lái)了。BDB取數(shù)據(jù)的速度每秒大概是100w條(2CPU*2Core Xeon(R) E5410 @ 2.33GHz環(huán)境測(cè)試,單條數(shù)據(jù)幾十字節(jié)),如果你想取得更高的性能建議自己寫。
四,優(yōu)化你的網(wǎng)卡
首先ethtool ethx 看看你的外網(wǎng)出口是不是Speed: 1000Mb/s 。
對(duì)于多核服務(wù)器,運(yùn)行top命令,然后按一下1,就能看到每個(gè)核的使用情況。如果發(fā)現(xiàn)cpuid=0的那顆使用率明顯高于其他核,那就說(shuō)明id=0的cpu將來(lái)也許會(huì)成為你的瓶頸。然后可以用mpstat(非默認(rèn)安裝)命令查看系統(tǒng)中斷分布,用cat /proc/interrupts 網(wǎng)卡中斷分布。
下面這個(gè)數(shù)據(jù)是我們已經(jīng)做過(guò)優(yōu)化了的服務(wù)器中斷分布情況:
[yangjian2@D08043466 ~]$ mpstat -P ALL 1
Linux 2.6.18-53.el5PAE (D08043466)??????12/15/2008
01:51:27 PM??CPU???%user???%nice????%sys %iowait????%irq???%soft??%steal???%idle????intr/s
01:51:28 PM??all????0.00????0.00????0.00????0.00????0.00????0.00????0.00??100.00???1836.00
01:51:28 PM????0????0.00????0.00????0.00????0.00????0.00????0.00????0.00??100.00????179.00
01:51:28 PM????1????0.00????0.00????0.00????0.00????0.00????0.00????0.00??100.00????198.00
01:51:28 PM????2????1.00????0.00????0.00????0.00????0.00????0.00????0.00??100.00????198.00
01:51:28 PM????3????0.00????0.00????0.00????0.00????0.00????0.00????0.00??100.00????346.00
01:51:28 PM????4????0.00????0.00????0.00????0.00????0.00????0.00????0.00??100.00????207.00
01:51:28 PM????5????0.00????0.00????0.00????0.00????0.00????0.00????0.00??100.00????167.00
01:51:28 PM????6????0.00????0.00????0.00????0.00????0.00????0.00????0.00??100.00????201.00
01:51:28 PM????7????0.00????0.00????0.00????0.00????0.00????0.00????0.00??100.00????339.00
沒優(yōu)化過(guò)的應(yīng)該是這個(gè)樣子:
yangjian2@xk-6-244-a8 ~]$ mpstat -P ALL 1
Linux 2.6.18-92.1.6.el5 (xk-6-244-a8.bta.net.cn)????????12/15/2008
02:05:26 PM??CPU???%user???%nice????%sys %iowait????%irq???%soft??%steal???%idle????intr/s
02:05:27 PM??all????0.00????0.00????0.00????0.12????0.00????0.00????0.00???99.88???1593.00
02:05:27 PM????0????0.00????0.00????0.00????0.00????0.00????0.00????0.00??100.00???1590.00
02:05:27 PM????1????0.00????0.00????0.00????0.00????0.00????0.00????0.00??100.00??????0.00
02:05:27 PM????2????0.00????0.00????0.00????0.00????0.00????0.00????0.00??100.00??????2.00
02:05:27 PM????3????0.00????0.00????0.00????0.00????0.00????0.00????0.00??100.00??????0.00
02:05:27 PM????4????0.00????0.00????0.00????0.00????0.00????0.00????0.00??100.00??????0.00
02:05:27 PM????5????0.00????0.00????0.00????0.00????0.00????0.00????0.00??100.00??????0.00
02:05:27 PM????6????0.00????0.00????0.00????0.00????0.00????0.00????0.00??100.00??????0.00
02:05:27 PM????7????0.00????0.00????0.00????0.00????0.00????0.00????0.00??100.00??????0.00
對(duì)于32bit的centos5,mpstat -P ALL 1表現(xiàn)跟第一種情況一樣,分布比較平均,但是一但有了訪問量,就可以看到差距。cat /proc/interrupts 看起來(lái)更直觀些,很清楚的知道哪個(gè)網(wǎng)卡的中斷在哪個(gè)cpu上處理。
其實(shí),當(dāng)你遇到網(wǎng)卡中斷瓶頸的時(shí)候證明你的網(wǎng)站并發(fā)度已經(jīng)相當(dāng)高了,每秒三五萬(wàn)個(gè)請(qǐng)求還至于成為瓶頸。除非你的應(yīng)用程序同時(shí)也在消耗cpu0的資源。對(duì)于這種情況,建議使用多進(jìn)程模式,每個(gè)進(jìn)程用 sched_setaffinity綁定特定的cpu,把cpu0從用戶事物中解放出來(lái),專心處理系統(tǒng)事物,當(dāng)然包括中斷。這樣你的極限應(yīng)該能處理20w+ http req/s (2CPU*4Core服務(wù)器)。但是對(duì)于多線程模式來(lái)說(shuō),我們就顯得無(wú)能為力了,因?yàn)槲覀內(nèi)绻胧褂枚嗪?#xff0c;就沒法不用cpu0。目前的方法只有兩個(gè):一,轉(zhuǎn)化為多進(jìn)程,然后進(jìn)程內(nèi)再使用多線程。二,讓你的網(wǎng)卡中斷分散在多個(gè)cpu上(目前只有硬件解決方案,感謝xiaodong2提供的技術(shù)支持)。 (修正:后來(lái)仔細(xì)讀了幾遍man手冊(cè),發(fā)現(xiàn)sched_setaffinity綁定特定的cpu對(duì)于多線程也是適用的,并且實(shí)驗(yàn)通過(guò),只需要將第一個(gè)參數(shù)置為0。這對(duì)cpu0的解放是個(gè)很好的發(fā)現(xiàn)。)
將網(wǎng)卡中斷分散在多個(gè)cpu硬件解決方案: 我們新加了一塊網(wǎng)卡(前提是這個(gè)網(wǎng)卡支持中斷分布),然后通過(guò)通過(guò)linux bonding將兩個(gè)網(wǎng)卡比如eth0,eth1聯(lián)合成一個(gè)通道bond0(當(dāng)然這里還涉及到交換機(jī)的調(diào)整),然后bond0就有了2G的帶寬吞吐量。把eth0的中斷處理幫定在cpu 0-3,把eth1中斷處理幫定在cpu 4-7,這樣中斷就被分布開了。這樣會(huì)帶來(lái)一些額外的cpu開銷,但是跟好處相比可以忽略不計(jì)。我在網(wǎng)卡優(yōu)化過(guò)的32bit服務(wù)器上測(cè)試http請(qǐng)求處理極限為 40w+ req/s,將近提升了一倍。
五,調(diào)整內(nèi)核參數(shù)
我的內(nèi)核心參數(shù)調(diào)整原則是,哪個(gè)遇到瓶頸調(diào)哪個(gè),謹(jǐn)慎使用,不能憑想象亂調(diào)一氣。看下面例子,其中default是我們公司定做的系統(tǒng)默認(rèn)的一些參數(shù)值。add by yangjian2并非全部都要調(diào)整,我只挑幾個(gè)比較重要的參數(shù)說(shuō)明一下,更多TCP方面的調(diào)優(yōu)請(qǐng)參見 man 7 tcp 。
#++++++++++++++++++default++++++++++++++++++++++++++++++
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_tw_buckets = 180000
net.ipv4.tcp_sack = 1
net.ipv4.tcp_window_scaling = 1
net.ipv4.tcp_rmem = 4096????????87380???4194304
net.ipv4.tcp_wmem = 4096????????16384???4194304
#++++++++++++++++++add by yangjian2++++++++++++++++++++++
net.ipv4.tcp_max_syn_backlog = 65536
net.core.netdev_max_backlog =??32768
net.core.somaxconn = 32768
net.core.wmem_default = 8388608
net.core.rmem_default = 8388608
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_timestamps = 0
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_syn_retries = 2
net.ipv4.tcp_tw_recycle = 1
#net.ipv4.tcp_tw_len = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_mem = 94500000 915000000 927000000
net.ipv4.tcp_max_orphans = 3276800
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++
maxfd: 對(duì)于系統(tǒng)所能打開的最大文件描述符fd,可以通過(guò)以root啟動(dòng)程序,setrlimit()設(shè)置maxfd后,再通過(guò)setuid()轉(zhuǎn)為普通用戶提供服務(wù),我用的 int set_max_fds(int maxfds); 函數(shù)是zhongying提供的。這比用ulimit來(lái)的方便的多,不曉得為什么那么多開源軟件都沒這樣用。
net.ipv4.tcp_max_syn_backlog = 65536 : 這個(gè)參數(shù)可以肯定是必須要修改的,默認(rèn)值1024,我google了一下,幾乎是人云亦云,沒有說(shuō)的明白的。 要講明白得從man listen說(shuō)起,int listen(int sockfd, int backlog);??早期的網(wǎng)絡(luò)編程都中描述,int backlog 代表未完成隊(duì)列SYN_RECV狀態(tài)+已完成隊(duì)列ESTABLISHED的和。但是這個(gè)意義在Linux 2.2以后的實(shí)現(xiàn)中已經(jīng)被改變了,int backlog只代表已完成隊(duì)列ESTABLISHED的長(zhǎng)度,在AF_INET協(xié)議族中(我們廣泛使用的就是這個(gè)),當(dāng)int backlog大于SOMAXCONN (128 in Linux 2.0 & 2.2)的時(shí)候,會(huì)被調(diào)整為常量SOMAXCONN大小。這個(gè)常量可以通過(guò)net.core.somaxconn來(lái)修改。而未完成隊(duì)列大小可以通過(guò)net.ipv4.tcp_max_syn_backlog來(lái)調(diào)整,一般遭受syn flood攻擊的網(wǎng)站,都存在大量SYN_RECV狀態(tài),所以調(diào)大tcp_max_syn_backlog值能增加抵抗syn攻擊的能力。
net.ipv4.tcp_syncookies = 1 :?當(dāng)出現(xiàn)syn等候隊(duì)列出現(xiàn)溢出時(shí)象對(duì)方發(fā)送syncookies。目的是為了防止syn flood攻擊 , 默認(rèn)值是 0。?不過(guò)man listen說(shuō)當(dāng)啟用syncookies時(shí)候,tcp_max_syn_backlog的sysctl調(diào)整將失效,和這個(gè)描述不是很符合。參見下面兩個(gè)描述分別是man listen和man 7 tcp:
When syncookies are enabled there is no logical maximum length and this tcp_max_syn_backlog sysctl??setting??is??ignored.
Send out??syncookies??when the syn backlog queue of a socket overflows.
但我可以肯定的說(shuō)這個(gè)選項(xiàng)對(duì)你的性能不會(huì)有提高,而且它嚴(yán)重的違背TCP協(xié)議,不允許使用TCP擴(kuò)展,除非遭受攻擊,否則不推薦使用。
net.ipv4.tcp_synack_retries = 2?:?對(duì)于遠(yuǎn)端的連接請(qǐng)求SYN,內(nèi)核會(huì)發(fā)送SYN + ACK數(shù)據(jù)報(bào),以確認(rèn)收到上一個(gè) SYN連接請(qǐng)求包。這是所謂的三次握手( threeway handshake)機(jī)制的第二個(gè)步驟。這里決定內(nèi)核在放棄連接之前所送出的 SYN+ACK 數(shù)目。如果你的網(wǎng)站SYN_RECV狀態(tài)確實(shí)挺多,為了避免syn攻擊,那么可以調(diào)節(jié)重發(fā)的次數(shù)。
net.ipv4.tcp_syn_retries = 2 :?對(duì)于一個(gè)新建連接,內(nèi)核要發(fā)送多少個(gè) SYN 連接請(qǐng)求才決定放棄。不應(yīng)該大于255,默認(rèn)值是5,對(duì)應(yīng)于180秒左右。這個(gè)對(duì)防止syn攻擊其實(shí)是沒有用處的,也沒必要調(diào)節(jié)。
net.ipv4.tcp_max_orphans = 3276800 : 這個(gè)最好不要修改,因?yàn)槊吭黾?,將消耗~64k內(nèi)存。即使報(bào)錯(cuò) TCP: too many of orphaned sockets 也有可能是由于你的net.ipv4.tcp_mem過(guò)小,導(dǎo)致的Out of socket memory,繼而引發(fā)的。
net.ipv4.tcp_wmem = 4096 16384 4194304 :??為自動(dòng)調(diào)優(yōu)定義每個(gè)socket使用的內(nèi)存。第一個(gè)值是為socket的發(fā)送緩沖區(qū)分配的最少字節(jié)數(shù)。第二個(gè)值是默認(rèn)值(該值會(huì)被 wmem_default覆蓋),緩沖區(qū)在系統(tǒng)負(fù)載不重的情況下可以增長(zhǎng)到這個(gè)值。第三個(gè)值是發(fā)送緩沖區(qū)空間的最大字節(jié)數(shù)(該值會(huì)被wmem_max覆蓋)。
net.ipv4.tcp_rmem = 4096 87380 4194304 : 接收緩沖區(qū),原理同上。
net.ipv4.tcp_mem = 94500000 915000000 927000000 :
low:當(dāng)TCP使用了低于該值的內(nèi)存頁(yè)面數(shù)時(shí),TCP不會(huì)考慮釋放內(nèi)存。
pressure:當(dāng)TCP使用了超過(guò)該值的內(nèi)存頁(yè)面數(shù)量時(shí),TCP試圖穩(wěn)定其內(nèi)存使用,進(jìn)入pressure模式,當(dāng)內(nèi)存消耗低于low值時(shí)則退出pressure狀態(tài)。
high:允許所有tcp sockets用于排隊(duì)緩沖數(shù)據(jù)報(bào)的內(nèi)存頁(yè)數(shù)。
一般情況下這個(gè)值是在系統(tǒng)啟動(dòng)時(shí)根據(jù)系統(tǒng)內(nèi)存數(shù)量計(jì)算得到的,如果你的dmesg報(bào) Out of socket memory,你可以試著修改這個(gè)參數(shù),順便介紹3個(gè)修改方法:
1, echo "94500000 915000000 927000000" > /proc/sys/net/ipv4/tcp_wmem
2, sysctl -w "net.ipv4.tcp_mem = 94500000 915000000 927000000"
3, net.ipv4.tcp_mem = 94500000 915000000 927000000??(vi /etc/sysctl.conf?然后?sysctl -p生效)
下面命令也許能提供些信息,在你修改tcp參數(shù)時(shí)做個(gè)參考:
[sports@xk-6-244-a8 nbahttpd_beta4.0]$ cat??/proc/net/sockstat
sockets: used 1195
TCP: inuse 1177 orphan 30 tw 199 alloc 1181 mem 216
UDP: inuse 0 mem 0
RAW: inuse 0
FRAG: inuse 0 memory 0
其他我就不多說(shuō)了,知道這些基本就能解決絕大部分問題了。
六,衡量Web Server的性能指標(biāo)
我認(rèn)為一個(gè)好的Server應(yīng)該能在有限的硬件資源上將性能發(fā)揮到極限。
Web Server的衡量指標(biāo)并非單一,要根據(jù)具體應(yīng)用類型而定。比如財(cái)經(jīng)實(shí)時(shí)圖片系統(tǒng),我們關(guān)注它每秒輸出圖片數(shù)量。NBA js直播放系統(tǒng),我們關(guān)心他的同時(shí)在線connections和當(dāng)時(shí)的每秒請(qǐng)求處理量。行情系統(tǒng),我們關(guān)心它c(diǎn)onnections和請(qǐng)求處理量的同時(shí)還要關(guān)心每個(gè)請(qǐng)求平均查詢多少支股票。但總體來(lái)說(shuō)同時(shí)在線connections和當(dāng)時(shí)的每秒請(qǐng)求處理量是兩個(gè)最重要的指標(biāo)。
對(duì)于圖片系統(tǒng)再說(shuō)一句,我覺得大圖片和小圖片是應(yīng)該區(qū)別對(duì)待的,小圖片不應(yīng)該產(chǎn)生磁盤 I/O 。
Nginx是我見過(guò)的Web Server中性能比較高的一個(gè),他幾乎是和我的server同時(shí)誕生,可能還更早些,框架很不錯(cuò),我覺得目前版本稍微優(yōu)化下,支持10w connections不成問題。 lighttpd也不錯(cuò),我對(duì)他的認(rèn)識(shí)還是停留在幾年前的性能測(cè)試上,它的性能會(huì)比nginx遜色一些。他們都支持epoll,sendfile,可以起多個(gè)進(jìn)程worker,worker內(nèi)部使用非阻塞,這是比較優(yōu)良的I/O的模型。Squid,Apache,都是骨灰級(jí)軟件了,好處就是支持的功能多,另許多輕量級(jí)Server望塵莫及,可是性能太一般了,祝愿他們?cè)缛罩貙憽?br />
插點(diǎn)小插曲,我在財(cái)經(jīng)項(xiàng)目組的時(shí)候,有的同事來(lái)我們組一年多了,問我是不是管機(jī)器的,我點(diǎn)點(diǎn)頭,后來(lái)又有比較了解我的同事說(shuō)我是系統(tǒng)管理員,我說(shuō)“恩”。其實(shí)我的主業(yè)是寫程序的。也許是我太低調(diào)了,覺得那些陳年往事不值再提,以至于別人對(duì)我做的東西了解甚少, 今天我就高調(diào)一把,公布一些我寫的程序的性能指標(biāo)。我們的系統(tǒng)近幾年來(lái)說(shuō)在性能上是領(lǐng)先業(yè)內(nèi)的(不爭(zhēng)世界第一,那樣壓力太大,第二就很好,也許正在看我blog的你一不留神就把我超了呢 ^-^ ),高效的原因很重要的一點(diǎn)是由于它是根據(jù)服務(wù)特點(diǎn)量身訂做的。
實(shí)驗(yàn)環(huán)境數(shù)據(jù): 我寫了個(gè)HTTP服務(wù)框架,不使用磁盤I/O,簡(jiǎn)化了邏輯處理部分,只會(huì)輸出 "hello world!"?程序部署在192.168.0.1上(2cup*4Core,硬件和系統(tǒng)都做過(guò)優(yōu)化),我在另外8臺(tái)同等配置服務(wù)器上同時(shí)執(zhí)行命令??./apache/bin/ab -c 1000??-n 3000000???-k??"http://192.168.0.1/index.html" 幾乎同時(shí)處理完畢,總合相加 40w req/s,我相信這是目前硬件水平上的極限值 。
真實(shí)環(huán)境數(shù)據(jù):2cup*4Core Mem 16G, 64bit centos5,單機(jī)23w+ connections, 3.5w req/s時(shí),CPU總量消耗 1/8,內(nèi)存消耗0.4%(相當(dāng)于正好消耗了一個(gè)Core+64M Mem)。在30w+ connections, 4.6w req/s 時(shí),CPU總量消耗 1/4,內(nèi)存消耗 0.5% 。保守地說(shuō),只要把網(wǎng)卡中斷分散一下,單機(jī)50w+ connections很easy。??更多數(shù)據(jù)圖文參見“NBA js直播的發(fā)展歷程”一節(jié)。
有些人了解我是由于財(cái)經(jīng)的實(shí)時(shí)行情系統(tǒng),雖然每天處理近百億的http請(qǐng)求處理量還不錯(cuò),但那并非我的得意之作,相反我覺得那個(gè)寫的有些粗糙,至少有一倍以上的性能提升空間。對(duì)于行情系統(tǒng),我還是很想把它做成push的,目標(biāo)仍然是單機(jī)50w+在線,無(wú)延遲推送,可惜本人js功底太爛,所以要作為一個(gè)長(zhǎng)期的地下項(xiàng)目去做,如果可能,我想一開始就把它作為一個(gè)開源項(xiàng)目來(lái)做。
我個(gè)人比較喜歡追求性能極限,公司對(duì)此暫時(shí)還不是很認(rèn)可,或者說(shuō)重視程度還不夠,可能是由于我們的硬件資源比較充裕吧。盡管如此,只要我認(rèn)為對(duì)企業(yè)有價(jià)值的,就依然會(huì)堅(jiān)持做下去,我的目標(biāo)是獲得業(yè)界的認(rèn)可。同時(shí)我相信中國(guó)的未來(lái)不缺乏互聯(lián)網(wǎng)用戶,當(dāng)有人燒不起錢的時(shí)候想起了我,那我就是有價(jià)值的。
這里說(shuō)的有點(diǎn)多了,不過(guò)放心,ppt我會(huì)做的相當(dāng)簡(jiǎn)單。
七,NBA js直播的發(fā)展歷程
這一節(jié)就談下這個(gè)項(xiàng)目發(fā)展過(guò)程中所遇到的瓶頸,以及如何解決的。
應(yīng)該是06年吧,當(dāng)時(shí)NBA 比賽比較火,woocall負(fù)責(zé)高速模式圖文直播放,普通模式和動(dòng)態(tài)比分?jǐn)?shù)據(jù)等都放在一群破服務(wù)器上,大概有十幾20臺(tái),這些破服務(wù)器有些扛不住了。
因?yàn)榈诙煊幸粓?chǎng)比較大的比賽,我想埋連接在線上測(cè)一下效果,于是連夜把財(cái)經(jīng)實(shí)時(shí)行情server改寫成了NBA JS直播server. 當(dāng)時(shí)有兩臺(tái) Intel(R) Xeon(TM) CPU 3.00GHz 雙cpu的服務(wù)器,在F5后面。先啟用一臺(tái)服務(wù)器,比賽開始前靜悄悄的,不一會(huì),迅速串到了20w connections,再往上增長(zhǎng),就慢的幾乎不可訪問, ethtool eth0??, Speed: 100Mb/s, 網(wǎng)卡出口帶寬跑滿了(那時(shí)候支持千兆的交換機(jī)還不多)。迅速把另一臺(tái)服務(wù)器啟用,后來(lái)又卡了,好象是F5處理能力不足。 后來(lái)升級(jí)服務(wù)器出口帶寬為1G,當(dāng)然這需要交換機(jī)支持千兆口,更換網(wǎng)線,服務(wù)器也從F5后面轉(zhuǎn)移出來(lái),通過(guò)DNS直接輪詢。
后來(lái)又測(cè)試了幾次,等到新申請(qǐng)的Intel(R) Xeon(R) CPU 5120??@ 1.86GHz, 雙核雙cpu服務(wù)器一到,就開始大規(guī)模部署,比賽更火了,不巧的是行情也火了起來(lái),我的財(cái)經(jīng)實(shí)時(shí)圖片系統(tǒng)和行情系統(tǒng)也是帶寬殺手,同時(shí)我也成了服務(wù)器殺 手,到處蹭服務(wù)器。IDC帶寬出口開始告急,我每天開始關(guān)注哪個(gè)機(jī)房還有富余帶寬,有的機(jī)房被我們跑的太滿,開始有人勸我們遷移到別的機(jī)房。后來(lái) yangguang4勸我支持gzip輸出,我覺得動(dòng)態(tài)壓縮太耗費(fèi)cpu,不知道預(yù)先壓縮是否可行,寫個(gè)程序試了一把,沒問題,于是NBA JS直播的的帶寬一下子被砍掉了70%,而且沒浪費(fèi)一點(diǎn)我們的cpu,賺大了。
之后的兩年里NBA JS服務(wù)一直很穩(wěn)定,我?guī)缀醵紱]怎么看過(guò),2007年的一場(chǎng)體育賽事中,單機(jī)達(dá)到25w+ connections,2.86w req/s ,cpu空閑30% ,見下圖 (2CPU*2Core 1.86GHZ服務(wù)器) 。直到奧運(yùn)期間,有場(chǎng)賽事,woocall瞬間仍給我近200w connections,網(wǎng)通的服務(wù)器被秒殺了1/3。 這其實(shí)就是善意的DDOS攻擊,這些用戶如果正常進(jìn)入是沒有問題了,瞬間扔過(guò)來(lái),超出了操作系統(tǒng)極限,系統(tǒng)掛掉了,我的服務(wù)也over了。在調(diào)整內(nèi)核參數(shù)里有講,怎么修改內(nèi)核參數(shù)提高服務(wù)器抗秒殺能力,但是不能杜絕。
下圖為2007年一場(chǎng)比賽時(shí),單機(jī)25w+ connections,2.86w req/s,的狀態(tài)(2CPU*2Core 1.86GHZ):
?
奧運(yùn)結(jié)束后,我對(duì)服務(wù)器程序和架構(gòu)做了調(diào)整,砍掉了2/3的服務(wù)器。 但我沒留意的是,同樣connections,實(shí)際http請(qǐng)求增加了一倍,因?yàn)樾律狭艘粋€(gè)flash方位圖,里面增加了3個(gè)js,增加就增加吧,既然砍了就不準(zhǔn)備再恢復(fù)了。但是服務(wù)在2CPU*4Core centos5 32bit上的表現(xiàn)卻讓我很失望,跑不過(guò)2CPU*2Core centos4 32bit 。 我開始懷疑程序升級(jí)的時(shí)候是不是有什么地方?jīng)]考慮到,開始調(diào)程序,折騰幾天沒有結(jié)果,癥狀是單機(jī)支撐12.5萬(wàn)時(shí)候沒有任何異常,內(nèi)存使用1%左右,總cpu使用了5%左右,load 0.5,但是再增加0.1w用戶server肯定崩潰,每次都是相同的表現(xiàn),我知道在什么地方卡住了。 后來(lái)看了下dmesg,才恍然大悟,我是被32bit centos 5的內(nèi)核暗殺的(Out of memory: Killed process xxx)。
32位機(jī)上LowFree一般是會(huì)變化的(cat /proc/meminfo??| grep LowFree),最大不能超過(guò)880M(這個(gè)值不能改變除非用hugemem內(nèi)核),在centos4 上有內(nèi)核參數(shù)vm.lower_zone_protection(單位是M)來(lái)調(diào)節(jié)LowFree,默認(rèn)vm.lower_zone_protection=0 ,LowFree=16M,但是在centos5上這個(gè)參數(shù)貌似被取消了,改變不了。 從使用經(jīng)驗(yàn)來(lái)看,也就是你能申請(qǐng)16M~880M這么大的連續(xù)內(nèi)存,但是16M以上不保證你能申請(qǐng)的到。centos4用到64M以上都沒什么問題,centos5 用40M+ 就被OOM Killer給斃了。
本周開始使用64bit centos5.2進(jìn)行測(cè)試,遷移很順利,沒有修改一行代碼,他們把整個(gè)16G物理內(nèi)存都作為L(zhǎng)owFree,這下可以隨便揮霍了(盡管如此我還是會(huì)節(jié)約的),前幾天的比賽,這個(gè)64bit機(jī)跑了18w connections,很安靜,未見異常,等有大比賽再檢驗(yàn)下,沒問題的話就開始大規(guī)模使用64bit系統(tǒng)。
目前看來(lái),如果成功遷移64bit系統(tǒng)似乎可以高枕無(wú)憂了,但是我還是有兩個(gè)憂慮:
1,再突然甩200w connections給我,我不敢保證能扛的住,因?yàn)楝F(xiàn)在服務(wù)器數(shù)量消減太多,需要yangguang4那邊做策略調(diào)整,在比賽結(jié)束后,平滑的把用戶丟給我,這應(yīng)該有個(gè)持續(xù)過(guò)程,而不是一秒內(nèi)的瞬間。
2,我猜這個(gè)系統(tǒng),單機(jī)支撐到30w conections的時(shí)候會(huì)遇到瓶頸,因?yàn)榫W(wǎng)卡的中斷集中在cpu0上,沒有均衡開。我們有硬件解決方案已經(jīng)實(shí)現(xiàn)(每個(gè)服務(wù)器會(huì)多2000RMB開銷)我只部署了一臺(tái),但是軟的還沒實(shí)現(xiàn),寄希望于xiaodong2 。
補(bǔ)充:
昨天的比賽中,一臺(tái)64bit機(jī),單機(jī)支撐30w+ connections,cpu0空閑率只剩6%,和我的預(yù)料是一致的。當(dāng)時(shí)的CPU總量被我用掉近 1/4,內(nèi)存被我用掉 0.5% 。
下圖為30w+ connections, 4.6w req/s 的時(shí)候我的程序使用的資源情況(2cpu*4Core):
下圖為cpu使用分布情況,cpu0空閑率只剩 6% (2cpu*4Core):
另外附上一個(gè)23w connections, 3.5w req/s 的時(shí)候我的程序使用的資源情況(2cpu*4Core),當(dāng)時(shí)cpu只被用掉1/8,內(nèi)存被用掉 0.4% ,cpu沒有發(fā)揮線性增加的作用,我肯定不說(shuō)能我可以支撐23w*8,但是保守地說(shuō),只要把網(wǎng)卡中斷分散一下,單機(jī)50w+ connections很easy。
?
八,新浪財(cái)經(jīng)實(shí)時(shí)行情系統(tǒng)的歷史遺留問題 (7 byte = 10.68w RMB/year)
這點(diǎn)我還是提下吧,估計(jì)我不說(shuō),大家也想不到。
先感謝wangyun同學(xué)的大膽使用才有了今天的財(cái)經(jīng)實(shí)時(shí)行情系統(tǒng)(當(dāng)初是從一臺(tái)PIII 900服務(wù)器上發(fā)展起來(lái)的,前幾天剛被我下線)不過(guò) "hq_str_"??這7個(gè)字節(jié)的前綴,也是他造成的,當(dāng)初他說(shuō)改抓取頁(yè)面有些麻煩,就讓我寫死在server里,雖然意識(shí)到將來(lái)也許會(huì)有隱患,但我還是照做了。見下面返回?cái)?shù)據(jù):
http://hq.sinajs.cn/list=s_sz000609,s_sz000723,s_sh000001
我算了一筆帳,行情好的時(shí)候每秒會(huì)產(chǎn)生30~40w個(gè)請(qǐng)求,一般一個(gè)請(qǐng)求會(huì)請(qǐng)求3~50只股票,保守點(diǎn)就按每個(gè)請(qǐng)求5只股票來(lái)計(jì)算,每秒會(huì)產(chǎn)生200w只股票查詢信息。 由于這7個(gè)字節(jié)產(chǎn)生的帶寬為: 200w??*??7byte??* 8bit / 1024 /1024 = 106.8 M??,而往往我們的帶寬要按峰值來(lái)準(zhǔn)備,按1G帶寬100w RMB/year 計(jì)算,每年耗費(fèi)10.68w RMB。把這筆錢拿給我做獎(jiǎng)金,我會(huì)很happy的 ^-^ . 現(xiàn)在因?yàn)楹芏囗?yè)面都使用了行情數(shù)據(jù),想修改,代價(jià)很高。
所以設(shè)計(jì)系統(tǒng)的時(shí)候一定要考慮的稍微遠(yuǎn)一些,哪怕當(dāng)時(shí)只是一點(diǎn)點(diǎn)微不足道的地方,要考慮將來(lái)訪問規(guī)模變大了會(huì)是什么后果。還有就是要敢于堅(jiān)持自己的原則。
系統(tǒng)架構(gòu)篇
-----------------------------------------------------------------------------------------
一,系統(tǒng)部署(高并發(fā),可擴(kuò)展)
二,負(fù)載均衡LVS(高可用,低成本)
三,IDC分布,DNS解析(快速)
-----------------------------------------------------------------------------------------
一,系統(tǒng)部署(高并發(fā),可擴(kuò)展)
本來(lái)想畫在手稿上然后掃描上去的,貌似方法太土,在朋友的幫助下費(fèi)了n個(gè)小時(shí)用Visio畫了個(gè),感覺很好看 ^-^ 。這一篇將主要圍繞這個(gè)圖來(lái)講述。
首先從數(shù)據(jù)源說(shuō)起,所謂狡兔三窟,我們數(shù)據(jù)源也是按三路設(shè)計(jì),以保證IDC內(nèi)部和不同IDC之間實(shí)現(xiàn)災(zāi)備。源頭轉(zhuǎn)發(fā)機(jī)A,B,C擁有往集群中任何一臺(tái)服務(wù)器同步數(shù)據(jù)的權(quán)限,所以他們?nèi)齻€(gè)有一個(gè)活著,數(shù)據(jù)就可以同步更新,而且可以自動(dòng)切換。從源頭轉(zhuǎn)發(fā)機(jī)到其他各IDC的數(shù)據(jù)都是雙路的,然后每個(gè)IDC的前兩臺(tái)服務(wù)器具備轉(zhuǎn)發(fā)功能,往IDC內(nèi)部其他服務(wù)器分發(fā)數(shù)據(jù),同一IDC內(nèi)部的主備轉(zhuǎn)發(fā)機(jī)可以自動(dòng)切換。這樣就實(shí)現(xiàn)了數(shù)據(jù)同步更新的高可用性。
介紹下這個(gè)集群里的角色,備機(jī)A來(lái)自行情系統(tǒng),兼任源頭轉(zhuǎn)發(fā)的異地備份。系統(tǒng)內(nèi)的另外兩個(gè)備機(jī)屬于輕負(fù)荷服務(wù)器,80端口空出來(lái),必要時(shí)候只要一啟動(dòng),就會(huì)立即自動(dòng)加入到LVS后面服役。除了A以外所有具備轉(zhuǎn)發(fā)功能的機(jī)器同時(shí)也是集群內(nèi)的普通成員,需要提訪問供服務(wù)的。各IDC的LVS本身也是有主備的,可以實(shí)現(xiàn)自動(dòng)切換。
整個(gè)系統(tǒng)增減服務(wù)器非常方便,用戶根本感覺不到,備機(jī)的啟用更快,也就3~5秒,具備很好的擴(kuò)展性。
我們的數(shù)據(jù)從源頭上就是使用我編寫的myzip壓縮好了的,后綴名用"*.mz" ,比如 a.js.mz ,一直到用戶的瀏覽器端才解壓。數(shù)據(jù)傳輸量小,速度快。 源頭轉(zhuǎn)發(fā)機(jī)上同時(shí)運(yùn)行一個(gè)checkchange的程序,確保內(nèi)容實(shí)際更新過(guò)的文件才往其他IDC轉(zhuǎn)發(fā),這樣能有效的減少傳輸文件數(shù)量,以達(dá)到更快的更新速度。
另外,跨IDC系統(tǒng)部署,很重要的一點(diǎn)是,內(nèi)網(wǎng)連通,路由選擇,這影響數(shù)據(jù)傳輸速度的關(guān)鍵。北京的各機(jī)房間一般都有比較好的專線連通,只需要把路由打通就ok了。跨IDC的,一般都使用vpn來(lái)做內(nèi)網(wǎng)傳輸,有條件的使用專線,這個(gè)比較昂貴,省著點(diǎn)用。另外跨網(wǎng)通,電信,和移動(dòng)機(jī)房的一般都從雙線機(jī)房路由,或者說(shuō),從到不同信息服務(wù)商連通性都比較好的機(jī)房路由。總之跨IDC數(shù)據(jù)傳輸,要做到各IDC之間的傳輸速度心中有數(shù)。
最后,請(qǐng)稍微注意下系統(tǒng)的安全性,包括數(shù)據(jù)傳輸?shù)陌踩?#xff0c;和網(wǎng)絡(luò)安全性,避免遭受攻擊。
二,負(fù)載均衡LVS(高可用,低成本)
LVS 有三種模式,NAT,TUN,DR,其中DR是最高效的,下面我將主要介紹DR的應(yīng)用。更多LVS資料參見?LVS項(xiàng)目中文文檔。?目前我們公司的LVS應(yīng)用規(guī)模在國(guó)內(nèi)應(yīng)該至少可以排前三,更多技術(shù)細(xì)節(jié)請(qǐng)咨詢我們的LVS大牛xiaodong2.
下面是DR單臂模式的系統(tǒng)結(jié)構(gòu)圖:
下面引用一下官網(wǎng)的介紹: 在VS/DR 中,調(diào)度器根據(jù)各個(gè)服務(wù)器的負(fù)載情況,動(dòng)態(tài)地選擇一臺(tái)服務(wù)器,不修改也不封裝IP報(bào)文,而是將數(shù)據(jù)幀的MAC地址改為選出服務(wù)器的MAC地址,再將修改后 的數(shù)據(jù)幀在與服務(wù)器組的局域網(wǎng)上發(fā)送。因?yàn)閿?shù)據(jù)幀的MAC地址是選出的服務(wù)器,所以服務(wù)器肯定可以收到這個(gè)數(shù)據(jù)幀,從中可以獲得該IP報(bào)文。當(dāng)服務(wù)器發(fā)現(xiàn) 報(bào)文的目標(biāo)地址VIP是在本地的網(wǎng)絡(luò)設(shè)備上,服務(wù)器處理這個(gè)報(bào)文,然后根據(jù)路由表將響應(yīng)報(bào)文直接返回給客戶。
我在財(cái)經(jīng)時(shí)要使用LVS的初衷只是為了解決負(fù)載的均衡性,因?yàn)镈NA輪詢各前端服務(wù)器上連接數(shù)有不小的差距,那時(shí)候我們老大阿圖對(duì)于這個(gè)項(xiàng)目給予了很大支持,還親自組織過(guò)幾次會(huì)議。話說(shuō)恰巧yingyuan做了個(gè)新技術(shù)講座,我從中發(fā)現(xiàn)LVS/DR后想讓它幫忙修改負(fù)載均衡算法,后來(lái)部署上以后發(fā)現(xiàn),不用修改,均衡的很,再后來(lái)xiaodong2接手后對(duì)性能和穩(wěn)定性做了很大的提升,我們使用兩年來(lái)沒出過(guò)問題。另外lvs還額外帶來(lái)了兩個(gè)好處,高可用性,和可伸縮性。是可以隨時(shí)把lvs后面的一臺(tái)服務(wù)器下掉,扛走,用戶是不知道的。服務(wù)器壞了也不用著急修,也不用修改DNS(另外DNS的層層cache影響不是一時(shí)半會(huì)就能消除的)。新增加一臺(tái)服務(wù)器也是同理,最絕的就是備機(jī)的啟用可以用秒來(lái)衡量(這些F5都能實(shí)現(xiàn),代價(jià)不菲)。財(cái)經(jīng)應(yīng)用對(duì)公司內(nèi)lvs的項(xiàng)目推動(dòng)有不可磨滅的貢獻(xiàn) ,xiaodong2也這么說(shuō)地 :) 。
三,IDC分布,DNS解析(快速)
這里思路跟CDN是一致的,盡量減少主干線路上的擁塞,讓用戶就近訪問,以達(dá)到最快的數(shù)據(jù)傳輸速率。
我們要做的就是了解自己應(yīng)用的用戶分布情況,然后再結(jié)合現(xiàn)有資源以及各地網(wǎng)絡(luò)出口特征,信息服務(wù)商的特征來(lái)部署我們的服務(wù)。
1,各省市網(wǎng)絡(luò)用戶分布依次排名(數(shù)據(jù)來(lái)自cnnic2007年的統(tǒng)計(jì)):
廣東 13.4%
山東 8.2%
江蘇 7.5
浙江
四川
河北
河南
福建
上海
遼寧
北京
湖南
山西
黑龍江
2,運(yùn)營(yíng)商的網(wǎng)絡(luò)分布特點(diǎn):
網(wǎng)通:以北京為超核心的放射性結(jié)構(gòu)。山東應(yīng)該是網(wǎng)通最大的用戶,但它的網(wǎng)絡(luò)存在瓶頸,會(huì)有丟包,造成外面訪問它慢,它訪問別人也慢。對(duì)此我們沒有必要浪費(fèi)珍貴的主干帶寬,在濟(jì)南布個(gè)點(diǎn),同步一份數(shù)據(jù)過(guò)去就,讓他們?cè)谧约菏?nèi)訪問,訪問速度會(huì)立刻提升n倍。
電信:以幾大省市為核心的環(huán)狀結(jié)構(gòu),省市內(nèi)部也是大環(huán)套小環(huán)。其中以廣東用戶最多,必須要部點(diǎn)的地方。記得很久以前我拿到一份數(shù)據(jù)說(shuō),上海人訪問本IDC的數(shù)據(jù),不如訪問廣東的速度快,不曉得現(xiàn)在是否還存在這種情況。電信有7個(gè)主要核心,分布在廣州,上海,江蘇,西安,成都,武漢,北京。
教育網(wǎng):以國(guó)內(nèi)主要的八個(gè)結(jié)點(diǎn)為核心。這八個(gè)結(jié)點(diǎn)分布在北京,西安,成都,廣州,武漢,南京,上海,和沈陽(yáng)。
3,DNS解析時(shí)候需要權(quán)衡的:
現(xiàn)在了解了這些信息,那我們開始討論如何部署我們的服務(wù)。要考慮兩個(gè)問題:一,要部署在哪幾個(gè)IDC。二,每個(gè)IDC部署的服務(wù)器數(shù)量。三,DNS如何按區(qū)域劃片。
要部署在哪幾個(gè)IDC ?
其實(shí)這里還涉及到規(guī)模化應(yīng)用的好處,一個(gè)小應(yīng)用就部署了N多個(gè)IDC顯然不劃算。如果我的應(yīng)用上了規(guī)模,我可以在每個(gè)省都部署上,那樣用戶體驗(yàn)將非常好,而且規(guī)模化以后會(huì)有專業(yè)人員對(duì)應(yīng)用進(jìn)行優(yōu)化。所以公司里有動(dòng)態(tài)池,和靜態(tài)池這樣的公用平臺(tái)是好事(也許將來(lái)還會(huì)有我的js池)。如果我們的服務(wù)還沒有上升到公司級(jí)別的規(guī)模,那就得考慮下取舍。
網(wǎng)通:東北三省,可以在沈陽(yáng)和哈爾濱選擇一個(gè)部署,有條件可以都部署。沈陽(yáng)到北京的速率比哈爾濱到北京的速率快一倍,而哈爾濱到沈陽(yáng)的速率,還不如到北京的快。北京,如果只讓我在網(wǎng)通部署一個(gè)點(diǎn)的話,毫無(wú)疑問我會(huì)選擇北京,其他所有結(jié)點(diǎn)到它的速率都比較快,但是北京的帶寬比較昂貴。天津,這個(gè)點(diǎn)重要性僅次于北京,可以輻射河北,河南,江蘇,離北京也比較近,價(jià)錢便宜。太原,可以輻射到西北一帶。山東,前面已經(jīng)說(shuō)過(guò),最好要部署的。
電信:那七個(gè)核心結(jié)點(diǎn)上部署了,速度就有保證。具體覆蓋范圍。廣州覆蓋周邊幾省,上海覆蓋本地,江浙一帶。 武漢覆蓋華中一帶,西安可以覆蓋西北5省,成都覆蓋西南5省。
每個(gè)IDC部署的服務(wù)器數(shù)量?
這要根據(jù)具體應(yīng)用來(lái)決定。比如財(cái)經(jīng)用戶網(wǎng)通,電信比例:3:4 而體育是 1:2 。教育網(wǎng)用戶一般占1/30左右。 這里還不能單純考慮用戶分布,還要考慮IDC內(nèi)部災(zāi)備和IDC間災(zāi)備,是要有個(gè)取舍的。拿咱們的某個(gè)具體項(xiàng)目來(lái)說(shuō),教育網(wǎng),夠不上一臺(tái)服務(wù)器,但是不得不部,因?yàn)樗L問外界實(shí)在太慢了,我就住在學(xué)校里,也為了方便自己。我把北京作為主要結(jié)點(diǎn)部署了3臺(tái),天津,其實(shí)一臺(tái)就夠了,山東一臺(tái)有點(diǎn)多。但是考慮到北京IDC一旦倒了,實(shí)力相當(dāng)?shù)腎DC可以災(zāi)備,同時(shí)考慮到,天津,和山東只有一臺(tái),idc內(nèi)部,都無(wú)法實(shí)現(xiàn)災(zāi)自動(dòng)切換。所以,我選擇天津兩臺(tái),山東不部署,以性能換安全。
DNS如何按區(qū)域劃片?
原則,就近分片,以達(dá)到最快傳輸速率。其次,考慮到各IDC間快速切換比較容易,DNS解析文件要寫的簡(jiǎn)潔一些。另外,DNS解析有有個(gè)缺陷,每個(gè)單獨(dú)域名里寫在最前面的那個(gè)ip,它被輪詢到的概率要比同組的服務(wù)器高10%,而且隨著同組服務(wù)器的增多,這個(gè)差距會(huì)變大。所以最解析時(shí)候,每個(gè)IDC我都把硬件性能最好的服務(wù)器ip放在最前面。
另外:
做系統(tǒng)架構(gòu)不提數(shù)據(jù)庫(kù),有點(diǎn)過(guò)不去。這塊問題可以請(qǐng)教我們的DBA大牛zongwen同學(xué)。數(shù)據(jù)庫(kù)是我將來(lái)一年的學(xué)習(xí)重點(diǎn),爭(zhēng)取一年后在DB方面能達(dá)到我們DBA六層功力。
?
Cache為王篇
?---------------------------------------------------------------------------------------
一,Cache, 王道也
二,Cache 基本原理介紹
三,我劃分的3個(gè)刷新級(jí)別
四,我對(duì)HTTP協(xié)議做的一點(diǎn)創(chuàng)新(?maxage=6000000)
五,Yslow優(yōu)化網(wǎng)站性能的14條軍規(guī)點(diǎn)評(píng)
六,上線了 !=??Finished
七,提速度同時(shí)節(jié)約成本方法匯總
-----------------------------------------------------------------------------------------
一,Cache,王道也
我覺得系統(tǒng)架構(gòu)不應(yīng)該僅僅是搭建一個(gè)強(qiáng)硬的能承受巨大并發(fā)壓力的后臺(tái),前端頁(yè)面也是需要架構(gòu)的而且同等重要,不理解前臺(tái)的的后臺(tái)工程師是不合格的。中國(guó)人講究鋼柔相濟(jì),后臺(tái)強(qiáng)硬只能說(shuō)你內(nèi)功深厚,前端用的巧,那叫四兩撥千斤。
一般后臺(tái)工程師很少關(guān)心前端如何使用自己的資源,而前端工程師,不知道自己的一個(gè)簡(jiǎn)單的用法會(huì)對(duì)后端造成多大影響。我會(huì)給出一些數(shù)據(jù),來(lái)震撼下你的眼球。
二,Cache 基本原理介紹?(參考Caching Tutorial)
為什么使用Cache?
1,減少延遲,讓你的網(wǎng)站更快,提高用戶體驗(yàn)。
2,避免網(wǎng)絡(luò)擁塞,減少請(qǐng)求量,減少輸出帶寬。
補(bǔ)充一個(gè)cache的原則:不更新的資源就不應(yīng)該讓它再次產(chǎn)生HTTP請(qǐng)求,如果強(qiáng)制產(chǎn)生了請(qǐng)求,那么就看看能否返回304。
Cache的種類?
瀏覽器Cache,代理Cache,網(wǎng)關(guān)Cache。
后端還有 disk cache ,server cache,php cache,不過(guò)不屬于我們今天討論范圍。
Cache如何工作的?
1,如果響應(yīng)頭告訴cache別緩存它,cache不對(duì)它做緩存;
2,如果請(qǐng)求需要驗(yàn)證的或者是需要安全性的,它將不被緩存;
3,如果響應(yīng)頭里沒有ETag或Last-Modifed header這類元素,而且也沒有任何顯式的信息告訴如何對(duì)數(shù)據(jù)保鮮,則它被認(rèn)為不可緩存。
4,在下面情況下,一個(gè)緩存項(xiàng)被認(rèn)為是新鮮的(即,不需到原server上檢查就可直接發(fā)送給client):
????它設(shè)置了一個(gè)過(guò)期時(shí)間或age-controlling響應(yīng)頭,而且現(xiàn)在仍未過(guò)期。
????如果瀏覽器cache里有某個(gè)數(shù)據(jù)項(xiàng),并且被被設(shè)置為每個(gè)會(huì)話(session)過(guò)程中只檢查一次;
????如果一個(gè)代理cache里能找個(gè)某個(gè)數(shù)據(jù)項(xiàng),并且它是在相對(duì)較長(zhǎng)時(shí)間之前更新過(guò)的。
????以上情況會(huì)認(rèn)為數(shù)據(jù)是新鮮的,就直接走cache,不再查詢?cè)磗erver。
5,如果有一項(xiàng)過(guò)期了,它將會(huì)讓原server去更新它,或者告訴cache這個(gè)拷貝是否還是可用的。
怎么控制你的Cache?
Meta tags :在html頁(yè)面中指定,這個(gè)方法只被少數(shù)瀏覽器支持,Proxy一般不會(huì)讀你html的具體內(nèi)容然后再做cache決策的。
Pragma: no-cache : 一般被大家誤用在http響應(yīng)頭中,這不會(huì)產(chǎn)生任何效果。而實(shí)際它僅僅應(yīng)該用在請(qǐng)求頭中。 不過(guò)google的Server: GFE/1.3 響應(yīng)中卻這樣用,難道人家也誤用了呢。
Date: 當(dāng)前主機(jī)GMT時(shí)間。
Last-Modified : 文件更新GMT時(shí)間,我在響應(yīng)頭中帶上這個(gè)元素的時(shí)候,通常瀏覽器在cache時(shí)間內(nèi)再發(fā)請(qǐng)求都會(huì)稍帶上If-Modified-Since,讓我們判斷需要重新傳輸文件內(nèi)容,還是僅僅返回個(gè)304告訴瀏覽器資源還沒更新,需要緩存策略的服務(wù)器肯定都得支持的。有了這個(gè)請(qǐng)求,head請(qǐng)求在基本沒太多用處了,除非在telnet上調(diào)試還能用上。
If-Modified-Since :??用在請(qǐng)求頭里,見Last-Modified 。
Etag: 標(biāo)識(shí)資源是否發(fā)生變化,etag的生成算法各是各樣,通常是用文件的inode+size+LastModified進(jìn)行Hash后得到的,可以根據(jù)應(yīng)用選擇適合自己的。Last-Modified 只能精確到秒的更新,如果一秒內(nèi)做了多次更新,etag就能派上用場(chǎng)。貌似大家很少有這樣精確的需求,浪費(fèi)了http header的字節(jié)數(shù),建議不要使用。
更正:Etag 其實(shí)在某種情況下可以很好的減少數(shù)據(jù)傳輸。在stonehuang的提醒下我才恍然大悟,轉(zhuǎn)眼好幾個(gè)月了也一直忘記更新。Etag應(yīng)用場(chǎng)景。比如,數(shù)據(jù)為php的動(dòng)態(tài)輸出。每次請(qǐng)求把上一次Etag帶來(lái),跟本次計(jì)算的Etag進(jìn)行比較,相等就可以避免一次數(shù)據(jù)傳輸。(最后修改時(shí)間 2009.12.07)
Expires :??指定緩存到期GMT的絕對(duì)時(shí)間,這個(gè)是http 1.0里就有的。 這個(gè)元素有些缺點(diǎn),一,服務(wù)器和瀏覽器端時(shí)間不一致時(shí)會(huì)有問題。 二,一旦失效后如果忘記重新設(shè)置新的過(guò)期時(shí)間會(huì)導(dǎo)致cache失效。三,服務(wù)器端需要根據(jù)當(dāng)前Date時(shí)間 + 應(yīng)該cache的相對(duì)時(shí)間去計(jì)算這個(gè)值,需要cpu開銷。 我不推薦使用。
Cache-Control:
這個(gè)是http 1.1中為了彌補(bǔ) Expires 缺陷新加入的,現(xiàn)在不支持http 1.1的瀏覽器已經(jīng)很少了。
max-age:?指定緩存過(guò)期的相對(duì)時(shí)間秒數(shù),max-ag=0或者是負(fù)值,瀏覽器會(huì)在對(duì)應(yīng)的緩存中把Expires設(shè)置為1970-01-01 08:00:00 ,雖然語(yǔ)義不夠透明,但卻是我最推薦使用的。
s-maxage:?類似于max-age,只用在共享緩存上,比如proxy.
public:?通常情況下需要http身份驗(yàn)證的情況,響應(yīng)是不可cahce的,加上public可以使它被cache。
no-cache:?強(qiáng)制瀏覽器在使用cache拷貝之前先提交一個(gè)http請(qǐng)求到源服務(wù)器進(jìn)行確認(rèn)。這對(duì)身份驗(yàn)證來(lái)說(shuō)是非常有用的,能比較好的遵守 (可以結(jié)合public進(jìn)行考慮)。它對(duì)維持一個(gè)資源總是最新的也很有用,與此同時(shí)還不完全喪失cache帶來(lái)的好處,因?yàn)樗诒镜厥怯锌截惖?#xff0c;但是在用之前都進(jìn)行了確認(rèn),這樣http請(qǐng)求并未減少,但可能會(huì)減少一個(gè)響應(yīng)體。
no-store:??告訴瀏覽器在任何情況下都不要進(jìn)行cache,不在本地保留拷貝。
must-revalidate:?強(qiáng)制瀏覽器嚴(yán)格遵守你設(shè)置的cache規(guī)則。
proxy-revalidate:?強(qiáng)制proxy嚴(yán)格遵守你設(shè)置的cache規(guī)則。
用法舉例:??Cache-Control: max-age=3600, must-revalidate
其他一些使用cache需要注意的東西,不要使用post,不要使用ssl,因?yàn)樗麄儾豢杀籧ache,另外保持url一致。只在必要的地方,通常是動(dòng)態(tài)頁(yè)面使用cookie,因?yàn)閏oolie很難cache。至于apache如何支持cache和php怎么用header函數(shù)設(shè)置cache,暫不做介紹,網(wǎng)上資料比較多。
如何設(shè)置合理的cache時(shí)間 ?
http://image2.sinajs.cn/newchart/min/n/sz000609.gif?1230015976759
拿我分時(shí)圖舉例,我們需要的更新頻率是1分鐘。但為了每次都拿到最新的資源,我們?cè)诤竺婕恿藗€(gè)隨機(jī)數(shù),這個(gè)數(shù)在同一秒內(nèi)的多次刷新都會(huì)變化。我們的js雖然能夠很好的控制,一分鐘只請(qǐng)求一次,但是如果用戶點(diǎn)了刷新按紐呢?這樣的調(diào)用是完全cache無(wú)關(guān)的,連返回304的機(jī)會(huì)都沒有。
試想,如果很多人通過(guò)同一個(gè)代理出去的,那么所有的請(qǐng)求都會(huì)穿透代理,弄不好被網(wǎng)管封掉了。如果我們做只做一秒的cache,對(duì)直接訪問源服務(wù)器的用戶沒太多影響,但對(duì)于代理服務(wù)器來(lái)說(shuō),他的請(qǐng)求可能會(huì)從10000 req/min 減少為 60 req/min ,這是160倍。
對(duì)于我們行情圖片這樣的情況,刷新頻率為1分鐘,比較好的做法是把后面的隨機(jī)數(shù)(num)修改為 num=t-t%60 其中t是當(dāng)前時(shí)間戳 ,這樣你一分鐘內(nèi)刷這個(gè)url是不變的,下一分鐘會(huì)增加1,會(huì)再次產(chǎn)生一個(gè)新請(qǐng)求。而我的max-age設(shè)置為默認(rèn)59秒,即使設(shè)置120秒其實(shí)也沒什么影響。可能你會(huì)說(shuō)萬(wàn)一趕上臨界點(diǎn)可能拿不到最新的數(shù)據(jù),其實(shí)對(duì)用戶來(lái)說(shuō),用那個(gè)多變的隨即數(shù)和我這個(gè)分鐘級(jí)的隨即數(shù),看到的效果是相同的下面我給你分析一下: 如果用戶打開了我們的分時(shí)間頁(yè)面,當(dāng)前隨即數(shù)對(duì)他來(lái)說(shuō)是新的,所以他會(huì)拿到一個(gè)當(dāng)前最新的圖片,然后他點(diǎn)了刷新按紐,用戶會(huì)產(chǎn)生http請(qǐng)求,即使url沒變,服務(wù)器有最新圖片也一定會(huì)返回,否則返回304,一分鐘后js刷新圖片,分鐘數(shù)加了1,會(huì)得到全新資源。這和那個(gè)隨時(shí)變化的隨即數(shù)效果有區(qū)別嗎?都拿到了最新的數(shù)據(jù),但是卻另外收益了cache帶來(lái)的好處,對(duì)后端減少很多壓力。
三,我劃分的3個(gè)刷新級(jí)別
名詞解釋 全新請(qǐng)求: url產(chǎn)生了變化,瀏覽器會(huì)把他當(dāng)一個(gè)新的資源(發(fā)起新的請(qǐng)求中不帶If-Modified-Since)。
更正:在firefox后來(lái)的版本中對(duì)此做了改進(jìn),傾向于更多的使用cache,曾經(jīng)訪問過(guò)的都會(huì)盡量捎帶If-Modified-Since頭。這些表現(xiàn)和IE一致。修改部分用紅色標(biāo)出。(最后修改時(shí)間 2009.12.07)
注: sports.sinajs.cn 在IE下的表現(xiàn)存在一個(gè)小bug,由于不是使用的strncpy,導(dǎo)致IE下難以返回304,
需要修改一行代碼,把比較字符串長(zhǎng)度設(shè)置為29即可解決。不過(guò)目前本人已不在職,難以修改。
情況一 FF 捎帶的頭: If-Modified-Since????Mon, 07 Dec 2009 10:54:43 GMT
情況二 IE 捎帶的頭: If-Modified-Since????Mon, 07 Dec 2009 10:54:43 GMT; length=6
1,在地址欄中輸入http://sports.sinajs.cn/today.js?maxage=11地址按回車。重復(fù)n次,直到cache時(shí)間11秒過(guò)去后,才發(fā)起請(qǐng)求,這個(gè)請(qǐng)求會(huì)帶If-Modified-Since。
2,按F5刷新.??在你發(fā)起一個(gè)全新的請(qǐng)求以后,然后多次按F5都會(huì)產(chǎn)生一個(gè)帶If-Modified-Since的請(qǐng)求。
3, ctrl+F5 ,總會(huì)發(fā)起一個(gè)全新的請(qǐng)求。
下面是按F5刷新的例子演示: http://sports.sinajs.cn/today.js?maxage=11
( 如果這個(gè)值大于瀏覽器最大cache時(shí)間maxage,將以瀏覽器最大cache為準(zhǔn))
----------------------------------------------------------發(fā)起一個(gè)全新請(qǐng)求
GET /today.js?maxage=11 HTTP/1.1
Host: sports.sinajs.cn
Connection: keep-alive
HTTP/1.x 200 OK
Server: Cloudia
Last-Modified: Mon, 24 Nov 2008 11:03:02 GMT
Cache-Control: max-age=11????(瀏覽器會(huì)cache這個(gè)頁(yè)面內(nèi)容,然后將cache過(guò)期時(shí)間設(shè)置為當(dāng)前時(shí)間+11秒)
Content-Length: 312
Connection: Keep-Alive
---------------------------------------------------------- 按F5刷新
GET /today.js?maxage=11 HTTP/1.1
Host: sports.sinajs.cn
Connection: keep-alive
If-Modified-Since: Mon, 24 Nov 2008 11:03:02 GMT???(按F5刷新,If-Modified-Since將上次服務(wù)器傳過(guò)來(lái)的Last-Modified時(shí)間帶過(guò)來(lái))
Cache-Control: max-age=0
HTTP/1.x 304 Not Modified???
Server: Cloudia
Connection: Keep-Alive
Cache-Control: max-age=11???(這個(gè)max-age有些多余,瀏覽器發(fā)現(xiàn)Not Modified,將使用本地cache數(shù)據(jù),但不會(huì)重新設(shè)置本地過(guò)期時(shí)間)
----------------------------------------------------------
繼續(xù)按F5刷新n次.......
這11秒內(nèi)未產(chǎn)生http請(qǐng)求.直到11秒過(guò)去了...............
----------------------------------------------------------按F5刷新
GET /today.js?maxage=11 HTTP/1.1
Host: sports.sinajs.cn
Connection: keep-alive
If-Modified-Since: Mon, 24 Nov 2008 11:03:02 GMT?(多次按F5都會(huì)產(chǎn)生一個(gè)帶If-Modified-Since的請(qǐng)求)
Cache-Control: max-age=0
HTTP/1.x 304 Not Modified
Server: Cloudia
Connection: Keep-Alive
Cache-Control: max-age=11
----------------------------------------------------------按F5刷新
GET /today.js?maxage=11 HTTP/1.1
Host: sports.sinajs.cn
Connection: keep-alive
If-Modified-Since: Mon, 24 Nov 2008 11:03:02 GMT??(同上 ...)
Cache-Control: max-age=0
HTTP/1.x 304 Not Modified
Server: Cloudia
Connection: Keep-Alive
Cache-Control: max-age=11
----------------------------------------------------------
四,我對(duì)HTTP協(xié)議做的一點(diǎn)創(chuàng)新(?maxage=6000000)
上面看到了url后面有???maxage=xx??這樣的用法,這不是一個(gè)普通的參數(shù),作用也不僅僅是看起來(lái)那么簡(jiǎn)單。他至少有以下幾個(gè)好處:
1,可以控制HTTP header的的 max-age 值。
2, 讓用戶為每個(gè)資源靈活定制精確的cache時(shí)間長(zhǎng)度。
3, 可以代表資源版本號(hào)。
首先談?wù)搶?duì)后端的影響:
服務(wù)器實(shí)現(xiàn)那塊,不用再load類似mod_expires,mod_headers 這樣額外的module,也不用去加載那些規(guī)則去比較,它屬于什么目錄,或者什么文件類型,應(yīng)該cache多少時(shí)間,這樣的操作是需要開銷的。
再說(shuō)說(shuō)對(duì)前端的影響:
比如同一個(gè)分時(shí)行情圖片,我們的分時(shí)頁(yè)中需要1分鐘更新,而某些首頁(yè)中3分鐘更新好。不用js控制的話,那我cache應(yīng)該設(shè)置多少呢????有了maxage就能滿足這種個(gè)性化定制需求。
另一種情況是,我們?yōu)榱薱ache,把某個(gè)圖片設(shè)置了一個(gè)永久cache,但是由于需求,我必須更新這個(gè)圖片,那怎么讓用戶訪問到這個(gè)更新了的圖片呢? 從yahoo的資料和目前所有能找到的資料中都描述了同一種方法,更改文件名字,然后引用新的資源。 我覺得這方法太土, 改名后,老的還不能刪除,可能還有地方在用,同一資源可能要存兩份,再修改,又得改個(gè)名,存3份,不要不把inode當(dāng)資源。 我就不那樣做,只需要把maxage=6000000 修改成 maxage=6000001 ,問題就解決了。
maxage=6000000 所產(chǎn)生的威力 (內(nèi)存塊消耗減少了250倍??,請(qǐng)求數(shù)減少了37倍) :
體育那邊要上一個(gè)新功能,一開始動(dòng)態(tài)獲取那些數(shù)據(jù),我覺得那樣太浪費(fèi)動(dòng)態(tài)池資源,就讓他們把xml文件到轉(zhuǎn)移到我的js池上來(lái),為了方便,他們把那個(gè)84k的flash文件也放在了一起,而且是每個(gè)用戶必須訪問的。 說(shuō)實(shí)在的,我不歡迎這種大塊頭,因?yàn)樗豢蓧嚎s,按正常來(lái)說(shuō),它應(yīng)該代表一個(gè)3M的文件。我的服務(wù)器只這樣設(shè)計(jì)的,如果一次發(fā)送不完的就暫存在內(nèi)存里,每個(gè)內(nèi)存塊10k,如果不帶參數(shù)默認(rèn)maxage=120 。 我發(fā)現(xiàn),由于這個(gè)文件,10w connections的時(shí)候,我消耗了10000個(gè)內(nèi)存塊。我自己寫的申請(qǐng)連續(xù)內(nèi)存的算法也是消耗cpu地, 一個(gè)84k的文件,發(fā)送一次后,剩余的64k就應(yīng)該能裝的下,于是我把最小內(nèi)存塊大小改為64K。 這樣消耗10w conn的時(shí)候消耗1500個(gè)左右內(nèi)存快,雖然內(nèi)存消耗總量沒怎么變小,但是它能更快的拿到64K的連續(xù)內(nèi)存資源,cpu也節(jié)約下來(lái)了。接下來(lái)我讓meijun把所應(yīng)用的flash資源后面加上maxage=6000000 (大概=79天,瀏覽器端最長(zhǎng)cache能達(dá)到著個(gè)就不錯(cuò)了), 10w connections的時(shí)候,只消耗了不到40個(gè)內(nèi)存塊,也就是說(shuō)內(nèi)存塊消耗減少了250倍??,請(qǐng)求數(shù)減少了37倍。??35w+ connections, 5.67w req/s的時(shí)候也就消耗100塊左右,比線性增加要少很多。也就是這點(diǎn)發(fā)現(xiàn)讓我有了做這個(gè)技術(shù)分享的沖動(dòng),其他都是順便講講。
五,Yslow優(yōu)化網(wǎng)站性能的14條軍規(guī)點(diǎn)評(píng)
其中黑色部分,跟后端是緊密相連的,在我們的內(nèi)容中都已經(jīng)涉及到了,而且做了更深入的討論。蘭色部分,5,6,7是相關(guān)頁(yè)面執(zhí)行速度的,構(gòu)建前端頁(yè)面的人應(yīng)該注意的。 11屬于避免使用的方法。 紅色部分我著重說(shuō)一下:
gzip 我不推薦使用,因?yàn)橛行┰缙贗E支持的不好,它的表現(xiàn)為直接用IE訪問沒問題,用js嵌進(jìn)去,就不能正常解壓。這樣的用戶占比應(yīng)該在2%左右。這個(gè)問題我跟蹤了近一個(gè)月,差點(diǎn)放棄使用壓縮。 后來(lái)發(fā)現(xiàn)我以前用deflate壓縮的文件卻能正常訪問。 改用deflate問題解決。apache 1.x使用mod_gzip ,到了 2.x 改用cmod_deflate,不知道是否跟這個(gè)原因有關(guān)。 另外對(duì)于小文件壓縮來(lái)說(shuō),deflate 可比 gzip 省不少字節(jié)。
減少 DNS 查詢: 這里也是有個(gè)取舍的,一般瀏覽器最多只為一個(gè)域名創(chuàng)建兩個(gè)連接通道。 如果我一個(gè)頁(yè)面嵌了 image.xx.com 的很多圖片,你就會(huì)發(fā)現(xiàn),圖片從上往下一張張顯示出來(lái)這個(gè)過(guò)程。這造成了瀏覽器端的排隊(duì)。 我們可以通過(guò)增加域名提高并發(fā)度,例如 image0.xx.com ,image1.xx.com ,image2.xx.com,image3.xx.com 這樣并發(fā)度就提上去了,但是會(huì)造成很多cache失效,那很簡(jiǎn)單,假如我們對(duì)文件名相加,對(duì)4取mod,就能保證,某個(gè)圖片只能通過(guò)某個(gè)域名進(jìn)行訪問。 不過(guò),我也很反對(duì)一頁(yè)面請(qǐng)求了數(shù)十個(gè)域名,很多域名下只有一到兩個(gè)資源的做法,這樣的時(shí)間開銷是不劃算的。
另外,我在這里再添一個(gè)第15條:錯(cuò)開資源請(qǐng)求時(shí)間,避免瀏覽器端排隊(duì)。
隨著ajax的廣泛使用,動(dòng)態(tài)刷新無(wú)處不在,體育直播里有個(gè)頁(yè)面調(diào)用了我一個(gè)域名下的6個(gè)文件,3個(gè)js,3個(gè)xml。 刷新頻率大致是兩個(gè)10秒的,兩個(gè)30秒的,兩個(gè)一次性載入的。觀察發(fā)現(xiàn)正常響應(yīng)時(shí)間都在7ms,但是每過(guò)一會(huì)就會(huì)出現(xiàn)一次在100ms以上的,我就很奇怪,服務(wù)器負(fù)載很輕呢。meijun幫我把刷新時(shí)間錯(cuò)開,11秒的,9秒的,31秒的,這樣響應(yīng)在100ms以上的概率減少了好幾倍,這就是所謂的細(xì)節(jié)決定成敗吧。
1. 盡可能的減少 HTTP 的請(qǐng)求數(shù) ????[content]
2. 使用 CDN(Content Delivery Network) ????[server]
3. 添加 Expires 頭(或者 Cache-control ) ????[server]
4. Gzip 組件 ????[server]
5. 將 CSS 樣式放在頁(yè)面的上方 ????[css]
6. 將腳本移動(dòng)到底部(包括內(nèi)聯(lián)的) ????[javascript]
7. 避免使用 CSS 中的 expression_r_r_r_rs ????[css]
8. 將 JavaScript 和 CSS 獨(dú)立成外部文件 ????[javascript] [css]
9. 減少 DNS 查詢 ????[content]
10. 壓縮 JavaScript 和 CSS (包括內(nèi)聯(lián)的) ????[javascript] [css]
11. 避免重定向 ????[server]
12. 移除重復(fù)的腳本 ????[javascript]
13. 配置實(shí)體標(biāo)簽(ETags) ????[css]
14. 使 AJAX 緩存 ???
六,上線了 !=??Finished
奧運(yùn)期間我按1500w~2000w connections在線,設(shè)計(jì)了一套備用系統(tǒng),現(xiàn)在看來(lái),如果用戶真達(dá)到了這個(gè)數(shù)目我會(huì)很危險(xiǎn),因?yàn)橛胁糠址?wù)器引入了32bit的centos 5未經(jīng)實(shí)際線上檢驗(yàn),而我當(dāng)時(shí)簡(jiǎn)單的認(rèn)為它應(yīng)該和centos 4表現(xiàn)出一樣的特性。所以現(xiàn)在未經(jīng)過(guò)完全測(cè)試的lib庫(kù)和新版本,我都很謹(jǐn)慎的使用。沒在真實(shí)環(huán)境中檢驗(yàn)過(guò),不能輕易下結(jié)論。
很多項(xiàng)目組好象不停的忙,做新項(xiàng)目,上線后又繼續(xù)下個(gè)新項(xiàng)目,然后時(shí)不時(shí)的轉(zhuǎn)過(guò)頭去修理以前的bug。如果一個(gè)項(xiàng)目上線后,用戶量持續(xù)上升,就應(yīng)該考慮優(yōu)化了,一個(gè)人訪問,和100w人訪問,微小的修改對(duì)后端影響是不能比較的,不該請(qǐng)求的資源就讓它c(diǎn)ache在用戶的硬盤上,用戶訪問塊了,你也省資源。上線僅僅代表可以交差了而已,對(duì)于技術(shù)人員來(lái)說(shuō)持續(xù)的對(duì)一個(gè)重要項(xiàng)目進(jìn)行跟蹤和優(yōu)化是必要的。
七,提速度同時(shí)節(jié)約成本方法匯總
1,編寫節(jié)約的HTTP服務(wù)器 (高負(fù)載下速度明顯提升,節(jié)約5~10倍服務(wù)器)
對(duì)一些重要的服務(wù)器量身定做。或者選用比較高效的開源軟件進(jìn)行優(yōu)化。
2,不同服務(wù)混合使用??(節(jié)約1~2倍服務(wù)器)
如果我們一臺(tái)服務(wù)器只支持30w conn的話,那么剩余的75% cpu資源,95%的內(nèi)存資源,和幾乎所有的磁盤資源都可以部署動(dòng)態(tài)池系統(tǒng),我覺得DB對(duì)網(wǎng)卡中斷的消耗還是有限的,我也不用新買網(wǎng)卡了。
3,對(duì)于純數(shù)據(jù)部分啟用新的域名(速度有提升,上行帶寬節(jié)約1倍以上)
比如我們另外購(gòu)買了sinajs.cn 來(lái)做數(shù)據(jù)服務(wù),以避免cookie,節(jié)約帶寬. Cookie不但會(huì)浪費(fèi)服務(wù)器端處理能力,而且它要上行數(shù)據(jù),而通常情況上行比下行慢。
4, 使用長(zhǎng)連接 (速度明顯提升,節(jié)約帶寬2倍以上,減少網(wǎng)絡(luò)擁塞3~無(wú)數(shù)倍)
對(duì)于一次性請(qǐng)求多個(gè)資源,或在比較短的間隔內(nèi)會(huì)有后續(xù)請(qǐng)求的應(yīng)用,使用長(zhǎng)連接能明顯提升用戶體驗(yàn),減少網(wǎng)絡(luò)擁塞,減少后端服務(wù)器建立新連接的開銷。
5,數(shù)據(jù)和呈現(xiàn)分離,靜態(tài)數(shù)據(jù)和動(dòng)態(tài)數(shù)據(jù)分離?(速度明顯提升,同時(shí)節(jié)約3倍帶寬)
div+css 數(shù)據(jù)和呈現(xiàn)分離以后,據(jù)說(shuō)文件大小能降到以前的1/3。
把頁(yè)面中引用的js文件分離出來(lái),把動(dòng)態(tài)部分和靜態(tài)部分也分離開來(lái)。
6,使用deflate壓縮算法?(速度明顯提升,節(jié)約3.33倍帶寬)
一般來(lái)說(shuō)壓縮過(guò)的文件大小不到以前的30% 。
將上面分離出來(lái)的數(shù)據(jù)進(jìn)行壓縮(累計(jì)節(jié)約帶寬10倍)。
7, 讓用戶盡可能多的Cache你的資源 (速度明顯提升,節(jié)約3~50倍服務(wù)器資源和帶寬資源)
將上面分離出來(lái)的css和不經(jīng)常變動(dòng)的js數(shù)據(jù)部分cache住合適的時(shí)間。(理想情況,累計(jì)節(jié)約帶寬30~500倍) 。
以上改進(jìn)可以讓速度大幅度提升的同時(shí),服務(wù)器資源節(jié)約 5~20 倍 ,減少網(wǎng)絡(luò)擁塞3~無(wú)數(shù)倍, 上行帶寬節(jié)約1倍以上,下行帶寬節(jié)約30~500倍,甚至更多。
?
實(shí)例分析篇
--------------------------------------------------------------------------------------------------
一,自選股分析
二,NBA比賽分析
三,播客分析
四,開心網(wǎng)分析
-----------------------------------------------------------------------------------------
下面的圖片都是在教育網(wǎng)訪問的情況,我故意放大了某些缺陷,這樣可以很好的模擬沒有部署服務(wù)的地區(qū)對(duì)用戶體驗(yàn)的影響。我只能針對(duì)我熟悉和了解的項(xiàng)目進(jìn)行分析,另外還有我們經(jīng)常訪問的網(wǎng)站也會(huì)被拿來(lái)做素材分析。我們老大也說(shuō)問題暴露出來(lái),能推動(dòng)解決的話也很好,大家別拍我。
一,自選股分析
某天我終于在教育網(wǎng)部署了一臺(tái)行情服務(wù),興致沖沖的回家一試,貌似沒啥變化,該慢還慢。打開頁(yè)面過(guò)程持續(xù)了幾十秒,然后終于露出了行情,我再電擊每個(gè)組合的時(shí)候就出現(xiàn)了上面的一幕。看了下firebug,最慢資源排名前三依次為:高效計(jì)數(shù)服務(wù),secure-cn統(tǒng)計(jì)服務(wù),動(dòng)態(tài)池服務(wù)。
高效計(jì)數(shù)服務(wù)是早期我參與的項(xiàng)目,那時(shí)候資源有限,全部部署在了網(wǎng)通。
secure-cn統(tǒng)計(jì)服務(wù): 這個(gè)服務(wù)不慢是不正常的,到處都嵌,還不能不嵌。
動(dòng)態(tài)池?cái)?shù)據(jù)庫(kù)很牛,但在偏遠(yuǎn)地區(qū)也鞭長(zhǎng)莫及。這個(gè)缺點(diǎn)比較典型:
一,沒有在教育網(wǎng)部署。
二,沒有保持長(zhǎng)連接。
三,沒有使用cahce
四,沒有使用壓縮
五,長(zhǎng)達(dá)2.46K的http 請(qǐng)求header,捎帶大量cookie,見下面。
解決方法:我分析了下,下面這個(gè)數(shù)據(jù)變化很慢的,主要放一些市盈率和用戶股票列表。市盈率可以通過(guò)去年的每股收益來(lái)計(jì)算,以年計(jì),可以變通一下。用戶股票列表我也好幾個(gè)月沒更新了,大家并不是總更新。所以這部分?jǐn)?shù)據(jù)是可以被設(shè)置一個(gè)很長(zhǎng)的cache的,如果用戶更新了股票列表,我們也只需要在maxage版本號(hào)上加1就ok了。另外,用戶點(diǎn)了一個(gè)組合,接看來(lái)也都要看幾個(gè)別的組合,沒有維持長(zhǎng)連接顯然不合理的。在沒有部點(diǎn)的idc,壓縮就能明顯的提升響應(yīng)速度,這里就沒考慮。那個(gè)cookie太長(zhǎng)點(diǎn)了吧,真的用的了那么長(zhǎng)嗎。
http://vip.stock.finance.sina.com.cn/portfolio/stock.php?rn=1228707043897&pid=1245111&type=complete
----------------------------------------------------------------------------------------
GET /portfolio/stock.php?rn=1228707043897&pid=1245111&type=complete HTTP/1.1
Host: vip.stock.finance.sina.com.cn
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.4) Gecko/2008102920 Firefox/3.0.4
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-cn,zh;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: gb2312,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Cookie: CurrentBar=attend; CurrentTab=state; CombinationSelected=154148; CommisionCookie=0; StampCookie=0; FeeCookie=0; BX=7t1oh653u6qvb&b=3&s=4k; SINA_NEWS_CUSTOMIZE_city=%u5317%u4EAC; userId=C7DHwoAi-ryCr69CGgyc3czekbyphdy5hcxQNhFcN6zCNe; FINA_VISITED_S=sh601988|-y?L,sh580989|W*JTP1,sh601988|-y?L,sh601988|-y?L,sh580989|W*JTP1,sh601988|-y?L,sh601988|-y?L,sh601988|-y?L,sh580989|W*JTP1; Iask2_visitID=10.217.21.44.177601199668733612; UNIPROCT=342-0-0:2; hold_sinabar_name=iyangjian2005997; UNIPROPATH=2:iyangjian2005997:0::1:|*|202.112.174.100.97191204115419966|pid:342-0-0-0-0|classad.sr/|st:25.906|et:1204118703312||hp:unkown|lb:1|*|; SINAPUID=10.217.21.64.250871201592749264; vjuids=-5600fbe60.117402dbc5e.0.42a2debdf9f46; VISITED_FANCHAN_SINA_ZHANGYQ=SINA_BEIJING; S_WC_USRTOK=SFyLe9; stat=0806201608589720436533; MY_STOCK_LIST_2=sh600602; visited_futures=SI%7CCL%7CGC%7CCAD%7CTRB%7Cau0812%7CCC%7CPBD%7CCF907%7CNID; SINA_FINANCE=iyangjian2005997%3A1181509184%3A2; visited_funds=000011%7Csh000011%7C159902%7C160314%7C377016%7C270005%7C202009; SINA_FINANCE_SELECT_TYPE=stock; vjuid=-12b4fad5c.1174d78e8a5.0.6099c257a27eb; vjlast=1199616063; vjlast=1199616063,1228706963,10; sina_sort_default=117; SHOW_TIP_BOX=1; FINA_V_S_2=sz000609,sz000723,sh000001,sz002242,sz002274,sz000049,sz002272,sh600432,sh601186,sh601390,sh600036,sz000625; hk_visited_stocks=HSI%7C04338%7CHSCEI%7CHSCCI; visited_cfunds=050007%7Csz161010; __utma=269849203.390390911.1226996335.1226996335.1226996335.1; __utmz=269849203.1226996335.1.1.utmccn=(direct)|utmcsr=(direct)|utmcmd=(none); SINAGLOBAL=202.112.174.100.224381203683121713; Apache=202.112.174.100.771641228691763829; SessionID=e9bc0f217040ae10439d85f422f3187a; SINA_PORTFOLIO=sz000514%2Csh600729%2Csh600438%2Csh600528%2Csh600678%2Csh600877%2Csh600039%2Csh601005%2Csh600875%2Csz001696%2Csz000628%2Csh600116
Cache-Control: max-age=0
HTTP/1.x 200 OK
Date: Mon, 08 Dec 2008 03:32:52 GMT
Server: Apache
Cache-Control: no-cache
Expires: Mon, 08 Dec 2008 03:34:52 GMT
Connection: close
Transfer-Encoding: chunked
Content-Type: text/html; charset=GBK
----------------------------------------------------------------------------------------
如果不是這幾個(gè)資源的引用,這個(gè)頁(yè)面的速度將非常快。
?
這里引用了某些未在教育網(wǎng)部署的服務(wù),導(dǎo)致半天出不了數(shù)據(jù)。
?
由于引入了mark.sina.com.cn的數(shù)據(jù)導(dǎo)致整個(gè)頁(yè)面卡在那里。引用別人數(shù)據(jù)的時(shí)候你了解過(guò)他們是怎么分布自己服務(wù)的嗎?可能稍有不慎拖垮整個(gè)頁(yè)面。
二,NBA比賽分析
?
這里的js真的有必要每次都發(fā)起請(qǐng)求嗎?連續(xù)請(qǐng)求3同域個(gè)資源,為什么不維持下長(zhǎng)連接?
?
這些圖片的304響應(yīng)為什么都在秒級(jí)以上?
三,播客分析
這些圖片和視頻由于解析錯(cuò)誤,教育網(wǎng)用戶被解析到廣州服務(wù)器組,導(dǎo)致不可訪問。
四,開心網(wǎng)分析
打開開心網(wǎng),看到最多的就是人物圖片,我就僅僅針對(duì)圖片進(jìn)行下分析:
1,瀏覽一個(gè)新人的頁(yè)面,大概要下載30~40張小圖片。使用單一的pic.kaixin001.com域名,不能提高并發(fā),可以考慮多域名取模。
2,圖片請(qǐng)求帶了cookie,上行帶寬浪費(fèi)點(diǎn)無(wú)所謂,但是會(huì)影響響應(yīng)速度和用戶體驗(yàn)。
3, /logo/10/51/50_105146_1.jpg ,他們?cè)O(shè)置了一個(gè)比較大的maxage,通過(guò)改名來(lái)實(shí)現(xiàn)更新大可不必,我用我的方法更好。
4,每次點(diǎn)刷新頁(yè)面,都會(huì)重新加載很多圖片,雖然很多是304,我覺得絕大部分就不應(yīng)該發(fā)這個(gè)請(qǐng)求。
5,他用的是ChinaCache的CDN,Server: nginx,我不知道ChinaCache對(duì)這個(gè)server修改到什么程度。統(tǒng)計(jì)發(fā)現(xiàn)這個(gè)人物小圖片大都在2k左右。很多才1k多。沒有必要把他們當(dāng)作圖片處理。盡量不產(chǎn)生磁盤i/o,包括fstat這樣的系統(tǒng)調(diào)用,甚至sendfile這樣的zero copy系統(tǒng)調(diào)用,我覺得都浪費(fèi). 同時(shí)還要保證圖片更新立刻被感應(yīng)到。??
其他方面還有很多可以改進(jìn)的,想讓他們的頁(yè)面響應(yīng)速度上一個(gè)等級(jí),節(jié)約更多帶寬和服務(wù)器資源并非難事。
動(dòng)態(tài)應(yīng)用篇
---------------------------------------------------------------------------------------
一, 引子
二,總體結(jié)構(gòu)圖
三,系統(tǒng)結(jié)構(gòu)綜述
四,環(huán)境配置以及底層基礎(chǔ)類庫(kù)
五, Memcache & Mysql 常用場(chǎng)景案例
六,更多待續(xù) ......
-----------------------------------------------------------------------------------------
一, 引子
張三豐當(dāng)初傳授傳授張無(wú)忌太極劍法的時(shí)候,剛傳授完,就問是否已經(jīng)忘記。直到張無(wú)忌說(shuō)招式已經(jīng)忘光了,才算學(xué)會(huì)。當(dāng)時(shí)很不理解,現(xiàn)在終于明白了,招隨心出,不應(yīng)受到固定招式的限制。總有人問我什么樣的架構(gòu)是不是就好,或者某個(gè)很有名的網(wǎng)站用什么架構(gòu),我們就要用嗎?甚至覺得直接拿個(gè)別人的配置文件或參數(shù),性能就會(huì)突飛猛進(jìn)。那么請(qǐng)問你知道別人當(dāng)時(shí)為什么那么做嗎?做架構(gòu)亦如學(xué)太極劍法,也請(qǐng)你學(xué)習(xí)別人的架構(gòu)的時(shí)候,理解了就忘記掉,因?yàn)槿魏渭軜?gòu)本身都有環(huán)境局限性,然后用心思考,分析,真正理解你所處的環(huán)境,從實(shí)際需求出發(fā),你才能做出最適合你應(yīng)用的架構(gòu)。
很多人對(duì)寫server很感興趣,甚至一開始就直接考慮從底層協(xié)議進(jìn)行優(yōu)化,我說(shuō)遠(yuǎn)還沒到那個(gè)程度,請(qǐng)找出你真正的瓶頸。優(yōu)化一定是從宏觀到微觀的,有時(shí)候一個(gè)系統(tǒng)結(jié)構(gòu),業(yè)務(wù)邏輯,或策略的優(yōu)化,帶來(lái)的效果甚至更為可觀。對(duì)于每天請(qǐng)求處理量小于100億的系統(tǒng),我都不建議自己去寫server。如果說(shuō)寫高性能server的技能是我手中的那把太極劍的話,那么發(fā)揮出太極劍法的威力并不依賴于那把劍,而是取決于對(duì)心法的理解。那把劍既然可以是專用server,當(dāng)然也可以是開源的那幾款經(jīng)典server,取決于你對(duì)設(shè)計(jì)容量,以及硬件成本,維護(hù)成本,時(shí)間等多方面的預(yù)期。架構(gòu)本身就是一個(gè)取舍的過(guò)程。
現(xiàn)在有這樣的一個(gè)項(xiàng)目,就拿我上一篇文章里提到的自選股來(lái)舉例吧,用戶可以在各個(gè)市場(chǎng)里創(chuàng)建自己的多個(gè)投資組合,然后在組合里定制自己關(guān)注的股票。描述一下環(huán)境以及需求。
環(huán)境:注冊(cè)用戶小幾千萬(wàn),同時(shí)在線預(yù)計(jì)峰值不到10萬(wàn)。
需求:
1,3G,IM,Mail,Web 任何一個(gè)地方以及不同的主流瀏覽器更新數(shù)據(jù),其他地方立即可見。
2,北京,天津,上海,深圳,任何一個(gè)IDC數(shù)據(jù)更新,其他三個(gè)IDC立即可見。
3,各IDC附近用戶,獲取股票列表的平均響應(yīng)速度要控制在20ms以內(nèi)。
4,IDC間專線中斷服務(wù)不能受影響。
5,IDC內(nèi)部的相同功能的服務(wù)器,允許宕掉一半,服務(wù)完全不受影響。
6,IDC允許宕掉一到兩個(gè),受災(zāi)IDC 95%的用戶服務(wù)影響不超過(guò)5分鐘。
7,另外,需要開發(fā)人員能夠在這個(gè)系統(tǒng)上快速開發(fā),要具備易用性,良好擴(kuò)展性以及移植性。
基于以上的需求構(gòu)建的實(shí)現(xiàn),動(dòng)態(tài)應(yīng)用篇側(cè)重點(diǎn)主要是如何快速響應(yīng),同步更新,如何容災(zāi),消除安全隱患,讓系統(tǒng)更穩(wěn)定,如何容易遷移和擴(kuò)展應(yīng)用,如何讓程序員容易使用這個(gè)平臺(tái)。這個(gè)系統(tǒng)性能不是主要關(guān)注的問題(10萬(wàn)同時(shí)在線,確實(shí)比較微量,何況僅有的不多的壓力,也通過(guò)后面介紹的各種緩存機(jī)制轉(zhuǎn)移的差不多了),架構(gòu)上采用當(dāng)前主流經(jīng)典架構(gòu)(Nginx+PHP Fast-CGI+APC+Mysql+Memcache+LVS+Linux),各層次之間低偶合,每一層都具備單獨(dú)優(yōu)化的空間,?隨著用戶量的增加,我再逐步推出動(dòng)態(tài)應(yīng)用處理并發(fā)和壓力方面的文章。
二,總體結(jié)構(gòu)圖
(實(shí)際結(jié)構(gòu)中去掉了中繼slave,改成所有slave直連master,這樣結(jié)構(gòu)更簡(jiǎn)單,易于管理和故障恢復(fù)。)
三,系統(tǒng)結(jié)構(gòu)綜述
此系統(tǒng)主要分為幾個(gè)低偶合的層次組裝而成,具備多IDC分布的特性。從底往上依次為DB層,MC池子層,Nginx+PHP Fast-CGI層,LVS層,然后通過(guò)DNS接入用戶。每一層都具備良好的擴(kuò)展性以及災(zāi)備能力。
?
DNS:
從大的結(jié)構(gòu)上來(lái)說(shuō),此系統(tǒng)分布在4個(gè)主要IDC,網(wǎng)通電信各2。?機(jī)器數(shù)量按照網(wǎng)通:電信?1:2的比例配置,同一運(yùn)營(yíng)商下的兩IDC機(jī)器數(shù)量等同。這樣在宕掉一個(gè)IDC的情況下,可以通過(guò)切換DNS,臨時(shí)訪問相近的IDC,達(dá)到IDC間災(zāi)備。
?
LVS層:
每個(gè)IDC的接入層通過(guò)LVS作四層負(fù)載均衡。IDC內(nèi)部任何接入機(jī)器宕掉,可以通過(guò)failover機(jī)制在一分鐘內(nèi)自動(dòng)摘除。
?Nginx+PHP Fast-CGI層:
通過(guò)fpm管理PHP Fast-CGI進(jìn)程,Nginx通unix域協(xié)議與fpm通信。轉(zhuǎn)發(fā)?*.php的請(qǐng)求以及響應(yīng)。使用APC作為OP代碼加速器,加速php響應(yīng)。PHP直接與MC池子或Mysql層進(jìn)行交互。
?
MC池子層:
每個(gè)IDC內(nèi)部的多個(gè)MC機(jī)是作為一個(gè)整體來(lái)管理的,也就是說(shuō),假如你有100個(gè)數(shù)據(jù),4個(gè)MC機(jī),那么每個(gè)MC上只會(huì)存25個(gè)數(shù)據(jù),而不會(huì)每個(gè)都存100個(gè)。好處是,獲得了相當(dāng)于以前4倍的內(nèi)存池,?增大了緩存命中率。更額外的好處是,減少了,本IDC內(nèi)部各MC間數(shù)據(jù)同步的時(shí)間開銷,使得本IDC任何一服務(wù)器更新的數(shù)據(jù),同IDC內(nèi)其他服務(wù)器立即可見。也使得IDC之間MC的同步更為節(jié)約,每個(gè)IDC一個(gè)池子,每個(gè)池子只需要寫一份數(shù)據(jù)。
?MC池子增減機(jī)器的問題:如果是傳統(tǒng)的hash算法根據(jù)key值,將數(shù)據(jù)平均的hash到4臺(tái)機(jī)器上,那么按照新的hash規(guī)則,增加一臺(tái)機(jī)器后,將會(huì)有近80%的緩存失效,造成大量數(shù)據(jù)遷移。所以我們改用Consistent Hashing,首先求出memcached服務(wù)器(節(jié)點(diǎn))的哈希值,并將其配置到0~2的32次方的圓上。然后用同樣的方法求出存儲(chǔ)數(shù)據(jù)的鍵的哈希值,并映射到圓上。然后從數(shù)據(jù)映射到的位置開始順時(shí)針查找,將數(shù)據(jù)保存到找到的第一個(gè)服務(wù)器上。如果超過(guò)2的32次方仍然找不到服務(wù)器,就會(huì)保存到第一臺(tái)memcached服務(wù)器上。它能最大限度地抑制鍵的重新分布。而且,有的實(shí)現(xiàn)還采用了虛擬節(jié)點(diǎn)的思想,使得分布更加均勻。由服務(wù)器臺(tái)數(shù)(m)和增加的服務(wù)器臺(tái)數(shù)(n)計(jì)算增加服務(wù)器后的命中率計(jì)算公式如下:(1 - n/(n+m) ) * 100??,按這個(gè)計(jì)算,增加一臺(tái)機(jī)器后還將得到80%的命中率。更較幸運(yùn)的是這個(gè)算法已經(jīng)被php所支持,可以通過(guò)php.ini設(shè)置。
?宕機(jī)重啟后初始化的問題:如果初始化的數(shù)據(jù)只從一臺(tái)DB上獲得,那么在高峰期間必將壓垮DB,所以,我們需要將這個(gè)壓力,分散到本IDC的多臺(tái)DB上。細(xì)節(jié)會(huì)在基礎(chǔ)類庫(kù)中進(jìn)行講述。
?異地MC池子的數(shù)據(jù)同步:已經(jīng)封裝在基礎(chǔ)類庫(kù)中,更新MC時(shí)指定特定的參數(shù),就會(huì)在其他異地MC池子上也進(jìn)行更新。所以我們?nèi)绻诒本┨砹艘粋€(gè)數(shù)據(jù),那么在深圳也會(huì)立刻看到。
DB層:
主從結(jié)構(gòu)集群,主庫(kù)可切換,從庫(kù)互為備份。從DB宕掉一臺(tái),可以自動(dòng)跳過(guò),不影響服務(wù),目前通過(guò)基礎(chǔ)類庫(kù)實(shí)現(xiàn),以后會(huì)采用內(nèi)部DNS。所有從庫(kù)直接和主庫(kù)相連,結(jié)構(gòu)簡(jiǎn)單,方便管理。不過(guò)寫的數(shù)據(jù)量相對(duì)比較小的,而且我們?cè)试SDB延遲(由MC來(lái)保證實(shí)時(shí)性,或者異地本來(lái)就可以接受短暫的延遲),這里將不是問題。
?MC和DB寫隊(duì)列:
在專線中斷時(shí)不影響寫操作(讀都是本地的,當(dāng)然也不受影響),而且在更新數(shù)據(jù)時(shí)能夠快速返回(因?yàn)椴恍枰h(yuǎn)程通信)。
具體來(lái)說(shuō)就是將MC的異地寫,以及DB的寫操作封裝成直接追加寫本地隊(duì)列文件。每個(gè)機(jī)器上有個(gè)守護(hù)進(jìn)程,每0.1秒檢查一次,有則rename,然后開始將隊(duì)列數(shù)據(jù)逐條往深圳數(shù)據(jù)中心post,同時(shí)每成功一條記錄當(dāng)前offset,以便意外宕掉后接著上次的后面處理。(優(yōu)化點(diǎn)的可以改為批量確認(rèn),冒進(jìn)點(diǎn)的,可以使用內(nèi)存盤優(yōu)化寫速度)。如果線路不通,或者數(shù)據(jù)中心寫失敗,post程序?qū)leep and retry,線路恢復(fù)則繼續(xù),不會(huì)丟數(shù)據(jù)。這里有個(gè)細(xì)節(jié)需要注意,就是往數(shù)據(jù)中心同步的時(shí)候,盡量發(fā)往同一臺(tái)機(jī)器,也即綁定,以保證序列的順序,這個(gè)保證可以通過(guò)對(duì)post守護(hù)進(jìn)程的pid取mod來(lái)實(shí)現(xiàn),如果綁定的機(jī)器出了故障,則應(yīng)該選定另外一臺(tái)機(jī)器綁定,一旦檢測(cè)到以前的機(jī)器好了,則應(yīng)該切回來(lái),以保證應(yīng)有的負(fù)載均衡。另外,最外層的接入端也同樣需要注意這個(gè)問題,我們對(duì)lvs做了會(huì)話保持,以確保同一用戶在相當(dāng)長(zhǎng)的一段時(shí)間內(nèi),會(huì)訪問到同一臺(tái)機(jī)器。對(duì)于僅僅使用DNS輪詢,又沒有使用長(zhǎng)連接的服務(wù),如果在短時(shí)間內(nèi)做數(shù)據(jù)更新,肯定會(huì)出現(xiàn)亂序的問題。除非你各服務(wù)器時(shí)間都很精準(zhǔn),而且,每個(gè)記錄寫的時(shí)候打上精確到ms的時(shí)間戳。
?然后,深圳數(shù)據(jù)中心接收到post的數(shù)據(jù)后,根據(jù)消息類型按一定規(guī)則的命名分別存放,這樣即使增加一種新的消息,也可以方便的使用隊(duì)列。比如收到MC的消息后,要寫三份,分別發(fā)往三個(gè)IDC,各自不受影響。DB,可以根據(jù)不同的port實(shí)例子來(lái)創(chuàng)建隊(duì)列,免得有一個(gè)port宕了,影響其他。具體實(shí)現(xiàn)細(xì)節(jié)可以自己調(diào)整,同樣得有個(gè)程序定期(比如每0.1秒,0.01秒也無(wú)所謂,對(duì)cpu的消耗非常小)檢查隊(duì)列文件存在就rename,然后往master里寫。
?以上則能保證,DB的master宕掉,或者專線的中斷情況下,用戶服務(wù)基本不受影響,只是異地的同步會(huì)延遲一些。額外收獲是將來(lái)做DB升級(jí)調(diào)整的時(shí)候,可以用隊(duì)列分流,分別做不同的處理。
?祛除安全隱患:
不管你的系統(tǒng)設(shè)計(jì)的多么完美,疏忽這一條足以致命。
以上設(shè)計(jì)看起來(lái)似乎已經(jīng)沒什么問題了,各種容災(zāi),異常也基本考慮到了,不幸的是,這個(gè)平臺(tái)并不是僅僅給設(shè)計(jì)者自己使用,如果有個(gè)新手使用了 file_get_contents($url);??如果url所依賴的服務(wù)器負(fù)載過(guò)重,那么整個(gè)系統(tǒng)都有被拖垮的危險(xiǎn)。動(dòng)態(tài)應(yīng)用的一個(gè)鐵律就是,凡是依賴本系統(tǒng)外部資源的地方必須加超時(shí)限制,盡可能的減少依賴。file_get_contents的超時(shí)不是很精確,推薦使用curl的庫(kù)進(jìn)行封裝,可以設(shè)置connect超時(shí),也可以設(shè)置整個(gè)函數(shù)的執(zhí)行時(shí)間超時(shí)。我一般會(huì)設(shè)置my_curl();函數(shù)的最大默認(rèn)執(zhí)行時(shí)間為一秒,因?yàn)槭菑膬?nèi)網(wǎng)拉取數(shù)據(jù),一秒還拉不到,肯定有問題。另外,別忘記了mysql讀服務(wù),當(dāng)某一idc的一臺(tái)slave連接數(shù)滿了以后,如果沒設(shè)置過(guò)mysql.connect_timeout=1;那這個(gè)IDC的服務(wù)會(huì)整個(gè)被拖垮,因?yàn)槟J(rèn)的是60秒。(mysql的寫由隊(duì)列保證,不存在此問題)。
另外,我們項(xiàng)目還有個(gè)特殊性,即,每次拉取用戶股票信息時(shí),需要從后臺(tái)專線到深圳進(jìn)行身份合法性驗(yàn)證,由于歷史的原因,這個(gè)驗(yàn)證系統(tǒng)暫不具備多IDC分別的能力。假設(shè)北京的專線斷了,雖然用戶的數(shù)據(jù)是沒問題,但是驗(yàn)證卻通不過(guò),會(huì)導(dǎo)致獲取數(shù)據(jù)失敗。同樣這個(gè)驗(yàn)證是要加超時(shí)的,另外為了在專線斷掉的情況下依然正常提供服務(wù),就需要在得不到驗(yàn)證的情況下,要通過(guò)代理的手段到鄰近的IDC去驗(yàn)證。同時(shí),我們也支持另外一種驗(yàn)證方式,對(duì)于已經(jīng)通過(guò)驗(yàn)證的用戶,專線中斷一小段時(shí)間,比如一小時(shí),服務(wù)是可以不受影響。
總之就是盡量低偶合,少依賴,加超時(shí)。另外一個(gè)維護(hù)的安全,則由安全中心把關(guān),我就不多說(shuō)了。
四,環(huán)境配置以及底層基礎(chǔ)類庫(kù)
?PHP網(wǎng)絡(luò)版環(huán)境:每IDC之間差異化的Nginx環(huán)境變量配置,使得相同的應(yīng)用程序在不同的IDC運(yùn)行時(shí),使用各自IDC內(nèi)部的MC以及DB等資源。
?PHP??Shell版環(huán)境:
PHP??Shell是指通過(guò)命令行方式執(zhí)行的php腳本程序。
比如/path/php/bin/php test.php
或在php程序第一行加上??
#!/path/php/bin/php?
然后賦予php腳本可執(zhí)行權(quán)限,使作為shell程序運(yùn)行。
由于php作為shell運(yùn)行時(shí),無(wú)法繼承nginx配置的環(huán)境變量。所以它必須依賴一個(gè)獨(dú)立的配置文件。
?由于圖片里含有帳戶等敏感信息,就不在此貼了。
底層基礎(chǔ)類庫(kù):
底層基礎(chǔ)類庫(kù),起到粘合劑的作用,將環(huán)境配置,服務(wù)器資源等全部結(jié)合起來(lái),使得這些資源以及配置信息對(duì)上層開發(fā)人員透明,無(wú)須考慮。總的來(lái)說(shuō)有以下一些功能。
?
1,??兩環(huán)境融合,天衣無(wú)縫。?php?網(wǎng)絡(luò)環(huán)境和shell使用同一基礎(chǔ)類庫(kù),代碼無(wú)任何一行差異。使得平時(shí)編寫的php網(wǎng)絡(luò)程序,以及類庫(kù)積累,可以方便的直接用來(lái)做shell編程,進(jìn)行復(fù)用。具體原理是,類庫(kù)需要用到配置信息時(shí),先通過(guò)if( isset ($_ENV["SERVER_SOFTWARE"]) )變量判斷自己是否網(wǎng)絡(luò)環(huán)境,如果是就直接使用配置項(xiàng)比如:_SERVER["DB_stock_host"]?,若不是,則先將配置文件數(shù)據(jù)項(xiàng),section名和下面的字段相加轉(zhuǎn)化成?_SERVER["DB_stock_host"] = “m3306_sz_gtimg_cn” ;?跟網(wǎng)絡(luò)環(huán)境一致后,再繼續(xù)后面操作。
?[DB_stock]
host = m3306_sz_gtimg_cn
類庫(kù)目前除支持模擬DNS外,還直接兼容真實(shí)域名以及IP地址,方便將來(lái)進(jìn)行數(shù)據(jù)遷移。之所以沒有直接使用 DNS是也有歷史原因的。
?
2,??對(duì)DB資源的封裝。
?在沒有內(nèi)部DNS的情況下,將DB讀寫分離,帳戶選擇,連接的建立(包括?何時(shí)候真正建立連接,建立長(zhǎng)連接,還是短連接,連接的綁定,以及生命周期),負(fù)載均衡以及failvoer等封裝成對(duì)用戶透明的如下簡(jiǎn)單用法:
?//指定以讀(r)?或?寫(w)?的方式打開一個(gè)庫(kù)。不指定的情況下,默認(rèn)是”r”方式打開。
$db_r =new MYSQL(“testdb”,“r”);?
?<?
require_once (dirname(__FILE__).'/../mysql.php');
?$db_w =new MYSQL("test","w");
?$arr = array(??"id" => "8",??"name" => "yangjian8"??);
?if( $db_w->insert("test",$arr) )
{
???????echo "query ok ...<br>";
}else
{
???????echo "query failed ...<br>";
???????echo "errno=$db_w->errno<br> errmsg=$db_w->errmsg<br>";
}
?echo "read ............<br>";
?$db_r =new MYSQL("test","r");
$sql = "select * from test";
?
if( $result = $db_r->query($sql))
{
???????while ($row = mysql_fetch_array($result, MYSQL_BOTH))
???????{
??????????????printf ("id: %s name: %s<br>", $row[id], $row[name]);
???????}
???????$db_r->free();??//free result. if you not free it,it will auto free at the end of the php script.
}else???//if not success,you can print the error info.
{
???????echo "errno=$db_r->errno<br> errmsg=$db_r->errmsg<br>";
}
3,??對(duì)MC池子資源的封裝。
簡(jiǎn)化MC池子使用方法,支持異地MC數(shù)據(jù)同步。
//if rset=1,the mc data will sync to other idcs, default 0.
function set($key, $value, $flag, $expire, $r_set=0)
<?php
require_once (dirname(__FILE__).'/../memcache.php');
$mc = new MC("test");
for($i=0;$i<2;$i++)
{
???????$mc->set("key".$i,"value".$i,0,100);
}
for($i=0;$i<2;$i++)
{
???????echo $mc->get("key".$i);
???????echo "<br>";
}
DB以及MC的異地寫已經(jīng)封裝進(jìn)去,對(duì)上層開發(fā)人員來(lái)說(shuō)都是透明的。
注意:我在引用頭文件時(shí)候,都會(huì)使用類似require_once (dirname(__FILE__).'/../mysql.php');?的方法。我暫且管它叫動(dòng)態(tài)絕對(duì)路徑。它的好處是,
跟相對(duì)路徑比:當(dāng)一個(gè)頭文件被多層引用時(shí),目錄結(jié)構(gòu)又不一致,不會(huì)找不到。
也不會(huì)去搜索所有可能的目錄,執(zhí)行多余的fstat以及open操作。
跟絕對(duì)路徑相比:如果我將整個(gè)項(xiàng)目,包括頭文件全部平移到別的目錄,不需要挨個(gè)修改文件。
當(dāng)然,你也可以采用另外一個(gè)方法,將本項(xiàng)目相關(guān)的配置信息絕對(duì)路徑放在一個(gè)統(tǒng)一的文件里,然后通過(guò)任何一種方法引用那個(gè)文件。
?
五,?Memcache & Mysql 常用場(chǎng)景案例
?經(jīng)典篇:
更新數(shù)據(jù):寫全局MC,然后再寫DB。
讀數(shù)據(jù):先讀MC,命中返回?cái)?shù)據(jù)。不命中則讀DB,更新到本地MC,然后返回?cái)?shù)據(jù)。
(這幾個(gè)邏輯圖由kinggen同學(xué)提供,看起來(lái)比文字直觀多了,感謝一下)
為什么更新數(shù)據(jù)寫全局MC,而讀數(shù)據(jù)不命中只寫本地MC?
因?yàn)楦聰?shù)據(jù)寫全局保證了只要MC中cache存在則肯定是最新的,要么就不存在。不存在的情況可以從DB中補(bǔ)充。
如果更新數(shù)據(jù)和不命中的情況下都只寫本地MC會(huì)有什么后果?
因?yàn)镸C不會(huì)主動(dòng)獲得數(shù)據(jù)更新,如果更新數(shù)據(jù)不寫全局,會(huì)造成其他IDC的cache在失效以前仍然是舊的。出現(xiàn)數(shù)據(jù)不同步的現(xiàn)象。
進(jìn)階篇:
這里是在動(dòng)態(tài)數(shù)據(jù)中引入靜態(tài)數(shù)據(jù)的last modify特性,以使得在動(dòng)態(tài)應(yīng)用中可以返回HTTP 304狀態(tài)。只比較最后更新時(shí)間便可以做出判斷,減少后續(xù)邏輯處理以及數(shù)據(jù)內(nèi)容傳輸,快速做出響應(yīng)。對(duì)于讀多,寫少的項(xiàng)目,意義巨大。
對(duì)于js調(diào)用的部分,并不等同與刷新,要想讓每次都產(chǎn)生請(qǐng)求,而且還帶If-Modified-Since過(guò)去,必須加個(gè)max-age=1。只能精確到1秒。
更新數(shù)據(jù):將數(shù)據(jù)和LM寫到全局MC,然后將數(shù)據(jù)寫到DB,不用把LM也寫入,LM只存在于MC中。
讀數(shù)據(jù):如果MC中存在LM: 比較瀏覽器請(qǐng)求帶過(guò)來(lái)的LM,?大于等于MC中LM則直接返回304。否則返回?cái)?shù)據(jù)和最新的LM。如果MC中不存在LM:把當(dāng)前的響應(yīng)時(shí)間作為L(zhǎng)M存在本地MC中,然后返回?cái)?shù)據(jù)和此LM。
?
?
返回?cái)?shù)據(jù)方法同經(jīng)典篇里的讀數(shù)據(jù)。取時(shí)間請(qǐng)用 $_SERVER['REQUEST_TIME']。
LM的cache時(shí)間可以設(shè)置的盡量長(zhǎng)些,比如一個(gè)月。
?PHP中動(dòng)態(tài)數(shù)據(jù)使用Last-Modified加速原理詳細(xì)說(shuō)明:
動(dòng)態(tài)應(yīng)用項(xiàng)目中充分利用LM來(lái)加速響應(yīng),減少邏輯處理以及數(shù)據(jù)傳輸。
最初考慮是用etag實(shí)現(xiàn),引入這一機(jī)制并不僅僅是為了節(jié)約帶寬。它還用來(lái)減化應(yīng)用程序邏輯。比如正常取一個(gè)數(shù)據(jù),需要取好幾個(gè)表的東西,大概消耗200ms。如果我把etag作為數(shù)據(jù)版本來(lái)用,只需要取memcache里的版本號(hào)判斷一下,對(duì)于大多數(shù)用戶來(lái)說(shuō),都沒更新數(shù)據(jù),就不用走后面的判斷了,直接返回304狀態(tài)。但是IE6里,如果同時(shí)使用gzip,又使用etag,etag就會(huì)失效。這是ie6的bug,沒有遵守http 1.1。
現(xiàn)在使用方法,把數(shù)據(jù)最后更改時(shí)間戳作為版本。模擬靜態(tài)數(shù)據(jù)使用Last-Modified 。這樣做的缺陷是,單位只能精確到秒,如果一秒內(nèi)做多次修改,將不能區(qū)分。不過(guò)對(duì)我們目前應(yīng)用來(lái)說(shuō),精確到一秒已經(jīng)足夠用了,用戶的動(dòng)作沒那么快。另外,還有一個(gè)細(xì)節(jié),將決定這個(gè)機(jī)制能否應(yīng)用在我們的項(xiàng)目中。我們既要使用緩存,又要其他任終端,或者瀏覽器通過(guò)js拿數(shù)據(jù)的時(shí)候立刻拿到最新的。大家知道,如果你使用了Last-Modified,通過(guò)js在當(dāng)前瀏覽器下再次取數(shù)據(jù)的時(shí)候,瀏覽器不會(huì)發(fā)起任何請(qǐng)求,新數(shù)據(jù)當(dāng)然無(wú)從拿到。如果能讓瀏覽器發(fā)送請(qǐng)求的時(shí)候帶上If-Modified-Since,又能每次都讓瀏覽器產(chǎn)生請(qǐng)求,便能解決問題。
于是,我們通過(guò)php輸出數(shù)據(jù)的時(shí)候同時(shí)使用這樣的兩個(gè)頭信息,便達(dá)到了目的。
Cache-Control: max-age=1
Last-Modified: Tue, 11 May 2010 10:58:11 GMT
這樣做的假設(shè)是:用戶點(diǎn)一個(gè)組合查看數(shù)據(jù),然后用戶在手機(jī)上添加一個(gè)股票信息,然后用戶切到了別的組合,然后又切回這個(gè)組合查看數(shù)據(jù),這4個(gè)動(dòng)作不可能在同一秒內(nèi)完成。我反正是做不到那么快,超人例外。
?
轉(zhuǎn)自:http://blog.sina.com.cn/s/articlelist_1181509184_0_1.html
擴(kuò)展知識(shí):一致哈希算法、delate壓縮算法、NAT/TUN/DR
總結(jié)
以上是生活随笔為你收集整理的实践:服务器编写/系统架构/cache的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 杨建:网站加速--实例分析篇
- 下一篇: 联想小新air pro 13的 win1