本節討論 Web 系統中最重要的行程及其內部組織。

客戶端#

最重要的 Web 客戶端是瀏覽器(Web browser):讓使用者從伺服器抓取網頁並顯示在螢幕上,藉此在網頁間導覽。瀏覽器通常把超連結顯示成使用者一次點擊就能選取的形式。

瀏覽器曾經是簡單的程式,但那是很久以前的事了。邏輯上它由幾個元件組成:

  • 顯示後端(display back end)與網路函式庫:瀏覽器(理想上)應與平台無關,這通常靠使用標準圖形函式庫與標準網路函式庫達成。
  • 渲染引擎(rendering engine):包含正確顯示文件的所有程式碼——至少要剖析 HTML 或 XML,也可能需要解譯腳本。多數情況下只內建 Javascript 解譯器,但理論上也可納入其他解譯器。
  • 瀏覽器引擎(browser engine):提供讓使用者瀏覽文件、選取部分內容、觸發超連結等機制。

圖 12-5:Web 瀏覽器的邏輯元件

瀏覽器設計者面對的一個問題是:瀏覽器要能輕易擴充,原則上要能支援伺服器回傳的任何文件型別。多數採取的做法是提供**外掛(plug-in)**機制:

  • 外掛是可動態載入瀏覽器的小程式,用來處理特定的文件型別(通常以 MIME 型別表示)。
  • 外掛必須在本地可用,可能需要使用者先從遠端伺服器下載。
  • 外掛對瀏覽器提供標準介面,也期待瀏覽器提供標準介面;邏輯上它們是渲染引擎的延伸。

另一個常用的客戶端行程是 Web 代理(Web proxy)。它原本的用途是讓瀏覽器能處理 HTTP 以外的應用層協定:例如要從 FTP 伺服器抓檔案,瀏覽器可對本地的 FTP 代理發出 HTTP 請求,由代理抓取檔案後以 HTTP 包裝回傳。

圖 12-6:瀏覽器不會說 FTP 時使用 Web 代理

如今多數瀏覽器本身就支援多種協定(或可動態擴充支援),已不再因此需要代理。但代理仍因其他理由被使用:過濾請求與回應(趨近應用層防火牆)、記錄、壓縮——而最重要的是快取(見「一致性與複製」一節)。廣泛使用的 Web 代理是開源專案 Squid。

Apache 伺服器#

Apache 是目前最流行的 Web 伺服器,估計承載了約 70% 的網站。Apache 是複雜的軟體,隨著 Web 文件型態的大量擴充,伺服器必須高度可設定、可擴充,同時大體上與特定平台無關。

  • 平台獨立:Apache 提供自己的基本執行環境——Apache Portable Runtime(APR),一個為檔案處理、網路、鎖、執行緒等提供平台無關介面的函式庫,再針對不同作業系統實作。擴充 Apache 時,只要只呼叫 APR、避免呼叫平台專屬函式庫,可攜性大致就有保障。
  • 從某個角度看,Apache 可視為一個「對進來的請求產生回應」的完全通用伺服器。當然其中有各種隱含相依與假設,使它主要適合處理 Web 文件請求:HTTP 幾乎總是實作在 TCP 之上,因此 Apache 核心假設所有請求都遵循基於 TCP 的連線導向通訊——不改核心就無法妥善處理基於 UDP 的請求。

除此之外,Apache 核心對「請求該怎麼處理」的假設很少,其組織的根本是**掛鉤(hook)**的概念:

  • 掛鉤是一個「特定功能群」的佔位處。Apache 核心假設請求分成若干**階段(phase)**處理,每個階段由幾個掛鉤構成,每個掛鉤代表處理請求時要執行的一組類似動作。
  • 例如:把 URL 轉譯成本地檔名的掛鉤(幾乎每個請求都需要)、寫入記錄的掛鉤、檢查客戶端身分的掛鉤、檢查存取權限的掛鉤、檢查請求對應 MIME 型別的掛鉤等。掛鉤依預定順序處理——Apache 在此明確強制了請求處理的控制流程。
  • 掛鉤上掛的函式全由獨立的**模組(module)**提供。原則上開發者可以更改 Apache 要處理的掛鉤集合,但更常見的是撰寫模組,內含要在標準掛鉤中被呼叫的函式:每個掛鉤可包含一組符合特定函式原型(參數列與回傳型別)的函式,編譯 Apache 時指定哪個函式加到哪個掛鉤。
  • 模組可能有數十個,每個掛鉤通常含多個函式。模組一般視為互相獨立,同一掛鉤內的函式以任意順序執行;但 Apache 也能處理模組相依——讓開發者指定不同模組的函式的處理順序。

