《構建高性能web站點》筆記--應用程序篇

起因

大概花了一個月不到的時間,看完了這本400頁不到的書《構建高性能web站點》,不得不說這是我第一次真正意義上完全看完一本書,儘管曾經看過許多技術類的書。其中一個原因,就是大部分的技術類書籍偏向枯燥,即使是本着某種虔誠的目的和願望去閱讀,仍然很容易中途放棄。但是這本書卻不同,它十分能吸引我的閱讀願望,幾乎在所有的環節上能夠引起我的共鳴思考,於是便快速的閱讀了一遍此書。

作者主要以典型的LAMP爲例子,我幾乎沒有接觸過這方面,但是相信思想是一致的,學思想打基礎纔是關鍵。因此,本文是以概要性的總結爲主。

 

概念

吞吐率:一個衡量web服務性能的指標,表徵每秒處理請求的次數。該指標受到3方面的因素影響:併發用戶數、總請求數、請求資源的類型。有時在請求總數一定的情況下,併發用戶越多,吞吐率反而越高;另外,請求一個幾kb的文件和請求一個幾m的文件,最終完成處理的時間顯然是不一樣的。因此,吞吐率是一個比較綜合的指標,並不是指併發能力。

 

概覽圖

下圖簡單描述了用戶瀏覽器,代理服務器,web服務器和應用程序服務器的典型關係,當然通常應用程序服務器同時包含在web服務器中,這裏爲了區分職責而分開畫了。

image

 

緩存

構建高性能web站點時,拋開基礎架構(數據庫分區的問題也包括在基礎架構中了),在應用程序、編碼層面主要要考慮的問題就是緩存的設計,合理的緩存設計可以使提供動態網頁服務的網站性能大幅度提高。當然,在架構階段設計緩存解決方案,絕非簡單的技術問題,需要從業務出發,再結合各種技術。下面按照一次HTTP請求的順序,對每個環節的緩存設計從技術角度進行討論。

 

客戶端緩存

可以利用客戶端瀏覽器的緩存機制,來減少瀏覽器對服務端的請求次數(當然在服務端進行圖片等資源合併,並結合css圖片定位技術,也可以減少HTTP請求),利用好HTTP的緩存協商,可以設計出靈活的客戶端緩存方案。在HTTP頭中下面的內容與緩存協商有關:

Last-Modified:動態頁面通過主動推送該值,暗示瀏覽器在下次請求同一個url的時候,優先使用If-Modified-Since值與服務端進行緩存協商,如果緩存沒有過期,那麼服務端可以不用重新計算動態網頁,通過返回304通知瀏覽器。網站的靜態資源往往使用這種方法。但是該方法有一個缺點:有時,文件的最後更改時間雖然改了,但是內容卻沒有變,這樣無法充分發揮瀏覽器緩存的能力。

ETag:Web服務器爲每個url生成一個散列值,增加在HTTP頭的ETag標記中,瀏覽器會優先使用If-None-Match加上這個散列值來協商緩存過期。通過對靜態文件的內容進行md5變換,可以生成散列值,這樣可以彌補Last-Modified的不足。但是隨之帶來的是服務端md5變換的計算開銷。

Expires:上述兩種方式,雖然可以使服務端多少避免了反覆的動態網頁解析和計算,但瀏覽器還是必須通過HTTP請求來進行協商,並沒有真正意義上減少請求的次數。通過在HTTP頭中添加Expires標記可以明確的告知瀏覽器過期形式,瀏覽器會徹底減少請求的次數。

 

反向代理緩存

在web服務器前端,還有反向代理服務器緩存。反向代理服務器本質上就是代理服務器,只是將外網的請求轉發給內網的web服務器處理,他們都工作在應用層,能夠理解HTTP協議。正向代理服務器具有HTTP緩存、HTTP過濾等功能,反向代理服務器同樣具有HTTP緩存的能力,而且還具備一定程度上的安全性。一切HTTP友好的動態程序同樣能夠很好的在反向代理服務器上實現緩存。重量級的squid、輕量級的varnish、甚至是Nginx這樣的web服務器軟件,都可以勝任反向代理服務。

上述的代理服務器軟件產品,通過各種配置可以緩存基於HTTP協議的web響應。

 

Web服務器緩存

Web服務器有可能支持基於url的緩存(基於key-value對),這類似反向代理緩存。緩存通常可以通過配置存儲在內存或磁盤上,在緩存有效期的問題上,通常是基於HTTP協議中的頭部信息判斷。但是使用這樣的機制需要注意:

  1. 動態程序會可能變得依賴於特定的web服務器
  2. 注意編寫面向HTTP緩存友好的動態程序,會使你的動態程序更有生命力

