網路城邦
上一篇 回創作列表 下一篇   字體:
【專家解說】 流行20年的架構設計原則SOLID可能已經不適合微服務了
2021/12/06 12:57:37瀏覽296|回應0|推薦0

編譯 | 核子可樂、Tina  微服務體系下,我們需要重新審視以往的架構設計原則。  

2000 年,Robert C. Martin 給架構師們總結出了一套原則來指導大家進行軟件設計,Michael Feathers 隨后按首字母將其總結成 SOLID 原則。從那時起,面向對象的 SOLID 設計原則就不斷出現在相關書籍當中,并成為業界廣為人知的指導方針:單一職責原則、開 / 閉原則、里氏替換原則、接口隔離原則、依賴倒置原則。

在過去的這二十年里,軟件開發領域一直在快速演進,特別是近幾年云原生和微服務的發展,在微服務體系下,“SOLID 原則是否適合現代軟件工程”引起了廣泛討論。

無論是支持 SOLID 原則的還是不支持的,他們都一致認為 SOLID 原則不再像以前那樣被大多數程序員普遍使用。

其中一篇是 Daniel Orner 最近發表的,他認為 SOLID 原則仍然是現代軟件架構的基礎,但許多 SOLID 真正關心的事情,比如類和接口、數據隱藏和多態,已經不再是程序員每天要處理的事情,因此他建議重新定義原始的 SOLID 原則。

從事編程已有 30 多年并在大學里教授碩士課程的 Paulo Merson 態度更為鮮明,他認為雖然 SOLID 原則有利于 OOP,但并不完全適用于微服務:SOLID 設計范式中處理的元素(類、接口、層次結構等)與常規分布式系統中的元素、特別是微服務中的元素存在著本質區別。

同時 Paulo Merson 分析了微服務設計的全過程,對應提出了一套新的原則“IDEALS”,希望能夠幫助大家充分理解微服務架構,更嫻熟地駕馭這股新興的技術力量。

適用于微服務的架構設計原則

幾年前,有人問我“SOLID 原則適不適用于微服務?”當時我正好在教授微服務設計課程,所以經過一番思考,我給出的答案是“部分適用”。

幾個月后,我決定整理出一套專門針對微服務架構的基本設計原則,而且希望能跟 SOLID 一樣上口好記。為什么非得弄出個原則不可?因為在開發行業當中,我們在微服務解決方案的設計和實施方面已經投入了六年多的時間。在此期間,越來越多的工具、框架、平臺乃至支持產品都圍繞微服務架構建立起極其豐富的技術格局。而選擇越多樣,新手微服務開發者在自己的項目里越可能被淹沒在眾多設計決策與技術選項當中。如果能夠整理出一組核心原則,無疑將幫助開發人員把自己的設計決策朝著正確的微服務發展方向推進一大步。

雖然 SOLID 原則中也有部分內容適用于微服務,但面向對象終究是一種設計范式,它處理的元素(類、接口、層次結構等)與常規分布式系統中的元素、特別是微服務中的元素仍存在著本質區別。

因此,我們提出了以下微服務設計原則:接口隔離、可部署性、事件驅動、可用性高于一致性、松散耦合、單一職責。

這些原則當然不可能涵蓋微服務解決方案的整個設計決策范圍,但至少已經觸及到創建現代微服務系統的那些關鍵問題與成功因素。下面,我們將一同了解為什么符合這“IDEALS”原則的微服務才是好的微服務。

接口隔離

最初的接口隔離原則強調 OO 類應該使用“胖”接口。更確切地講,不要把客戶可能需要的所有方法塞進同一個類接口,而是應該提供多個單獨接口來滿足每種類型客戶的特定需求。

微服務架構其實是面向服務架構的一種特殊化形式,其中接口(即服務契約)的設計一直非常重要。從 2000 年初開始,SOA 文獻就規定出一切服務客戶端都應遵循的規范模型或規范模式。但 SOA 身上帶著種種舊時代的氣息,并不能匹配我們當下在服務契約設計和處理方式上出現的復雜變化。在微服務時代,同一服務邏輯往往對接多個客戶端程序(前端),這也正是我們在微服務架構中強調接口隔離的主要原因。

在微服務中實現接口隔離

微服務接口隔離的目標,是確保每種類型的前端都能對接最匹配其需求的服務契約。例如,移動原生應用希望調用以短 JSON 表示的數據響應端點;Web 應用則使用完整 JSON 表示;舊版桌面應用程序在調用時同樣需要完整表示、但要求使用 XML 格式。另外,不同的客戶端往往會使用不同的協議。例如,外部客戶端可能希望使用 HTTP 來調用 gRPC 服務。

