第1章和第2章講述可伸縮系統的核心概念與軟件設計原則。強烈建議認真閱讀這兩章,這部份內容包含了開發一個可伸縮的Web系統甚至開發一個良好軟件的基本原理和設計原則,是其它一切技巧和方法的元規則。第9章講述可伸縮的系統運維及可伸縮的我的和團隊,若是你正處在一個高速發展的創業團隊中,若是你對從技術走向管理感興趣,我相信你能夠從本章的內容中收穫多多。前端
習慣的舊秩序被互聯網打破了;這是一個創新的時代,任何奇思妙想都有被用戶接受成爲現實的可能java
線下的思惟轉換爲線上的思惟算法
互聯網催生了一種新型的生活方式,也催生了適應於互聯網的一種新的人羣。數據庫
鏈接人羣和線上生活的紐帶就是一個一個的互聯網及移動互聯網的應用。編程
做爲應用的典型IT形態,互聯網應用經歷了獨佔型應用、SOA應用,在雲時代就是雲原生(Cloud Native)的應用。後端
構建一個良好的可伸縮的應用,不只僅是一個優秀工程師的職責,同時也表明了對一種生活方式的承認。設計模式
彈性架構的概念,軟件設計的原則,以及如何構建一個優質的互聯網應用。數組
一個最初由兩三個工程師開發的產品雛形,如何通過逐漸地重構、演化、迭代、伸縮,最終成爲一個巨無霸系統?瀏覽器
第一章和第二章講述可伸縮系統的核心概念與軟件基本設計原則,這部份內容包含了開發一個可伸縮的Web系統甚至開發一個良好的軟件的基本原理和設計原則,是其它一切技巧和方法的元規則。緩存
能夠在工做中遇到問題時快速瀏覽,尋找方法和靈感。
可伸縮系統的設計是一種權衡的藝術,必須對第一種方案的優缺眯都瞭如指掌,才能在面對實際問題時作出最合適的選擇。
高併發可伸縮系統的設計看似紛繁複雜龐大無比,實際上關鍵的核心技術也就那麼幾樣,若是深刻掌握了這些關鍵技術,就抓住了可伸縮系統設計的核心。這幾樣關鍵技術,可能須要在不一樣場景,從不一樣視角反覆思考琢磨,才能真正掌握。
胸有成竹
極具彈性的系統,在快速變化的內外部環境中保持快速響應的能力。
那些在傳統企業中須要花費全年時間才能搞定的事情,創業公司可能必須在幾個星期內搞定。若是足夠成功而且足夠幸運,可能須要在幾個星期內把你的系統的處理能力擴大十倍。固然,也可能在幾個月後又把系統的處理能力縮小回去。
就伸縮性技術自己而言,目前不少公司提供這方面的基礎服務,好比亞馬遜雲、微軟雲、Google雲,以及阿里雲,以及阿里雲,騰訊雲等,這些雲服務可讓你徹底沒必要考慮伸縮性方法的問題而只需關注自身的業務需求。
要徹底理解伸縮性須要一個漸進的過程。
在閱讀過程當中把這些基礎設施圖及架構圖再畫一次,這不只有助於理本書的內容,若是隻是匆匆一掃而過,可能會忽略不少有用的信息。
伸縮性是指系統能夠根據需求和成本調整需求和成本自身處理能力的一種能力。伸縮性經常意味着系統能夠改變自身的處理能力以知足更多用戶訪問、處理更多數據而不會對用戶體驗形成任何影響。此外,還有一件重要的事情必須提醒工程師們注意,伸縮性不只須要伸(加強系統處理能力,即擴容),有時候也須要縮(縮減系統處理能力,即縮容)。同時,這種伸縮還必須相對比較省錢和快速。
正如在不一樣場景下對伸縮性有不一樣要求,能夠在不一樣維度上對伸縮性進行度量。
伸縮性主要從如下幾個方面度量:
處理更多數據
處理更高的併發
度量併發的指標一般是:應用系統能夠同時服務多個用戶。
對於一個Web應用系統而言,併發度的意思是最多有多少個用戶能夠同時訪問你的網站而不會感到訪問速度變慢。
因爲服務器的CPU數目是有限的,能同時運行的線程數也是有限的,所以處理高併發是一件很是有挑戰的事。若是還要同步某些代碼的執行以保證數據的一致性,那些這個挑戰將變得更加艱難。
更高的併發也意思着系統須要同時打開更多的鏈接,啓動更多線程,處理更多消息,CPU須要更屢次上下文切換。
處理更高頻次的用戶交互
用戶交互是指你的系統和你的用戶之間的交互頻次。這個維度和併發度看起來很像,可是稍有不一樣。交互頻次衡量你的用戶和你的服務器交換信息的頻繁程度。與交互頻次相關的最大挑戰是響應得延遲。
若是應用的交互頻次在增加,你就必須讓應用響應得更快速,這就要求系統有更快的讀寫速度進而將系統併發度推到一個更高的高度。
例如若是你作的是一個Web站點,用戶大概須要第15秒或2分鐘從一個頁面跳轉到另外一個頁面;
但若是你作的是一個多人交互的移動遊戲,用戶可能須要每秒種和服務器通訊好幾回,所以交互頻次相對獨立於用戶併發度。
一般會綜合上述三個維度雲考量一個系統的伸縮性。通常說來,實現系統伸縮性的過程,擴容比縮容更重要也更常見。
性能更多的是衡量系統處理一個請求或者執行一個任務須要花費多長時間
伸縮性則更多關注系統是否可以隨着請求數量的增長(或減小)而相應地擁有適應的處理能力
EG:你有100個併發用戶,每一個用戶平均第5秒發送一個請求,那麼系統吞吐量就是每秒20個請求。
性能指的就是系統須要花費多少時間處理這每秒20個請求
伸縮性指的是在不影響用戶體驗的前提下,系統最多可以處理多少個併發用戶及用戶最多能發送多少個請求
軟件產品的伸縮性也許會受限於軟件開發團隊的規模。若是要系統具備伸縮性,那麼對應的軟件開發團隊也必須具備伸縮性,不然就沒有足夠的工程師資源去快速影響需求變動。
儘管看起來軟件開發團隊的伸縮性和技術無關,但事實上系統架構和設計會影響團隊規模。
若是你的系統設計是緊耦合的,全部人都在一份代碼上工做,那麼你就很難擴展你的工程師團隊
因爲團隊的溝通效率和團隊規模成指數關係,所以當完成同一個工做的技術團隊的規模達到8-15人時,效率就會變得很是低。
爲了更好地體會伸縮性對創業公司的影響,讓咱們試着從公司視角觀察。問你本身:制約咱們公司持續發展的問題有那些?
答案不只包括咱們前面提到的處理同吞吐量致使的技術架構方面的問題,也包括開發過程、團隊、代碼結構等一系列問題。
這裏展現的不少伸縮性演化架構階段僅僅在你開始作規劃時有參考意義。
不少狀況下,真實世界並不徹底按照這個方式去演化,更可能會經歷屢次重寫。還有一些時候,系統在設計和誕生之初就處於某個演化的特定階段並一直保持不變,或者在發現架構制約的時候直接向上跳躍一到兩個階段而不是逐步演化
儘可能避免徹底重寫應用,特別是創業公司。重寫老是比你預期的時間要長,也比你預估的難度更大。基於個人經驗,一次重寫帶來的麻煩須要兩年才能終結。
除DNS外,網站服務器須要響應各類Web頁面、圖片、CSS文件和視頻文件,全部的響應都只在這一臺服務器上處理,全部的網絡流量都只通過這一臺服務器進行傳輸。
最便宜的主機方案是共享主機,用戶只購買一個用戶賬號而沒有管理權限,這個賬號和其它客戶的賬號安裝在一個服務器上。這種主機方案適用於那些只須要一個最小的Web站點或者只有一個着陸頁(Loading Page)的網站。可是這種方案限制太多,不值得推薦。
瓶頸:
你的用戶在持續增加,所以訪問量在持續增加。每一個用戶訪問都會對服務器形成負載壓力,服務每一個用戶又須要更多的計算資源,包括內存、CPU時間,以及磁盤讀寫(I/O)。
你的數據庫存儲的數據在持續增加。在這種狀況下,數據庫因爲須要更多的CPU、內存和I/O請求而變慢。
你要擴展系統增長新的功能,這使得每一次用戶請求都要消耗更多的系統資源
上面這些因素經常會疊加在一塊兒。
一旦你的應用達到服務器的極限(因爲網絡流量、數據處理規模或者併發度等因素的增加),就必須決定如何去伸縮系統。
有兩種不一樣的伸縮性方案:
垂直伸縮
水平伸縮
經過升級硬件和網絡吞吐能力能夠實現垂直伸縮。因爲不須要改變應用架構,也不須要重構任何東西,因此一般被認爲是最簡單的短時間伸縮性方案。
垂直伸縮的方案有不少:
經過使用RAID(獨立冗餘磁盤陣列)增長I/O吞吐能力。I/O吞吐量和磁盤存儲是數據庫服務器的主要瓶頸。使用RAID並增長磁盤數量有助於將讀寫請求分佈到多個磁盤上。最近幾年,RAID10變得格外流行,這種RAID方案即提供了數據冗餘存儲又提升數據吞吐能力。從應用角度看,整個RAID看起來就是一個數據卷標,可是在實際底層實際包含了多個磁盤共同對外提供讀寫訪問。
經過切換到SSD(固態硬盤)改善I/O訪問速度。隨着固態硬盤技術愈來愈成熟,價格愈來愈低,SSD也變得愈來愈流行。根據不一樣的其準測試方法,SSD隨機讀寫速度大約比傳統磁盤快10到100倍。應用經過替換SSD硬盤能夠節約更多I/O等待時間。不過,順序讀寫的速度提高就沒有這麼高了,因此現實中應用性能提高也沒有特別巨大。事實上,大多數開源數據庫(好比MySQL)會優化數據結構和算法,儘量多地執行順序硬盤操做而不是隨機操做。而某些數據存儲系統,好比Cassandra,作的更完全,全部的寫操做和多數讀操做都只使用順序I/O操做,這也使得SSD更缺少吸引力。
經過增長內存減小I/O操做(若是你的應用部署在獨立的物理服務器上,128GB內存是一個比較實惠的常規配置)。增長內存意味着文件系統有更多的緩存空間,應用程序有更多的工做內存。此外,內存大小對於數據庫服務器效率的提高也格外重要。
經過升級網絡接口或增長網絡接口提升網絡吞吐能力。若是你的服務器須要處理大量視頻等媒體內容,也許須要升級網絡供應商鏈接,甚至升級網絡適配器以得到更高的吞吐能力。
更新服務器得到更多處理器或者更多虛擬核。擁有12或者24線程(虛擬核)的服務器是一個比較實惠且合理的升級方案。CPU和虛擬核越多,可以同時執行的進程數就越多,系統所以變得更快,這不只僅是由於多個進程能夠沒必要共享同一個CPU,還由於操做系統不須要在同一核執行多個進程而執行沒必要要的上下文切換。
垂直伸縮的一些嚴重的制約:
成本。當越過某個點後,垂直伸縮會變得格外昂貴。
垂直伸縮是有極限的,這是個比較大的問題。不管你願意花多少錢,內存都不可能無限地增長下去。相似的限制還有CPU的速度,每臺服務器的虛擬核數目,硬盤的速度。簡單的說,到了某個極限,沒有任何硬件能力可以繼續增長。
操做系統的設計或者應用程序自身制約着垂直伸縮最多隻能達到某個點。舉個例子,你不能經過增長CPU數目而無限增長MySQL的處理能力,由於同時鎖競爭也會增長(特別是若是你使用MyISAM這種比較老的MySQL存儲引擎)
若是多個線程共享諸如內存、文件這類資源時,須要用鎖進行同步訪問。低效的鎖管理會致使鎖競爭成爲瓶頸。
應用操做應該使用細粒度的鎖,不然,應用須要花費很長的時間去等待釋放鎖。一旦鎖競爭成爲瓶頸,增長更多的CPU核數也不會改善應用的處理能力。
高性能的開源應用或商業應用能夠擴容到幾十個CPU核,然而,在購買新硬件前最好仍是要確認下系統的擴容伸縮能力。因爲高效的鎖管理是一項有挑戰的任務,須要大量的經驗和詳細的調試,因此本身開發的應用一般在鎖競爭方面作的差一點。在一些極端的狀況下,由於在設計之初就沒有考慮任何高併發的場景,結果致使增長CPU覈對應用處理能力提高沒有任何幫助。
垂直伸縮不會對系統架構產生任何影響。你能夠垂直伸縮擴容任何一臺服務器、網絡鏈接或者路由器而無須修改任何代碼或者重構任何東西。惟一要作的就是用更強大更快速的硬件替換現有的硬件。
單一服務器,可是是更強大的服務器
服務分離背後的核心理念是你應該將整個Web應用切分紅一組不一樣的功能模塊,而後將它們獨立部署。這種基於功能將系統劃分紅獨立可伸縮的模塊的方式稱爲功能分割
另外一個簡單的方案是經過在不一樣的物理機上安裝不一樣類型的服務,使得一個系統的不一樣部分被分離部署在不一樣物理服務器上。要這種場景下,一個服務能夠是一個相似Web服務器這樣的應用(好比Apache)或者是一個數據庫引擎(好比MySQL)。這就使得應用服務器和數據庫分離到不一樣的服務器上。
相似地,也能夠把FTP、DNS、緩存及其它服務器部署在不一樣的物理機器上。
對單一服務器部署進行可伸縮擴容,對可分離的服務進行分隔部署是一種比較輕的解決方案。不過,這種伸縮方案並不能一直持續進行下去,一旦你的全部服務類型都已經分別部署在獨立服務器上,就沒有辦法用這種方法擴容下去了。
緩存是一種重要的服務,目標是經過快速返回提早生成好的內容下降請求響應延遲。緩存是構建可伸縮系統的一種重要技術。
服務器經過在第三方數據中心,數據中心由一組安裝不一樣功能的服務器組成。每一個服務器都承擔不一樣的角色,好比Web服務器、數據庫服務器、FTP、緩存等。
對單一服務器部署而言,服務分離是一種巨大進步。相比之前,能夠將負載壓力分攤到更多的服務器上,並且能夠按需進一步對每一個服務器進行垂直伸縮。
若是某個微型網站受歡迎,訪問量大,就會把它分離出來,部署在一臺獨立的Web服務器和一臺獨立的數據庫服務器上。
一個Web應用利用功能分割將負載分佈在更多服務器的場景。應用的每一個部分都使用不一樣的二級域名,這樣就能夠基於Web服務器的IP地址進行流量分發。
不一樣的功能模塊也許部署在不一樣的服務器上,這些不一樣的功能模塊也會有不一樣的垂直伸縮需求。顯然,一個系統越是可以分割成不一樣的部件,每一個部件就越有彈性越好。
隨着應用不斷髮展用戶不斷增加,可能經過CDN服務減輕應用網絡流量負載壓力。
CDN的全稱是Content Delivery Network,即內容分發網絡。其基本思路是儘量避開互聯網上有可能影響數據傳輸速度和穩定性的瓶頸和環節,使內容傳輸的更快、更穩定。
CDN是一種提供靜態文件(好比圖片、JavaScript、CSS及視頻)全球分佈的服務。它的工做原理有點像HTTP代理。用戶若是須要下載的圖片、JavaScript、CSS或者視頻內容,能夠經過鏈接CDN服務器下載而不是鏈接應用服務器。若是CDN服務器沒有用戶須要的內容,CDN服務器會請求服務器獲取這部份內容而後再緩存到CDN服務器上。一旦文件緩存在CDN服務器上,後面的用戶就不再須要鏈接應用服務器了。
經過將應用集成到某個CDN服務,能夠顯著減小應用服務器須要的網絡帶寬。能夠用更少的Web服務器提供Web應用靜態內容。CDN會從距離用戶最近的服務器提供靜態內容,進而加速這些用戶的頁面加載時間。
咱們不是必定要增長不少的服務器或者學習如何對HTTP代理進行伸縮。咱們只須要簡單地使用第三方的服務,而後依賴它提供的伸縮性能力就能夠了。雖然這看起來有點像"伸縮性遊戲的小把戲」,但它真的是很是強大的手段,特別是在創業公司的早期開發階段,可能根本就沒有足夠的時間和金錢去調查這些技術。
水平伸縮是指經過增長服務器提高計算能力的一類架構方法。
水平伸縮被認爲是伸縮性的聖盃,水平伸縮能夠克服垂直伸縮帶來的單位計算成本 隨着計算能力增長而迅速飆升的老是。另外水平伸縮老是能夠增長更多服務器,這樣就不會像垂直伸縮那樣遭遇到單臺服務器的極限。
水平伸縮能夠經過修改應用架構在後期進行「添加」,可是大多數狀況下,必須付出至關的開發代價。
一個真正意義上的可伸縮系統不須要很強大的服務器,甚至相反,它們運行在大量的廉價商業服務器上,而不是少許的強大的服務器上。
水平伸縮技術帶來的好處要在系統發展的後期才能體現出來。一開始,水平伸縮由於技術比較複雜須要更多的工做量,會花費更多的成本。
這些成本體如今可水平伸縮的架構比基本的架構須要部署更多的服務器
水平伸縮的架構須要更有經驗的工程師去構建並維護它們
不過,一旦你的系統的計算能力達到某個點,水平伸縮就變成更好的策略。
使用水平伸縮,能夠沒必要花費購買頂級服務器的高昂的價格,也不會觸及垂直伸縮會出現的天花板(買不到更強大的硬件)
水平伸縮使用CDN這樣的第三方服務不只更節約成本,並且更透明。
雲服務商願意爲高流量的客戶提供更低的價格,由於這些客戶以前的付費已經覆蓋了必要的維護集成成本。對於訪問量很大的客戶,他們也確實很在乎價格高低。
具備水平伸縮特性的系統和先前架構演化階段說起的系統的不一樣之在於:
數據中心的每一種服務器角色均可以經過增長服務器進行擴容伸縮
事實上,在不一樣演化階段能夠部分地實現水平伸縮,有的服務實現水平伸縮,而有的服務則不實現。
達到真正意義上的水平伸縮很困難並且須要豐富的經驗。所以,構建水平伸縮的系統先從那些容易作到的地方作起。好比Web服務器、緩存;暫時難以作到的地方,好比數據庫及其它的持久存儲
在演化的這一階段,一些應用使用輪詢DNS服務實現Web服務器流量分發。
輪詢DNS不是實現Web服務器流量分發的惟一手段
輪詢DNS是DNS服務器的特性之一,它容許將一個域名解析到多個IP地址中的一個。通常的DNS服務器會將一個域名解析到一個單一的域名。然而輪詢DNS容許將一個域名映射到多個IP地址上,每一個IP地址都指向不一樣的機器。所以,每次用戶請求域名解析,DNS都會返回這些IP地址中的一個。目的就是將每個用戶訪問的流量分發到Web服務器集羣中的某一臺上,不一樣的用戶在不知情的狀況下鏈接到不一樣服務器上。一旦用戶接收到一個IP地址,他就會只與這臺選中的服務器通訊---至關於服務器是有狀態的,若是服務器宕機也會有其它的老是,服務器集羣上訪問壓力也會不均勻。
架構演化的最後階段就是打造一個全球最大的Web站點,實現全球用戶的可伸縮性。一旦你服務的用戶從幾百萬擴展到全球,你須要的數據中心就不止一個。一個數據中心能夠部署不少的服務器,可是其它大洲用戶的體驗可能並很差,並且多個數據中心也能讓你比較從容地應對那些可能的宕機事件(水災、火災引發的停電)
服務全球用戶須要面臨不少挑戰,用到不少技巧,其中一個技巧是使用GeoDNS服務。
GeoDNS是一種基於客戶地理位置進行IP地址解析的DNS服務。通常DNS服務器收到一個域名,比較baidu.com,對臺解析成一個IP地址。GeoDNS從用戶視角看也是這樣,然而,GeoDNS會基於用戶的地理位置返回不一樣的IP地址。一個歐洲用戶和一個澳洲用戶獲得的IP地址可能不一樣,結果是每一個用戶都會訪問到距他最近的一個Web服務器。GeoDNS的目標就是將用戶分發到離他最近的數據中心進而實現最小的網絡延遲。
基礎設施層面的另外一個擴展是在全球範圍部署若干邊緣緩存(edge-cache)服務器,進一步減小網絡延遲。邊緣緩存服務器的使用依賴於應用的天然特性。邊緣緩存服務器最高效的用法是像反向代理服務器那樣緩存整個頁面。固然,邊緣服務器也會提供其它服務。
邊緣緩存是一種距離用戶較近的HTTP緩存服務器,便於部分緩存用戶的HTTP流量。從用戶瀏覽器發起的請求到達邊緣緩存服務器,邊緣緩存服務器決定從緩存中直接返回響應頁面,仍是經過發送背景請求到Web服務器獲取響應頁面的其它部分最後進行組合。
邊緣緩存服務器還能夠決定某個頁面是不可緩存的,也能夠決定是否能夠代理整個Web服務。邊緣緩存服務器能夠緩存整個頁面也能夠緩存頁面片斷。
隨着應用將來的持續發展,你也許會考慮將主數據中心切分紅多個數據中心並把它們部署到離用戶較近的位置。經過將應用和數據存儲部署到離用戶較近位置的方式,能夠實現更短的訪問延遲和更少的網絡成本。
數據中心基礎架構高層概覽1-10
前端:應用棧睥第一個部分,包含了直接與用戶設備交互的一系列組件。前端可能在數據中心內,也可能在數據中心外,這要看部署細節和第三方服務的使用狀況。這些組件不包含任何業務邏輯,主要目的是提高系統處理能力和伸縮能力。
負載均衡器是一種軟件或者硬件組件,能夠將訪問某個IP地址的訪問流量分發到多個服務器上,這些服務器則隱藏在負載均衡器的後面。負載均衡器能夠將用戶訪問負載平均地分發到多個服務器上,而且容許動態地增長或者移除服務器。因爲用戶只能看到負載均衡器,因此能夠在任什麼時候候添加新的Web服務器而無須中止服務。
Web應用層:整個應用棧的第二層是Web應用層,由Web應用服務器集羣構成,主要職責是處理用戶的HTTP請求生成最後的HTML頁面。這些服務器一般用輕量級的Web開發框架(PHP、JAVA、Ruby、Groovy等)構建,實現最小的業務邏輯,主要任務是生成用戶界面。整個Web應用層的主要用途是處理用戶交互並轉換成內部服務調用。這個Web層越簡單功能越少,整個系統越好。複雜業務邏輯應該在Web服務層完成,實現更好的複用及減小需求變動,由於展現層的變動是最頻繁的【與用戶交互的Web頁面或App】.
Web服務層:應用棧的第三層是Web服務層,由各類Web服務組成。這是很是關鍵的一層,包含了最主要的應用邏輯。展現層剝離出來的邏輯,就集中在這一層。高內聚的業務邏輯,更容易實現功能分隔;功能分隔後的服務,能夠獨立地對它進行伸縮。譬如電子商務應用中的產品類目服務和用戶信息服務是有徹底不一樣的伸縮性需求的。
附加組件,對象緩存和消息隊列。
對象緩存服務器既在前端服務器使用,也在Web服務中使用,主要目的是減輕數據存儲服務器的負載壓力及經過對部分數據進行預先計算實現響應加速。
消息隊列被用來 將某些處理延遲到稍後的階段處理,而且將處理操做委託給消息處理者服務器。發送給消息隊列的消息一般來源於前端應用及Web服務,而後這些消息被特定的消息處理者機器處理。
定時任務,不會去響應用戶請求,它們是離線的做業處理服務器,提供相似異步通知、訂單處理,以及其它一些容許較高延遲的功能。
數據持久層:通常來講,這是最難以進行水平伸縮的一層。
圖1-10中數據中心基礎架構鳥瞰圖中各組件的佈局是通過深思熟慮的,主要目的是幫助那些相對比較慢的下降負載訪問壓力。
只有在很是必要的狀況下,Web服務層纔會鏈接搜索引擎及主要數據存儲去讀寫必要的信息。
有一點須要特別注意,就是沒有必要爲了伸縮性實現圖1-10中的全部組件。相反,要儘量少地使用不一樣種類的技術,由於每增長一種技術,就是在增長系統的複雜度,也是在增長維護的成本 。使用不少不一樣的技術看起來很酷,可是卻讓發佈、維護、調試變得更困難。
若是應用只是須要一個簡單的搜索功能,也許只要一個前端服務器和一個搜索引擎集羣就可以知足全部的伸縮性需求。
若是能增長服務器實現現有的每一層的伸縮性,而且也能知足所有的業務需求,爲何還要不厭其煩地使用各類額外的組件呢?
應用架構是將業務邏輯放在應用架構的核心位置,是關於業務模型的演化,不是關於某個框架或者某個特定技術,也不是關於java、PHP、PostgreSQL或者數據庫表結構的。
業務需求驅動着各類決策。
沒有正確的領域模型和正確的業務邏輯,數據庫、消息隊列及Web框架都沒有任何意義。
無論應用是一個社交網站,仍是一個藥品服務,或者是一個視頻APP,它總歸是有某些業務須要一個領域模型。經過將這個模型放到架構的核心,確保各類組件圍繞這個核心展開,服務於這個業務,而不是其它什麼東西。
若是把技術放在覈心位置,也許會獲得一個很讚的Rails應用,但不太可能獲得一個很棒的藥品服務應用。
市面上已經有不少不錯的關於領域驅動設計和軟件架構的書,能夠幫助更好地熟悉軟件設計的最佳實踐。
領域模型是關於應用要解決的業務問題的本質描述。
領域模型表示一個應用的核心功能,重點在於業務而不是技術。領域模型解釋了關鍵術語、角色和操做,而不關心技術實現。
一個自動櫃員機(ATM)的領域模型的關鍵詞是:現金、賬戶、負債、信用、身份認證、安全策略等。
同時,領域模型不關心硬件和軟件實現。
在系統內部,應用被分解成多個(高度自治的)Web服務。
應用架構高層鳥瞰圖1-11
應用架構的核心是主要業務邏輯
前端:主要職責是成爲用戶的接口,用戶經過網頁、移動APP或者Web服務調用完成和應用的交互。是介於公開接口和內部服務調用的處理層。
前端能夠被視做是應用的「皮膚」或者應用的插件,是系統向用戶呈現的功能展現,所以不該該是系統的核心或者重點。
通常來講,前端應該儘量保持簡單。
經過保持前端簡單,能夠複用更多業務邏輯。
服務的簡單與職責單一,使服務只關注邏輯而不是視圖呈現。
業務邏輯只存在Web服務層,能夠避免視圖和業務強耦合的問題,而且能夠實現前端的獨立伸縮,即只關注處理較高的併發訪問請求。
能夠認爲前端是一個可移除的插件,能夠用不一樣的語言重寫、能夠輸入各類類型的前端。
能夠移除一個基於HTTP的前端輸入而插入一個移動應用的前端或者一個命令行前端。
即前端展現要與核心業務邏輯分離。
前端不該該關注數據庫或第三方服務。容許前端代碼出現業務邏輯會致使代碼難以複用及系統更高的複雜度而難以維護。
能夠容許前端發送事件消息給消息隊列,以及容許使用後端緩存,消息隊列和緩存都是提高性能和改善伸縮性的重要手段。
不管是緩存整個HTML頁面仍是HTML片斷,都會比在構建HTML時緩存數據庫查詢更節省處理時間。
Web服務:處理主要流程和實現業務邏輯的地方。要點服務職責單一,方便複用和伸縮。
SOA就像雪花,不會有兩個徹底相同。--David Linthicum
Web服務在整個應用架構中處於中心位置,這種架構一般被稱爲面向服務的體系架構(SOA)。
SOA是一種以低耦合和高度自治的服務爲中心的軟件架構,主要目標是實現業務需求。
SOA傾向於全部的服務都基於有清晰定義的約定,而且都使用相同的通訊協議。
在SOA定義中,不太關注SOAP、REST、JSON或者XML,這都是實現上的細枝末節而已,不論使用什麼技術或者協議,只要你的服務是鬆耦合的並解決了一組特定的業務需求就能夠了。
SOA不是全部問題的惟一答案,還有其它一些架構:好比分層架構、六角架構和事件驅動架構。
分層架構:是一種將不一樣功能劃分到不一樣層次的架構。低層的組件暴露一組API給高層組件使用,不太低層組件永遠不會依賴高層組件提供的功能。
分層架構的一個例子是操做系統及其各類組件。
每一層都消費其低層提供的服務,可是低層永遠不會消費上層提供的服務。
另外一個比較好的例子是TCP/IP編程棧,每一層都依賴低層提供的協議並增長新的功能。
分層能夠強制結構化並減小耦合,低層組件變得更簡單和系統,其它部分更少耦合。替換低層組件只要實現相同API就能夠了。
分層構架的一個重要影響方面是越到底層穩定性越強。
變動高層組件的API很容易,由於依賴它們的東西不多。可是變動低層的API就不划算了,由於有大量代碼依賴這些已經存在的API。
六角架構認爲業務邏輯是架構的中心,全部數據存儲、客戶端、其它系統之間的交互都是平等的。業務邏輯和每個非業務邏輯組件之間都有一個約定,可是沒有底層和頂層的劃分。
在六角架構中,用戶和應用的交互 與 應用和數據庫系統的交互沒有區別。它們都存在於應用業務邏輯以外並且都遵循一個嚴格的約定。定義好這些邊界,就能夠用一個自動化測試驅動代替一我的 或 用某個存儲引擎代替數據庫系統而不會對系統核心形成任何影響。
簡單地說,是以一種不一樣的方式去思考行動,即 對已經發生的事件作出反應。
傳統編程模型中,咱們認爲咱們是請求某項工做要完成的人。
譬如,createUserAccount(),咱們指望在咱們等待結果的過程當中,這個操做會被執行完成,
而後咱們繼續後面的過程。
EDA中,咱們不會等待事情被作完。
不管何時,咱們和其它組件交互,咱們只是宣佈某件事情已經發生,而後就處理後面的過程了。
能夠認爲Web服務層由若干高度自治的應用組成,每一個Web服務本身就是一個應用。
Web服務也能夠彼此依賴,不過說到底仍是少互相依賴爲好。
Web服務提供一個更高層次的抽象,可讓它看清整個系統並理解它。
每一個服務都隱藏了自身實現的細節並呈現一個簡化的高層次的API。
支撐技術
消息隊列、應用緩存、主數據存儲、搜索引擎等,這些一般是用一些其它技術實現,通常都是一些第三方的產品,經過配置和咱們的系統集成起來。
數據庫(主數據存儲)僅僅是一種技術,是實現的細節而已。
咱們並不關心須要多少服務器,如何進行伸縮,怎麼進行數據複製及容災,甚至如何存儲數據。
若是決定更換持久層存儲或者更換緩存後端,應該作到只更換數據鏈接組件就能夠,保證整個架構不受影響。
經過將數據存儲的抽象化,能夠自由選擇各類數據庫,不論MySQL仍是其它數據庫。
若是應用邏輯有不一樣的需求,也能夠考慮NoSQL數據存儲或者內存型存儲。
第三方服務在咱們的控制以外,處於咱們系統邊界以外。由於咱們不能控制它們,咱們也就不能期望它們一直動做正常、沒有BUG、像咱們指望的那樣快速伸縮。
架構是從軟件設計的角度看
基礎設施是從系統工程師的視角看
每一種視角都是從不一樣方面展現同一問題:如何構建可伸縮的軟件。
許多實際項目中遇到的伸縮性問題其實能夠歸因於違反了軟件設計的核心原則。軟件設計原則比伸縮性更抽象更通用
使事物儘量簡單,可是不要過於簡單。---阿爾伯特.愛因斯坦
軟件自然就是錯綜複雜的。
判斷一個東西是否是過於簡單的時候,首先要回答的是對誰而言及對何時而言。譬如,對你而言仍是對客戶而言過於簡單?是對如今開發而言仍是對未來維護而言?
簡單不是走捷徑,不是爲手邊的問題找一個最快的方案。
重溫寫過的代碼,區分複雜度,以此尋找簡單的方案。從本身的錯誤開始是學習的每一步。
培養敏銳的感受和能力,從而快速判斷出長期而言什麼是更簡單的方案。
若是有機會可以找到一個導師或者可以和擅長髮現簡單的人一塊兒共事,會進步更快。
1.1 隱藏複雜與構建抽象
隱藏複雜與構建抽象是實現簡化最好的方法之一。
人類的大腦處理能力有限的,要麼對系統總體有個瞭解而不知道細節,要麼知道系統的一小部分細節而不瞭解總體。
不過,若是系統很龐大,是沒法保持總體簡單的,你能作的只是保持局部簡單。
局部簡單的主要方式是確保在設計和實現兩個方面上,任何單個的類、模塊、應用的設計目標及工做原理都能被快速理解。
看到一個類時,可以快速理解它的工做原理而無須知道系統其它部分的所有細節。只看着這個類就明白這個類能幹什麼。
看一個模塊的時候,能不看這個模塊自己,而是把這個模塊看成一大堆類來看,而每個類都是可理解的。
再縮小。當看一個應用的時候,能一眼看出那些關鍵的模塊和它們的主要功能,而無須知道模塊裏的類的細節。
看整個系統的時候,能快速看出頂層的應用和它們的職責而無須知道每一個應用是如何運行的。
節點表示類,邊表示類之間的依賴。
爲了實現局部簡單,須要將不一樣的功能分割到不一樣的模塊中,這樣當你從一個比較高的抽象層次去觀察應用時,無須操做每一個模塊的職責是什麼,只需考慮這些模塊是如何交互的就能夠了。
模塊的層面,能夠忽略模塊內部的細節,只關注模塊間的交換便可。
在龐大又複雜的系統中,當你建立一個獨立服務的時候,須要添加一個抽象層。
服務暴露這個更高層次的抽象,實現這些抽象所承諾的功能,從而隱藏其複雜性。
1.2 避免過分設計
會不由自主地被那些想象出來的問題牽着鼻子走,不知不覺地就過分設計了,最後會開發出一個比實際需求複雜得多的大而無當的系統。
好的設計方法是能夠在後期逐漸添加新的功能特性,而不是一開始就開發一個超級大的系統。
要比一開始就設計好所有的功能爲未來各類可能進行開發好的多。
"這個設計是否能夠更簡單而且能夠在未來依然保持彈性"
每次進行軟件設計前都要問本身。
「這裏我須要如何作出權衡」
「這裏我是否真的須要」
也建議你和業務相關方多接觸,儘可能去了解什麼是最大的風險及還有什麼沒有搞清楚。
不然,你會按照那些沒有用的教條花費大量的時間去構建一個沒人用的系統。
1.3 嘗試測試驅動開發TDD
接受TDD的方法論能夠提升簡化性。
TDD是一種開發實踐:先寫測試代碼,而後寫功能實現代碼。
好處:
1.沒有單元測試就沒有代碼,因此也就沒有無用的代碼。
因爲開發先寫了測試,就不會寫那些不必的代碼,不然就又要去寫測試代碼。 😙
2.UT能夠被當作某種文檔,能夠展現代碼的實現意圖、具體用法、指望的執行結果等
TDD會強制工程師從用戶的角度看待問題,有助於開發更清晰更簡單的接口。由於是先寫UT,因此必需要先想象如何使用你要開發的組件,就不會只想着這個組件的內部實現細節。
這種細微的差異會帶來代碼設計上的巨大提升和應用程序接口API的簡化
要從用戶視角思考問題,這樣就會更好地開發那些易於用戶調用的代碼。
別人使用的你開發的接口,那麼他想調用的方法是什麼?
他想傳遞的參數是什麼?
他期待返回的響應結果是什麼?
1.3 從軟件設計的簡化範例中學習
若是複雜性處理得夠好,反而難以體會軟件簡化的理念。若是事情老是順其天然,若是能夠絕不費力地理解系統,那麼也許你遇到的是精雕細琢後的某種簡化。認識到你使用的系統是良好設計的系統是一種很是讚的體驗。不管什麼時候發現這一點,仔細分析它並尋找其中的模式。Grails、Hadoop、Google Maps API,都是一些簡化作得很好的範例,值得認真研究。 1.3.1 Grails是Groovy語言上的一個Web框架。Grails是一個簡化如何變得透明的極好的例子。隨着研究這個框架的深刻並使用,你會意識到這裏的每同樣東西都被仔細考慮過。你會看到事情問題和指望的同樣,擴展功能老是絕不費力。你會意識到這個框架就得更簡單是不可想象的。Grails是一件讓開發變得容易的傑做。 Grails in Action及Spring Recipes1.3.2 Hadoop 熟悉MapReduce編程範式及Hadoop平臺。Hadoop是開源技術領域的傑做,能夠處理PB級的數據。Hadoop是一個龐大而複雜的平臺,可是它很好隱藏了期複雜的實現。開發發現它解決了多少困難的問題,才讓開發者處理幾乎無限的數據變得如此簡單。若是想對MapReduce和Hadoop有個基本的瞭解,推薦閱讀MapReduce原始論文及Hadoop in Action
1.3.2 Google Maps API一直都以一種格外簡單的方式使這些API在解決複雜問題的同時保持足夠的彈性。one person->workshop->studio
小結:簡單對你的系統的伸縮性有一些潛在的價值。沒有簡單性,工程師就無法理解代碼,不能理解軟件,系統就沒法保證持續發展。必定要記住:
2.低耦合(coupling)【高內聚(cohesion)】:第二個重要的設計原則是保持系統各個部分之間儘可能低耦合。
耦合用來度量兩個組件之間互相關聯及依賴的程度。耦合度越高,依賴強。低耦合是指兩個組件之間只有必要的一點點關聯和依賴,而沒有耦合則是指兩個組件之間徹底不感知對方的存在。
保持系統低耦合對於系統的健康及伸縮性相當重要,它甚至會影響團隊的土氣。低耦合和高耦合的不一樣影響:
高耦合意味着任何一行代碼的改動都須要檢查系統各個部分是否存在問題。耦合度越高,各類出乎意料的依賴越多,引入BUG的機會越高。譬如,若是對用戶認證作了一點改動,而後你想起還必須重構其它五個模塊,由於這些模塊都依賴了認證的內部實現。
低耦合能夠保證複雜性局部化,即複雜只體如今模塊內部,不影響整個系統。將系統分解成低耦合的多個部分,能夠安排多個工程師分別獨立開發各個部分。這樣就能夠招募更多的工程師擴展你的團隊,每一個人均可以在不瞭解整個系統細節的狀況下針對局部模塊進行開發。
在更高層面上進行解耦合意味着將系統分紅多個應用,每一個應用都只關注相對較小的一部分功能。這樣每一個應用就能夠按需分別伸縮。有些應用須要更多CPU,而有些應用則須要更多IO吞吐能力或者更多內存。經過將系統解耦成不一樣部分,能夠針對具體應用特色提供相應的硬件配置以達到更好的伸縮性。
2.1促進低耦合。
這個準則適用於類之間的依賴,模塊之間的依賴,以及應用之間的依賴系統是所有---它包括了一切:你開發的全部應用和你係統環境中用到的全部軟件。應用是系統內部最高層次的抽象,它們提供最高層次的應用服務。你也許會用到五個賬戶應用,五個資產管理應用,或者五個文件存儲應用。在應用內部會有一個或者多個模塊負責實現更精細量多具體的功能特性。就像不一樣的應用一般是由不一樣的團隊開發和部署,不一樣模塊(好比信用卡處理、PDF展示、FTP接口)也應該對依賴它的開發團隊保持足夠獨立,以便它們能夠並行開發。若是你不夠信任某個團隊開發的某個特定模塊,那最好不要讓這個模塊和你的應用耦合得太緊。最後,模塊內部包括了一些類,這是最小的抽象粒度。一個類應該只有一個目的並且代碼最好不要超過兩三屏。
能夠使用public、protected、private關鍵字促進低耦合。應該儘量將方法聲明爲private或protected你的類越少訪問其它類,你就越少須要關注這些類的工做原理。由於private方法只在類的內部被調用,因此這些方法能夠被更容易地重構和修改,類的複雜性也被侷限在類的內部,不用處處查找這些方法在哪裏被使用,是否會引發潛在的BUG。暴露太public方法,就會增長外部代碼使用它們的機會。一旦一個方法是public的,你就不知道它會在哪裏被使用,你就不得不在應用中仔細查找那些調用它的地方。
在寫代碼的時候,要吝嗇一點。在知足需求的狀況,儘可能少暴露內部信息和功能。暴露的信息越多越早,越增長耦合性,將來需求變動的時候越麻煩。這個法則也適用於各個抽象級別,不管是類、模塊,仍是應用。
要在較高的抽象層面下降耦合度,你須要讓系統不一樣部分的接觸面儘可能少。低耦合可讓你重構或者替換系統中的任何部分而不用對其他部分作太多調整。找到解耦與過分設計之間的平衡是個藝術活,工程師常常會忽略抽象的重要性。能夠經過設計圖來幫助本身作出正確的權衡。當你畫應用設計圖的時候,鏈接兩個部分之間表示依賴關係的線條的數目決定接觸面的大小。
低耦合,模塊B的對外開放功能被隔離爲一個很小的子集並被顯式地聲明爲public,這樣能夠下降和外界的接觸面,而且讓模塊有更好的私密性。低耦合的應用中模塊間沒有循環依賴。2.2 避免沒必要要的耦合在實踐中,某些被普遍使用的方法事實上會增長耦合性。一個典型的致使沒必要要的耦合的例子是經過public的getter和setter方法暴露類的private成員變量。這種用法源於java Bean的一些早期實踐,當時提供getter和setter方法主要是爲了適應代碼操做工具和IDE。這種實踐在Java社區範圍內都被徹底地誤解了。代碼和IDE集成這種不良的習慣也會影響到其餘的語言和平臺。
最好一開始的時候所有方法都是private,而後視狀況將那些須要的方法改爲protected或public。
有時這些作是有合理緣由的,可是更多的時候是源於糟糕的API設計,好比須要調用一個初始化方法。類及模塊的客戶端使用者應該不須要知道類的設計者指望他們如何使用這些類及模塊。
若是你要求你的客戶端使用者必須知道API須要以何種方式被調用,那麼你就是在增長耦合性。
處於不一樣層次的應用、模塊、類之間若是出現循環依賴,就意味着在設計的耦合性方面比較糟糕。通常來講,畫系統架構設計圖很容易暴露循環依賴。一個良好設計的模塊架構圖,看起來像一棵樹(有向無環圖),而不像一個社交網絡圖。2.3 低耦合範式
要想很好地理解低耦合必須經歷大量的實踐。你像理解簡單同樣,你能夠經過閱讀別人的代碼,分析別人的系統獲取大量的經驗。低耦合設計的一個很好的例子是UNIX命令行編程及管道(pipe)的用法。另外一個比較好的低耦合的例子是SLF4J。強烈建議瞭解SLF4J的結構,而且和Log4J及Java Logging API作個對比。SLF4J的角色像是一個非直接層,把log接口調用和實現的複雜性分離開來,顯露了一組更簡單更清晰更容易理解的API。低耦合是開發彈性軟件的最基本的原則之一。建議把設計低耦合的模塊放在一個更高的優先級上。
3.不要重複本身(DRY)我認爲避免重複是最有價值的原則之一。只作一次,這是極限編程的格式--Martin Fowler重複本身意味着一樣一件事你要作好幾回。
若是你把一樣一件事作了一次又一次,就是在浪費生命。不要把一件事作好幾回,而是去作些比較酷的事,好比開發一些新特性或爲客戶思考一些更好的解決方案。試着說服你所在的團隊或者你的老闆採起一些基本措施以免重複----好比,若是有重複的代碼那麼代碼審查不經過,每一個新寫的類都必須先寫單元測試。
讓開發工程師浪費時間作重複的事有不少緣由:
採用低效的過程:在軟件開發的各個階段都會發生,設計新功能、交付、開會等,經過對這些地方進行持續改進能夠得到良好的收益。得到反饋、持續改進、而後重複這個過程。不少時候,團隊意識到本身在浪費時間卻迫不得已。當你聽到「咱們就是這麼幹的」或者「咱們一直都這麼幹」時,經常意味着這裏有低效的過程並有改進的機會。
缺少自動化當手工部署、編譯、測試、打包、配置開發機器、準備服務器、爲API寫文檔的時候,-----這就是在浪費時間。巨大的浪費就是創建在這種不斷增長的微小的工做量之上。從第一天開始就嘗試將編譯部署的工做自動化,固然你也須要一直維護這些自動化的工具。
非得本身發明,也就是常說的重複發明輪子這個問題的表現症狀是:不復用已有的代碼而非要本身寫代碼。年輕的工程師,樂於重寫那些很容易就能用到的東西(對公司內部或者開源世界而言),好比實現hash功能、排序、b樹、MVC框架或者數據庫抽象層。雖然這種重複不是字面意思上的重複,但這依然是在浪費時間。由於徹底能夠使用別人已經開發好的工具和代碼庫。任什麼時候候,想寫一個通用類庫的時候,都先上網搜一下,一般已經有不少實現得很好的開源代碼了。
複製粘貼代碼爲了節約時間,把這索代碼複製粘貼過來,而後只對其中一小部分作了一點改動。祝賀你,你如今有兩份類似的代碼須要維護了。總會有某個時候,你會意識到你對這部分代碼作的任何改動都不得不在其餘地方再改一次,總會有一些已經修復有BUG會再一次出現,由於你忘了修改粘貼過來的那部分代碼了。試着讓團隊創建一些規則:好比「咱們永遠不會複製粘貼代碼」。同時給每一個人權力在代碼評審時指出重複的代碼,讓每一個人彼此之間都感覺到一些良性的壓力。
「這個代碼不會用第二次因此就湊合一下」的態度有時,這些問題會再一次出現,你就不得再也不用一次這些當初被看成一次性的隨便搞搞的代碼。那麼問題來了,這些代碼即沒有文檔,又沒有UT,也沒有合適的設計,很難用。更糟糕的是,其它工程師可能會複製粘貼這些隨便搞搞的代碼用在他本身的一次性腳本上,悲劇再一次上演。
我相信複製粘貼代碼是一個常見的問題。出現這種狀況主要是由於R&D一般沒有意識到本身寫的代碼越多,維護的成本和代價越大。複製粘貼致使應用的代碼變得更多,更多的代碼致使維護的成本更高,隨着各類技術問題的堆積,這種成本會呈指數級上升。應用中存在重複代碼,一個地方修改就須要全部重複的地方都修改,不要追蹤複製的代碼之間的差別,對全部複製粘貼的代碼作迴歸測試。因爲複雜度隨代碼行數成指數級增加,因此複製粘貼的代碼極其昂貴。事實上,複製粘貼代碼是一個很是嚴重的問題。處理代碼複製粘貼多是一件讓人很頭痛的事情,不過沒有什麼事是重構不能搞定的。一個好的開端是在代碼庫中搜索每個重複的功能。一旦你這麼作了,你就能夠更好地瞭解哪些組件受到影響而且該如何重構它們,能夠考慮建立一個抽象類或者將重複代碼抽取到一個獨立的更通用的組件裏。
另外一個對付複製粘貼代碼的好辦法是使用設計模式和共享類庫。設計模式是解決一類通用問題的抽象方法,是軟件設計層面的解決方案,能夠應用到各類系統各類領域中。設計模式須要考慮如何組織OO代碼、依賴、交互,可是不須要考慮具體的業務問題。設計模式會建議如何在模塊中組織代碼,可是不會指出須要使用算法或者業務特性及如何動做。也能夠在更高層面上經過Web服務去對付代碼複製。不要在每一個應用中開發相同的功能,而是建立一個服務,而後在整個公司範圍內複用這個服務。
阻止功能複製的一個辦法是讓功能使用變得最簡單。 例如,你的類庫提供了20個功能,80%的時間被使用的都只是其中的5個功能,那麼保持這5個功能儘量地容易使用。
容易使用的東西更容易被複用
若是你的類庫是能夠最容易完成一件事情,那麼每一個人都樂意使用你的類庫。若是使用你的類庫很困難,那麼你們就會想重複再搞一個。
4.基於約定編程,是解耦的主要方式。
基於約定編程是將客戶端代碼和功能提供代碼解耦的主要方式。客戶端僅僅看到這些約定並只依賴這些約定編程。將系統不一樣部分解耦並將變化隔離開,這會對開發帶來不少好處
約定是一組功能提供者承諾實現的規則
客戶端代碼能夠理解這些規則並依賴它們。它們描述了哪些軟件提供了哪些功能,可是不須要客戶端代碼知道這些功能是如何實現的
「約定」這個詞在不一樣場合表明不一樣意思。
當討論OOP的成員方法時,約定是指方法的簽名,定義了指望的輸入參數和指望的返回結果。約定不會規定結果該如何計算出來,這是實現的細節,使用無須關心這些。
當討論類時,約定是指類的public接口,包含了全部可訪問的方法和簽名。
到抽象層,模塊的約定包含了全部的公開可見的類/接口及它們的方法簽名。
對整個應用而言,約定一般意味着一些描述Web服務API的表格
約定有助於將客戶端和功能提供者代碼解耦。只須要保持按約定開發,客戶端和功能提供者能夠各自獨立修改。這會讓代碼更隔離更簡單。在設計代碼的時候,儘量建立明確的約定,也儘量地依賴約定(而不是實現)進行開發。每一個提供者都是獨立的模塊,因爲它們都履行相同的約定,客戶端能夠使用其中任何一個而無須直接知道究竟使用的是哪個。在客戶端中,任何完成相同約定的代碼都是同等的,這樣進行重構、UT、修改實現都變得很是容易。
想一想讓基於約定的編程更簡單,能夠考慮把約定看成真實的法律文件。當人們贊成按法律文件作事的時候,他們會變得對細節更敏感,由於若是某個條款沒作到的話,他們就要承擔相應的責任。在軟件設計中也相似,低耦合的約定的每一個部分都是在增長將來的責任。做爲一個功能提供者,太多沒必要要的東西就是在增長未來的負擔,由於任什麼時候候若是你想作出改變,你就不得不和全部的客戶對改變的約定進行談判(這個改變會在整個系統中蔓延)
設計類的時候,首先考慮的是客戶端真正須要的功能是什麼,而後設計一個最小的接口看成約定,最後實現代碼以完成約定。針對類庫和Web服務也遵循一樣的方式,當你暴露一個Web服務API的時候,應該是明確的並要仔細考慮究竟暴露給客戶端的是什麼。須要的時候,應該作到容易增長新特性及發佈更多數據,可是開始的時候應該儘量地實現一個簡單的約定。HTTP是基於約定編程的範例。HTTP被不一樣系統使用不一樣的語言在不一樣平臺中實現,而後,HTTP是最流行的協議之一。有些HTTP協議約定的客戶端是Web Browser,好比Firefox、Chrome。它們被不一樣的組織以不一樣方式實現,按不一樣的進度更新和發佈。HTTP提供者,主要是Apache,IIS,Tomcat這樣的Web服務器,代碼也被不一樣組織用不一樣的技術實現,而且被獨立地部署在全世界的各個地方。更棒的是,還有不少人們不知道的其它技術實現的HTTP約定,好比Web緩存服務器Varnish和Squid,同時實現了客戶端和提供者。客戶端和提供者經過HTTP協議鬆耦合。儘管因爲生態環境的複雜性,全部的應用都被互聯網聯繫在一塊兒,但HTTP仍是體現出實現上的彈性及提供者透明的可替代性。HTTP是經過約定進行解耦合的一個漂亮的例子,全部應用都有一個共同點,它們實現或依賴一個相同的約定。
4.畫架構圖 「你知道真正的架構是什麼嗎?是一門畫線的藝術。畫一條線鏈接依賴並指明依賴的方向」--Robert C.Martin
一張架構圖能夠表述不少信息,所謂一圖勝千言。經過架構圖,能夠將系統設計文檔化,跟其餘人分離信息,也能夠幫助本身更好地瞭解本身的設計。特別是適應了敏捷開發實踐,學了不少創業方法學之後,不會花太多時間作前期設計,可是這並不意味着一直都不須要。
若是你以爲畫架構圖挺難的,你能夠嘗試先畫那些已經開發好的系統的架構圖。你本身開發過的系統畫架構圖更容易一點,由於你畢竟更瞭解。等你熟悉了各類架構圖的畫法,再開始前期設計架構圖。從使用者的視角觀察類接口的設計並進一步充實它們,試着爲接口寫單元測試,而後畫一些簡單的架構草圖。經過假想用戶視角及畫簡單架構圖,你能夠在寫代碼以前驗證本身的設計並發現其中的缺陷。等你熟悉了畫各類架構圖之後,儘可能多作前期設計。若是你發現前期設計很難作的話,也不要沮喪,從先寫代碼變成先作設計原本就不是一件容易的事,因此請作好準備花幾個月甚至幾年的時間去熟悉這個過程。
假設你須要設計一個斷路器組件。斷路器是一種設計模式,用於加強系統健壯性,保護系統不受其它組件(或者第三方系統)失敗的影響。斷路器在你的代碼執行某個操做以前先去檢查其可用性。
開始的時候先寫主要接口的代碼草稿,
而後寫使用者代碼草稿驗證接口。使用者代碼能夠是單元測試,也能夠是一些無須編譯的代碼草稿。這個階段僅僅是確認接口設計是明晰的易於使用的。
而後你就能夠畫一個時序圖展現使用者如何和斷路器進行交互,以此進一步確認設計的正確性。
最後,再畫一個類圖的草圖,檢查是否違反某些設計原則,結構是否簡單清晰。
我相信遵循前面討論的這些簡單的設計過程就能夠在創業公司作好前期設計工做了。你能夠從多個角度審視本身的設計,並會所以獲益良好。同時,你也能夠下降開發風險,不至於作了一個不靠譜的設計,等發現的時候就已經掉坑裏了。在寫設計文檔或者瞭解大規模系統的時候,有三種架構圖特別有用:
用例圖
類圖
模塊圖
隨着你的公司規模越大,你的團隊人越多,你越會從這三種架構圖中獲益。
4.1.用例圖,是一種比較的圖
用例圖不考慮技術方案,僅僅關注業務需求,是一種將功能特性和業務需求提煉出來的好辦法。當你要作一個新功能的時候,先畫一個簡單的用例圖。用例圖包含用一個小人圖標表示的角色、角色能夠執行的操做,以及各個操做之間的關係結構。用例圖也能夠呈現與外部系統的交互,好比遠程Web服務API或者任務調度器。用例圖不須要包含太多需求細節,作到簡單、清晰、易於閱讀就好的。畫用例圖的時候儘可能在一個較高的層次上提煉關鍵操做和業務過程,而不是陷入到海量的細節中去。ATM應用的用例圖雖然ATM機包含大量的關於認證、安全、事務處理方面的細節,可是用例圖只須要關心ATM完成用戶的哪些操做就能夠了。從這個角度看,按鈕在屏幕上怎麼排列或者ATM怎麼去實現每一個功能,這些都不重要。你只要關注一個需求的概要,就能夠了解ATM系統的主要意圖並定義各類約定。
4.2.類圖
一個典型的類圖包含接口、類、關鍵方法名,以及它們的關係。因爲每一個類都用一個節點表示,每一個依賴都用一條線表示,因此從類圖上很容易看出耦合關係,能夠看出哪些類耦合度更高,那些類更獨立。簡單看每一個節點上鍊接的線的數目就能夠判斷出這個節點包含的依賴有多少。類圖是一種很是好的可視化手段,很好地展示了類、接口及其交互關係。簡化的E-mail模塊類圖 ,這裏的關鍵元素是類、接口、最重要的方法及依賴關係,不一樣的依賴關係用不一樣類型的線條表示。在這個例子中,EmailService有兩個實現類,一個使用SMTP協議直接發送E-mail,另外一個將E-mail加入隊列延遲發送。EmailService也是一個基於約定編程的例子,任何依賴EmailService的程序均可以使用SMTP或者隊列發送E-mail,卻無須知道E-mail最終是使用哪一個實現類發送的。接口只能依賴其它接口而不能依賴具體類that's to say ,要儘量地依賴接口編程。
4.3.模塊圖
模塊圖能夠認爲是代碼層面經縮略圖,是類圖的上面一層架構圖。模塊不一樣關注類和接口,而是關注系統更大粒度的組成部分,展現模塊之間的依賴和交互。模塊能夠是一個包,也能夠是邏輯上實現特定功能的組件。eg:一個支付服務(PaymentService)模塊圖的例子,展現了一個實現支付功能的應用中支付服務和其餘部分之間的依賴關係。模塊圖一般關注應用中的特定功能有關的部分,而忽略那些不想幹的部分。因爲系統未來會變得更大,因此最好一開始就畫多個模塊圖,每一個模塊圖只關注一個特定的功能,而不是把全部的功能都畫在一個模塊圖中。理想狀況下,每一個模塊圖都應該足夠簡單,你能所有記住並在腦子裏重畫一次。
建模語言UML及設計模式推薦使用ArgoUML做爲桌面UML繪圖工具,這是一個開源的Java應用程序,能夠在整個公司內協做使用,無須 將軟件設計上傳到雲上。若是你喜歡基於雲的方案進行在線協做設計,試試draw.io,這是一個集成了Google Drive的免費且容易使用的在線服務。 draw.io是個人最愛,本書裏幾乎全部的架構圖都是用draw.io畫的。5.單一職責
單一職責原則是說你的類應該只有一個職責而不宜更多。單一職責能夠下降耦合,增長簡單性,易於重構,代碼複用及單元測試----這些 目前爲止,討論過的全部的核心原則。遵循這個原則設計出來的類更簡單更小,所以易於重構和複用。有時候就眼前來講,也許無視新功能是否爲某個類的職責,直接往一個類裏簡單地添加方法更容易然而,這樣作幾個月後,你的類會變得很是龐大和其餘類的耦合也更緊密。你會看到類之間出現的各類交互操做並不像你指望的那樣,這些類會進行一些無關的操做。同時,因爲類變得很龐大,所以幾乎很難徹底理解它的行爲和角色。隨着時間的推移,這種狀況會變得愈來愈嚴重,幾乎每行代碼都會帶來複雜度的巨大增長。5.1改善單一職責事實上,並不存在一個必須遵照的法則去衡量你的類是否符合單一職責原則。不過仍是有一些最佳實踐幫助你作出判斷。
一個類的代碼少於2-4屏
確保一個類的依賴不超過5個接口/類
確保一個類有一個明確的目標
用一句話總結一個類的職責,並把這句話放在類名上面做爲類的註釋。若是你發現很難用一句話總結一個類的職責,那有可能這個類的職責並不單一
若是你的類不能知足這些最佳實踐,那你可能須要好好看看這個類而且準備重構。在更高的抽象層面,你須要將功能按模塊分隔並避免功能重疊。具體作法能夠參照類的設計------試着用一句話總結模塊或者應用的功能,不過要從更高層次總結。例如,你能夠說:「文件存儲系統容許用戶上傳文件、管理文件並支持複雜的文件搜索」,這樣應用目標看起來就很清晰。能夠使用一個明確的接口(好比一個Web服務定義)限制模塊職責範圍並將其從其它部分隔離開來。
5.2單一職責的例子咱們看一個E-mail地址驗證的例子。若是你把驗證邏輯直接放在建立用戶賬號的代碼裏。就沒有辦法在其餘地方複用了。你就不得不複製粘貼這些代碼或者在這些原本沒什麼關聯的類之間進行一些讓人難受的依賴,總之,你會破壞軟件設計的核心原則。把驗證邏輯剝離出來,你就能夠保證其只有一個實現並能在其它複用。過了一段時間, 你若是須要對驗證邏輯進行修改,好比須要在域名中支持UTF-8編碼,只須要重構這個類就能夠了。-------------單一職責, 單獨變化設計一個類專門負責E-mail驗證比將這部分代碼處處複製更簡單,更容易將變化隔離開來。單一職責原則的另外一個做用是你會變得更樂於寫可測試的代碼。由於類的內容變得更少邏輯、更少依賴,因此更容易進行獨立測試。進一步掌握單一職責的辦法是研究策略、迭代器、代理、適配器模式。瞭解領域驅動設計及更好的軟件設計
6.開閉原則
開閉原則是指,當需求變動或者增長新功能時,你不須要修改現有的代碼。開閉的意思是:
若是咱們設計的代碼在未來擴展新功能的時候無須修改,那麼咱們就遵循了開閉原則。正如Robert C.Martin所言,開閉原則可讓咱們保留作出選擇的餘地,延遲到未來作出某些細節決定,這樣就減小了現有代碼的需求變動。這個原則的目的是增長軟件的彈性,使未來變動的代價 最小化。eg.考慮一個排序算法,須要對一個記錄僱員對象的數組基於姓名進行排序。通常實現中,你能夠在一個類裏包含算法及其餘所有必要的代碼,假設類就叫EmployeeArraySorter。你能夠在這個類裏只暴露一個方法,容許對僱員對象數組進行排序,而後就能夠說這個功能開發完成了。雖然這樣是能解決問題,可是顯然不夠有彈性,事實上,這是一個很是僵硬的實現。因爲全部的代碼都在一個類裏,你幾乎不能在不修改代碼的狀況下擴展或者增長任何功能。假如你有一個新的需求,須要對城市基於人口進行排序,就不能複用這些已經開發好的代碼。這時,你就不得不面臨一個尷尬的選擇,要麼擴展EmployeeArraySorter作一些和原先設計徹底無關的事,要麼複製粘貼這個類的代碼而後再修改。幸運的是,你還有第3個選擇,就是重構這個類使其符合開閉原則。開閉原則須要你將這個排序問題分解成幾個更小的任務 ---社會分工的進一步細化??第個任務都就應該是獨立的,不影響其他部分的複用。你能夠設計一個接口Comparator用來比較兩個對象,再設計一個接口Sorter用來執行排序算法。Sorter會利用Comparator對輸入的數組進行排序。例如,你想改變排序對象,只須要增長一個新的Comparator實現便可,無須對現有代碼作任何修改。若是你想改變排序算法,也不須要修改Comparator或者Sorter,只要從新建立一個Sorter接口的實現便可。各個不一樣部分獨立變化,能夠減小變化影響的範圍,也能夠擴展示有的代碼而無須對它們作任何改動。另外一個開閉原則的好例子是各類MVC框架。這些框架因爲其簡單和易擴展的特性而普遍應用於Web開發。回想一下,你何時須要修改MVC框架的某個組件? 若是這個MVC框架的架構設計得足夠好,答案是永不須要。不過,雖然不須要修改,但你仍是能夠擴展MVC中的某個組件,增長新的路由、攔截請求、返回不一樣的響應、覆蓋缺省的行爲。沒必要修改現有的框架代碼,就能擴展其功能,這就是開閉原則所起的做用。 和掌握其餘設計原則同樣,學習開閉原則,開始的時候先熟悉瞭解各類框架。觀察開閉原則的各類實現思路,領會開閉原則的實現模式,例如Java語言實現的SpringMVC框架就是一個很好的開閉原則的例子。用戶能夠彈性地實現各類功能而無須修改框架自己,而客戶端代碼跟框架也幾乎沒有多少耦合。經過使用annotation和基於約定編程,你開發的絕大部分類甚至都不知道Spring框架的存在!7.依賴注入依賴注入是一種下降耦合 改善開閉特性的簡單技術。依賴注入對類須要依賴的對象提供一個引用實例,而無須類本身建立依賴的對象。
儘量地讓類不要知道如何去建立須要的依賴,以及這些依賴來自哪裏,是如何實現的。這看起來只是從「拉取」變成「推送」的一個微小變化,但事實上會對軟件設計的彈性產生巨大影響。咱們來看一個CD播放器的依賴注入的例子。全部的CD播放器都持有一個CD接口,知道如何去讀取音軌,如何對音樂進行解碼,讀取CD內容的Laser device的光學參數是什麼。插入到CD播放器的CD是一個依賴,沒有CD,CD播放器就無法正常工做。將發現依賴的職責交給用戶,CD播放器就能夠保持簡單。從而使CD播放器的利用性更強,它沒必要知道burn到CD上的每個標題,也沒必要知道專輯上的每一個組合,只須要知道CD的各類可能形式及其組合形式就能夠了。CD播放器須要你(用戶)去提供一個可讀的CD,一旦你提供了一個CD實例,CD播放器就能夠正常工做了。此外,還有一個附帶的好處就是CD播放器容許插入非標準的CD。你能夠插入一張空白盤(NothingToDo對象的實例),或者一張音軌畸變的測試盤,這樣你就能夠作異常測試了。eg:經過硬編碼的方式記錄各類CD,若是你想播放一張新CD,就不得不修改它的代碼。-----一個負擔太重的CD播放器。再看一下使用依賴注入方式實現的CD播放器,沒必要知道任何CD自身的信息,只須要提供一個CD的實例就能夠。使用這種方式,你就能夠實現對新增開放(播放新的CD)而對修改關閉(CD播放器永不須要修改),這裏面還有基於接口的約定,CD的編碼方式要和CD播放器的解碼想匹配。
若是使用得當,依賴注入能夠有效下降類的局部複雜度,使其更簡單。無須知道誰來提供依賴的實例,也無須知道實例從何而來,這樣類就能夠聚焦本身的主要職責。類的代碼更簡單,不須要太瞭解系統的其餘部分,不太須要變化和單元測試。將集成的代碼從類中移除,能夠使類更加獨立、可複用、可測試。依賴注入在面向對象編程OOP社區已經有多年實踐。實現依賴注入不須要任何框架。學習依賴注入推薦Spring框架或者Grails框架,它們是依賴注入方面很是好的例子。
8.控制反轉IOC
之前面向過程,一個方法寫到底,相關的外面依賴都須要本身初始化,核心業務邏輯和外面依賴的初始化及管理都堆積在一塊兒IoC 反轉是對這種狀況的改變,相關依賴經過DI的方法注入,外面依賴的控制交於容器或第三方
控制反轉(IOC)是一種從類中移除職責的方法,從而使類更簡單,與系統其它部分更少耦合。控制反轉的核心是沒必要知道是誰、怎樣、什麼時候建立和使用對象,儘量地簡單易於理解,對於軟件設計而言,各部分互相知道得越少越好。
IOC在一些框架中重度使用,包括Spring、Symfony、Rails,甚至JavaEE容器。你沒必要建立類的初始化方法,這些通通交給框架來作。IOC框架接收Web請求建立對應處理類的實例交給對應的模塊處理。IOC也被稱爲好萊塢原則,好萊塢原則Hollywood principle是說「don‘t call us, we‘ll call you「運用到實踐中,就是你的類實例沒必要知道本身何時 被建立,被誰使用,本身的依賴又是如何被建立的。你的類是一個插件,外部力量決定這些類何時被建立,如何被使用。想象一下只用純Java不須要任何框架開發一整個Web應用。沒有Web服務器,沒有框架,沒有API,只有Java。爲了完成這樣一個複雜的應用,你須要本身寫很是多的代碼。即便你決定使用一些第三方的庫,你也必須本身控制整個應用的流程。經過使用框架,你能夠減小代碼的複雜度 。不止是能夠減小須要寫的代碼的數量,還能夠減小開發者須要關注的事情。須要作的只是讓框架回調你的代碼,框架會建立類的實例。請求到達的時候,框架會調用你的方法進行處理,並控制執行的流程從一個擴展點到另外一個擴展點。eg: 一個用Java(沒有用任何框架)編寫的Web應用。這個例子中,大量的代碼模塊用於和外界進行通訊。應用須要本身打開網絡端口,寫日誌文件,鏈接外部系統,管理線程,解析消息。應用須要控制全部的事情,這意味着你須要考慮太多與業務無關的事。若是你使用IOC框架,框架處理了大量的非業務事務,應用甚至不須要關心這些事情的存在。雖然系統仍是要處理這些事情,可是都和應用無關。這不會改變整個系統的複雜性,可是會極大地下降應用的複雜性。IOC是一個很通用的概念。你能夠爲任何類型的應用建立各類控制反轉的框架,不僅是MVC框架或者Web請求處理框架。
視頻這些東西,超過半分鐘,10秒或20秒,他以爲沒興趣,他就換了。 執行困難
短是一個感受,以爲這個時間,有沒有講透
公衆的節目,衆口難調
記得第一道菜的樣子
一個好的IOC框架包含以下特性:
能夠爲框架開發插件
插件是獨立的,能夠被隨時插入移除
框架能夠自動檢測到插件,或者經過配置文件加載插件
框架爲每一種插件類型定義接口,而不會與具體插件耦合
基於IOC框架寫代碼就像把錢放在魚缸裏。魚缸裏能夠放不少魚,它們互不干擾,可是它們生活在一個它們不能控制的世界裏。你決定魚缸的環境是什麼樣的,何時餵魚。你就是一個ICO框架,你的魚就是插件,它們生活在一個被保護的本身殊不知道的泡泡裏。
9.爲伸縮而設計爲伸縮而設計是一個有難度的藝術活,本書中描述的每一項技術都要付出相應的成本。做爲一名工程師,你須要在沒完沒了的伸縮和每一種對應的方案實踐之間進行仔細的權衡。不要爲了那些永遠也用不着的伸縮性需求進行過分設計,要認真評估系統最可能的實際伸縮性需求進行相應的設計。
從業界狀況看,不少創業公司徹底等不到作任何系統伸縮就倒閉了(這樣的公司大概佔90%)。剩下的大部分公司也只須要較少的伸縮性就夠(這樣的公司佔了大概9%)。只有極少數的公司會持續成長,須要進行水平伸縮(只佔1%左右)。
目前爲止,咱們討論的各類設計原則都是爲解決複雜性和耦合性,同時,這些原則大部分也有助於改善系統伸縮性。隨着你對伸縮性的深刻了解,能深入意識到伸縮性方案均可以濃縮成三個基本設計方法。
增長副本:同一組件重複部署到多臺機器
功能分割:基於功能將系統分割成更小的子系統
數據分片:在每臺機器上都只部署一部分數據
這些方法會提供某一方面的好處,同時也須要付出另外一方面的成本。9.1 增長副本若是你從頭開始構建一個系統,最簡單最多見的伸縮性策略是增長副本。副本指的是組件或者服務器副本。任什麼時候候兩個副本都是能夠互換的,任何請求在任何副本上處理都是同樣的。換句話說,發送請求到任何一個副本上響應都是同樣的。經過增長副本實現伸縮時,須要關注應用狀態如何存儲及副本之間如何同步狀態變動。因此這種方式最適用於無狀態的服務,無須同步狀態。若是應用是無狀態的,那麼對於請求而言,一臺新服務器和一臺已經存在的服務器沒有區別。
無狀態服務是指一個服務不會依賴本地狀態,處理一個請求也不會影響到服務自身的行爲方式。要得到正確的結果無須特定的服務器實例。
增長副本實現伸縮不是無狀態服務的專利。事實上,數據庫使用這種方式經過副本進行伸縮已經多年。經過增長副本進行伸縮就像醫院裏的急救室。若是你預算充足,能夠僱用足夠多的職業醫生而且購買足夠多的手術室與設備,就能夠很容易地讓不少病人獲得救治。醫生和病房的數量同樣,能救治的病人數目同樣。對於Web層而言,增長副本是最容易、成本最低的伸縮方案。增長副本實現伸縮的主要挑戰是對於有狀態的服務難以用這種方式伸縮,須要找個辦法同步狀態以實現副本任意可交換。9.2 功能分割第二個重要的伸縮性策略是功能分割,適用於各個地方 各個層面。這個方案的核心是發現系統的各個功能部分,而後將它們建立獨立的子系統。當咱們討論基礎設施架構的時候,功能分割批的是物理服務器分離部署。將數據中心的服務器根據類型進行劃分,有對象緩存服務器、消息隊列服務器、隊列工做者服務器、Web服務器、數據存儲服務器、負載均衡器等。這裏每個組件均可以部署到主應用服務器上,可是這麼多年來,工程師們逐漸到更好的辦法是將這些功能組件部署到獨立的服務器上。功能分割的另外一種更先進的方式是:將系統切分紅一些自給自足的應用。主要適用於Web服務層,是面向服務架構(SOA)的一種主要實踐。回到eBay競價應用這個例子,若是你有一個Web服務器層,就能夠構建一系統的鬆耦合的Web服務處理實現各類功能。這些服務能夠擁有本身的邏輯資源,諸如數據存儲、隊列、緩存等。一個功能分割的場景,兩個獨立的功能:資料服務和調度服務。這些服務能夠共享底層基礎設施,也能夠擁有獨立部署的底層基礎設施,好比數據存儲等。給服務更多的自主性,一方面能夠更好地基於約定編程,另外一方面也可讓服務本身決定須要什麼樣的組件及如何對本身進行伸縮。功能分割經常使用於底層:將應用分割成多個模塊,而後將不一樣類型的軟件部署在不一樣的服務器上(好比,數據庫和Web服務部署在不一樣服務器上)。在大一點的公司,也會對頂層進行功能分割,即建立多個獨立的Web服務。這種狀況下,須要將單個應用切分紅多個小一點的功能服務。這樣作還有一個好處是多個團隊能夠基於各自的代碼庫 獨立開發,不一樣的服務也能夠根據各自的伸縮性需求獨立伸縮。功能分割會帶來不少好處,可是也有缺點。分割後的功能,最開始時會帶來不少管理方面的困難。功能分割能夠分割的數量 有上限,限制了對功能分割這種伸縮性方案的使用。你不能無限地使用這種方式對應用無休止地切分,以使Web服務變得更小。
9.3數據分片第三種主要的伸縮性策略是將數據分片,而後將不一樣的分片數據保存在不一樣的機器上,以保證每臺服務器上存儲的數據量都不會太大。這種方式也被稱爲無共享架構原則,每臺服務器都擁有本身的一個數據子集,彼此之間徹底獨立,每一個節點都徹底自治。這樣,每一個節點均可以獨立變動本身控制的部分數據,而無須在各個節點之間複製狀態變動信息。無共享狀態意味着沒有數據須要同步,不須要鎖,失敗也會被隔離,由於節點之間沒有依賴。 再來看eBay競價應用。回顧一下,首先咱們經過增長服務器(增長備份)實現伸縮,而後將Web服務切分紅兩個 獨立的服務,這樣咱們就得到了兩種不一樣的伸縮方式。此外,還有一種伸縮方式:對數據負載的分佈式伸縮。這種方式的一個例子是:對Web服務的對象緩存中的數據進行切分。 要想讓對象緩存實現伸縮,一個辦法是增長副本,可是這就須要在全部的服務器之間同步狀態。另外一個辦法是功能分割,能夠將Web服務器響應緩存在一臺緩存服務器,而數據查詢結果緩存在另外一臺服務器上。不過這兩種方法都不怎麼樣,都沒有辦法讓咱們的伸縮持續下去。
根據緩存對象的首字母將緩存對象分佈到不一樣的服務器上。實踐中,應用會使用更復雜的方式進行數據分片,但基本根據是同樣的。每一個服務器都獲得一個數據子集,並只需管理這個子集便可。因爲每一個服務器都只存儲較少的數據,因此服務器能夠把更多數據存儲在內存中並獲得更快的響應速度。未來若是須要更多存儲能力,只須要增長服務器並修改映射關係便可。仍是用醫院作類比。若是醫院提供預定就診服務,就能夠用數據分片的方式對專家(數據)進行伸縮。不能讓病人每次預定到的都是不一樣的專家,解決這個問題的一個辦法是將醫生的名字寫到每一個病人的病例卡中。病人來掛號時,就把分配給對應的醫生,這樣客服人員能夠很容易地根據預定幫病人找到醫生。即將數據請求須要的數據--->某個緩存服務器 是固定的,每一個緩存服務器只須要存儲部分數據便可數據分片配合增長備份,能夠實現幾乎無限的伸縮性。只要能對數據進行正確的切分,就能增長更多的用戶,處理更多的併發鏈接,收集更多的數據,將系統部署在更多的服務器上。不幸的是,數據分片幾乎是最複雜*、代價最昂貴的技術。數據分片最大的挑戰是在進行數據訪問前,必須先找到存儲須要的數據所在的分區的服務器,而若是一個查詢涉及多個數據分區,那麼實現就變得異常低效和困難。
10.自愈設計
一個足夠大的系統必定處於某種部分失效的連續狀態之中---Justin Sheehy
一個系統被認爲是可用的,就是說從用戶角度它一直按預期運行,完成其功能,而並不關心其內部是否存在某種失效狀態,只要不影響用戶使用就能夠。按句話說,你須要讓你的系統表現出全部組件都在正常運行,即便出現某些設備宕機或者進行發佈維護也是如此。
一個高可用的系統對於其它用戶而言,任什麼時候候都是可用的。對於高可用沒有一個絕對的衡量準則,不一樣的系統有不一樣的高可用需求。系統的可用性通常不會用一個絕對值直接度量,而是用「幾個9」來衡量。咱們說一個系統兩個9,是說這個系統99%的時間可用,也就是每一年大概有3.5天不可用(365天*0.01=3.65天)。相對地,一個系統有6個9的可用性,是說這個系統99.999%的時間可用,每一年不可用時間5分鐘。
能夠想象,不一樣的業務場景有不一樣的可用性要求。要記住的是,系統越大,系統失效的機率越高。若是你須要鏈接5個Web服務,每一個服務鏈接3個數據存儲,那麼你就要依賴15組件。任何一個組件失效,整個系統都會不可用,除非你能優雅地控制失效進行透明地失效轉移。當你進行伸縮擴容的時候,失效也會變得更加頻繁。你有1000臺服務器,那麼天天均可能有幾臺服務器宕掉。糟糕的事情可能遠不止於此,不少緣由都會致使失效,好比停電斷網,以及人爲失誤。設計一個伸縮性架構必須把各類失效狀態看成常態,而不是特殊狀況對待。若是想設計一個高可用的系統,就必須作最壞的準備才能獲得最好的期待。要不停地想哪裏會出錯,出錯後該怎麼辦。
保持系統高可用是一種態度,將這種態度發揮到極致的一個例子是Netflix開發的一個叫作Chaos Monkey(搗蛋鬼)的系統。Netflix可用性工程師認爲證實一個系統真的高可用的方式是,真的去製造一些事故,而後看這個系統怎麼處理。Chaos Monkey就是這樣一個服務,它會在上班時間隨機關掉一些 Netflix的基礎設施組件。這看起來很荒謬是否是?公司可能都會所以而掛掉。可是最後真的證實了他們的系統能夠處理任何類型的失效。
另外一種相似的高可用態度是一個叫作Crash-Only(只需宕機)的概念。Crash-Only的鼓吹者認爲系統應該隨時爲宕機作好準備,系統任什麼時候候重啓,都無須人工干預。這意味着系統是否正在處理請求,處理隊列消息仍是作任何類型的工做,都可以本身檢測到本身的失敗,修復必要的錯誤數據、重啓,而後像正常時同樣工做。CouchDB是一個很流行的開源數據庫,它就是遵循這個原則開發的,這個系統不提供任何的關閉功能。若是想關閉一個CouchDB實例,只須要終止進程便可。
如何才能證實你的系統可以處理各類失效情況?我曾經見證過不少宕機事件,有的由於本地服務器存儲了狀態,有的由於不能正確處理網絡延遲。持續地 測試各類失效場景是提升系統可用性的重要方式。實踐中,確保系統高可用的主要手段是消除失效單點的風險和優雅地進行失效轉移。
失效單點是指基礎設施中任何一個部分對系統的正常工做都是必要的。失效單點的一個可能的場景是域名系統(DNS)服務器,若是你只有一個域名服務器的話。另外一個可能的場景是數據庫主服務器或者文件存儲服務器。
識別失效單點的一個簡單方法是畫出數據中心架構圖,每一個設備(路由器、服務器、交換機等)都畫上去,而後問本身,當某個設備宕機的時候會發生什麼。當識別出失效單點之後,就和業務團隊一塊兒討論對其作冗餘是划算。有時,冗餘很便宜很簡單,有時又很昂貴很複雜,這須要仔細權衡。冗餘是指數據或組件有至少兩個備份。當其中一個備份失效了,系統能夠使用另外一個備份服務用戶。一個缺少冗餘的系統須要特別關注,這方面的最佳實踐是準備一個災難恢復計劃(有時候也叫業務持續計劃),記錄對系統各類災難性問題如何進行恢復。最後,若是你想要一個高可用的徹底耐受各類失效的系統,也許該實現系統的自愈功能。自愈比優雅地處理失效更進一步,它可以檢測並自動修復問題,無須人工干預。能自愈的系統是Web應用的聖盃,不過想打造這樣一個系統也是很是困難且代價高昂的,比嘴上說說不知道要難多少倍。這裏給一個自愈的例子---開源數據庫Cassandra處理失效的辦法。Cassandra中系統能夠透明地處理數據節點的失效。一旦集羣發現某個節點失效,就馬上中止發送新的請求給失效的節點。對於客戶端而言,失效的時間就是發現失效節點須要的時間。一旦失效節點被放到黑名單裏,客戶端就能夠正常讀寫數據,集羣中其他的節點會對失效的節點提供數據冗餘。當失效節點恢復上線後,會帶着丟失的數據運行,好像系統什麼都沒有發生同樣。相同地,若是用一個新的空白服務器節點替換一個失效的節點,也不須要系統管理員像傳統關係數據庫那樣,必須從備份節點恢復數據。增長一個新的空白數據節點會引發Cassandra集羣的數據同步,過一段時間,新加入的機器就填滿了數據。若是一個系統能夠檢測到自身 存在部分失效或者潛在的不可用,而且能儘快進行自我恢復,這個系統就是自愈的。
平均恢復時間是可用性方程的一個重要組成部分。你越快檢測到,去處理,進行修復,可用性就越高。可用性的真實計算公式是:平均失效時間/(平均失效時間+平均恢復時間)。減小平均恢復時間,就能夠增長可用性,即便失效次數無法控制的狀況下也是如此。當使用AWS Web服務這類雲主機服務時,因爲雲服務商使用廉價硬件,他們會在低失效比率和低價之間權衡。這種狀況下,你無法控制平均失效時間,因此只能更多地關注平均恢復時間。
小結不管你是軟件工程師、架構師、開發組長、仍是技術經理,軟件設計原則對你都很是重要。軟件工程是一門關於信息決策、創造商業價值、爲將來作準備的技術。要記住:
它們能爲你指明方向,增長你成功的機率,可是最終,仍是要你本身決定什麼樣的方式最適合你的系統。做爲一名軟件工程師或者架構師,你的工做就是根據金錢、時間、能力,提出對實現商業目標最好的解決方案。若是你意識到本身角色的重要性,你就應該讓本身的思想開放,用各類視角考慮問題。最「乾淨」的方案不必定老是最好的方案,由於可能會花費更多的開發時間或者會引入一些沒必要要的管理成本。好比,解耦合和過分設計之間的那條線就很是細。你的工做就是識別出這些誘惑,避免帶來美好的想象向着那些彷佛酷斃了的方向越走越遠。根據業務需求作出決策,在伸縮性、彈性、高可用、成本、推向市場的時間之間作出權衡。
每一個系統都是不一樣的,每家公司的需求也是不一樣的,你要發現本身和之前和別的工程師工做場景的不一樣之處。構建可伸縮軟件的路不止一條,技術要好,開發工具要順手,還要去發現業務驅動因素。但願本章提到的各類設計原則爲你設計高質量軟件系統開了個好頭。Come on!
九 伸縮性的其它維度
本書主要內容是關於設計與開發可伸縮的Web應用的一些技術細節,這些內容的主要受衆是軟件工程師。事實上,開發一個可伸縮的系統不僅是寫代碼,還有其它可伸縮的維度也須要關注。
運維可伸縮性生產環境可以運行多少臺服務器?系統一旦部署在上百臺服務器上,如何高效地管理這些服務器就會成爲一個挑戰。若是每增長20臺服務器就須要多招一個運維管理員,那麼運維就無法快速廉價地伸縮。
我的影響力的伸縮性你我的能爲客戶和公司創造多大的價值?隨着創業公司的成長,你我的的影響力也應該同步增加。經過擴展你的職責範圍及對各個業務主管施加影響,你的工做效率和我的績效會變得更高。
工程師團隊的伸縮性創業公司的工程師人數達到多少後工做效率公降低?隨着公司的成長,公司須要在不影響工做效率的狀況下招募更多的工程師壯大技術部門。這意味着須要創建良好的工程師文化,規劃合理的團隊和系統架構,保證你們可以可伸縮地 並行開發與協做。
優先級=價值/成本