圖 12-7:Apache Web 伺服器的一般組織

結果是一個極其多才多藝的 Web 伺服器。本章稍後討論的 Globule(作者群在阿姆斯特丹自由大學開發的自製內容遞送網路)就是以 APR 為基礎實作成 Apache 的擴充,且大體上獨立於其他 Apache 擴充。

Web 伺服器叢集#

Web 客戶端—伺服器本質的一個重要問題是:單一伺服器很容易過載。許多設計採用的實際解法是把伺服器複製到一個**叢集(cluster)**上,再用獨立機制(例如前端,front end)把客戶端請求導向其中一個副本——這是第 2 章討論過的水平分散(horizontal distribution)的例子。

圖 12-8:伺服器叢集搭配前端使用的原理

這種組織的關鍵在於前端的設計,因為所有流量都經過它,可能成為嚴重的效能瓶頸。前端一般分為兩類:

  • 傳輸層交換器(transport-layer switch):客戶端發出 HTTP 請求時會對伺服器建立 TCP 連線,交換器單純依某種伺服器負載量測,把 TCP 連線上的資料轉給其中一台伺服器;回應回到交換器,再轉給請求的客戶端。可搭配第 3 章討論過的 TCP 交遞(TCP handoff)做最佳化。缺點:交換器看不到 TCP 連線上送來的 HTTP 請求內容,頂多只能依伺服器負載決定轉發。
  • 內容感知請求分配(content-aware request distribution):一般而言更好的做法——前端先檢視進來的 HTTP 請求,再決定轉給哪台伺服器。優點:同一文件的請求總是轉給同一台伺服器,該伺服器就能有效快取該文件、提升回應速度;也可以把文件集合分散到各伺服器,而非每台都複製全部文件,更有效利用儲存容量,還能用專門伺服器處理音訊、視訊等特殊文件。代價是前端要做很多工作。

理想上我們想同時擁有 TCP 交遞的效率與內容感知分配的功能——做法是把前端的工作分散開,再結合傳輸層交換器。搭配 TCP 交遞時前端有兩項任務:請求初次進來時決定由哪台伺服器負責後續與客戶端的通訊;轉發該已交遞 TCP 連線上客戶端的後續 TCP 訊息。這兩項任務可以這樣分工:

  • 分派器(dispatcher):決定 TCP 連線該交遞給哪台伺服器。
  • 分配器(distributor):監看已交遞連線的進入 TCP 流量。
  • 交換器(switch):把 TCP 訊息轉給分配器。

客戶端首次接觸該 Web 服務時,其 TCP 連線建立訊息被轉給某個分配器,分配器再聯絡分派器決定連線交遞給哪台伺服器;此後交換器被告知,該連線的所有後續 TCP 訊息直接送往選定的伺服器。

圖 12-9:可擴充的內容感知 Web 伺服器叢集

延伸做法:不用前端的叢集組織
  • 輪流式 DNS(round-robin DNS):讓單一網域名稱對應多個 IP 位址。解析網站主機名稱時,客戶端瀏覽器會收到一串位址,每個位址對應叢集中一台伺服器。瀏覽器通常挑清單上第一個位址,而像 BIND 這樣流行的 DNS 伺服器會輪轉它回傳清單的順序——於是請求就簡單地分散到叢集各伺服器上。
  • 同一 IP 位址:完全不用任何中介,讓每台 Web 伺服器都有相同的 IP 位址。此時必須假設所有伺服器都接在同一個廣播 LAN 上:HTTP 請求到達時,接在該 LAN 的 IP 路由器把它轉給所有伺服器,各伺服器再執行同一個分散式演算法,決定性地選出由誰處理該請求。

各種 Web 叢集組織方式與替代方案,可參考 Cardellini 等人(2002)的優秀綜述。