以下是一個典型的高負載 web 應用示例: 上圖展示了一個典型的,三層架構的高性能 Web 應用。這種成熟的架構多年以來已被廣泛部署于包括 Google、Yahoo、Facebook、Twitter、Wikipedia 在內的諸多大型 Web 應用中。 |
位于三層構架中最外層的反向代理服務器負責接受用戶的接入請求,在實際應用中,代理服務器通常至少還要完成以下列表中的一部分任務:
目前比較有名的反向代理服務包括:Apache httpd+mod_proxy / IIS+ARR / Squid / Apache Traffic Server / Nginx / Cherokee / Lighttpd / HAProxy 以及 Varnish 等等。 |
應用服務層位于數(shù)據(jù)庫等后端通用服務層與反向代理層之間,向上接收由反向代理服務轉發(fā)而來的客戶端訪問請求,向下訪問由數(shù)據(jù)庫層提供的結構化存儲與數(shù)據(jù)查詢服務。 應用層實現(xiàn)了 Web 應用的所有業(yè)務邏輯,通常要完成大量的計算和數(shù)據(jù)動態(tài)生成任務。應用層內的各個節(jié)點不一定是完全對等的,還可能以 SOA、μSOA 等架構拆分為不同服務集群。 上圖給出了一個典型的高并發(fā)、高性能應用層節(jié)點工作模型。每個 Web 應用節(jié)點(在圖 5中由標有"App"字樣的方框表示)通常都會工作在自己的服務器(物理服務器或VPS)之上,多個應用節(jié)點可以有效地并行工作,以方便地實現(xiàn)橫向擴展。 在上圖所示的例子中,Web 應用節(jié)點由 IO 回調線程池、Web 請求隊列以及后臺工作線程池等三個重要部分組成,其伺服流程如下:
上述步驟粗略描述了一個典型 Web 應用節(jié)點的工作方式。值得注意的是,由于設計思想和具體功能的差異,不同的 Web 應用間,無論在工作模式或架構上都可能存在很大的差異。 需要說明的是,與 epoll/kqueue/event ports 等相位觸發(fā)的通知機制不同,對于 Windows IOCP 和 POSIX AIO Realtime Signal 這類邊緣觸發(fā)的 AIO 完成事件通知機制,為了避免操作系統(tǒng)底層 IO 完成隊列(或實時信號隊列)過長或溢出導致的內存緩沖區(qū)被長時間鎖定在非分頁內存池,在上述系統(tǒng)內的 AIO 回調方式實際上是由兩個獨立的線程池和一個 AIO 完成事件隊列組成的:一個線程池專門負責不間斷地等待系統(tǒng) AIO 完成隊列中到達的事件,并將其提交到一個內部的 AIO 完成隊列中(該隊列工作在用戶模式,具有用戶可控的彈性尺寸,并且不會鎖定內存);與此同時另一個線程池等待在這個內部 AIO 完成隊列上,并且處理不斷到達該隊列的 AIO 完成事件。這樣的設計降低了操作系統(tǒng)的工作負擔,避免了在極端情況下可能出現(xiàn)的消息丟失、內存泄露以及內存耗盡等問題,同時也可以幫助操作系統(tǒng)更好地使用和管理非分頁內存池。 作為典型案例:包括搜索引擎、Gmail 郵件服務在內的大部分 Google Web 應用均是使用 C/C++ 實現(xiàn)的。得益于 C/C++ 語言的高效和強大,Google 在為全球 Internet 用戶提供最佳 Web 應用體驗的同時,也實現(xiàn)了在其遍及全球的上百萬臺分布式服務器上完成一次 Web 搜索,總能耗僅需 0.0003 kW·h 的優(yōu)異表現(xiàn)。關于 Google Web 應用架構以及硬件規(guī)模等進一步討論,請參考:http://en.wikipedia.org/wiki/Google 以及 http://en.wikipedia.org/wiki/Google_search。 |
數(shù)據(jù)庫服務為上層 Web 應用提供關系式或結構化的數(shù)據(jù)存儲與查詢支持。取決于具體用例,Web 應用可以使用數(shù)據(jù)庫連接器之類的插件機制來提供對不同數(shù)據(jù)庫服務的訪問支持。在這種架構下,用戶可以靈活地選擇或變更最適合企業(yè)現(xiàn)階段情況的不同數(shù)據(jù)庫產(chǎn)品。例如:用戶可以在原型階段使用 SQLite 之類的嵌入式引擎完成快速部署和功能驗證;而在應用的初期階段切換到廉價的 MySql 數(shù)據(jù)庫解決方案;等到業(yè)務需求不斷上升,數(shù)據(jù)庫負載不斷加重時再向 Clustrix、MongoDB、Cassandra、MySql Cluster、ORACLE 等更昂貴和復雜的解決方案進行遷移。 Memcached 服務作為一個完全基于內存和 <Key, Value> 對的分布式數(shù)據(jù)對象緩沖服務,擁有令人難以置信的查詢效率以及一個優(yōu)雅的,無需服務器間通信的大型分布式架構。對于高負載 Web 應用來說,Memcached 常被用作一種重要的數(shù)據(jù)庫訪問加速服務,因此它不是一個必選組件。用戶完全可以等到現(xiàn)實環(huán)境下的數(shù)據(jù)庫服務出現(xiàn)了性能瓶頸時在部署它。值得強調的是,雖然 memcached 并不是一個必選組件,但通過其在 YouTube、Wikipedia、Amazon.com、SourceForge、Facebook、Twitter 等大型 Web 應用上的多年部署可以證明:memcached 不但能夠在高負載環(huán)境下長期穩(wěn)定地工作,而且可以戲劇性地提升數(shù)據(jù)查詢的整體效率。有關 memcached 的進一步討論,請參考:http://en.wikipedia.org/wiki/Memcached。 當然,我們也應該注意到:以 memcached 為代表的分布式緩存系統(tǒng),其本質上是一種以犧牲一致性為代價來提升平均訪問效率的妥協(xié)方案——緩存服務為數(shù)據(jù)庫中的部分記錄增加了分布式副本。對于同一數(shù)據(jù)的多個分布式副本來說,除非使用 Paxos、Raft 等一致性算法,不然無法實現(xiàn)強一致性保證。 矛盾的是,memory cache 本身就是用來提升效率的,這使得為了它使用上述開銷高昂的分布式強一致性算法變得非常不切實際:目前的分布式強一致性算法均要求每次訪問請求(無論讀寫)都需要同時訪問包括后臺數(shù)據(jù)庫主從節(jié)點在內的多數(shù)派副本——顯然,這還不如干脆不使用緩存來的有效率。 另外,即使是 Paxos、Raft 之類的分布式一致性算法也只能在單個記錄的級別上保證強一致。意即:即使應用了此類算法,也無法憑此提供事務級的強一致性保證。 除此之外,分布式緩存也增加了程序設計的復雜度(需要在訪問數(shù)據(jù)庫的同時嘗試命中或更新緩存),并且還增加了較差情形下的訪問延遲(如:未命中時的 RTT 等待延遲,以及節(jié)點下線、網(wǎng)絡通信故障時的延遲等)。 與此同時,可以看到:從二十年前開始,各主流數(shù)據(jù)庫產(chǎn)品其實均早已實現(xiàn)了成熟、高命中率的多層(磁盤塊、數(shù)據(jù)頁、結果集等)緩存機制。既然分布式緩存有如此多的缺陷,而數(shù)據(jù)庫產(chǎn)品又自帶了優(yōu)秀的緩存機制,它為何又能夠成為現(xiàn)代高負載 Web App 中的重要基石呢? 其根本原因在于:對于十年前的技術環(huán)境來說,當時十分缺乏橫向擴展能力的 RDBMS(SQL)系統(tǒng)已成為了嚴重制約 Web App 等網(wǎng)絡應用擴大規(guī)模的瓶頸。為此,以 Google BigTable、Facebook Cassandra、MongoDB 為代表的 NoSQL 數(shù)據(jù)庫產(chǎn)品,以及以 memcached、redis 為代表的分布式緩存產(chǎn)品紛紛粉墨登場,并各自扮演了重要作用。 與 MySQL、ORACLE、DB2、MS SQL Server、PostgreSQL 等當時的 "傳統(tǒng)" SQL數(shù)據(jù)庫產(chǎn)品相比,無論 NoSQL 數(shù)據(jù)庫還是分布式緩存產(chǎn)品,其本質上都是以犧牲前者的強一致性為代價,來換取更優(yōu)的橫向擴展能力。 應當看到,這種取舍是在當時技術條件下做出的無奈、痛苦的抉擇,系統(tǒng)因此而變得復雜——在需要事務和強一致性保障,并且數(shù)據(jù)量較少的地方,使用無緩存層的傳統(tǒng) RDBMS;在一致性方面有一定妥協(xié)余地,并且讀多寫少的地方盡量使用分布式緩存來加速;在對一致性要求更低的大數(shù)據(jù)上使用 NoSQL;如果數(shù)據(jù)量較大,同時對一致性要求也較高,就只能嘗試通過對 RDMBS 分庫分表等方法來盡量解決,為此還要開發(fā)各種中間件來實現(xiàn)數(shù)據(jù)訪問的請求分發(fā)和結果集聚合等復雜操作……各種情形不一而足,而它們的相互組合和交織則再次加劇了復雜性。 回顧起來,這是一個舊秩序被打破,新秩序又尚未建立起來的混亂時代——老舊 RMDBS 缺乏橫向擴展能力,無法滿足新時代的大數(shù)據(jù)處理需求,又沒有一種能夠替代老系統(tǒng)地位,可同時滿足大部分用戶需求的普適級結構化數(shù)據(jù)管理方案。 這是一個青黃不接的時代,而 BigTable、Cassandra、memcached 等產(chǎn)品則分別是 Google、Facebook 以及 LiveJournal 等廠商在那個時代進行 "自救" 的結果。這樣以:"花費最小代價,滿足自身業(yè)務需求即可" 為目標的產(chǎn)物自然不太容易具備很好的普適性。 然而今天(2015),我們終于就快要走出這個窘境。隨著 Google F1、MySQL Cluster(NDB)、Clustrix、VoltDB、MemSQL、NuoDB 等眾多 NewSQL 解決方案的逐步成熟以及技術的不斷進步,橫向擴展能力逐漸不再成為 RDBMS 的瓶頸。今天的架構設計師完全可以在確保系統(tǒng)擁有足夠橫向擴展能力的同時,實現(xiàn)分布式的事務級(XA)強一致性保證: 如上圖所示,在 NewSQL 具備了良好的橫向擴展能力后,架構中不再迫切需要分布式緩存和 NoSQL 產(chǎn)品來彌補這方面的短板,這使得設計和開發(fā)工作再次回歸到了最初的簡潔和清晰。而對象存儲(Object Storage)服務則提供了對音頻、視頻、圖片、文件包等海量非結構化BLOB數(shù)據(jù)的存儲和訪問支持。 這樣簡潔、清晰、樸素的架構使一切看起來仿佛回歸到了多年以前,對象存儲服務就像 FAT、NTFS、Ext3 等磁盤文件系統(tǒng),NewSQL 服務則好像當年 MySQL、SQL Server 等 "單機版" 數(shù)據(jù)庫。但一切卻又已不同,業(yè)務邏輯、數(shù)據(jù)庫和文件存儲均已演進成為支持橫向擴展的高可用集群,在性能、容量、可用性、可靠性、可伸縮性等方面有了巨大的飛躍:人類總是以螺旋上升的方式不斷進步——在每一次看似回歸的變遷中,實包含了本質的升華。 隨著 GlusterFS、Ceph、Lustre 等可 mount 且支持 Native File API 的分布式文件系統(tǒng)越來越成熟和完善,也有望于大部分場合下逐漸替換現(xiàn)有的對象存儲服務。至此 Web App 架構的演進才能算是完成了一次重生——這還算不上是涅槃,當我們能夠在真正意義上實現(xiàn)出高效、高可用的多虛一(Single System Image)系統(tǒng)時,涅槃才真正降臨。那時的我們編寫分布式應用與如今編寫一個單機版的多線程應用將不會有任何區(qū)別——進程天然就是分布式、高可用的! |
小到集中部署于單臺物理服務器或 VPS 內,大到 Google 遍及全球的上百萬臺物理服務器所組成的分布式應用。前文描述的三層 Web 應用架構體現(xiàn)出了難以置信的可伸縮性。 具體來說,在項目驗證、應用部署和服務運營的初期階段,可以將以上三層服務組件集中部署于同一臺物理服務器或 VPS 內。與此同時,可通過取消 memcached 服務,以及使用資源開銷小并且易于部署的嵌入式數(shù)據(jù)庫產(chǎn)品來進一步降低部署難度和系統(tǒng)整體資源開銷。 隨著項目運營的擴大和負載的持續(xù)加重,當單服務器方案和簡單的縱向擴展已無法滿足項目運營負荷時,用戶即可通過將各組件分布式地運行在多臺服務器內來達到橫向擴展的目的。例如:反向代理可通過 DNS CNAME 記錄輪轉或 3/4 層轉發(fā)(LVS、HAProxy等)的方式實現(xiàn)分布式負載均衡。應用服務則可由反向代理使用基于輪轉或最小負載優(yōu)先等策略來實現(xiàn)分布式和負載均衡。此外,使用基于共享 IP 的服務器集群方案也能夠實現(xiàn)負載均衡和容錯機制。 與此類似,memcached 和數(shù)據(jù)庫產(chǎn)品也都有自己的分布式運算、負載均衡以及容錯方案。此外,數(shù)據(jù)庫訪問性能瓶頸可通過更換非關系式(NoSQL)的數(shù)據(jù)庫產(chǎn)品,或使用主-從數(shù)據(jù)庫加復制等方式來提升。而數(shù)據(jù)庫查詢性能則可通過部署 memcached 或類似服務來極大程度地改善。 |