朱曄的互聯網架構實踐心得S1E7:三十種架構設計模式(上)

設計模式是前人經過大量的實踐總結出來的一些經驗總結和最佳實踐。在通過多年的軟件開發實踐以後,回過頭來去看23種設計模式你會發現不少平時寫代碼的套路和OO的套路和設計模式裏總結的相似,這也說明了你悟到的東西和別人悟到的同樣,通過大量實踐總能趨向性得出一些最佳實踐的結論。架構設計也是同樣,這裏結合本身的理解分析一下微軟給出的雲架構的一些模式。話說微軟幹這方面的事情真的很厲害,以前翻譯過的《微軟應用架構指南》寫的也很不錯。有了模式的好處是,技術人員和技術人員之間的對話能夠絕不費力的經過幾個模式關鍵詞進行交流,就像如今你們溝通提到職責鏈模式,若是雙方都理解這個模式的意義那麼這五個字替代的可能就是半小時的解釋。廢話很少說,接下去來看一下這些其實已經很熟悉親切的模式。
前端

管理和監控

一、大使模式:建立表明消費者服務或應用程序發送網絡請求的幫助服務


進程外的代理服務(以前介紹中間件的時候也提到了,不少框架層面的事情能夠以軟件框架的形式寄宿在進程內,也能夠以獨立的代理形式作一個網絡中間件)。這裏的大使模式意思就是這麼一個網絡代理進程,用於和遠端的服務進行通信,完成下面的工做:算法

· 服務路由數據庫

· 服務熔斷後端

· 服務跟蹤設計模式

· 服務監控緩存

· 服務受權安全

· 數據加密性能優化

· 日誌記錄服務器

因爲是獨立進程的網絡服務,因此這個模式適合於咱們有多語言多框架都須要幹一樣的事情,那麼咱們的框架中客戶端部分的不少工做能夠移出來放到大使服務中去。固然了,多一層網絡調用多一層開銷,大使服務的部署也要考慮到性能不必定能夠集中部署,這些都是要考慮的問題。網絡

二、反腐模式:在現代應用程序和遺留系統之間實現裝飾或適配器層

使用一層防腐層來做爲新老系統通信的中間人。這樣新系統能夠徹底使用新的通信方式和架構方式,老的系統又不用進行特別改造能夠暫時保留,等老系統不用以後能夠廢棄這個反腐層。這種模式適合新老系統遷移的過渡方案,不屬於永久使用的架構設計模式。

三、外部配置存儲:將應用程序部署包中的配置信息移動到中心化的位置

這個模式說的就是能夠有一個外部的配置服務來保存配置信息,在以前第五篇文章介紹中間件的時候我詳細說明過配置服務的功能。無論是處於管理運維的角度仍是方便安全的角度,具備配置共享配置外存特色的獨立配置服務對於大型的網站來講必不可少。實現的話有不少開源項目提供了配置服務,見以前個人文章。

四、網關聚合模式:使用網關將多個單獨的請求聚合到一個請求中

應用程序若是須要和多個服務交互的話,在中間構建起一個聚合網關層,網關併發發出多個請求給後面的服務,而後彙總數據給到應用程序。這種模式有幾個好處:

· 容許併發調用多個服務提升性能,容許只返回部分數據

· 網關裏能夠作一些彈性設計方案(熔斷、重試、限流)

· 網關裏能夠作一些緩存方案

· 對於外網通信的時候,可讓網關做爲一個網絡中間層

固然,使用這種模式須要考慮到網關的負載、高可用、高性能(異步IO)等等。

其實這種模式不只僅用於純後端服務之間的通信,不少面向前端的API請求都會作一個聚合層,這樣前端能夠只發一個請求的狀況下任意向後端一次性索取多個API的返回,減小網絡請求次數提升性能。

實現上最簡單的方式可使用OpenResty或Nginx實現。

五、網關卸壓模式:把共享或特定的服務功能放到網關代理

名字有點難以理解,其實這種模式咱們可能一直在用。就是用一個代理網關層作一些和業務無關的又麻煩的點,好比SSL,實現上用Nginx實現就很簡單。咱們常常會對外啓用HTTPS服務,而後對內服務實際提供的是HTTP接口,經過網關作一下協議轉換。

六、網關路由模式:使用單個端點將請求路由到多個服務