我們當然不可能將同一服務契約(即規范模型)強加給所有類型的服務客戶端,而應選擇“隔離接口”以確保不同類型的客戶端總能匹配它們實際需要的服務接口。但具體要怎么實現?目前最流行的解決方案就是使用 API 網關。這種網關可以實現消息格式轉換、消息結構轉換、協議橋接、消息路由等功能。還有另一種流行的替代方案,即面向前端的后端(BFF)模式。在這種情況下,我們會為每種類型的客戶端設置一個 API 網關——也就是讓每個客戶端擁有不同的 BFF,如下圖所示。

可部署性(開發者側)

縱觀整個軟件發展歷史,設計工作一直集中在關于實現單元(模塊)的組織方式以及運行時元素(組件)的交互設計決策身上。架構策略、設計模式乃至其他設計策略強調的就是如何在各個層級中組織軟件元素、避免過度依賴、為某些類型的組件分配特定角色或關注點,并為“軟件”空間中的其他設計決策提供指導。但對于微服務開發人員來說,還有其他一些超越了常規軟件元素的關鍵設計決策需要考量。

作為開發人員,我們早就意識到將軟件正確打包并部署至適當的運行時拓撲中的重要意義。然而,我們從來沒有像現在這樣高度關注微服務的部署與運行時監控。這第二條原則被稱為“可部署性”,這方面技術與設計決策已經成為決定微服務成敗的關鍵。而它的主要意義基于這樣一個簡單事實——微服務架構顯著增加了需要部署的單元數量。

因此,IDEALS 中的“D”提醒微服務開發人員,他們還需要保證軟件及其后續版本能夠始終滿足用戶的隨時使用需求。總之,可部署性設計包括:

  • 配置運行時基礎設施,包括容器、pod、集群、持久性、安全性及網絡等。

  • 對微服務進行規模伸縮,或者將其從一個運行時環境遷移至另一環境。

  • 加快提交 + 構建 + 測試 + 部署流程。

  • 最大限度減少當前版本更替時的停機時間。

  • 同步相關軟件的版本變化。

  • 監控微服務的健康狀況,以快速識別并修復故障。

實現良好的可部署性

自動化是實現高效部署的關鍵。要實現自動化,我們需要正確選擇工具與技術,這也正是自微服務架構出來以來變化最大的領域所在。因此,微服務開發人員應該在工具與平臺方面拓展思路,并始終以懷疑的態度審視不同選擇帶來的助益與挑戰。

下面來看開發者們在一切微服務解決方案中都應認真考慮的可部署性策略與技術:

  • 容器化與容器編排:容器化微服務能夠降低跨平臺及跨云環境時的復制與部署難度;編排平臺則提供共享資源與機制,用于實現路由、擴展、復制、負載均衡等功能。Docker 與 Kubernetes 已經成為當前容器化與容器編排層面的客觀行業標準。

  • 服務網格(Service mesh):此類工具可用于實現流量監控、策略執行、身份驗證、RBAC、路由、斷路器、消息轉換等功能,幫助容器編排平臺完成通信。目前流行的服務網格方案包括 Istio、Linkerd 以及 Consul Connect。

  • API 網關:通過攔截對微服務的調用,API 網關產品提供一系列豐富的功能,包括消息轉換與協議橋接、流量監控、安全控制、路由、緩存、請求節流以及 API 配額與斷路等。這方面的典型方案包括 Ambassador、Kong、Apiman、WSO2 API Manager、Apigee 以及 Amazon API Gateway 等。

  • 無服務器架構:通過將服務部署至遵循 FaaS 范式的無服務器平臺,大家可以回避由容器編排帶來的大部分復雜性與運營成本。AWS Lambda、Azure Functions 以及 Google Cloud Functions 都是無服務器平臺的典型選項。

  • 監控工具:在將微服務分布在我們的本地和云基礎設施之后,對關于系統運行狀態的問題進行預測、檢測與通知就變得至關重要。目前市面上有多種監控工具可以選擇,例如 New Relic、CloudWatch、Datadog、Prometheus 以及 Grafana。

  • 日志整合工具:微服務往往會輕松將部署單元的數量提升至新的數量級,因此我們需要專門的工具整合這些組件的日志輸出,并靈活使用搜索、分析與警報生成等功能。這方面的高人氣工具包括 Fluentd、Graylog、Splunk 及 ELK(Elasticsearch、Logtstash 以及 Kibana)。

  • 跟蹤工具:這些工具可以檢測您的微服務,之后生成、收集并可視化跨服務調用的運行時跟蹤數據。它們還能幫助您發現性能問題,甚至幫助您了解整體架構。目前的主流跟蹤工具包括 Zipkin、Jaeger 以及 AWS X-Ray。

  • DevOps:無論是基礎設施配置還是事件處理,只有保證開發人員與運營團隊密切溝通并協作,微服務架構才能真正發揮良好效力。

  • 藍 - 綠部署與金絲雀發布:這些部署策略能夠在發布微服務新版本時實現零或近零停機,并在發現問題時快速切換回原始版本。

  • 基礎設施即代碼(IaC):這一實踐能夠盡可能減少構建 - 部署周期中的人為交互,加快流程速度、降低出錯幾率與審計難度。

  • 持續交付:這是縮短提交到部署周期、同時保持解決方案質量穩定的必要實踐。傳統的 CI/CD 工具包括 Jenkins、GitLab CI/CD、Bamboo、GoCD、CircleCI 以及 Spinnaker。最近,新的 GitOps 工具(例如 Weaveworks 與 Flux)也加入戰團,嘗試將 CD 與 IaC 結合起來。

  • 外部化配置:這一機制允許將配置屬性存儲在微服務部署單元之外以降低管理難度。

