三十種架構設計模式(下)


三十種架構設計模式(下)

數據管理模式

16、分片模式:將數據存儲區劃分為一組水平分區或分片

三十種架構設計模式(下)

三十種架構設計模式(下)

三十種架構設計模式(下)

一直有一個說法就是不到沒路可走的時候不要考慮數據庫分片。有的時候業務量大到單個業務表在經過緩存+隊列削峰等措施之後的平均的TPS超過1萬,單表實在是扛不住,還是隻能考慮分片手段。

分片前:

  • 需要根據數據分佈、壓力情況、業務邏輯確定分片的方式,按照條件還是範圍還是哈希等等(三個圖展示了三種策略)。
  • 需要進行業務代碼改造,改掉所有不允許的SQL。
  • 需要確定用HardCode方式還是框架方式還是中間件方式做數據路由。

分片後:

  • 需要有運維工具可以對這麼多套分片的數據進行統一的加索引等操作。
  • 最好有數據倉庫可以彙總所有數據,使得adhoc查詢可以更方便。
  • 最好有輔助工具可以用來幫助定位數據會在哪個分片中。

17、靜態內容託管模式:將靜態內容部署到基於雲的存儲服務,可以將它們直接傳遞給客戶端

相信互聯網公司90%+肯定都使用了這個模式。把靜態資源從動態網站中剝離由Nginx等高性能服務器來處理靜態資源,然後使用三方CDN對靜態資源進行加速,不但減輕了動態網站的負載而且數據在邊緣節點加速讓用戶的訪問跟快,使用單獨的一個或多個子域名做靜態資源還能提高下載資源的並行度提高網頁加載的速度。

使用CDN來進行資源加速一般有主動數據傳送到CDN存儲和在CDN配置回源站拉取兩種方式,文件類一般使用主動推送數據,靜態資源類一般使用回源方式。在使用CDN的時候考慮下面的問題:

  • CDN以什麼方式來認定同一個文件的,CDN提供了什麼工具來刷新邊緣節點的緩存?根據不同的策略做相應的緩存刷新方案。
  • 源站對於相同的文件需要有一致性(最好版本變化後文件名變化),不能今天是這個版本明天是另一個版本,這樣很可能導致邊緣節點緩存了不同版本的文件,導致各種怪問題。
  • 使用了CDN後不同地區的用戶訪問的都是CDN節點上的數據,一旦出現問題排查比較困難,考慮引入前端的錯誤處理框架來記錄前端出現腳本錯誤時的調用棧,方便定位問題。

18、索引表模式:為查詢經常引用的數據存儲區中的字段創建索引

三十種架構設計模式(下)

在第三第五兩篇文章中我都提到了索引表的做法。出於下面的原因,我們會考慮索引表:

  • 雖然我們的關係型數據庫大多支持主鍵之外的非聚集索引,但是在某些情況下直接對大表做很多索引性能並不好。
  • 做了Sharding後我們確實沒有辦法以分片鍵之外的維度來查詢數據。
  • 希望以空間換時間,直接把某個維度的複合查詢作為主鍵單獨保存一份數據。

不過需要考慮一點索引只有在數據區分度高的情況下才能發揮價值,如果90%以上的數據都是相同的值,那麼走索引進行查詢性能會比全表掃還要差一點。

設計和實現模式

19、前端專用的後端模式:通過使用單獨的接口來分離讀取數據和更新數據的操作

三十種架構設計模式(下)

這裡說的是不同的前端配以不同的專用後端。比如PC網站和APP的後端是兩套程序。這種模式是否適合其實還是看兩端的後端提供的數據差異有多大,我們總是希望可以儘量統一一套後端,業務邏輯不用重複寫,但是我們要考慮到PC網站和APP的差異性:

  • APP系統的接口交互一般會簽名驗證,有的時候還會加密
  • PC系統的流程一般和APP系統不一樣
  • PC一個頁面能顯示的內容會比APP一個界面顯示的更多
  • 安全性設計上PC和APP不一樣,APP很少有圖形驗證碼

考慮到這些差異,我們是在一個工程內根據來源做適配,還是獨立兩套工程來做獨立的後端取決於差異度有多大了。

