![]() ![]() ![]() |
|
|
|
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”提醒微服務開發人員,他們還需要保證軟件及其后續版本能夠始終滿足用戶的隨時使用需求。總之,可部署性設計包括:
自動化是實現高效部署的關鍵。要實現自動化,我們需要正確選擇工具與技術,這也正是自微服務架構出來以來變化最大的領域所在。因此,微服務開發人員應該在工具與平臺方面拓展思路,并始終以懷疑的態度審視不同選擇帶來的助益與挑戰。 下面來看開發者們在一切微服務解決方案中都應認真考慮的可部署性策略與技術:
微服務架構的主要作用就是創建(后端)服務,而這些服務通常會使用以下三種通用型連接器之一:
前兩者通常為同步性質,其中 HTTP 調用的使用頻率明顯更高。通常,服務需要調用其他服務組合,而且多數情況下服務組合內的交互具有同步性質。相反,如果我們創建(或適配)參與的服務以接入并接收來自隊列 / 主題的消息,則需要創建一個事件驅動架構。(有些朋友可能對消息驅動和事件驅動之間的區別存在爭議,本文則基本不做區分,會交替使用這兩個術語來引出 Apache Kafka、RabbitMQ 及 Amazon SNS 等消息代理產品)。 事件驅動架構的一大核心優勢,在于顯著提高了系統的可擴展性與吞吐量。而這一優勢之所以能夠實現,是因為消息發送方不會因阻塞而等待響應,而且多個接收方可以通過發布 - 訂閱的方式并行使用同一消息 / 事件。 事件驅動型微服務IDEALS 中的“E”強調的是應盡量在微服務建模中引入事件驅動性質,這樣才能更好地滿足當今軟件解決方案的可擴展性與性能需求。此外,這種設計也有助于松散耦合的實現,因為消息發送者與接收者(雙方均為微服務)將彼此獨立、互不了解。另外,因為這種設計能夠應對微服務的暫時中斷,在后續重新處理排隊消息,所以系統可靠性也將得到提升。 當然,事件驅動型微服務(也稱反應式微服務)同樣會帶來一定挑戰。由于處理是異步激活且并行發生,因此可能需要設置同步點與相關標識符。我們還需要在設計中考慮消息錯誤和丟失問題,包括在必要時引入事件糾正與數據變更撤銷機制(例如 Saga 模式)。對于由事件驅動架構承載的面向用戶交易,應認真考慮用戶體驗,確保最終用戶了解當前進展和事故細節。 可用性高于一致性CAP 定理本質上提供了兩種選擇:要可用性,還是要一致性。我們看到業界付出了巨大努力才讓大家獲得了用一致性換可用性的選項,這就是最終一致性機制。原因很簡單:如今的最終用戶耐心很差,根本不可能忍受糟糕的可用性。想象一下每年購物節時的網店,如果我們在產品瀏覽時顯示的庫存數量與購買時更新的實際庫存之間硬性保持強一致性,那么數據變更將產生大量開銷。而且一旦某些涉及庫存更新的服務暫時無法訪問,那么目錄就無法顯示庫存信息,后續下單、結算等服務也將隨之癱瘓!相反,如果我們選擇可用性優先(即接受偶爾不一致的風險),用戶則可以根據稍稍過時的庫存數據進行購買。沒錯,終歸會有幾百或者幾千分之一的用戶因為結賬時庫存信息不正確而被迫取消訂單,我們可以向他們發郵件道歉。但從用戶整體和業務運作的角度來看,這種情況終究要好對全體用戶都承受更慢的訪問速度或者更低的訪問穩定性。所以可用性高于一致性,沒有爭議。 當然,也有部分業務操作確實需要強一致性。但正如 Pat Helland 所指出,在馬上得到答案和得到正確答案之間,大多數人想要的其實是馬上得到答案。 具有最終一致性的高可用性在微服務當中,實現可用性的主要策略在于數據復制。我們可以采用不同的設計模式,也可以在必要時把多種模式組合使用:
日常工作中常用的 CQRS 設計如下圖所示。我們可以使用運行在集中式 Oracle 數據庫上的 REST 服務處理操作更改數據的 HTTP 請求(這種情況下,該服務仍使用各微服務對應的數據庫)。只讀 HTTP 請求會轉入不同的后端服務,再由這些后端服務從基于 Elasticsearch 文本的數據存儲處讀取數據。定期執行 Spring Batch Kubernetes cron 作業以根據 Oracle DB 上執行的數據變更操作,對 Elasticsearch 存儲內容進行更新。這種設置使得兩套數據存儲之間始終保持最終一致性;而且即使 Oracle DB 或者 cron 作業失效,查詢服務也仍然可用。 松散耦合在軟件工程中,耦合是指兩個軟件元素之間的相互依賴程度。對基于服務的系統而言,傳入耦合主要涉及服務用戶如何與服務進行交互。我們知道這種交互應該通過服務契約進行。另外,契約不應與實現細節或特定技術緊密耦合。服務屬于能夠被不同程序調用的分布式組件。有時,服務托管方甚至不清楚所有服務用戶具體在哪里(公共 API 服務就是最典型的示例)。因此,一般應盡量避免變更契約。如果服務契約與服務邏輯或者技術緊密耦合,那么當邏輯或技術需要演進時,就很容易影響到業務的正常運行。 服務通常需要與其他服務或其他類型的組件進行交互,由此產生傳出耦合。這種交互的存在會直接影響到服務自治的運行時依賴關系。如果服務的自治性較低,則其行為的可預測性也將保持在較低水平:在理想情況下,服務的實際速度、可靠性與可用性由其需要調用的最慢、最不可靠且可用性最差的組件決定。 服務間的松散耦合策略IDEALS 原則中的“L”提醒我們要注意服務間的耦合關系,特別是微服務間的耦合情況。我們可以使用并組合多種策略以管理傳入與傳出松散耦合。此類策略示例包括:
最初的單一職責原則(SRP)旨在強調 OO 類應具有內聚功能。但在同一個類里包含多個職責會自然導致緊密耦合,進而衍生出難以擴展的脆弱設計成果,很可能在變更期間發生意外不到的宕機。而且如大家所知,這是一項說起來容易、但正確實現難度極高的設計原則。 單一職責的概念也可以擴展到微服務架構內的服務內聚性層面。微服務架構規定各個部署單元應該只包含一項服務或者幾項內聚服務。如果某個微服務中充斥著大量不夠內聚的服務,則必然受到傳統單體式應用問題的影響。一旦過于臃腫,微服務在功能和技術堆棧方面將變得難以發展。另外,由于眾多開發人員會在同一部署單元中處理多個移動部件,因此持續交付也將變得無法為繼。 另一方面,如果在微服務劃分上粒度過細,則可能需要多次微服務交互才能滿足用戶請求。在最糟糕的情況下,數據變更可能會分布在不同的微服務中,進而催生出過于復雜的分布式事務場景。 對微服務進行正確的粒度劃分微服務設計真正趨于成熟的一個重要體現,就是創建出既不過粗、也不過細的微服務成果。這里的解決辦法不在于任何工具或技術,而在于適當的領域建模。我們可以通過多種方式為后端服務建模,并劃定出正確的微服務邊界。目前業界流行的微服務范圍設計方法是領域驅動設計(DDD)原則。簡單來講:
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 ?? 【問卦】 尋找暗物質的新思路:引力波或將揭示黑洞周圍的暗物質云【問卦】 高通打臉聯想,摩托羅拉絕不認輸!雷軍:跟小米拼,你有實力嗎?【最新研究】 演完梅艷芳的王丹妮,有港女的風采 |
|
( 休閒生活|其他 ) |