隨需而變:網站的可擴展架構算法
擴展性(Extensibility)數據庫
指對現有系統影響最小的狀況下,系統功能可持續擴展或提高的能力。表如今系統 基礎設施穩定不須要常常變動,應用之間較少依賴和耦合,對需求變動能夠敏捷響應。 它是系統架構設計層面的開閉原則(對擴展開放,對修改關閉),架構設計考慮將來功能 擴展,當系統增長新功能時,不須要對現有系統的結構和代碼進行修改。編程
伸縮性(Scalability)後端
指系統可以經過增長(減小)自身資源規模的方式加強(減小)本身計算處理事務 的能力。若是這種增減是成比例的,就被稱做線性伸縮性。在網站架構中,一般指利用 集羣的方式增長服務器數量、提升系統的總體事務吞吐能力。設計模式
1.構建可擴展的網站架構安全
幵發低耦合系統是軟件設計的終極目標之一,這一目標驅動着軟件開發技術的創新 與發展,從軟件與硬件的第一次分離到操做系統的誕生;從彙編語言到面向過程的開發 語言,再到面向對象的編程語言;從各類軟件工具集到各類開發框架;無不體現着下降 軟件系統耦合性這一終極目標。能夠說,度量一個開發框架、設計模式、編程語言優劣 的重要尺度就是衡量它是否是讓軟件幵發過程和軟件產品更加低耦合。服務器
顯而易見,低耦合的系統更容易擴展,低耦合的模塊更容易複用,一個低耦合的系 統設計也會讓開發過程和維護變得更加輕鬆和容易管理。網絡
筆者認爲,軟件架構師最大的價值不在於掌握多少先進的技術,而在於具備將一個 大系統切分紅#個低耦合的子模塊的能力,這些子模塊包含橫向的業務模塊,也包含縱 向的基礎技術模塊。這種能力一部分源自專業的技術和經驗,還有一部分源自架構師對 業務場景的理解、對人性的把握、甚至對世界的認知。數據結構
設計網站可擴展架構的核心思想是模塊化,並在此基礎之上,下降模塊間的耦合性, 提升模塊的複用性。咱們在本書第6章討論過網站經過分層和分割的方式進行架構伸縮,分層和分割也 是模塊化設計的重要手段,利用分層和分割的方式將軟件分割爲若干個低耦合的獨立的 組件模塊,這些組件模塊以消息傳遞及依賴調用的方式聚合成一個完整的系統。在大型網站中,這些模塊經過分佈式部署的方式,獨立的模塊部署在獨立的服務器 (集羣)上,從物理上分離模塊之間的耦合關係,進一步下降耦合性提升複用性。架構
模塊分佈式部署之後具體聚合方式主要有分佈式消息隊列和分佈式服務。
2.利用分佈式消息隊列下降系統耦合性
若是模塊之間不存在直接調用,那麼新增模塊或者修改模塊就對其餘模塊影響最小, 這樣系統的可擴展性無疑更好一些。
2.1 事件驅動架構
事件驅動架構(Event Driven Architecture )••經過在低縄合的模塊之間傳輸事件消息,以保持模塊的鬆散耦合,並藉助事件消息的通訊完成模塊間合做,典型的EDA架構就是 操做系統中常見的生產者消費者模式。在大型網站架構中,具體實現手段有不少,最常 用的是分佈式消息隊列,如圖7.1所示。
消息隊列利用發佈一訂閱模式工做,消息發送者發佈消息,一個或者多個消息接收 者訂閱消息。消息發送者是消息源,在對消息進行處理後將消息發送至分佈式消息隊列, 消息接受者從分佈式消息隊列獲取該消息後繼續進行處理。能夠看到,消息發送者和消 息接受者之間沒有直接耦合,消息發送者將消息發送至分佈式消息隊列即結束對消息的 處理,而消息接受者只須要從分佈式消息隊列獲取消息後進行處理,不須要知道該消息 從何而來。對新增業務,只要對該類消息感興趣,便可訂閱該消息,對原有系統和業務 沒有任何影響,從而實現網站業務的可擴展設計。
消息接受者在對消息進行過濾、處理、包裝後,構形成一個新的消息類型,將消息 繼續發送出去,等待其餘消息接受者訂閱處理該消息。所以基於事件(消息對象)驅動 的業務架構能夠是一系列的流程。因爲消息發送者不須要等待消息接受者處理數據就能夠返回,系統具備更好的響應 延遲;同時,在網站訪問高峯,消息能夠暫時存儲在消息隊列中等待消息接受者根據自 身負載處理能力控制消息處理速度,減輕數據庫等後端存儲的負載壓力。
2.2分佈式消息隊列
隊列是一種先進先出的數據結構,分佈式消息隊列能夠看做將這種數據結構部署到 獨立的服務器上,應用程序能夠經過遠程訪問接口使用分佈式消息隊列,進行消息存取 操做,進而實現分佈式的異步調用,基本原理如圖7.2所示。
消息生產者應用程序經過遠程訪問接口將消息推送給消息隊列服務器,消息隊列服 務器將消息寫入本地內存隊列後當即返回成功響應給消息生產者。消息隊列服務器根據 消息訂閱列表査找訂閱該消息的消息消費者應用程序,將消息隊列中的消息按照先進先出(FIFO)的原則將消息經過遠程通訊接口發送給消息消費者程序。
目前開源的和商業的分佈式消息隊列產品有不少,比較著名的如Apache ActiveMQ 等,這些產品除了實現分佈式消息隊列的通常功能,在可用性、伸縮性、數據一致性、 性能和可管理性方面也作了不少改善。
在伸縮性方面,因爲消息隊列服務器上的數據能夠看做是被即時處理的,所以相似 於無狀態的服務器,伸縮性設計比較簡單。將新服務器加入分佈式消息隊列集羣中,通 知生產者服務器更改消息隊列服務器列表便可。
在可用性方面,爲了不消費者進程處理緩慢,分佈式消息隊列服務器內存空間不 足形成的問題,若是內存隊列已滿,會將消息寫入磁盤,消息推送模塊在將內存隊列消 息處理完之後,將磁盤內容加載到內存隊列繼續處理。
爲了不消息隊列服務器宕機形成消息丟失,會將消息成功發送到消息隊列的消息 存儲在消息生產者服務器,等消息真正被消息消費者服務器處理後才刪除消息。在消息 隊列服務器宕機後,生產者服務器會選擇分佈式消息隊列服務器集羣中其餘的服務器發佈消息。
分佈式消息隊列能夠很複雜,好比能夠支持ESB(企業服務總線)、支持SOA (面向 服務的架構)等;也能夠很簡單,好比用MySQL也能夠看成分佈式消息隊列:消息生產 者程序將消息看成數據記錄寫入數據庫,消息消費者程序査詢數據庫並按記錄寫入時間 戳排序,就實現了一個事實上的分佈式消息隊列,並且這個消息隊列使用成熟的MySQL 運維手段,也能夠達到較高的可用性和性能指標。
3.利用分佈式服務打造可複用的業務平臺
使用分佈式服務是下降系統耦合性的另外一個重要手段。若是說分佈式消息隊列經過 消息對象分解系統耦合性,不一樣子系統處理同一個消息;那麼分佈式服務則經過接口分 解系統耦合性,不一樣子系統經過相同的接口描述進行服務調用。
巨無霸應用系統帶來以下幾點問題。
a.編譯、部署困難:對於網站開發工程師而言,打包構建一個巨型應用是一件痛苦 的事情,也許只是修改了一行代碼,輸入build命令後,抽完一支菸,回來一看,還在 building;又去喝了一杯水,回來一看,還在building;又去7—次廁所,回來一看,還在 building;好不容易build結束,一看編譯失敗,還得重來
b.代碼分支管理困難:複用的代碼模塊由多個團隊共同維護修改,代碼merge的時 候總會發生衝突。代碼merge —般發生在網站發佈的時候,常常和發佈過程當中出現的其餘問題互相糾結在一塊兒,顧此失彼,致使每次發佈都要拖到半夜三更。
c.數據庫鏈接耗盡:巨型的應用、大量的訪問,必然須要將這個應用部署在一個大 規模的服務器集羣上,應用與數據庫的鏈接一般使用數據庫鏈接池,以每一個應用10個連 接計,一個數百臺服務器集羣的應用將須要在數據庫上建立數千個鏈接。數據庫服務器 上,每一個鏈接都會佔用一些昂貴的系統資源,以致於數據庫缺少足夠的系統資源進行一 般的數據操做。 •
d.新增業務困難:想要在一個已經如亂麻般的系統中增長新業務,維護舊功能,難 度可想而知:一腳踩進去,發現全都是雷,什麼都不敢碰。許多新工程師來公司半年了, 仍是不能接手業務,由於不知道水有多深。因而就出現這種怪現象:熟悉網站產品的「老 人」忙得要死,加班加點幹活;不熟悉網站產品的新人一幫忙就出亂,跟着加班加點; 整個公司熱火朝天,加班加點,卻仍是常常出故障,新產品遲遲不能上線。
解決方案就是拆分,將模塊獨立部署,下降系統耦合性。拆分能夠分爲縱向拆分和 橫向拆分兩種。縱向拆分:將一個大應用拆分爲多個小應用,若是新增業務較爲獨立,那麼就直接 將其設計部署爲一個獨立的Web應用系統。橫向拆分:將複用的業務拆分出來,獨立部署爲分佈式服務,新增業務只須要調用 這些分佈式服務,不須要依賴具體的模塊代碼,便可快速搭建一個應用系統,而模塊內 業務邏輯變化的時候,只要接口保持一致就不會影響業務程序和其餘模塊。如圖7.4所示。
縱向拆分相對較爲簡單,經過梳理業務,將較少相關的業務剝離,使其成爲獨立的 Web應用。而對於橫向拆分,不但須要識別可複用的業務,設計服務接口,規範服務依 賴關係,還須要一個完善的分佈式服務管理框架。
3.1 大型網站分佈式服務的需求與特色
對於大型網站,除了 Web Service所提供的服務註冊與發現,服務調用等標準功能, 還須要分佈式服務框架可以支持以下特性。
負載均衡:對熱門服務,好比登陸服務或者商品服務,訪問量很是大,服務須要部署在一個集 羣上。分佈式服務框架要可以支持服務請求者使用可配置的負載均衡算法訪問服務,使 服務提供者集羣實現負載均衡。
失效轉移:可複用的服務一般會被多個應用調用,一旦該服務不可用,就會影響到不少應用的 可用性。所以對於大型網站的分佈式服務而言,即便是不多訪問的簡單服務,也須要集 羣部署,分佈式服務框架支持服務提供者的失效轉移機制,當某個服務實例不可用,就 將訪問切換到其餘服務實例上,以實現服務總體高可用。
高效的遠程通訊:對於大型網站,核心服務天天的調用次數會達到數以億計,若是沒有高效的遠程通 信手段,服務調用會成爲整個系統性能的瓶頸。
整合異構系統:因爲歷史發展和組織分割,網站服務可能會使用不一樣的語言開發並部署於不一樣的平 臺,分佈式服務框架須要整合這些異構的系統。
對應用最少侵入:網站技術是爲業務服務的,是否使用分佈式服務須要根據業務發展規劃,分佈式服 務也須要漸進式的演化,甚至會出現反覆,即便用了分佈式服務後又退回到集中式部署,分佈式服務框架須要支持這種漸進式演化和反覆。固然服務模塊自己須要支持可集中式 部署,也可分佈式部署。
版本管理:爲了應對快速變化的需求,服務升級不可避免,若是僅僅是服務內部實現邏輯升級, 那麼這種升級對服務請求者而言是透明的,無需關注。但若是服務的訪問接口也發生了 變化,就須要服務請求者和服務提供者同時升級纔不會致使服務調用失敗。企業應用系 統能夠申請停機維護,同時升級接口。可是網站服務不可能中斷,所以分佈式服務框架 須要支持服務多版本發佈,服務提供者先升級接口發佈新版本的服務,並同時提供舊版 本的服務供請求者調用,當請求者調用接口升級後才能夠關閉舊版本服務。
實時監控:對於網站應用而言,沒有監控的服務是不可能實現高可用的。分佈式服務框架還需 要監控服務提供者和調用者的各項指標,提供運維和運營支持。
3.2分佈式服務框架設計
大型網站須要更簡單更高效的分佈式服務框架構建其SOA ( Service Oriented Architecture面向服務的體系架構)。據稱Facebook利用Thrift ( 一個開源的遠程服務調用框架)管理其分佈式服務,服務的註冊、發現及調用都經過Thrift完成,但對於一個大型網站可使用的分佈式服務框架,僅有Thrift還遠遠不夠,遺憾的是,Facebook沒有開源其基於Thrift的分佈式服務框架。目前國內有較多成功實施案例的開源分佈式服務框架是 阿里巴巴的 Dubbo (http://code.alibabatech.com/wiki/display/dubbo/Home/ )。咱們以阿里巴巴分佈式開源框架Dubbo爲例,分析其架構設計,如圖7.6所示。
服務消費者程序經過服務接口使用服務,而服務接口經過代理加載具體服務,具體 服務能夠是本地的代碼模塊,也能夠是遠程的服務,所以對應用較少侵入:應用程序只 須要調用服務接口,服務框架根據配置自動調用本地或遠程實現。服務框架客戶端模塊經過服務註冊中心加載服務提供者列表(服務提供者啓動後自 動向服務註冊中心註冊本身可提供的服務接口列表),査找須要的服務接口,並根據配置 的負載均衡策略將服務調用請求發送到某臺服務提供者服務器。若是服務調用失敗,客 戶端模塊會自動從服務提供者列表選擇一個可提供一樣服務的另外一臺服務器從新請求服 務,實現服務的自動失效轉移,保證服務高可用。
Dubbo的遠程服務通訊模塊支持多種通訊協議和數據序列化協議,使用NIO通訊框 架,具備較高的網絡通訊性能
4.利用幵放平臺建設網站生態圈
大型網站爲了更好地服務本身的用戶,開發更多的增值服務,會把網站內部的服務 封裝成一些調用接口開放出去,供外部的第三方開發者使用,這個提供開放接口的平臺 被稱做開放平臺。第三方開發者利用這些開放的接口幵發應用程序(APP)或者網站,爲更多的用戶提供價值。網站、用戶、第三方開發者互相依賴,造成一個網站的生態圈, 既爲用戶提供更多的價值,也提升了網站和第三方開發者的競爭能力和盈利能力。
開放平臺是網站內部和外部交互的接口,外部須要面對衆多的第三方開發者,內部 須要面對網站內諸多的業務服務。雖然每一個網站的業務場景和需求都各不相同,可是開 放平臺的架構設計卻大同小異,如圖7.7所示。
API接口:是幵放平臺暴露給開發者使用的一組API,其形式能夠是RESTful、 WebService、RPC等各類形式。
協議轉換:將各類API輸入轉換成內部服務能夠識別的形式,並將內部服務的返回 封裝成API的格式。
安全:除了通常應用須要的身份識別、權限控制等安全手段,開放平臺還須要分級 的訪問帶寬限制,保證平臺資源被第三方應用公平合理使用,也保護網站內部服務不會 被外部應用拖垮。
審計:記錄第三方應用的訪問狀況,並進行監控、計費等。
路由:將開放平臺的各類訪問路由映射到具體的內部服務。
流程:將一組離散的服務組織成一個上下文相關的新服務,隱藏服務細節,提供統 一接口供開發者調用。