20、計算資源整合模式:將多個任務或操作整合到單個計算單元中

三十種架構設計模式(下)

這個模式從資源節省的角度來說我們的計算單元任務可以進行一些合併,減少因為資源限制導致不必要的開銷。

21、選舉模式:通過選舉一個實例作為負責管理其它實例的負責人,來協調分佈式應用程序中的協作任務實例集合執行的操作

對於分佈式服務,我們趨向於把服務設計為無狀態可以任意擴展的,但是在某些業務場景下我們不得不在服務中選舉出一個Leader(Primary節點,Master節點)來做一些不適合重複做的協調管理工作。這個時候我們需要有算法來做選舉。

最常見的實現方式是使用Zookeeper來實現,我們知道ZK的znode有Sequence和NonSequence兩種,前者多個客戶端只有一個可創建成功同名節點,後者創建後會自動加上序列號命名多個客戶端可以創建多個同名節點,利用這個特性有兩種常見實現方式:

  • 非公平實現。多個客戶端同時創建EPHEMERAL+NONSEQUENCE節點。只有一個可以創建成功,創建成功的就是Leader,其它的Follower需要註冊watch,一旦Leader放棄節點(注意,EPHEMERAL意味著Leader待機後Session結束節點被刪除),再一次重複之前的過程註冊節點搶佔成為Leader。這個模式實現簡單,問題是在節點數量過多的時候一旦發生重新競選,這個時候可能會有性能問題。
  • 公平實現。多個客戶端同時創建EPHEMERAL+SEQUENCE節點。客戶端都可以創建成功節點,客戶端如果判斷自己是最小的節點則為Leader否則為Follower。每一個Follower都去watch序號比自己小的節點(大家都看前一位)。一旦有Leader節點因為宕機被刪除(還是EPHEMERAL特性),收到通知的節點會看自己是不是最小的序號,如果是的話成為Leader。節點宕機後,原先watch宕機節點的客戶端重新watch比自己序號小的有效節點。這個模式實現複雜,但是由於watch的都只是一個節點所以不會發生像非公平實現的性能問題,而且競選根據節點序號來而不是搶佔式所以顯得Leader的選舉公平有序。

22、管道和過濾器模式:將需要執行復雜處理的任務分解成可以重複使用的一系列單獨的元素

三十種架構設計模式(下)

在軟件設計模式中過濾器構成的管道這種模式很常見(圖上的業務邏輯就是Handler,之前的那些Task就是Filter,模式上可以是Filter+Handler也可以是Filter+Handler+Filter也可以是Handler+Filter),不管是Spring MVC框架也好,Netty這種網絡框架也好都提供了這樣的設計。每一個過濾器單獨完成一個功能,可以獨立插拔隨意組合配置成一套管道,不但數據處理的整個過程清晰可見還增加了靈活性。

對於架構上也可以有這樣的模式,在數據源進入到業務邏輯處理之前(或之後,或前後),我們可以配置一系列的數據過濾器完成各種數據轉化和處理的任務。Task和Task之間可以是同步調用,也可以使用MQ做一定的可伸縮性設計。還可以把過濾器的配置信息保存在配置系統中甚至根據上下文動態構建出管道,實現更靈活的前置或後置流程處理。

消息模式

23、競爭消費者模式:使用多個併發消費者來處理在同一消息通道上接收的消息

三十種架構設計模式(下)

這裡說的是消息隊列的消息消費者是一組對等的消費者,通過競爭方式來拉取數據執行。之前提到過這是MQ的最常見的一種模式,一般而言我們會部署多個消費節點進行負載均衡,在負載較大的時候可以方便得增加消費者進行消費能力擴容。不過對於這種模式消費者應當是對等的無狀態的,在某個消費者在消費失敗的時候消息重新回到隊列隨後可能會被另一個消費者進行處理。

24、重試模式:在應用程序嘗試連接到服務或網絡資源遇到預期的臨時故障時,讓程序通過透明地重試以前失敗的操作來處理

三十種架構設計模式(下)