事件驅動

微服務架構的主要作用就是創建(后端)服務,而這些服務通常會使用以下三種通用型連接器之一:

  • HTTP 調用(指向 REST 服務)

  • 使用 gRPC 或 GraphQL 等特定平臺組件技術的 RPC 類調用

  • 通過消息代理隊列的異步消息

前兩者通常為同步性質,其中 HTTP 調用的使用頻率明顯更高。通常,服務需要調用其他服務組合,而且多數情況下服務組合內的交互具有同步性質。相反,如果我們創建(或適配)參與的服務以接入并接收來自隊列 / 主題的消息,則需要創建一個事件驅動架構。(有些朋友可能對消息驅動和事件驅動之間的區別存在爭議,本文則基本不做區分,會交替使用這兩個術語來引出 Apache Kafka、RabbitMQ 及 Amazon SNS 等消息代理產品)。

事件驅動架構的一大核心優勢,在于顯著提高了系統的可擴展性與吞吐量。而這一優勢之所以能夠實現,是因為消息發送方不會因阻塞而等待響應,而且多個接收方可以通過發布 - 訂閱的方式并行使用同一消息 / 事件。

事件驅動型微服務

IDEALS 中的“E”強調的是應盡量在微服務建模中引入事件驅動性質,這樣才能更好地滿足當今軟件解決方案的可擴展性與性能需求。此外,這種設計也有助于松散耦合的實現,因為消息發送者與接收者(雙方均為微服務)將彼此獨立、互不了解。另外,因為這種設計能夠應對微服務的暫時中斷,在后續重新處理排隊消息,所以系統可靠性也將得到提升。

當然,事件驅動型微服務(也稱反應式微服務)同樣會帶來一定挑戰。由于處理是異步激活且并行發生,因此可能需要設置同步點與相關標識符。我們還需要在設計中考慮消息錯誤和丟失問題,包括在必要時引入事件糾正與數據變更撤銷機制(例如 Saga 模式)。對于由事件驅動架構承載的面向用戶交易,應認真考慮用戶體驗,確保最終用戶了解當前進展和事故細節。

可用性高于一致性

CAP 定理本質上提供了兩種選擇:要可用性,還是要一致性。我們看到業界付出了巨大努力才讓大家獲得了用一致性換可用性的選項,這就是最終一致性機制。原因很簡單:如今的最終用戶耐心很差,根本不可能忍受糟糕的可用性。想象一下每年購物節時的網店,如果我們在產品瀏覽時顯示的庫存數量與購買時更新的實際庫存之間硬性保持強一致性,那么數據變更將產生大量開銷。而且一旦某些涉及庫存更新的服務暫時無法訪問,那么目錄就無法顯示庫存信息,后續下單、結算等服務也將隨之癱瘓!相反,如果我們選擇可用性優先(即接受偶爾不一致的風險),用戶則可以根據稍稍過時的庫存數據進行購買。沒錯,終歸會有幾百或者幾千分之一的用戶因為結賬時庫存信息不正確而被迫取消訂單,我們可以向他們發郵件道歉。但從用戶整體和業務運作的角度來看,這種情況終究要好對全體用戶都承受更慢的訪問速度或者更低的訪問穩定性。所以可用性高于一致性,沒有爭議。