這也是很常見的做法,咱們對外的接口多是/cart、/order、/search這樣的API,在其背後實際上是不一樣的服務,經過網關層進行轉發,不只僅能夠作後端服務的負載均衡和故障轉移,在後端服務變動切換對外API路徑(好比版本升級)的時候咱們也能夠進行靈活的路由,確保了對外接口的一致性。可使用Nginx來實現,相信大部分公司都是由Nginx這樣的網關來對外的,不會把域名直接解析到底層服務上對外。

七、健康端點監控模式:在應用程序中執行功能檢查,外部工具能夠按期經過暴露的端點訪問

這個模式實際上是挺重要的一點,有幾個點須要注意:

· 須要暴露哪些信息?不只僅是服務自己或框架自己是否啓動成功,儘量暴露出服務依賴的外部存儲或系統是否可用,緣由是網絡通信是複雜的,從外部看到某個服務可用不表明咱們的網站就能夠成功鏈接,若是底層的數據庫都沒法鏈接,即便這個網站自己啓動成功,那麼咱們應該認爲這個服務是不健康的。外部存儲即便對於A節點是能夠連通對於B節點不能連通也是有可能的,多是由於網絡問題或權限問題,還可能由於負載問題,有的時候對於長鏈接的請求A節點由於始終連着存儲不會有問題,新的B節點要求鏈接的時候由於超出最大鏈接限制沒法鏈接。若是有可能的話還暴露一些服務內部各類線程池、鏈接池和隊列的信息吧(對象數,隊列長度等),這些指標很關鍵,可是由於在程序內部因此外圍很難感知到,有了一些關鍵指標的外露對於排查性能問題會方便不少。

· 不僅是網站,服務也應該暴露出健康信息,一來咱們能夠在外部收集這些信息進行監控彙總,二來咱們的負載均衡器或發佈系統須要有一個方式來判斷服務是否可用,不可用的時候進行重啓或故障轉移。

· 對外的服務注意health端口的受權,這裏可能會有一些敏感信息,不宜讓匿名用戶看到。

實現上,咱們應當把health端口做爲插件形式集成到系統,配置一下便可啓用,用不着每個系統都本身開發一套。若是使用SpringBoot的話能夠直接使用Actuator模塊實現。

八、絞殺者模式:經過使用新的應用程序和服務逐漸替換特定功能部件來逐步遷移舊系統

名字挺嚇人,這個模式說的是如何作遷移。經過創建一個門面來做爲後端新老服務的路由,慢慢把服務替換爲新服務,最後當全部的服務都是新服務後刪除這個門面便可。這樣對於消費者感知不到這個遷移的過程。在上一篇文章中咱們提到的換引擎的方式其實說的是保留原有的門面,也是經過這個門面作底層引擎的替換。其實我以爲對於減小外圍影響這種模式是徹底能夠理所固然想到的,真正難的過程仍是以前說的數據遷移和底層服務實現的過程。

性能和可擴展性

九、緩存輔助模式:按需將數據從數據存儲加載到緩存中

這個模式說的不是廣義上的緩存使用,而是其中的一種使用方式。咱們對於緩存的使用通常有這麼幾種方式:

· 查緩存,不存在查庫,而後更新緩存

· 直接維護一大塊「全量」數據,儘可能和數據庫同步

這個模式說的是後一種方式,對於數據變更不大,這種模式是性能最好的,幾乎實現了100%的命中率,並且若是數據量不大能夠容納進進程的話不須要跨進程通信。往細緻一點去想,這裏還有一層性能優化的點,由於咱們在內存中維護了一套複雜的全量數據的數據結構,內存中對象的引用只是指針引用,內存中的數據搜索能夠很快,對於數據量不大可是關係複雜的數據,這個搜索效率能夠是數據庫的幾百倍。實現上通常會在應用程序啓動的時候把數據徹底加入內存,在後續經過一些策略進行數據更新:

· 定時更新同步數據,不一樣數據能夠有不一樣的更新頻率由後臺線程來更新

· 數據具備不一樣的過時時間,過時後由請求觸發主動更新或回調方式被動更新

· 數據修改後同步修改緩存和數據庫中的數據

十、命令和查詢責任分離模式:經過使用單獨的接口來分離讀取數據和更新數據的操做

英文縮寫是CQRS,看到這個關鍵字你可能會以爲有點熟悉了。CQRS原來講的是咱們能夠有兩套數據模型分別用於讀和寫。好處是,咱們可讓讀和寫具備徹底不一樣的數據結構,減小相互的干擾,減小權限控制的複雜度。這裏說的不必定是指架構層面咱們能夠這麼作,也指在程序內部,咱們能夠有兩套命令模型來處理讀寫這兩個事情,分別進行優化和定製。