重試適用於瞬態故障,之後會提到斷路器模式,兩種模式可以結合使用。首先說說重試的幾個發起人:

  • 讓用戶自己發起,遇到錯誤的時候及時返回錯誤信息,讓用戶自己稍後重試整個業務功能。這種方式不容易產生瞬時的壓力,但是體驗較差。
  • 在中間件自動發起,比如在RPC調用的時候遇到服務超時自動進行一定次數的重試,這樣可以在外部沒有感知的情況下有一定概率消除錯誤。這個方式要求服務是支持重試的。
  • 由業務邏輯手動發起,不同的業務邏輯根據需求在代碼中去寫重試的邏輯(當然也可以通過類似Spring-Retry這種組件來做)。實現繁瑣但是不容易出錯。
  • 由補償邏輯發起進行同步轉異步操作,非重要邏輯同步行則行,不行不在主流程重試,由單獨的異步流程進行重試補償。

重試也要考慮幾種策略:

  • 次數。最多重試幾次。
  • 異常。遇到什麼樣的異常(黑白名單)應該去重試。
  • 等待。考慮每次重試是相同的間隔呢還是有一個延遲的遞增,隨著重試次數增加而增加延時時間。

25、調度、代理、主管模式:在一組分佈式服務和其它遠程資源之間協調一組操作

三十種架構設計模式(下)

這個模式說的是三者的角色:

  • 調度負責安排任務,在執行每個步驟的時候維護任務的狀態,具體業務邏輯由代理負責。
  • 代理負責和遠程的服務和資源進行通訊,實現錯誤處理和重試。
  • 管理者負責監視任務的執行狀態,作為調度的補充,在合適的時候要求調度進行補償。

三個角色相互配合完成複雜的,具有較多遠程服務參與的任務,確保任務的最終有效執行。在之前架構三馬車一文中說到定時任務的時候提到過一種任務驅動表的模式,說到了一些驅動表的實現細節,其實整體和這個模式是類似的思想。當我們的一個複雜邏輯有多個步驟構成,每一步都依賴外部服務,這個時候我們可以選擇全程MQ+補償方式(樂觀方式),也可以選擇全程任務驅動的被動模式(悲觀方式),具體選擇取決於更看重可靠性還是及時性。

彈性模式

26、艙壁模式:將應用程序的元素隔離到池中,如果其中一個失敗,其它的將繼續運行

三十種架構設計模式(下)

資源隔離有好幾個層次,可以在進程內部做線程池或隊列的隔離,在微服務的服務劃分上考慮隔離出單獨的物理服務,或是在服務器層面通過虛擬化技術或Docker技術進行資源隔離。隔離了就不會相互影響,但是會有成本、性能、管理便利性方面的開銷。實現能夠根據需求分析出可能的資源相互影響的點,提前規劃隔離往往可以避免很多問題的發生。之前有遇到過幾個事故是這樣的:

  • 程序內部大量使用了Java8的ParallelStream特性進行並行處理,由於默認共享了相同的線程池,某一個業務的執行佔滿了線程影響了其它業務的正常進行。
  • 消息隊列因為沒有對執行過多次失敗的死信消息和正常的新消息進行隔離,導致一些業務下線後無法處理的死消息佔滿了整個隊列,正常消息無法消費。
  • 某個服務提供了類似文件上傳的重量級操作,也提供了數據查詢的輕量級操作,在上傳業務大的時候服務的線程都被IO所佔滿,導致其它查詢操作無法進行。

27、斷路器模式:連接到遠程服務或資源時, 處理可能需要花費時間來修復的故障

三十種架構設計模式(下)

分佈式應用環節多網絡環境複雜,如果遇到依賴服務調用失敗的情況我們或許可以進行重試期待服務馬上可以恢復,但是在某些時候依賴的服務是徹底掛了而不是網絡故障無法及時恢復,如果不考慮進行熔斷的,可能服務調用方會被服務提供方拖死。這個時候可以引入斷路器機制,如圖所示斷路器一般採用三態實現,瞬間恢復可能會讓底層服務壓力過大:

  • 關閉:出現錯誤的時候增加計數器
  • 打開:計數器到達閾值打開斷路器,直接返回錯誤
  • 半開:超時後允許一定的請求通過,成功率達到閾值關閉斷路器,操作還是失敗的話還是進入打開狀態