web 服務器還具有緩存文件描述符(類似句柄)的能力,這樣可以減少文件的open操作,同樣是一種減少系統調用的措施,這對於一些小文件有些效果,因爲文件越小花在open上的開銷將越來越佔有重要的比例。  

 

應用程序緩存

應用程序本身可以對動態內容進行緩存,這可以體現在三個層面:

  • 動態腳本緩存:每次腳本解析都需要消耗一定的時間,爲了加快這個速度,一些服務器端腳本語言都支持動態腳本的預編譯,比如我們熟悉的ASP.NET、JSP都有所謂的中間語言,解析這些中間語言會快很多。
  • 動態腳本框架支持的緩存:一些動態腳本框架支持緩存,同樣在選型的時候要關注一下這些框架的緩存機制和原理,比如緩存如何存放,過期如何設置,是否支持局部緩存等。
  • 應用程序自身實現緩存:應用程序根據特定的業務需求獨立設計緩存。

在緩存設計的具體技術上有以下幾點:

  • 緩存在內存,這種方式的優點就是減少了磁盤的讀寫,但是內存有限,單機不易擴展。
  • 緩存在分佈式緩存,這種方式的優點是減少了磁盤讀寫,並提高了可靠性和擴展性,但是由於需要網絡IO,性能稍遜於單機緩存。
  • 局部頁面緩存,對於一些頁面無法完整緩存的,可以考慮局部緩存。
  • 靜態化,將網站靜態化可以極大的提高性能,因爲用戶的請求不需要動態腳本處理。

 

服務器系統能力的制約因素

這部分內容對於所有的服務器(無論是代理服務器、web服務器還是其他),都具有普遍適用的意義。

  • 多進程、多線程的選擇和調度:進程切換和線程切換都需要一定的系統開銷,通常使用多線程模型的web服務器軟件比使用多進程,具備更優的性能。
  • 系統調用:一些需要從用戶模式切換到內核模式的函數調用可以稱爲系統調用,比如:打開文件。系統調用會有一定程度上的開銷,減少系統調用是可以加快處理速度的程序設計細節。
  • TCP鏈接保持:可以通過保持TCP鏈接來減少服務端和客戶端之間的創建和關閉TCP鏈接的操作。HTTP中的Connection:Keep-Alive就有這樣的功能
  • IO模型:由於CPU的速度遠遠比IO快,IO延遲往往成爲性能瓶頸,因此,IO模型十分重要。

各種IO模型:

PIO:CPU直接干預磁盤和內存的數據交互,即無論是數據從內存到磁盤還是磁盤到內存都要經過CPU寄存器。這樣的模型,可想而知,CPU有很多時間都需要等待慢速設備。

DMA(Direct Memory Access):CPU通過向DMA控制器發送指令來控制處理數據,數據處理完之後通知CPU,這可以很大程度上釋放CPU資源。

同步阻塞I/O:對於進程來說,一些系統調用爲了同步IO,會不同程度上阻塞進程,比如accept、send、read等。

同步非阻塞I/O:對於進程來說,一些系統調用可以在調用完之後立即返回,告知進程IO是否就緒,避免阻塞進程。

多路I/O就緒通知:對於同步非阻塞I/O的方式,進程仍然需要輪詢文件描述符(句柄)來得知哪些IO就緒了,而多路I/O就緒通知將這個過程改成回調通知。

內存映射:將文件與內存的某塊地址空間相映射,這樣可以想寫內存一樣寫文件。當然這種方式本質上跟寫文件沒有什麼區別。

直接I/O:在用戶進程地址空間和磁盤中間通常都會有操作系統管轄的內核緩衝區,當寫入文件時,一般是寫入這個緩衝區,然後由一些延遲策略來寫入磁盤。這樣做可以提高寫效率。但是對於諸如數據庫這樣的應用來說,往往希望自己管理讀寫緩存,避免內核緩衝區的無畏內存浪費。Linux的open函數支持O_DIRECT參數來進行直接IO。

sendfile:如果web服務器想發送一個文件,將會經歷如下過程:打開文件,從磁盤中讀取文件內容(這通常涉及到內核緩衝區數據複製到用戶進程),然後進程通過socket發送文件內容(這通常設計到用戶進程數據複製到網卡內核緩衝區),可以看到重複的數據複製是可以避免的。sendfile可以支持直接從文件內核緩衝區複製到網卡內核緩衝區。

 

總結

本文從應用程序層面總結了構建高性能web站點的要點,主要探討了緩存的實現技術。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章