微服務做爲架構風格幾乎成爲雲時代企業級應用的事實標準,構成微服務的技術元素自己卻並不是革命性。跨平臺的分佈式通訊框架、地址無關的服務註冊與發現、智能路由與編排等技術早已在CORBA、SOA時代實現了一遍又一遍,咱們不由好奇,微服務有什麼不一樣?本文是對企業分佈式應用的一次回顧,與前微服務時代相比,咱們究竟在哪些領域吸收了教訓,哪些方面持續搞砸。html
架構的關鍵在於構造合理的封裝抽象。良好的抽象構造如進程,由操做系統接管CPU調度、內存地址空間分配和I/O,程序員的心智今後解放,得以聚焦在業務邏輯上。糟糕的抽象每每引向萬丈深淵,大量精力被浪費在抽象泄露帶來的問題上。程序員
在分佈式系統中咱們關注組件、組件間的通訊以及伴隨的工程實踐,微服務在企業應用的上下文中就技術約束和業務價值間達成了更好的平衡點。web
讓咱們從組件間的通訊開始,最初人們認爲這只是須要被解決的技術要素。docker
(圖片來自:https://upload.wikimedia.org/wikipedia/en/thumb/f/f0/Orb.svg/802px-Orb.svg.png)數據庫
關於如何實現跨平臺的分佈式通訊,30年前誕生的CORBA架構在今天來看仍然很是漂亮:經過定義IDL/ORB/API咱們能夠將內存對象任意分佈於網絡中。只要共享IDL,對象能夠由C++/Java等不一樣的語言實現,其互相調用就像本地方法同樣簡單。然而實踐經驗告訴咱們,分佈式系統老是會出現本地調用不會發生的各類問題:網絡的開銷、傳輸的延遲、消息的超時和丟包、遠端系統的崩潰……物理世界的技術約束是沒法被忽略的,咱們沒有辦法把分佈式調用抽象成簡單的本地方法。所以Martin Fowler在他的< 企業應用架構模式>裏提出了著名分佈式對象第必定律:「不要分佈式你的對象」。相反,你應該把儘量多的操做置於進程以內,經過replicate整個應用的方式來實現系統的scale。編程
由分析師們發起的SOA運動從另外一個角度看待這個問題,Web Service應該是對企業資產和業務能力的封裝。咱們開始站在更高的維度,遠過程調用再也不只是技術意義上的集成。WSDL不只是通訊調用的接口,更是服務間的契約;UDDI不只是服務描述、發現、集成的中心,更是企業業務與服務的黃頁。WS-*在廠商的裹挾下發展成一應俱全,卻也沒幾我的能掌握。開發者們抱怨花了太多時間寫冗餘的XML制定所謂的規範,WSDL生成的客戶端也將不一樣服務耦合在一塊兒。是否有更加輕量敏捷的方式,讓咱們快點開始寫第一行生產代碼?json
因而咱們看到REST的興起。起初是做爲反叛,用更加輕量級的方式(http+json)使用Web。而後咱們發現"企業級"應用並不是須要ESB這樣昂貴的專有中間件,由"消費級"技術組成的萬維網是世界上最大規模的分佈式網絡,咱們應該向其學習如何構建健壯、可演化的系統。Roy Fielding那篇論文所提出的無狀態、可緩存等特徵已經深刻人心,而狹義上的REST API(基於資源的URI、HTTP動詞和狀態碼的標準接口)也成爲API設計的最佳實踐。api
既然API和網站同樣都是基於通用Web技術,API是否能夠像網站同樣做爲產品提供呢(APIs as product)?因而愈來愈多的企業開始將本身的業務能力封裝成API,提供給消費者,隨之而來的是更彈性的商業應用和更靈活的計費方式。不少組織也着手構建本身的API市場,把內部IT能力整合、複用,併爲孵化外部產品作準備。API已經成爲商業價值主張的一部分。數組
咱們從聚焦實現細節的rpc出發,來到了更具價值導向的REST API。即便構建內部系統,以消費者驅動的方式,也老是能幫助咱們設計出更加鬆耦合和易於演進的API。緩存
編程語言中的組件構造(如Java中的jar, C#中的dll)是軟件架構師們封裝可複用單元的最經常使用武器。組件做爲理論上的最小部署單元,在工程實踐中卻並不容易獨立變動。通常應用程序須要講多個組件打包成一個部署單元(如war包),連接在內存地址中進行調用。對單個組件的熱更新每每對組件間耦合和對象狀態管理有很高的要求,從新部署整個應用通常是默認選項。以進程爲邊界構建可獨立部署的服務成爲架構師的另外一項選擇。
早期的服務只是單純的技術構件,大多數組織從純粹的技術實現角度考慮服務的劃分。SOA的推進者們指出企業的信息資產應該被複用,信息孤島應該被打通。經過將不一樣的服務編排組合,咱們應該可以實現IT對業務更加靈活的支撐。
(圖片來自:0SOA in practice, Nicolai Josuttism, 2009)
SOA的服務建模通常採用業務流程驅動的方式。一個典型的SOA設計是由業務分析師自頂向下地對企業現有業務流程進行分析,經過BPM引擎對流程進行建模,向下分解成組合服務,並進一步拆分紅數據訪問服務(不少可憐的SOA實現中數據的訪問被拆分紅不一樣的讀服務和寫服務)。然而這帶來的問題是,服務跟服務間的耦合很是嚴重。當個人業務發生了變化,可能會須要修改不少不一樣的服務,涉及到多個團隊的溝通和協調。在運行時層面,服務器間的通訊很是頻繁,用戶在界面上的一次點擊按鈕,對應的後臺多層服務間的級聯通訊。這給系統性能和穩定性也帶來了巨大的挑戰。SOA式的服務建模從分析型思惟出發,卻每每低估了分佈式系統和跨團隊協調的複雜度,致使服務拆分粒度過細。
微服務的名字經常讓人誤解,但實施正確的微服務粒度可能並不"微"。Martin Fowler與James Lewis在開創微服務定義的一文中已經指出微服務應該圍繞完整的業務能力。今天咱們在作微服務設計時,經常利用領域驅動設計中的Bounded Context來進行服務邊界的劃分。假設你的庫存管理是一個獨立的業務子域,針對庫存的維護和操做應該被放到經過一個上下文和微服務中,由一個團隊進行開發維護。多數業務變動都發生在上下文內部,不涉及跨團隊協調。單個codebase內的重構和部署讓發佈更加容易。維護庫存所須要的信息查詢的調用多發生在進程內,更好的性能,同時無需處理額外的一致性問題。
微服務的另外一個特色在於Product over Project,這須要不一樣於傳統投資組合的預算管理與團隊組建。傳統的項目制將預算分配在相對短時間的服務開發過程當中,項目團隊關注的是如何將業務範圍(scope)實現,開發結束後服務轉交運維團隊進行維護,項目團隊則被解散進行其餘項目的開發。將微服務做爲產品運營則須要創建業務結果導向的穩定產品團隊。服務的設計不僅聚焦於當下需求,更須要考慮價值定位和產品願景。工程團隊則須要思考如何用有限成本支撐非線性的業務接入增加。
(圖片來自:Enterprise Architecture as Strategy, Ross et al, 2006
現在咱們對服務的定義已經超越了技術組件,領先的組織已經在嘗試將design thinking, business operating model應用到微服務設計中。
即便有了設計合理的服務於API,咱們仍然須要與之匹配的工程實踐才能將其順利實施。
今天仍有不少企業使用集中式的應用服務器部署應用:開發團隊將軟件包構建出來,再統一安裝到應用服務器中。對應用團隊來講,這每每意味着漫長的反饋週期和痛苦的自動化。咱們很早就推薦用Jetty這樣內嵌式的應用容器部署軟件,啓動更快,測試環境更接近生產。one Tomcat per VM的部署方式雖然運行時開銷較大,倒是前容器時代隔離性最好的服務部署模式。Docker將這個實踐更進一步,除了更輕量級的隔離,咱們第一次能夠將軟件和所依賴的環境自己打包成版本化的artifact,完全統一開發和生產環境。容器技術的成熟讓咱們能夠將部署去中心化,開發團隊能夠獨立部署一個服務。
數據庫耦合是影響服務獨立變動的另外一重要因素。相比代碼構成的應用軟件,數據庫schema更加難以變更。由於難以測試、難以兼顧性能優化和耦合的發佈週期等因素,服務間以數據庫集成成爲臭名昭著的反模式。服務間的集成應該依賴封裝好的顯示接口,而不是數據庫這種實現細節。咱們應該在兼顧數據一致性的狀況下,爲每一個微服務分配獨立的db schema甚至db instance。若是說十年前數據幾乎等同於關係數據庫。現在數 據則可能呈現出各類形態:鍵值、文檔、時間序列、圖...咱們徹底能夠採用更加合適的技術,以去中心化的方式進行微服務的數據治理。
即便將這一切都解耦,若是將交給一個集中的團隊去實施,頗有可能最終仍是獲得一個耦合的架構。這就是是著名的康威定律。康威定律告訴咱們「設計系統的架構受制於產生這些設計的組織的溝通結構」。但一樣咱們能夠將康威定律反轉應用:若是你想達成一個目標架構,則必須對團隊結構進行調整,使之和目標架構對齊。相比單體系統,微服務在運行時監控和運維所帶來的挑戰更大。"you build it, you run it"的DevOps文化成爲必須。監控運維再也不是Ops部門的事情,產品團隊必須對微服務的整個生命週期負責。受權的去中心化自治團隊是實施微服務的必要條件。
咱們在不少方向的確取得了進展。但即便在微服務時代,不少問題仍然在輪迴發生着,彷佛咱們老是沒法吸收歷史的教訓。讓咱們看一看那些揮之不去的反模式陰雲。
一個例子是開發者對強類型RPC代碼生成的依戀。儘管歷史經驗已經證實同步的rpc沒法爲分佈式通訊提供足夠好的封裝,假裝成本地方法調用的客戶端每每鼓勵程序員作出糟糕的接口設計:細粒度的頻繁調用、缺乏緩存和容錯處理。IDL生成客戶端也會致使服務間耦合,每次變動接口都須要升級數個相關服務。若是用可演進的REST API(如HATEOS)和tolerant reader模式,則能夠優雅地解決這個問題。然而新一代的開發者們仍是常常「從新」發現rpc的這些能力並陷入依賴——更快的序列化反序列化、類型安全和來自IDE的智能提示、經過spec反向生成代碼...分佈式計算先驅Vinoski不由感嘆「開發人員的便利性是否真的賽過正確性,可擴展性,性能,關注點分離,可擴展性和意外複雜性?」
另外一個揮之不去的陰影是ESB。ESB在將異構的應用wire在一塊兒有着關鍵的做用。然而當愈來愈多的職責被加入:數據報文的裁剪轉換、難以測試和版本控制的編排(orchection)邏輯、服務發現智能路由監控治理分佈式事務等All in One的solution將ESB變成了一個可怕的單點夢魘。因此微服務發出了「智能終端啞管道」的吶喊:咱們只是須要一個不那麼智能的代理處理可靠消息傳輸,將靈活的邏輯交給服務自己去編配(choreography)吧。
因而在典型的微服務架構裏,負載均衡、服務註冊發現、分佈式追蹤等組件以Unix way的方式各司其職。然而在利益誘惑和特性競爭壓力之下,不少廠商不斷將更多的功能放進他們的中間件,其中爲表明的Overambitious API gateways儼然要從新實現佔據中心的ESB。若是API gateway只是處理鑑權、限流等橫切層邏輯沒有問題,若是API gateway開始處理數據轉換和業務邏輯編排,你應該提升警戒!
儘管行業在不斷髮展,但不少時候人們仍然沿用舊的思惟,用新的技術去一遍遍從新實現這些舊的反模式。
你老是能夠在技術雷達裏追蹤微服務的state of art,現在這個領域的前沿方向是什麼,Service Mesh, Chaos Engineering, 仍是Observability as Code?然而歷史告訴咱們,新的技術在解決一些問題的同時,也可能會產生新的問題。更糟糕的是,咱們永遠沒法記住歷史,用新的工具更高效地重現舊日問題。
Technologies come and go, Principles stay forever。好在那些架構和實踐背後的原則是經久不變的。從操做系統到移動應用都會須要高內聚低耦合的架構,任何軟件開發都須要版本控制、自動化構建等實踐。謹記這些核心原則、謹記軟件被創造出來是爲了解決有價值的問題,能夠幫咱們更好的借鑑歷史的經驗,理解和採納新的技術。
文/ThougtWorks劉尚奇
本文首發於劉尚奇我的網站:https://6up7.com/a-retrospective-for-enterprise-distributed-application/
更多精彩洞見請關注微信公衆號:ThougtWorks洞見