實現模式的時候考慮下面注意點:

  • 考慮熔斷後怎麼來處理,熔斷後我們肯定拿不到實際的處理結果,這個時候考慮是功能降級還是採用後備的數據提供方來提供數據
  • 緊急的時候需要人工介入,最好在外部提供手動的方式可以干預斷路器的三態
  • 不同的業務考慮不同的斷路器打開閾值,每一個錯誤還能有不同的權重,比如對於下游程序返回了太多請求的錯誤,每次錯誤可以+2提高權重儘可能早斷路
  • 斷路器應當記錄熔斷時的請求原始信息,在之後必要的時候可以進行重放或數據修復工作
  • 注意設置好外部服務的超時,如果客戶端超時比服務端短,很可能進行錯誤的熔斷

實現上我們可以看一下Netflix的Hystrix進行進一步瞭解。

28、事務補償模式:撤消通過一系列步驟執行的工作,它們一起定義最終一致的操作

三十種架構設計模式(下)

這個模式說的是失敗時必須進行撤銷的操作,可以由一組補償程序來做相應的補償。在這裡我想說的更廣一點,在服務調用的時候,調用失敗有幾種可能:

  • 請求客戶端發出但是沒到服務端,業務邏輯沒有執行
  • 請求客戶端發出服務端收到也處理成功了,業務邏輯執行了客戶端沒收到結果
  • 請求客戶端發出服務端收到但處理失敗了,客戶端沒收到結果

所以在出現服務調用失敗或超時的時候,服務端執行究竟有沒有成功客戶端是不明確的(只有客戶端收到了明確的服務端返回的業務錯誤才真正代表執行失敗),這個時候需要有補償邏輯來同步服務端的執行狀態。如果這樣的補償不可避免而且需要補償的服務特別多,這樣的邏輯逐一來寫是一件很煩的事情,我們可以把這個工作封裝成一個補償中間件來處理:

  • 所有關鍵服務調用標記為需要自動補償
  • 補償中間件在數據庫記錄服務的調用狀態
  • 關鍵服務的提供者提供統一狀態查詢接口,消費者提供統一的補償回調接口(來處理成功和失敗的情況)
  • 補償中間件根據數據庫的記錄調用服務提供方的狀態查詢和服務消費方的補償回調接口進行補償

這樣,我們在服務調用的時候就不需要考慮補償邏輯的實現,只要實現這個標準即可。

安全模式

29、代客密鑰模式:使用向客戶端提供對特定資源或服務的有限直接訪問權限的令牌或密鑰

三十種架構設計模式(下)

這個模式說的是,在訪問敏感資源的時候,我們可以不必讓應用程序在其中作為一個代理轉一層做權限控制,而是生成一個密鑰,讓用戶直接拿著密鑰到資源池換數據。

一些CDN在提供資源上傳下載服務的時候一般都會提供類似的安全策略,需要實現生成Token才能去使用下載和上傳服務,避免了CDN數據被非法利用作為圖床的可能。

實現上比較簡單,應用程序和資源提供方約定好Token的生成算法,對資源+請求資源的時間+密鑰聯合在一起做簽名,資源提供方如果校驗到簽名不正確或Token過期或資源不匹配都將拒絕服務。

30、聯合身份模式:將認證委託給外部身份提供者

三十種架構設計模式(下)

這個模式說的是將身份驗證委託給專門的程序或模塊來做。使用專門的模塊來統一負責登錄授權不僅僅可以提供單點登錄的功能,而且服務實現上更簡單不需要每次都考慮登錄那套東西。實現上可以看一下Spring Security實現的OAuth 2.0。

總結一下,對於其中的很多模式,我們可以發現其實在之前的一些介紹或多或少有一些涉及。這裡提到的30種模式有些體現的是一些設計細節,有些體現的是一種設計理念,它們大多時候是組合使用的,適合的就是最好的,大家可以細細品味一下每種模式的適合場景,在合適的時候可以想到它或許會有一種豁然開朗的感覺。


原文出處:

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

https://www.cnblogs.com/lovecindywang/p/9674123.html


分享到:


相關文章: