《Web全棧工程師的自我修養》濃縮筆記(上)

四、HTTP

五、快取

HTTP 前端視角

每一個前端工程師都知道的基本優化方法是:儘量減少統一域下的HTTP請求數,以及儘量減少每個資源的體積。

儘量減少統一域下的HTTP請求數

瀏覽器常常限定了對同一域名發起的併發連線數的上限 。 各種瀏覽器普遍把這一上限設定為 4至 8個 。如果瀏覽器需要對某個域進行更多的連線 ,則需要在用完了當前連線之後 ,重複使用或者重新建立 T C P連線 。

由於瀏覽器針對資源的域名限制併發連線數 ,而不是針對瀏覽器位址列中的頁面域名 ,所以很多靜態資源可以放在其他域名下 (不同的子域名也被認為是不同的域名 ) 。如果您只有一臺伺服器 ,可以把這些不同的域名同時指向一個 I P ,也就提高了對這臺伺服器的併發連線數限制 (不過要小心伺服器壓力過大 ) 。

把靜態資源放在非主域名下 ,這種做法除了可以增加瀏覽器併發 ,還有一個好處是 ,減少HTTP請求中攜帶的不必要的cookie資料 ,因為這對頻寬和連結熟讀都造成了影響,所以我們一般把靜態資源放在單獨的域名下。

儘量減少每一個資源的體積

我們不光要限制請求數 ,還要儘量減少每一個資源的體積。因為資源的體積越大 ,在傳輸中消耗的流量就越多 ,等待時間也越久 。

在面試應聘者的時候,我會問的一個基礎題目是 “常用的圖片格式有哪些 ,它們的使用場景是什麼 ” 。如果能選擇合適的圖片格式 ,就能夠用更小的體積 ,達到更好的顯示效果 。對圖片格式的敏感 ,能反映出工程師對頻寬和速度的不懈追求 。此外 ,對於比較大的文字資源 ,必須開啟 gzip壓縮 。因為 gzip 對於含有重複 “單詞 ”的文字檔案 ,壓縮率非常高 ,能有效提高傳輸過程 。

HTTP 後臺視角

前端工程師對HTTP的關注點在於儘量減少同一域下的HTTP請求數 ,以及儘量減少每一個資源的體積 。與之不同 ,後臺工程師對於HTTP的關注在於讓伺服器儘快響應請求 ,以及減少請求對伺服器的開銷 。

提高伺服器的請求處理能力

Apache是市場份額最大的伺服器 ,超過 50%的網站執行在Apache上 。Apache通過模組化的設計來適應各種環境 ,其中一個模組叫做多處理模組(MPM)專門用來處理多請求的情況 。Apache安裝在不同系統上的時候會呼叫不同的預設MPM ,我們不用關心具體的細節,只需要瞭解Unix上預設的MPM是prefork。為了優化,我們可以改成worker模式 。

preforkworker模式的最大區別就是 ,prefork的一個程序維持一個連線 ,而worker的一個執行緒維持一個連線 。所以prefork更穩定但記憶體消耗也更大 ,worker沒有那麼穩定 ,因為很多連線的執行緒共享一個程序 ,當一個執行緒崩潰的時候 ,整個程序和所有執行緒一起死掉 。但是worker的記憶體使用要比prefork低得多 ,所以很適合用在高HTTP請求的伺服器上 。

在高連線併發的情況下 ,Nginx是Apache伺服器不錯的替代品或者補充 :一方面是Nginx更加輕量級 ,佔用更少的資源和記憶體;另一方面是Nginx處理請求是非同步非阻塞的 ,而Apache則是阻塞型的 ,在高併發下Nginx能保持低資源 、低消耗和高效能 。由於Apache和Nginx各有所長 ,所以經常的搭配是Nginx處理前端併發 ,Apache處理後臺請求 。值得一提的是 ,新秀Node.js也是採用基於事件的非同步非阻塞方式處理請求 ,所以在處理高併發請求上有天然的優勢 。

DDos攻擊

DDos是Distributed Denialof Service的縮寫,DDos攻擊翻譯成中文就是 “分散式拒絕服務 ”攻擊 。

攻擊者通過海量的請求 ,讓目標伺服器癱瘓 ,無法響應正常的使用者請求 ,以此達到攻擊的效果 。對於這樣的攻擊 ,幾乎沒有什麼特別好的防護方法 。除了增加頻寬和提高伺服器能同時接納的客戶數 ,另一種方法就是讓首頁靜態化 。

DDos攻擊者喜歡攻擊的頁面一般是會對資料庫進行寫操作的頁面,這樣的頁面無法靜態化,伺服器更容易宕機 。DDos攻擊者一般不會攻擊靜態化的頁面或者圖片,因為靜態資源對伺服器壓力小,而且能夠部署在CDN上 。

BigPipe

