典型 Web App 架構
以下是一個典型的高負載 web 應用示例:
上圖展示了一個典型的,三層架構的高效能 Web 應用。這種成熟的架構多年以來已被廣泛部署於包括 Google、Yahoo、Facebook、Twitter、Wikipedia 在內的諸多大型 Web 應用中。
反向代理服務
位於三層構架中最外層的反向代理伺服器負責接受使用者的接入請求,在實際應用中,代理伺服器通常至少還要完成以下列表中的一部分任務:
連線管理:分別維護客戶端和應用伺服器的連線池,管理並關閉已超時的長連線。
攻擊檢測和安全隔離:由於反向代理服務無需完成任何動態頁面生成任務,所有與業務邏輯相關的請求都轉發至後端應用伺服器處理。因此反向代理服務幾乎不會被應用程式設計或後端資料漏洞所影響。反向代理的安全性和可靠性通常僅取決於產品本身。在應用服務的前端部署反向代理伺服器可以有效地在後端應用和遠端使用者間建立起一套可靠的安全隔離和攻擊檢測機制。
如果需要的話,還可以通過在外網、反向代理、後端應用和資料庫等邊界位置新增額外的硬體防火牆等網路隔離裝置來實現更高的安全性保證。
負載均衡:通常使用輪轉(Round Robin)或最少連線數優先等策略完成基於客戶請求的負載均衡;也可以使用 SSI 等技術將一個客戶請求拆分成若干平行計算部分分別提交到多個應用伺服器。
分散式的 cache 加速:可以將反向代理分組部署在距離熱點地區地理位置較近的網路邊界上。通過在位於客戶較近的位置提供緩衝服務來加速網路應用。這實際上就構成了 CDN 網路。
靜態檔案伺服:當收到靜態檔案請求時,直接返回該檔案而無需將該請求提交至後端應用伺服器。
動態響應快取:對一段時間內不會發生改變的動態生成響應進行快取,避免後端應用伺服器頻繁執行重複查詢和計算。
資料壓縮傳輸:為返回的資料啟用 GZIP/ZLIB 壓縮演算法以節約頻寬。
資料加密保護(SSL Offloading):為與客戶端的通訊啟用 SSL/TLS 加密保護。
容錯:跟蹤後端應用伺服器的健康狀況,避免將請求排程到發生故障的伺服器。
使用者鑑權:完成使用者登陸和會話建立等工作。
URL別名:對外建立統一的url別名資訊,遮蔽真實位置。
應用混搭:通過SSI和URL對映技術將不同的web應用混搭在一起。
協議轉換:為使用 SCGI 和 FastCGI 等協議的後端應用提供協議轉換服務。
目前比較有名的反向代理服務包括:Apache httpd+mod_proxy / IIS+ARR / Squid / Apache Traffic Server / Nginx / Cherokee / Lighttpd / HAProxy 以及 Varnish 等等。
應用服務
應用服務層位於資料庫等後端通用服務層與反向代理層之間,向上接收由反向代理服務轉發而來的客戶端訪問請求,向下訪問由資料庫層提供的結構化儲存與資料查詢服務。
應用層實現了 Web 應用的所有業務邏輯,通常要完成大量的計算和資料動態生成任務。應用層內的各個節點不一定是完全對等的,還可能以 SOA、μSOA 等架構拆分為不同服務叢集。
上圖給出了一個典型的高併發、高效能應用層節點工作模型。每個 Web 應用節點(在圖 5中由標有"App"字樣的方框表示)通常都會工作在自己的伺服器(物理伺服器或VPS)之上,多個應用節點可以有效地並行工作,以方便地實現橫向擴充套件。
在上圖所示的例子中,Web 應用節點由 IO 回撥執行緒池、Web 請求佇列以及後臺工作執行緒池等三個重要部分組成,其伺服流程如下:
當一個 Web 請求到達後,底層作業系統通過 IOCP、epoll、kqueue、event ports、real time signal (posix aio)、/dev/poll、pollset 等各類與具體平臺緊密相關的 IO 完成(或 IO 就緒)回撥機制通知 AIO(Asynchronous IO)回撥執行緒,對這個已到達的 Web 請求進行處理。
在 AIO 回撥池中的工作執行緒接收到一個已到達的 Web 請求後,首先嚐試對該請求進行預處理。在預處理過程中,將會使用位於本地的快取記憶體來避免成本較高的資料庫查詢。如果本地快取命中,則直接將快取中的結果(仍然以非同步 IO 的方式)返回客戶端,並結束本次請求。
如果指定的 Web 請求要求查詢的資料無法被本地快取命中,或者這個 Web 請求需要資料庫寫入操作,則該請求將被 AIO 回撥執行緒追加到指定的佇列中,等待後臺工作執行緒池中的某個空閒執行緒對其進行進一步處理。
後臺工作執行緒池中的每個執行緒都分別維護著兩條長連線:一條與底層到資料庫服務相連,另一條則連線到分散式快取(memcached)網路。通過讓每個工作執行緒維護屬於自己的長連線,後臺工作執行緒池實現了資料庫和分散式快取連線池機制。長連線(Keep-Alive)通過為不同的請求重複使用同一條網路連線大大提高了應用程式處理效率和網路利用率。
後臺工作執行緒在 Web 請求佇列上等待新的請求到達。在從佇列中取出一個新的請求後,後臺工作執行緒首先嚐試使用分散式快取服務命中該請求中的查詢操作,如果網路快取未命中或該請求需要資料庫寫入等進一步處理,則直接通過資料庫操作來完成這個 Web 請求。
當一個 Web 請求被處理完成後,後臺工作執行緒會將處理結果作為 Web 響應以非同步 IO 的方式返回到指定客戶端。
上述步驟粗略描述了一個典型 Web 應用節點的工作方式。值得注意的是,由於設計思想和具體功能的差異,不同的 Web 應用間,無論在工作模式或架構上都可能存在很大的差異。
需要說明的是,與 epoll/kqueue/event ports 等相位觸發的通知機制不同,對於 Windows IOCP 和 POSIX AIO Realtime Signal 這類邊緣觸發的 AIO 完成事件通知機制,為了避免作業系統底層 IO 完成佇列(或實時訊號佇列)過長或溢位導致的記憶體緩衝區被長時間鎖定在非分頁記憶體池,在上述系統內的 AIO 回撥方式實際上是由兩個獨立的執行緒池和一個 AIO 完成事件佇列組成的:一個執行緒池專門負責不間斷地等待系統 AIO 完成佇列中到達的事件,並將其提交到一個內部的 AIO 完成佇列中(該佇列工作在使用者模式,具有使用者可控的彈性尺寸,並且不會鎖定記憶體);與此同時另一個執行緒池等待在這個內部 AIO 完成佇列上,並且處理不斷到達該佇列的 AIO 完成事件。這樣的設計降低了作業系統的工作負擔,避免了在極端情況下可能出現的訊息丟失、記憶體洩露以及記憶體耗盡等問題,同時也可以幫助作業系統更好地使用和管理非分頁記憶體池。
作為典型案例:包括搜尋引擎、Gmail 郵件服務在內的大部分 Google Web 應用均是使用 C/C++ 實現的。得益於 C/C++ 語言的高效和強大,Google 在為全球 Internet 使用者提供最佳 Web 應用體驗的同時,也實現了在其遍及全球的上百萬臺分散式伺服器上完成一次 Web 搜尋,總能耗僅需 0.0003 kW·h 的優異表現。關於 Google Web 應用架構以及硬體規模等進一步討論,請參考:http://en.wikipedia.org/wiki/Google 以及 http://en.wikipedia.org/wiki/Google_search。
資料庫和memcached服務
資料庫服務為上層 Web 應用提供關係式或結構化的資料儲存與查詢支援。取決於具體用例,Web 應用可以使用資料庫聯結器之類的外掛機制來提供對不同資料庫服務的訪問支援。在這種架構下,使用者可以靈活地選擇或變更最適合企業現階段情況的不同資料庫產品。例如:使用者可以在原型階段使用 SQLite 之類的嵌入式引擎完成快速部署和功能驗證;而在應用的初期階段切換到廉價的 MySql 資料庫解決方案;等到業務需求不斷上升,資料庫負載不斷加重時再向 Clustrix、MongoDB、Cassandra、MySql Cluster、ORACLE 等更昂貴和複雜的解決方案進行遷移。
Memcached 服務作為一個完全基於記憶體和 <Key, Value> 對的分散式資料物件緩衝服務,擁有令人難以置信的查詢效率以及一個優雅的,無需伺服器間通訊的大型分散式架構。對於高負載 Web 應用來說,Memcached 常被用作一種重要的資料庫訪問加速服務,因此它不是一個必選元件。使用者完全可以等到現實環境下的資料庫服務出現了效能瓶頸時在部署它。值得強調的是,雖然 memcached 並不是一個必選元件,但通過其在 YouTube、Wikipedia、Amazon.com、SourceForge、Facebook、Twitter 等大型 Web 應用上的多年部署可以證明:memcached 不但能夠在高負載環境下長期穩定地工作,而且可以戲劇性地提升資料查詢的整體效率。有關 memcached 的進一步討論,請參考:http://en.wikipedia.org/wiki/Memcached。
當然,我們也應該注意到:以 memcached 為代表的分散式快取系統,其本質上是一種以犧牲一致性為代價來提升平均訪問效率的妥協方案——快取服務為資料庫中的部分記錄增加了分散式副本。對於同一資料的多個分散式副本來說,除非使用 Paxos、Raft 等一致性演算法,不然無法實現強一致性保證。
矛盾的是,memory cache 本身就是用來提升效率的,這使得為了它使用上述開銷高昂的分散式強一致性演算法變得非常不切實際:目前的分散式強一致性演算法均要求每次訪問請求(無論讀寫)都需要同時訪問包括後臺資料庫主從節點在內的多數派副本——顯然,這還不如干脆不使用快取來的有效率。
另外,即使是 Paxos、Raft 之類的分散式一致性演算法也只能在單個記錄的級別上保證強一致。意即:即使應用了此類演算法,也無法憑此提供事務級的強一致性保證。
除此之外,分散式快取也增加了程式設計的複雜度(需要在訪問資料庫的同時嘗試命中或更新快取),並且還增加了較差情形下的訪問延遲(如:未命中時的 RTT 等待延遲,以及節點下線、網路通訊故障時的延遲等)。
與此同時,可以看到:從二十年前開始,各主流資料庫產品其實均早已實現了成熟、高命中率的多層(磁碟塊、資料頁、結果集等)快取機制。既然分散式快取有如此多的缺陷,而資料庫產品又自帶了優秀的快取機制,它為何又能夠成為現代高負載 Web App 中的重要基石呢?
其根本原因在於:對於十年前的技術環境來說,當時十分缺乏橫向擴充套件能力的 RDBMS(SQL)系統已成為了嚴重製約 Web App 等網路應用擴大規模的瓶頸。為此,以 Google BigTable、Facebook Cassandra、MongoDB 為代表的 NoSQL 資料庫產品,以及以 memcached、redis 為代表的分散式快取產品紛紛粉墨登場,並各自扮演了重要作用。
與 MySQL、ORACLE、DB2、MS SQL Server、PostgreSQL 等當時的 "傳統" SQL資料庫產品相比,無論 NoSQL 資料庫還是分散式快取產品,其本質上都是以犧牲前者的強一致性為代價,來換取更優的橫向擴充套件能力。
應當看到,這種取捨是在當時技術條件下做出的無奈、痛苦的抉擇,系統因此而變得複雜——在需要事務和強一致性保障,並且資料量較少的地方,使用無快取層的傳統 RDBMS;在一致性方面有一定妥協餘地,並且讀多寫少的地方儘量使用分散式快取來加速;在對一致性要求更低的大資料上使用 NoSQL;如果資料量較大,同時對一致性要求也較高,就只能嘗試通過對 RDMBS 分庫分表等方法來儘量解決,為此還要開發各種中介軟體來實現資料訪問的請求分發和結果集聚合等複雜操作……各種情形不一而足,而它們的相互組合和交織則再次加劇了複雜性。
回顧起來,這是一箇舊秩序被打破,新秩序又尚未建立起來的混亂時代——老舊 RMDBS 缺乏橫向擴充套件能力,無法滿足新時代的大資料處理需求,又沒有一種能夠替代老系統地位,可同時滿足大部分使用者需求的普適級結構化資料管理方案。
這是一個青黃不接的時代,而 BigTable、Cassandra、memcached 等產品則分別是 Google、Facebook 以及 LiveJournal 等廠商在那個時代進行 "自救" 的結果。這樣以:"花費最小代價,滿足自身業務需求即可" 為目標的產物自然不太容易具備很好的普適性。
然而今天(2015),我們終於就快要走出這個窘境。隨著 Google F1、MySQL Cluster(NDB)、Clustrix、VoltDB、MemSQL、NuoDB 等眾多 NewSQL 解決方案的逐步成熟以及技術的不斷進步,橫向擴充套件能力逐漸不再成為 RDBMS 的瓶頸。今天的架構設計師完全可以在確保系統擁有足夠橫向擴充套件能力的同時,實現分散式的事務級(XA)強一致性保證:
如上圖所示,在 NewSQL 具備了良好的橫向擴充套件能力後,架構中不再迫切需要分散式快取和 NoSQL 產品來彌補這方面的短板,這使得設計和開發工作再次迴歸到了最初的簡潔和清晰。而物件儲存(Object Storage)服務則提供了對音訊、視訊、圖片、檔案包等海量非結構化BLOB資料的儲存和訪問支援。
這樣簡潔、清晰、樸素的架構使一切看起來彷彿迴歸到了多年以前,物件儲存服務就像 FAT、NTFS、Ext3 等磁碟檔案系統,NewSQL 服務則好像當年 MySQL、SQL Server 等 "單機版" 資料庫。但一切卻又已不同,業務邏輯、資料庫和檔案儲存均已演進成為支援橫向擴充套件的高可用叢集,在效能、容量、可用性、可靠性、可伸縮性等方面有了巨大的飛躍:人類總是以螺旋上升的方式不斷進步——在每一次看似迴歸的變遷中,實包含了本質的昇華。
隨著 GlusterFS、Ceph、Lustre 等可 mount 且支援 Native File API 的分散式檔案系統越來越成熟和完善,也有望於大部分場合下逐漸替換現有的物件儲存服務。至此 Web App 架構的演進才能算是完成了一次重生——這還算不上是涅槃,當我們能夠在真正意義上實現出高效、高可用的多虛一(Single System Image)系統時,涅槃才真正降臨。那時的我們編寫分散式應用與現在編寫一個單機版的多執行緒應用將不會有任何區別——程序天然就是分散式、高可用的!
三層架構的可伸縮性
小到集中部署於單臺物理伺服器或 VPS 內,大到 Google 遍及全球的上百萬臺物理伺服器所組成的分散式應用。前文描述的三層 Web 應用架構體現出了難以置信的可伸縮性。
具體來說,在專案驗證、應用部署和服務運營的初期階段,可以將以上三層服務元件集中部署於同一臺物理伺服器或 VPS 內。與此同時,可通過取消 memcached 服務,以及使用資源開銷小並且易於部署的嵌入式資料庫產品來進一步降低部署難度和系統整體資源開銷。
隨著專案運營的擴大和負載的持續加重,當單伺服器方案和簡單的縱向擴充套件已無法滿足專案運營負荷時,使用者即可通過將各元件分散式地執行在多臺伺服器內來達到橫向擴充套件的目的。例如:反向代理可通過 DNS CNAME 記錄輪轉或 3/4 層轉發(LVS、HAProxy等)的方式實現分散式負載均衡。應用服務則可由反向代理使用基於輪轉或最小負載優先等策略來實現分散式和負載均衡。此外,使用基於共享 IP 的伺服器叢集方案也能夠實現負載均衡和容錯機制。
與此類似,memcached 和資料庫產品也都有自己的分散式運算、負載均衡以及容錯方案。此外,資料庫訪問效能瓶頸可通過更換非關係式(NoSQL)的資料庫產品,或使用主-從資料庫加複製等方式來提升。而資料庫查詢效能則可通過部署 memcached 或類似服務來極大程度地改善。
轉自 全棧IT技術前線,如侵刪。