當然,也有部分業務操作確實需要強一致性。但正如 Pat Helland 所指出,在馬上得到答案和得到正確答案之間,大多數人想要的其實是馬上得到答案。

具有最終一致性的高可用性

在微服務當中,實現可用性的主要策略在于數據復制。我們可以采用不同的設計模式,也可以在必要時把多種模式組合使用:

  • 服務數據復制模式:當微服務需要訪問屬于其他應用的數據(而且無法通過 API 調用獲取數據)時,則使用此種基本模式。我們會創建數據副本,使其隨時可供微服務使用。這種解決方案還需要配合數據同步機制(例如 ETL 工具 / 程序、發布訂閱消息、物化視圖等),負責定期或基于觸發器保證副本與主數據保持一致。

  • 命令查詢職責分離(CQRS)模式:在這里,我們將更改數據(命令)的操作與只讀數據(查詢)的操作在設計和實現層面區分開來。CQRS 通常以服務數據復制為基礎執行查詢,用以提高性能和自治性。

  • 事件溯源模式:我們并不會將對象的當前狀態存儲在數據庫內,而是只存儲影響到該對象的、純附加的、不可變的事件序列。當前狀態則通過重放這一系列事件獲得,這樣就能建立起數據的“查詢視圖”。因此,事件溯源通常需要建立在 CQRS 設計基礎之上。

日常工作中常用的 CQRS 設計如下圖所示。我們可以使用運行在集中式 Oracle 數據庫上的 REST 服務處理操作更改數據的 HTTP 請求(這種情況下,該服務仍使用各微服務對應的數據庫)。只讀 HTTP 請求會轉入不同的后端服務,再由這些后端服務從基于 Elasticsearch 文本的數據存儲處讀取數據。定期執行 Spring Batch Kubernetes cron 作業以根據 Oracle DB 上執行的數據變更操作,對 Elasticsearch 存儲內容進行更新。這種設置使得兩套數據存儲之間始終保持最終一致性;而且即使 Oracle DB 或者 cron 作業失效,查詢服務也仍然可用。

松散耦合

在軟件工程中,耦合是指兩個軟件元素之間的相互依賴程度。對基于服務的系統而言,傳入耦合主要涉及服務用戶如何與服務進行交互。我們知道這種交互應該通過服務契約進行。另外,契約不應與實現細節或特定技術緊密耦合。服務屬于能夠被不同程序調用的分布式組件。有時,服務托管方甚至不清楚所有服務用戶具體在哪里(公共 API 服務就是最典型的示例)。因此,一般應盡量避免變更契約。如果服務契約與服務邏輯或者技術緊密耦合,那么當邏輯或技術需要演進時,就很容易影響到業務的正常運行。

服務通常需要與其他服務或其他類型的組件進行交互,由此產生傳出耦合。這種交互的存在會直接影響到服務自治的運行時依賴關系。如果服務的自治性較低,則其行為的可預測性也將保持在較低水平:在理想情況下,服務的實際速度、可靠性與可用性由其需要調用的最慢、最不可靠且可用性最差的組件決定。

服務間的松散耦合策略

IDEALS 原則中的“L”提醒我們要注意服務間的耦合關系,特別是微服務間的耦合情況。我們可以使用并組合多種策略以管理傳入與傳出松散耦合。此類策略示例包括:

  • 點對點發布 - 訂閱:這些構建塊消息傳遞模式及其變體有助于實現松散耦合,因為發送方與接收方互不了解;其中響應式微服務(例如 Kafka 消費方)的契約將充當消息隊列的名稱與消息結構。

  • API 網關與 BFF:這類解決方案規定了一個中間組件,用于處理服務契約與客戶端想要查看的消息格式與協議間的一切差異,從而實現雙邊解耦。

  • 契約優先設計:通過這種獨立于任何現有代碼的契約設計方法,我們能夠避免創建出與特定技術或實現緊密耦合的 API。

  • 超媒體:對于 REST 服務,超媒體能幫助前端保持獨立于服務端點之外的存在定位。

  • Fa?ade 與 Adapter/Wrapper 模式:微服務架構中這些 GoF 模式的變體可以規定內部組件甚至服務,并防止不良耦合在微服務實現中傳播。

  • 每微服務對應一數據庫模式:通過這種模式,不僅能讓微服務獲得自治權,同時也避免其與共享數據庫進行直接耦合。

單一職責

最初的單一職責原則(SRP)旨在強調 OO 類應具有內聚功能。但在同一個類里包含多個職責會自然導致緊密耦合,進而衍生出難以擴展的脆弱設計成果,很可能在變更期間發生意外不到的宕機。而且如大家所知,這是一項說起來容易、但正確實現難度極高的設計原則。

單一職責的概念也可以擴展到微服務架構內的服務內聚性層面。微服務架構規定各個部署單元應該只包含一項服務或者幾項內聚服務。如果某個微服務中充斥著大量不夠內聚的服務,則必然受到傳統單體式應用問題的影響。一旦過于臃腫,微服務在功能和技術堆棧方面將變得難以發展。另外,由于眾多開發人員會在同一部署單元中處理多個移動部件,因此持續交付也將變得無法為繼。

另一方面,如果在微服務劃分上粒度過細,則可能需要多次微服務交互才能滿足用戶請求。在最糟糕的情況下,數據變更可能會分布在不同的微服務中,進而催生出過于復雜的分布式事務場景。

對微服務進行正確的粒度劃分

微服務設計真正趨于成熟的一個重要體現,就是創建出既不過粗、也不過細的微服務成果。這里的解決辦法不在于任何工具或技術,而在于適當的領域建模。我們可以通過多種方式為后端服務建模,并劃定出正確的微服務邊界。目前業界流行的微服務范圍設計方法是領域驅動設計(DDD)原則。簡單來講:

  • 服務(例如 REST 服務)所能具有的 DDD 聚合范圍。

  • 微服務所能具有的 DDD 有界上下文范圍。該微服務中的服務應對應于有界上下文內的聚合。

  • 對于微服務間通信,我們可以使用:當異步消息滿足需求時,則使用領域事件;當請求 - 響應連接器更適合時,則使用某種防腐層調用 API;當微服務需要來自其他 BC 的大量數據時,使用具有最終一致性的數據復制機制。

總    結

IDEALS 原則為大多數典型微服務設計提供了指導意見。當然,這些只是指導方針、絕非必然保證微服務設計成功的魔藥或者神咒。與以往技術實踐一樣,我們同樣需要對開發質量保持良好理解,并在設計決策中深刻體會不同選項間的利弊權衡。此外,我們還應了解有助于實現設計原則的設計模式與架構策略,同時更好地掌握市面上可用的技術方案。

幾年以來,我一直在使用 IDEALS 原則設計、實施并部署微服務。我也曾在設計研討會和演講中與來自不同組織的數百名軟件開發人員討論過這些原則,特別是每條原則背后的思路與策略。希望本文提出的 IDEALS 原則能夠幫助大家充分理解微服務架構,更嫻熟地駕馭這股新興的技術力量。

延伸閱讀:

https://www..com/articles/microservices-design-ideals/

https://stackoverflow.blog/2021/11/01/why-solid-principles-are-still-the-foundation-for-modern-software-architecture/

軟件工程師年滿 40 歲,下一步怎么走?

曝騰訊天美員工稅后收入250萬,月均20萬;傳阿里CEO張勇要放權;“人人影視字幕組”侵權案宣判,被告人獲刑三年半 | Q資訊

逃離被微軟支配的恐懼,.NET開發者們Fork了一個開源分支

為什么 Netflix “永不宕機”?


 活動推薦

從傳統數據庫到云原生數據庫,從時序數據庫到多模數據庫,企業所面臨的數據處理、存儲分析、計算等的諸多挑戰正不斷被解決。彈性擴容、一寫多讀、穩定可靠……企業需要怎樣的數據庫?

12 月 1 日,《數據 cool 談》第二期,阿里云、友邦人壽、海底撈、上海市新能源汽車數據中心的技術專家為你解讀!

點個在看少個 bug ??



【問卦】 尋找暗物質的新思路:引力波或將揭示黑洞周圍的暗物質云【問卦】 高通打臉聯想,摩托羅拉絕不認輸!雷軍:跟小米拼,你有實力嗎?【最新研究】 演完梅艷芳的王丹妮,有港女的風采
( 休閒生活其他 )
回應 推薦文章 列印 加入我的文摘
上一篇 回創作列表 下一篇

引用
引用網址:https://classic-blog.udn.com/article/trackback.jsp?uid=s7g2uvgq43834&aid=170753622