通俗來解釋,BigPipe首先把HTML頁面分為很多部分 ,然後在伺服器和瀏覽器之間建立一條管道 (BigPipe就是 “大管道 ”的意思 ) ,HTML的不同部分可以源源不斷地從伺服器傳輸到瀏覽器 。BigPipe首先輸送的內容是框架性HTML結構 ,這個框架結構可能會定義每個Pagelet模組的位置和寬高 ,但是這些Pagelet都是空的,就像只有鋼筋混泥土骨架的毛坯房 。

接下來管道里源源不斷地傳輸過來很多模組 ,這時候最開始載入在伺服器中的JS程式碼開始工作 ,它會負責把每一個模組依次渲染到頁面上,在使用者的感知上,頁面非常快地出現在眼前 ,但是所有的模組都顯示正在載入中 ,然後主要的區域 (比如重要的使用者動態 )優先出現,接下來是logo、邊欄和各種掛件等 。

為什麼BigPipe能夠讓伺服器對瀏覽器說“我這個請求還沒結束,我們保持這個連結不要斷開”呢?答案是HTTP1.1的分塊傳輸編碼。

HTTP1.1引入分塊傳輸編碼 ,允許伺服器為動態生成的內容維持HTTP持久連結。如果一個HTTP訊息(請求訊息或應答訊息)的Transfer Encoding訊息頭的值為chunked,那麼訊息體由數量不確定的塊組成 ——也就是說想傳送多少塊就傳送多少塊 ——並以最後一個大小為0的塊為結束 。

快取

1. 伺服器快取

基本的資料庫查詢快取 MySQL預設不開啟查詢快取 ,但我們可以通過修改MySQL安裝目錄中的 my.ini來設定查詢快取 。設定的時候可以根據實際情況配置緩衝區大小 、單個查詢的緩衝區大小等 。

可以在MySQL配置中增加這兩項

query_cache_size = SIZE 

SIZE是指為查詢快取開闢多大的空間 。預設是 0 ,也就是禁用查詢快取。

query_cache_type = OPTION

設定查詢快取的型別 ,可選的值有以下這三種 。

  • 0:設定查詢快取的型別 ,可選的值

  • 1:所有的快取結果都快取起來 ,除非查詢命令以 SELECTS_NO_CACHE 開始 。

  • 2:只快取查詢命令以 SELECT SQL_CACHE開始的查詢結果 。

所以 ,對於查詢操作遠遠多於修改操作的資料庫 ,開啟資料庫查詢快取是很有益的 ;但是對於修改操作很多的資料庫 ,由於快取經常會失效 ,就起不到加速的效果 。不僅如此 ,由於資料庫要花費時間寫快取 ,所以實際上速度更慢了 。

這裡需要注意的是 ,兩次 SQL 文字必須完全相同 。如果前後兩次查詢使用了不同的查詢條件 ,就會重新查詢 。

擴充套件資料庫快取:memcached memcached的快取失效採用的是按時間來過期的設計 。memcached相當於應用程式和資料庫之間的中間層 ,通過網路 API設定和呼叫 。memcached儲存的是名值對 ,而且設定了一個過期時間 ,只要過期時間沒有到 ,應用程式就會從memcached中獲取資料 。這時候即使發生了資料庫更新操作 ,快取的查詢結果也仍然是之前儲存的舊資料 ,直到設定的時間過期 。這樣提高了快取的效能 ,帶來的影響就是 ,資料可能是 “不新鮮 ”的 。

但是 memcached 也不是總是那麼有效,因為如果只有一臺伺服器,就用不到它的伺服器叢集的優勢,反而讓系統更慢 。

再加一層檔案快取 除了可以將資料庫查詢結果快取在記憶體中還可以將被頻繁造訪的資料快取在檔案中。檔案 I/O 比起記憶體有以下幾個好處。硬碟容量比記憶體大,所以可以快取更多資料。資料更安全,斷電之後資料還在。易於擴充套件,硬碟不夠用的時候還可以新增硬碟。但是檔案快取沒有記憶體快取快,只能作為記憶體快取的補充,在獲取資料時,先從最快的地方讀取,如果沒有就繼續往後找。查詢優先順序為:記憶體快取 檔案快取 資料庫 。

快取檔案不會過期,除非您刪掉它,否則任何被快取了的查詢會一直存在。快取系統允許您按頁面清除,或把所有快取都清除掉 。一般來說,您可以在某些事件(比如向資料庫添加了資料 )發生時用特定的函式來清除快取 。

靜態化 有兩種靜態化的方法,其中一種是類似 WordPress 的靜態化外掛,安裝很簡單,每次有新文章就自動生成靜態頁面。這種方法還是將資料儲存在資料庫中,只是會讀取資料庫之後生成一些靜態頁。這一種方法的原理跟檔案快取很相似 。

