SOLID 仍然與現代軟件架構相關嗎?

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2000 年,Robert C. Martin 總結出了一套原則來指導大家進行軟件設計,Michael Feathers 隨後按首字母將其總結成 SOLID 原則。從那時起,面向對象的 SOLID 設計原則就不斷出現在相關書籍當中,併成爲業界廣爲人知的指導方針:單一職責原則、開 \/ 閉原則、里氏替換原則、接口隔離原則、依賴倒置原則。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在過去的這二十年裏,軟件開發領域一直在快速演進,特別是近幾年雲原生和微服務的發展,在微服務體系下,“SOLID 原則是否適合現代軟件工程”引起了廣泛討論。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"無論是支持 SOLID 原則的還是不支持的,他們都一致認爲 SOLID 原則不再像以前那樣被大多數程序員普遍使用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其中一篇是"},{"type":"link","attrs":{"href":"https:\/\/stackoverflow.blog\/2021\/11\/01\/why-solid-principles-are-still-the-foundation-for-modern-software-architecture\/","title":"xxx","type":null},"content":[{"type":"text","text":" Daniel Orner 最近發表的"}]},{"type":"text","text":",他認爲 SOLID 原則仍然是現代軟件架構的基礎,但許多 SOLID 真正關心的事情,比如類和接口、數據隱藏和多態,已經不再是程序員每天要處理的事情,因此他建議重新定義原始的 SOLID 原則。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從事編程已有 30 多年並在大學裏教授碩士課程的 Paulo Merson 態度更爲鮮明,他認爲雖然 SOLID 原則有利於 OOP,但並不完全適用於微服務:SOLID 設計範式中處理的元素(類、接口、層次結構等)與常規分佈式系統中的元素、特別是微服務中的元素存在着本質區別。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同時 Paulo Merson 分析了微服務設計的全過程,對應提出了一套新的原則“IDEALS”,希望能夠幫助大家充分理解微服務架構,更嫺熟地駕馭這股新興的技術力量。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"適用於微服務的架構設計原則"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"幾年前,有人問我“SOLID 原則適不適用於微服務?”當時我正好在教授微服務設計課程,所以經過一番思考,我給出的答案是“部分適用”。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"幾個月後,我決定整理出一套專門針對微服務架構的基本設計原則,而且希望能跟 SOLID 一樣上口好記。爲什麼非得弄出個原則不可?因爲在開發行業當中,我們在微服務解決方案的設計和實施方面已經投入了六年多的時間。在此期間,越來越多的工具、框架、平臺乃至支持產品都圍繞微服務架構建立起極其豐富的技術格局。而選擇越多樣,新手微服務開發者在自己的項目裏越可能被淹沒在衆多設計決策與技術選項當中。如果能夠整理出一組核心原則,無疑將幫助開發人員把自己的設計決策朝着正確的微服務發展方向推進一大步。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"雖然 SOLID 原則中也有部分內容適用於微服務,但面向對象終究是一種設計範式,"},{"type":"text","marks":[{"type":"strong"}],"text":"它處理的元素(類、接口、層次結構等)與常規分佈式系統中的元素、特別是微服務中的元素仍存在着本質區別。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此,我們提出了以下微服務設計原則:接口隔離、可部署性、事件驅動、可用性高於一致性、鬆散耦合、單一職責。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這些原則當然不可能涵蓋微服務解決方案的整個設計決策範圍,但至少已經觸及到創建現代微服務系統的那些關鍵問題與成功因素。下面,我們將一同瞭解爲什麼符合這“IDEALS”原則的微服務纔是好的微服務。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"接口隔離"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最初的接口隔離原則強調 OO 類應該使用“胖”接口。更確切地講,不要把客戶可能需要的所有方法塞進同一個類接口,而是應該提供多個單獨接口來滿足每種類型客戶的特定需求。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"微服務架構其實是面向服務架構的一種特殊化形式,其中接口(即服務契約)的設計一直非常重要。從 2000 年初開始,SOA 文獻就規定出一切服務客戶端都應遵循的規範模型或規範模式。但 SOA 身上帶着種種舊時代的氣息,並不能匹配我們當下在服務契約設計和處理方式上出現的複雜變化。在微服務時代,同一服務邏輯往往對接多個客戶端程序(前端),這也正是我們在微服務架構中強調接口隔離的主要原因。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"在微服務中實現接口隔離"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"微服務接口隔離的目標,是確保每種類型的前端都能對接最匹配其需求的服務契約。例如,移動原生應用希望調用以短 JSON 表示的數據響應端點;Web 應用則使用完整 JSON 表示;舊版桌面應用程序在調用時同樣需要完整表示、但要求使用 XML 格式。另外,不同的客戶端往往會使用不同的協議。例如,外部客戶端可能希望使用 HTTP 來調用 gRPC 服務。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們當然不可能將同一服務契約(即規範模型)強加給所有類型的服務客戶端,而應選擇“隔離接口”以確保不同類型的客戶端總能匹配它們實際需要的服務接口。但具體要怎麼實現?目前最流行的解決方案就是使用 API 網關。這種網關可以實現消息格式轉換、消息結構轉換、協議橋接、消息路由等功能。還有另一種流行的替代方案,即面向前端的後端(BFF)模式。在這種情況下,我們會爲每種類型的客戶端設置一個 API 網關——也就是讓每個客戶端擁有不同的 BFF,如下圖所示。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/c1\/c10ad04ac851232735b80a8d2ca4efab.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"可部署性(開發者側)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"縱觀整個軟件發展歷史,設計工作一直集中在關於實現單元(模塊)的組織方式以及運行時元素(組件)的交互設計決策身上。架構策略、設計模式乃至其他設計策略強調的就是如何在各個層級中組織軟件元素、避免過度依賴、爲某些類型的組件分配特定角色或關注點,併爲“軟件”空間中的其他設計決策提供指導。但對於微服務開發人員來說,還有其他一些超越了常規軟件元素的關鍵設計決策需要考量。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"作爲開發人員,我們早就意識到將軟件正確打包並部署至適當的運行時拓撲中的重要意義。然而,我們從來沒有像現在這樣高度關注微服務的部署與運行時監控。這第二條原則被稱爲“可部署性”,這方面技術與設計決策已經成爲決定微服務成敗的關鍵。而它的主要意義基於這樣一個簡單事實——微服務架構顯著增加了需要部署的單元數量。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此,IDEALS 中的“D”提醒微服務開發人員,他們還需要保證軟件及其後續版本能夠始終滿足用戶的隨時使用需求。總之,可部署性設計包括:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"配置運行時基礎設施,包括容器、pod、集羣、持久性、安全性及網絡等。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對微服務進行規模伸縮,或者將其從一個運行時環境遷移至另一環境。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"加快提交 + 構建 + 測試 + 部署流程。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最大限度減少當前版本更替時的停機時間。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同步相關軟件的版本變化。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"監控微服務的健康狀況,以快速識別並修復故障。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"實現良好的可部署性"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"自動化是實現高效部署的關鍵。要實現自動化,我們需要正確選擇工具與技術,這也正是自微服務架構出來以來變化最大的領域所在。因此,微服務開發人員應該在工具與平臺方面拓展思路,並始終以懷疑的態度審視不同選擇帶來的助益與挑戰。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面來看開發者們在一切微服務解決方案中都應認真考慮的可部署性策略與技術:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"容器化與容器編排:容器化微服務能夠降低跨平臺及跨雲環境時的複製與部署難度;編排平臺則提供共享資源與機制,用於實現路由、擴展、複製、負載均衡等功能。Docker 與 Kubernetes 已經成爲當前容器化與容器編排層面的客觀行業標準。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"服務網格(Service mesh):此類工具可用於實現流量監控、策略執行、身份驗證、RBAC、路由、斷路器、消息轉換等功能,幫助容器編排平臺完成通信。目前流行的服務網格方案包括 Istio、Linkerd 以及 Consul Connect。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"API 網關:通過攔截對微服務的調用,API 網關產品提供一系列豐富的功能,包括消息轉換與協議橋接、流量監控、安全控制、路由、緩存、請求節流以及 API 配額與斷路等。這方面的典型方案包括 Ambassador、Kong、Apiman、WSO2 API Manager、Apigee 以及 Amazon API Gateway 等。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"無服務器架構:通過將服務部署至遵循 FaaS 範式的無服務器平臺,大家可以迴避由容器編排帶來的大部分複雜性與運營成本。AWS Lambda、Azure Functions 以及 Google Cloud Functions 都是無服務器平臺的典型選項。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"監控工具:在將微服務分佈在我們的本地和雲基礎設施之後,對關於系統運行狀態的問題進行預測、檢測與通知就變得至關重要。目前市面上有多種監控工具可以選擇,例如 New Relic、CloudWatch、Datadog、Prometheus 以及 Grafana。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"日誌整合工具:微服務往往會輕鬆將部署單元的數量提升至新的數量級,因此我們需要專門的工具整合這些組件的日誌輸出,並靈活使用搜索、分析與警報生成等功能。這方面的高人氣工具包括 Fluentd、Graylog、Splunk 及 ELK(Elasticsearch、Logtstash 以及 Kibana)。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"跟蹤工具:這些工具可以檢測您的微服務,之後生成、收集並可視化跨服務調用的運行時跟蹤數據。它們還能幫助您發現性能問題,甚至幫助您瞭解整體架構。目前的主流跟蹤工具包括 Zipkin、Jaeger 以及 AWS X-Ray。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"DevOps:無論是基礎設施配置還是事件處理,只有保證開發人員與運營團隊密切溝通並協作,微服務架構才能真正發揮良好效力。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"藍 - 綠部署與金絲雀發佈:這些部署策略能夠在發佈微服務新版本時實現零或近零停機,並在發現問題時快速切換回原始版本。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"基礎設施即代碼(IaC):這一實踐能夠儘可能減少構建 - 部署週期中的人爲交互,加快流程速度、降低出錯機率與審計難度。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"持續交付:這是縮短提交到部署週期、同時保持解決方案質量穩定的必要實踐。傳統的 CI\/CD 工具包括 Jenkins、GitLab CI\/CD、Bamboo、GoCD、CircleCI 以及 Spinnaker。最近,新的 GitOps 工具(例如 Weaveworks 與 Flux)也加入戰團,嘗試將 CD 與 IaC 結合起來。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"外部化配置:這一機制允許將配置屬性存儲在微服務部署單元之外以降低管理難度。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"事件驅動"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"微服務架構的主要作用就是創建(後端)服務,而這些服務通常會使用以下三種通用型連接器之一:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"HTTP 調用(指向 REST 服務)"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用 gRPC 或 GraphQL 等特定平臺組件技術的 RPC 類調用"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過消息代理隊列的異步消息"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前兩者通常爲同步性質,其中 HTTP 調用的使用頻率明顯更高。通常,服務需要調用其他服務組合,而且多數情況下服務組合內的交互具有同步性質。相反,如果我們創建(或適配)參與的服務以接入並接收來自隊列 \/ 主題的消息,則需要創建一個事件驅動架構。(有些朋友可能對消息驅動和事件驅動之間的區別存在爭議,本文則基本不做區分,會交替使用這兩個術語來引出 Apache Kafka、RabbitMQ 及 Amazon SNS 等消息代理產品)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"事件驅動架構的一大核心優勢,在於顯著提高了系統的可擴展性與吞吐量。而這一優勢之所以能夠實現,是因爲消息發送方不會因阻塞而等待響應,而且多個接收方可以通過發佈 - 訂閱的方式並行使用同一消息 \/ 事件。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"事件驅動型微服務"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"IDEALS 中的“E”強調的是應儘量在微服務建模中引入事件驅動性質,這樣才能更好地滿足當今軟件解決方案的可擴展性與性能需求。此外,這種設計也有助於鬆散耦合的實現,因爲消息發送者與接收者(雙方均爲微服務)將彼此獨立、互不瞭解。另外,因爲這種設計能夠應對微服務的暫時中斷,在後續重新處理排隊消息,所以系統可靠性也將得到提升。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當然,事件驅動型微服務(也稱反應式微服務)同樣會帶來一定挑戰。由於處理是異步激活且並行發生,因此可能需要設置同步點與相關標識符。我們還需要在設計中考慮消息錯誤和丟失問題,包括在必要時引入事件糾正與數據變更撤銷機制(例如 Saga 模式)。對於由事件驅動架構承載的面向用戶交易,應認真考慮用戶體驗,確保最終用戶瞭解當前進展和事故細節。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"可用性高於一致性"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"CAP 定理本質上提供了兩種選擇:要可用性,還是要一致性。我們看到業界付出了巨大努力才讓大家獲得了用一致性換可用性的選項,這就是最終一致性機制。原因很簡單:如今的最終用戶耐心很差,根本不可能忍受糟糕的可用性。想象一下每年購物節時的網店,如果我們在產品瀏覽時顯示的庫存數量與購買時更新的實際庫存之間硬性保持強一致性,那麼數據變更將產生大量開銷。而且一旦某些涉及庫存更新的服務暫時無法訪問,那麼目錄就無法顯示庫存信息,後續下單、結算等服務也將隨之癱瘓!相反,如果我們選擇可用性優先(即接受偶爾不一致的風險),用戶則可以根據稍稍過時的庫存數據進行購買。沒錯,終歸會有幾百或者幾千分之一的用戶因爲結賬時庫存信息不正確而被迫取消訂單,我們可以向他們發郵件道歉。但從用戶整體和業務運作的角度來看,這種情況終究要好對全體用戶都承受更慢的訪問速度或者更低的訪問穩定性。所以可用性高於一致性,沒有爭議。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當然,也有部分業務操作確實需要強一致性。但正如 Pat Helland 所指出,在馬上得到答案和得到正確答案之間,大多數人想要的其實是馬上得到答案。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"具有最終一致性的高可用性"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在微服務當中,實現可用性的主要策略在於數據複製。我們可以採用不同的設計模式,也可以在必要時把多種模式組合使用:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"服務數據複製模式:"},{"type":"text","text":"當微服務需要訪問屬於其他應用的數據(而且無法通過 API 調用獲取數據)時,則使用此種基本模式。我們會創建數據副本,使其隨時可供微服務使用。這種解決方案還需要配合數據同步機制(例如 ETL 工具 \/ 程序、發佈訂閱消息、物化視圖等),負責定期或基於觸發器保證副本與主數據保持一致。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"命令查詢職責分離(CQRS)模式:"},{"type":"text","text":"在這裏,我們將更改數據(命令)的操作與只讀數據(查詢)的操作在設計和實現層面區分開來。CQRS 通常以服務數據複製爲基礎執行查詢,用以提高性能和自治性。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"事件溯源模式:"},{"type":"text","text":"我們並不會將對象的當前狀態存儲在數據庫內,而是隻存儲影響到該對象的、純附加的、不可變的事件序列。當前狀態則通過重放這一系列事件獲得,這樣就能建立起數據的“查詢視圖”。因此,事件溯源通常需要建立在 CQRS 設計基礎之上。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"日常工作中常用的 CQRS 設計如下圖所示。我們可以使用運行在集中式 Oracle 數據庫上的 REST 服務處理操作更改數據的 HTTP 請求(這種情況下,該服務仍使用各微服務對應的數據庫)。只讀 HTTP 請求會轉入不同的後端服務,再由這些後端服務從基於 Elasticsearch 文本的數據存儲處讀取數據。定期執行 Spring Batch Kubernetes cron 作業以根據 Oracle DB 上執行的數據變更操作,對 Elasticsearch 存儲內容進行更新。這種設置使得兩套數據存儲之間始終保持最終一致性;而且即使 Oracle DB 或者 cron 作業失效,查詢服務也仍然可用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/35\/35fa9277b94c6471dbfa1cdb2ed75237.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"鬆散耦合"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在軟件工程中,耦合是指兩個軟件元素之間的相互依賴程度。對基於服務的系統而言,傳入耦合主要涉及服務用戶如何與服務進行交互。我們知道這種交互應該通過服務契約進行。另外,契約不應與實現細節或特定技術緊密耦合。服務屬於能夠被不同程序調用的分佈式組件。有時,服務託管方甚至不清楚所有服務用戶具體在哪裏(公共 API 服務就是最典型的示例)。因此,一般應儘量避免變更契約。如果服務契約與服務邏輯或者技術緊密耦合,那麼當邏輯或技術需要演進時,就很容易影響到業務的正常運行。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"服務通常需要與其他服務或其他類型的組件進行交互,由此產生傳出耦合。這種交互的存在會直接影響到服務自治的運行時依賴關係。如果服務的自治性較低,則其行爲的可預測性也將保持在較低水平:在理想情況下,服務的實際速度、可靠性與可用性由其需要調用的最慢、最不可靠且可用性最差的組件決定。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"服務間的鬆散耦合策略"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"IDEALS 原則中的“L”提醒我們要注意服務間的耦合關係,特別是微服務間的耦合情況。我們可以使用並組合多種策略以管理傳入與傳出鬆散耦合。此類策略示例包括:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"點對點發布 - 訂閱:"},{"type":"text","text":"這些構建塊消息傳遞模式及其變體有助於實現鬆散耦合,因爲發送方與接收方互不瞭解;其中響應式微服務(例如 Kafka 消費方)的契約將充當消息隊列的名稱與消息結構。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"API 網關與 BFF:"},{"type":"text","text":"這類解決方案規定了一箇中間組件,用於處理服務契約與客戶端想要查看的消息格式與協議間的一切差異,從而實現雙邊解耦。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"契約優先設計:"},{"type":"text","text":"通過這種獨立於任何現有代碼的契約設計方法,我們能夠避免創建出與特定技術或實現緊密耦合的 API。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"超媒體:"},{"type":"text","text":"對於 REST 服務,超媒體能幫助前端保持獨立於服務端點之外的存在定位。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"Façade 與 Adapter\/Wrapper 模式:"},{"type":"text","text":"微服務架構中這些 GoF 模式的變體可以規定內部組件甚至服務,並防止不良耦合在微服務實現中傳播。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"每微服務對應一數據庫模式:"},{"type":"text","text":"通過這種模式,不僅能讓微服務獲得自治權,同時也避免其與共享數據庫進行直接耦合。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"單一職責"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最初的單一職責原則(SRP)旨在強調 OO 類應具有內聚功能。但在同一個類裏包含多個職責會自然導致緊密耦合,進而衍生出難以擴展的脆弱設計成果,很可能在變更期間發生意外不到的宕機。而且如大家所知,這是一項說起來容易、但正確實現難度極高的設計原則。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"單一職責的概念也可以擴展到微服務架構內的服務內聚性層面。微服務架構規定各個部署單元應該只包含一項服務或者幾項內聚服務。如果某個微服務中充斥着大量不夠內聚的服務,則必然受到傳統單體式應用問題的影響。一旦過於臃腫,微服務在功能和技術堆棧方面將變得難以發展。另外,由於衆多開發人員會在同一部署單元中處理多個移動部件,因此持續交付也將變得無法爲繼。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另一方面,如果在微服務劃分上粒度過細,則可能需要多次微服務交互才能滿足用戶請求。在最糟糕的情況下,數據變更可能會分佈在不同的微服務中,進而催生出過於複雜的分佈式事務場景。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"對微服務進行正確的粒度劃分"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"微服務設計真正趨於成熟的一個重要體現,就是創建出既不過粗、也不過細的微服務成果。這裏的解決辦法不在於任何工具或技術,而在於適當的領域建模。我們可以通過多種方式爲後端服務建模,並劃定出正確的微服務邊界。目前業界流行的微服務範圍設計方法是領域驅動設計(DDD)原則。簡單來講:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"服務(例如 REST 服務)所能具有的 DDD 聚合範圍。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"微服務所能具有的 DDD 有界上下文範圍。該微服務中的服務應對應於有界上下文內的聚合。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於微服務間通信,我們可以使用:當異步消息滿足需求時,則使用領域事件;當請求 - 響應連接器更適合時,則使用某種防腐層調用 API;當微服務需要來自其他 BC 的大量數據時,使用具有最終一致性的數據複製機制。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"總    結"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"IDEALS 原則爲大多數典型微服務設計提供了指導意見。當然,這些只是指導方針、絕非必然保證微服務設計成功的魔藥或者神咒。與以往技術實踐一樣,我們同樣需要對開發質量保持良好理解,並在設計決策中深刻體會不同選項間的利弊權衡。此外,我們還應瞭解有助於實現設計原則的設計模式與架構策略,同時更好地掌握市面上可用的技術方案。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"幾年以來,我一直在使用 IDEALS 原則設計、實施並部署微服務。我也曾在設計研討會和演講中與來自不同組織的數百名軟件開發人員討論過這些原則,特別是每條原則背後的思路與策略。希望本文提出的 IDEALS 原則能夠幫助大家充分理解微服務架構,更嫺熟地駕馭這股新興的技術力量。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"延伸閱讀:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/www.infoq.com\/news\/2021\/11\/solid-modern-microservices\/","title":"xxx","type":null},"content":[{"type":"text","text":"https:\/\/www.infoq.com\/news\/2021\/11\/solid-modern-microservices\/"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/www.infoq.com\/articles\/microservices-design-ideals\/","title":"","type":null},"content":[{"type":"text","text":"https:\/\/www.infoq.com\/articles\/microservices-design-ideals\/"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/stackoverflow.blog\/2021\/11\/01\/why-solid-principles-are-still-the-foundation-for-modern-software-architecture\/","title":"","type":null},"content":[{"type":"text","text":"https:\/\/stackoverflow.blog\/2021\/11\/01\/why-solid-principles-are-still-the-foundation-for-modern-software-architecture\/"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章