如今通常的作法是相似於上圖的作法,爲讀寫配置兩套獨立的數據源,而且和事件溯源的方式結合起來作(見後面一節)。咱們來講說讀寫兩套模型在存儲上分離這個事情,在《相輔相成的存儲五件套》一文中咱們的架構圖其實就有這方面的意思。對於讀寫這兩個事情,咱們徹底能夠不用一套數據源,爲讀創建專門的物化視圖,能夠針對讀進行優化,避免在讀的時候作不少Join的工做,能夠把性能作到極致(後面會有物化視圖模式的介紹)。事件溯源+CQRS+物化視圖三者通常會結合起來使用。

十一、事件溯源模式:使用僅追加存儲去記錄描述對域中的數據採起的操做的完整系列事件

事件溯源(ES)是一種有趣的模式,說的是咱們記錄的不是數據的當前狀態而是疊加的數據變化序列(是否是想到了區塊鏈的數據記錄方式)。傳統的CRUD方式由於有更新這個操做,因此會產生性能併發方面的侷限性,並且咱們還須要配備額外的日誌來作審計,不然就產生了信息丟失。而事件溯源模式記錄的是事件而不是當前狀態,因此有下面的特色:

· 事件不可變,只是追加新的事件,沒有衝突,性能高

· 以事件驅動作外部處理,耦合低

· 保留第一手原始信息,信息沒有損耗

其實有一些業務場景下這種模式會比CRUD存儲更適合:

· 業務更看重數據的意圖和目的而不是當前的狀態,注重審計、回滾、歷史方面的功能

· 但願避免數據更新的衝突,但願數據的產生能有較高性能,又能接受數據狀態的最終一致性

· 整個系統中自己就是以事件在驅動的(咱們能夠想一下在真實的世界中,物體和物體之間相互影響,經過事件來影響,每一個物體觀察到其它物體發出的事件來作出本身的反映,這是最天然的,而不是觀察到別的物體屬性的變化來調整本身的屬性)

反過來講,業務邏輯很簡單的系統,須要強一致性的系統,數據不多更新的系統不適合這種模式。不知你所瞭解到的採用ES模式的業務場景有哪些?你們一塊兒交流一下。

十二、物化視圖模式:針對所需的查詢操做,當數據沒有理想地格式化時,在一個或多個數據存儲中的數據上生成預填充視圖

咱們在使用數據存儲的時候每每會更多考慮存儲而不是讀取。咱們使用各類數據庫範式來設計數據庫,在讀取數據的時候咱們須要作大量的關聯查詢以輸出符合須要的查詢結果。這個時候性能每每會成爲瓶頸,物化視圖是一種空間換時間的作法。與其在查詢的時候作關聯,倒不如提早保存一份面向於查詢和輸出的數據格式。所以,物化視圖適合下面的場景:

· 通過複雜的計算才能查詢出數據

· 背後存儲可能會有不穩定的狀況

· 須要鏈接多個不一樣類型的存儲才能查詢到結果

可是由於須要考慮到物化視圖計算保存的開銷,因此也不太適合於數據變化太頻繁的狀況,由於數據加工須要時間,因此不適合須要數據強一致性的場景。

實現上通常是基於消息監聽作額外維護一套物化視圖的數據源和主流程解耦。惠普的Vertica是一款高性能的列式分析數據庫,它的一個特性就是物化視圖,經過事先提供SQL語句直接緩存面向於統計的查詢結果,極大程度提升了性能,也是空間換時間的思想。

1三、基於隊列的負載均衡模式:使用一個隊列做爲任務和服務之間的緩衝區,平滑間歇性重負載

消息隊列咱們太熟悉了,以前咱們也反覆提升過好屢次,甚至我說這是架構三馬車之一。這個模式在這裏強調的是削峯的優點。這裏我還想提幾點:

· 引入消息隊列不會提升處理能力,而是會下降性能,只是咱們把耦合解開了容許每個部件單獨有本身的彈性,對於不能負荷的部分在隊列中進行緩衝,緩衝不是存儲不意味無限制

· 隊列看的是處理速度和入隊速度的比例,通常而言,咱們須要預先作評估確保處理TPS超過2倍的最高峯的入隊TPS,確保留出一半的富裕,這樣在業務邏輯有修改的時候處理TPS哪怕降低了30%,還能抗住壓力

1四、優先級隊列模式:肯定發送到服務的請求的優先級,使得具備較高優先級的請求更快地被接收和處理


區別於FIFO結構的隊列,優先級隊列容許消息標識處理優先級。這裏實現上如上面兩個圖有兩種方式:

· 消息優先級方式。在隊列中進行實時位置重排,永遠優先處理級別較高的消息。

· 不一樣的處理池方式。咱們能夠針對不一樣的處理級別配備專門的處理池來處理這些消息,高級別的消息具備更多的處理資源,更好的硬件來處理,這樣勢必會有較高的處理能力。

在方案選擇和實現上要考慮消息優先級是否須要絕對按照優先級來處理,仍是說相對優先處理便可,若是須要絕對優先那麼除了消息位置重排還須要有搶佔處理。還有,若是咱們採用第二種多池的方式來處理的話可能會發生低級別的消息處理時間比高級別的消息更快的可能性(若是二者處理業務邏輯是徹底不一樣的話)。

實現上的話RabbitMQ 3.5以上版本支持了消息優先級,實現的是第一種方式,在消息有緩衝的堆積的時候進行消息重排,消費端能夠先看到先處理優先級高的消息,這種方式在消費速度大於產出速度的場景下是沒法實現高級別消息優先處理的。

補充一點,對於隊列中的消息,還有一種須要特別考慮的就是一直停留在隊列的消息應當視爲低優先級或是死信消息來處理,最好是有單獨的消費者來處理,避免此類消息影響了整個隊列的處理,見過不少個事故是因爲隊列中被廢棄消息卡死致使完全喪失處理能力的。

1五、限流模式:控制應用程序,我的租戶或整個服務的實例消耗的資源

在作壓力測試的時候咱們會發現,隨着壓力的上升系統的吞吐慢慢變大並且這個時候響應時間能夠基本保持可控(1秒內),當壓力突破一個邊界後,響應時間一會兒會不可控,隨之系統的吞吐就會降低,最後會完全崩潰。任何系統對於壓力的負荷是有邊界的,超過這個邊界以後系統的SLA確定沒法知足標準,致使你們都沒法好好用這個服務。由於系統的擴展每每不是秒級能夠作到的,因此這個時候最快的手段就是限流,只有限流了才能保護如今的系統不至於突破這個邊界完全崩潰。對於業務量超大的系統搞活動,對關鍵服務甚至入口層面作限流是必然的,別無它法,淘寶雙11凌晨0點那一刻也能看到必定比例的下單被限流了。

常見的限流算法有這麼幾種:

· 計數器算法。最簡單的算法,資源使用加一,釋放減一,達到必定的計數拒絕服務。

· 令牌桶算法。按照固定速率往桶裏加令牌,桶裏最多存放n個令牌,填滿丟棄。處理的時候須要獲取令牌,獲取不到則拒絕請求。

· 漏桶算法。一個固定容量的漏洞,按照必定的速度流出水滴(任務)。能夠以任意速度流入水滴(任務),滿了則溢出丟棄。

令牌桶算法限制的是平均流入速度,容許必定程度的突發請求,漏桶算法限制的是常量的流出速率用於平滑流入的速度。實現上,經常使用的一些開源類庫都會有相關的實現,好比google的Guava提供的RateLimiter就是令牌桶算法。

限流模式有下面的一些注意事項:

· 限流須要快速執行,任何一個超出流量控制的請求不容許放行,不然沒有意義。

· 限流須要提早執行,最好在系統能力達到80%的時候進行限流,越晚限流風險越大。

· 能夠返回特定的限流控制錯誤代碼給客戶端,讓用戶知道這不是錯誤是限流,能夠稍後再試。

· 由於咱們的系統不少地方都會作限流,在監控圖上咱們最好對這類限流的曲線有敏感,限流後的曲線是一會兒失去了增加的梯度變爲了平穩的狀態,若是監控圖看的時間範圍太小的話會誤判這是一個正常的請求量。

· 限流能夠在邊緣節點作。咱們來考慮秒殺的場景,若是一秒有100萬個請求,這100萬個請求所有打到咱們的應用服務器沒有意義,咱們能夠在邊緣節點(CDN)甚至上作簡單的邊緣計算,讓這100萬個請求採用命中註定的方式直接隨機放棄其中的99.9%留下1000個請求,最終能夠進入咱們的業務服務,這樣TPS在1000通常是沒有問題的。因此不少時候咱們參與秒殺系統會在極端的時間內毫無思考告知你活動已結束,說明你已是被選中的命中註定的沒法進入後端系統來參與秒殺的那些人。

在下篇中咱們將會繼續介紹數據、安全、消息、彈性方面的一些架構模式。

相關文章
相關標籤/搜索