另一種方法就是直接拋棄資料庫 。比如有一些部落格作者會用 Jekyll 系統來寫部落格,將整個部落格站點靜態化。完全拋棄資料庫的好處是,可以將生成的靜態網頁直接託管在靜態資源站點,比如 GitHub Pages 或者 Amazon S3,而不用操心資料庫伺服器的問題,不光整個系統穩定很多,費用上也更加低廉 (GitHub更是完全免費的,而且提交 Markdown 原始碼後可以讓它在伺服器端生成站點) 。

2. 瀏覽器快取

當瀏覽器訪問一個站點的時候,網路連線是主要瓶頸,我們可以通過設定瀏覽器快取來跳過 HTTP請求。如果在瀏覽器設定快取,通常有兩個主要作用。

  • 使用者來說,減少請求可以更快地載入頁面,節省流量。如果使用者是在手機上用3G或4G訪問頁面,這一點就很關鍵。

  • 對網站來說,減少頻寬壓力和費用。假設有1億的訪問量,如果能把大小為 10KB的 CSS快取起來,可以節省不小的開支。

主要的兩種快取指令 第一種:Expires 這種快取是最快的,因為沒有任何 HTTP請求發生。當用戶需要這個資源,瀏覽器就直接從快取中讀取,不再需要詢問伺服器端的意見(伺服器端甚至不知道您在瀏覽 image.png) 。所以 HttpWatch是推薦對所有的靜態資源都設定Expires 。

第二種:Last-Modified 通過這種快取方式 ,無論資源是否發生了更新 ,仍然至少會發生一來一去 HTTPS 頭的傳輸和接收 ,所以速度比不上Expires 。

從伺服器端的角度來看 ,有時候我們並不希望對靜態資源的請求中大部分都返回304。因為這可能說明我們的很多使用者都在頻繁訪問站點 ,而且我們的資源很少更新 ,就好像它們一直問 “資源修改了嗎? ” ,我們一直回答 “沒有修改 ” 。這裡可以使用Expires來設定過期時間 ,這樣它們就不會 “煩我們 ”了 。對於伺服器管理員來說,保持304為一個合理的比例即可 。我們可以通過檢視伺服器的log ,檢視304響應與200響應的比例,來做出一個合理的快取策略 。

Restful Web API 表徵性狀態傳輸(Representational State Transfer,REST)是一種軟體架構風格。在 3種主流的Web服務實現方案中,因為REST模式最簡潔,也能合理地利用HTTP操作的語義,所以越來越多的Web服務開始採用REST風格設計和實現 。

Restful的目的是定義如何正確地使用Web標準,優雅地使用HTTP本身的特性。原則上是對資源、集合、服務(URL)、get、post 、put、delete(操作)的合理使用。舉例來說,如果請求一個資源,但是伺服器上沒有這個資源,這時候就應該對HTTPS頭設定404,而不是設定200。

HTTP1.1加入的Cache-Control 它的功能跟Expires類似,不過有更多的選項。Expires的值是一個日期,表示某日期之前都不再詢問。Cache-Control的值是 : maxage = 7776000, maxage的單位是秒,從瀏覽器接收到檔案之後開始計時。

按照HTTP規範 ,如果修改了請求資源的QueryString,就應該被視為一個新的檔案。

下面是推薦的瀏覽器快取設定最佳實踐。

  • 對於動態生成的HTML頁面使用HTTPS頭:Cache-Control:nocache

  • 對於靜態HTML面使用HTTPS頭:Last-Modified 。

  • 其他所有的檔案型別都設定Expires頭,並且在檔案內容有所修改的時候修改QueryString。

瀏覽器快取的現實世界 伺服器端可以設定快取規則,告訴瀏覽器應該如何遵循和實現,但在伺服器不能掌控的地方也許會出現一些意外。快取會被擠出。檔案有可能在運營商伺服器上被劫持。

第二個問題是 ,使用者的寬頻運營商為了提高速度 ,可能會在自己某節點伺服器上快取您的檔案(比如style.css?v1),好處是當用戶請求這個檔案的時候,運營商無需來您的伺服器上請求檔案,而自己直接就給出了。

問題來了,如果您的QueryString更新了(style.css?v2),按照HTTP規範,這理應被視為一個新的檔案,但是運營商仍然可能會拿自己節點的快取,而不是遵循規範。有點可惡對不對?這就是我們在使用者量極大的情況下偵測到的情況,雖不太常見,但是有可能發生。所以,為了保證更新的檔案下發到所有的使用者,我們會使用更加強硬的方法:修改檔名,而不是僅僅修改QueryString。

QQ空間靜態資源在瀏覽器端使用的快取策略。

  • 對於動態生成的HTML頁面使用HTTPS頭:Cache-Control:nocache

  • 對於靜態HTML頁面使用HTTPS頭 :Last-Modified 。

  • 其他所有的檔案型別都設定Cache-Control頭,並且在檔案內容有所修改的時候修改檔名。

Last updated