微服務架構-從理想到現(xiàn)實
注:本文為我最近閱讀《微服務架構設計模式》的一點感悟,我不準備詳細去寫對該書的讀書筆記記錄,而是結合我們自己所做的一些微服務架構實踐情況做一些總結和復盤。
從單體應用到微服務
任何一個新的架構模式或方法的出現(xiàn),一定是傳統(tǒng)架構模式遇到了問題。而對于單體應用來說常說的問題主要包括了如下幾個點。
單體應用規(guī)模太大,導致復雜度劇增,難以開發(fā)和管理
單體應用開發(fā),交付和變更周期變長,敏捷性跟不上
單體應用本身在性能,擴展性上出現(xiàn)了問題
在軟件架構設計里面,分而治之始終都是解決復雜性的一個關鍵思維方法。包括在傳統(tǒng)軟件架構設計里面對于大系統(tǒng)會進行子系統(tǒng)或組件劃分,會進行接口設計,集成設計等。
架構的核心思維仍然是:分解+集成
但是傳統(tǒng)架構在分解的時候沒有做到單個組件的高度獨立自治和徹底解構。這個高度獨立自治實際上要求組件或模塊從開發(fā),設計,測試,上線運行,后期運維全生命周期都做到高度獨立;同時還要求配合的開發(fā)團隊,組織結構設計也獨立。
也正是這個原因進一步發(fā)展出了微服務架構。也就是說微服務架構實際是傳統(tǒng)組件化架構設計思想的進一步強化,強化的核心就是獨立和解耦。
微服務架構思想的一個重點就是單體應用的拆分。
講微服務一定會談到擴展立方體。
對于傳統(tǒng)單體應用一般來講擴展的方法只能是X坐標軸的通過擴展實例方式的水平擴展。而另外兩個重要的擴展一個是功能拆分,一個是數(shù)據(jù)庫拆分。
對于功能拆分實際不是新鮮東西,在很早組件化架構設計我們就做過將不同的應用組件部署到不同的App Server服務器上,對于應用層你只需要考慮解決類似Session會話保存,全局配置和分發(fā)等問題即可,不會涉及到復雜的數(shù)據(jù)持久化問題。
而真正難的是在數(shù)據(jù)庫拆分。因為數(shù)據(jù)庫拆分引入了兩個大問題,其一是分布式事務問題,其二是大量操作跨庫帶來的分布式事務問題。
數(shù)據(jù)庫為何要拆分?
在單體應用發(fā)展到一定階段后,出現(xiàn)的性能問題往往都是DB層面的難以解決。數(shù)據(jù)庫集群很難做到水平彈性擴展,即使對于類似Oracle RAC等商用集群軟件,本身也有性能擴展瓶頸。性能擴展到2到3倍后已經無法再擴展。
因此單純從擴展實例擴展來說,后面衍生了讀寫分離集群,讀寫分離集群在應對讀操作占比大的業(yè)務場景下實際是最佳的一種擴展方式,在這種方式下數(shù)據(jù)庫本身并沒有拆分為多個實例,不會引入前面遇到的兩個大問題。只要在數(shù)據(jù)庫上搭建一個DaaS數(shù)據(jù)庫訪問中間件,那么數(shù)據(jù)庫底層的這種拆分和變化完全可以做到對上層應用開發(fā)透明。
當談到這里的時候,再來回顧一下微服務的一些核心點自然就更加清楚。
微服務是傳統(tǒng)單體的拆分,而且需要做到高度獨立自治
微服務的拆分不僅僅是功能拆分,更加重要是數(shù)據(jù)庫拆分
異步和解耦是微服務架構設計的重要指導思想
微服務間通過輕量高效的Http API接口交互協(xié)同
微服務實踐需要開發(fā)組織,持續(xù)集成,測試,交付方式配套支撐
場景和問題驅動
在前面談微服務的時候我就談到過,微服務架構本身將導致開發(fā),集成,運維復雜度全部增加。即使對于常說的高可用性,也僅僅是高可用性和彈性擴展能力增加,但是對于高可靠性反而是降低。
那么一個企業(yè)或團隊是什么原因實踐微服務?僅僅是微服務是一種架構發(fā)展趨勢,是技術熱點,是互聯(lián)網大廠都在用嗎?
而實際對于很多企業(yè)應用微服務可以看到,更多是團隊里面總有一些技術狂熱份子,熱衷于新架構,新技術,但是對于這些技術究竟解決了哪些業(yè)務場景下的問題并不關心。這直接就導致了大量技術在不恰當?shù)臅r候被應用。
并不是說微服務不好,而是說對于架構師來說應該基于業(yè)務,技術,團隊資源,成本等多個維度來綜合考慮究竟應該在什么時候用什么樣的技術最合適。
類似阿里的電商平臺,一天訪問量上億,接近百萬的TPS,高峰秒級成交都幾十萬筆,這種業(yè)務場景下各個模塊拆分為獨立中心,獨立數(shù)據(jù)庫是必然。不是說什么技術先進了必須使用,而是為了滿足和支撐業(yè)務必須微服務化。
那么傳統(tǒng)企業(yè)實踐微服務就一定要思考微服務究竟能解決什么問題。如果這個問題沒有想清楚就不要輕易去搞微服務。其次就是通過其他傳統(tǒng)方式能解決的也不要輕易去理想化的實踐微服務架構。
比如一個業(yè)務系統(tǒng)運行過程中數(shù)據(jù)庫出現(xiàn)性能瓶頸,這個時候你可以考慮的是將歷史數(shù)據(jù)進行遷移到備庫,增加備庫單據(jù)查詢能力;或者是前面談到的構建數(shù)據(jù)庫的讀寫分離集群或者在數(shù)據(jù)庫上構建二級索引緩存庫來解決性能問題。
再比如一些業(yè)務系統(tǒng)出現(xiàn)瓶頸,并不是常規(guī)的OLTP場景出現(xiàn)問題,而是單個業(yè)務系統(tǒng)數(shù)據(jù)庫同時在支撐OLTP和OLAP能力。這個時候你應該做到是將大量的查詢統(tǒng)計和數(shù)據(jù)分析功能移出,而不是對整個業(yè)務系統(tǒng)進行數(shù)據(jù)拆分。
這方面的例子還有很多,實際是想說明一點,很多業(yè)務系統(tǒng)的復雜度和性能問題遠遠沒有到必須進行功能拆分和數(shù)據(jù)庫拆分才能夠解決的地步。
微服務拆分策略
當聊微服務拆分的時候先談下傳統(tǒng)軟件架構設計里面的組件拆分。
對于組件拆分可以看到常規(guī)的組件拆分實際是在應用層而沒有到具體的數(shù)據(jù)庫。這就導致了組件拆分后,底層數(shù)據(jù)庫仍然是一個中心點和緊耦合點。包括組件拆分后,實際架構規(guī)劃的時候希望通過組件間通過接口調用協(xié)同的,后續(xù)在開發(fā)實現(xiàn)中往往也出現(xiàn)偏差,即常說的仍然是通過底層數(shù)據(jù)庫的關聯(lián)sql查詢等來解決問題。所以你看到情況是組件間并沒有什么接口調用,而大量的耦合都轉到底層數(shù)據(jù)庫的耦合上面。
到了微服務階段的拆分,實際上變成了兩個重要的事情,一個是數(shù)據(jù)庫的拆分,一個是基于數(shù)據(jù)庫拆分后的功能拆分。
對于拆分我前面寫過兩篇文章可以參考:
對領域模型和上下文邊界分析來劃分微服務的再思考
聊聊DaaS數(shù)據(jù)庫即服務和微服務下數(shù)據(jù)庫拆分
不論是采用的是傳統(tǒng)的結構化分析方法,還是當前類似DDD領域驅動建模里面的子域劃分,上下文邊界識別等。拆分的重點仍然是在數(shù)據(jù)庫拆分上面,只是DDD方法里面將其理解為核心領域對象拆分,領域對象拆分出來自然對應的數(shù)據(jù)庫和功能都進行了拆分。
先梳理清楚邊界并進行服務拆分,再基于業(yè)務協(xié)同和業(yè)務功能實現(xiàn)來梳理和定義API接口,也就是說微服務拆分工作實際包括了三個方面的事情。
即數(shù)據(jù)庫拆分,功能拆分,API接口識別和定義。
對于微服務拆分來說,真正的困難點是在微服務拆分的顆粒度上,當重新讀這本書的時候再次慶幸我們沒有按書里面這么理想化的方法去實踐微服務,去將微服務拆分的如此細,否則真正是思路一條。
因此在這里需要重申一個重要觀點。
微服務拆分不應該是按標準理論理想化的拆分到哪個顆粒度,而是應該根據(jù)實際的業(yè)務場景和擴展性需求,開發(fā)團隊管理需求拆分到一個合適的顆粒度。
比如你現(xiàn)在做的是一個外賣訂餐系統(tǒng),如果是類似美團APP這種規(guī)模進行細粒度的微服務拆分勢在必然。但是如果你的系統(tǒng)面對的市場僅僅是類似社區(qū),園區(qū)等私有云部署場景。那么這個系統(tǒng)從業(yè)務場景和并發(fā)量上都沒有必要做大的微服務拆分,實際架構上進行前后端分離,獨立緩存庫,讀寫分離基本完全可以解決問題。
因此微服務拆分到哪個程度,跟業(yè)務系統(tǒng)面對的業(yè)務場景和問題有關,而不要用標準的類似拆分方法策略去做拆分。
類似面向對象的分析設計,DDD領域驅動和領域對象分析識別,這些方法都沒有問題,這些完全可以應用到應用內部,應用內部也需要組件化拆分,而不是說組件化后每個組件就必須拆分為獨立的高度自治的微服務。
從API接口到異步解耦
對于微服務架構中的API接口設計,在前面我專門寫過文章來進行描述。在這里還是想重新來說明對API接口設計和暴露里面的兩個重要觀點。
對Http Rest API標準規(guī)范的理解
要知道Rest API更多的是一種面向資源的API接口設計方法。在早期我自己也堅持一個觀點即需要安裝理想化的Rest接口設計方法來設計接口。但是當前我更傾向于僅僅將Http API作為接口實現(xiàn)方式,是否Rest風格并不是重點。
即在接口實現(xiàn)中完全可以將所有接口實現(xiàn)POST接口方法,而不用去區(qū)分哪些必須用GET,哪些還得用PUT方法。雖然都實現(xiàn)為POST方法后會對一些接口的在線訪問和測試帶來一些不方便,但是這個完全可以通過其它方式去解決掉。
前后端分離和大量API接口暴露
API接口本身應該是粗粒度的,但是實際在很多項目里面看到往往將數(shù)據(jù)庫表的CRUD方法全部暴露為了API接口方法。這個一方面是領域對象層的貧血,一個是前后端分離導致原來數(shù)據(jù)訪問層的內部API接口全部都需要通過Http API接口方式暴露。
一個內部的IT系統(tǒng),本身僅僅內部局域網使用,只提供PC端的BS瀏覽界面也沒有對APP端的支撐需求,在這種情況下前后端分離沒有任何必要。前后端分離反而帶來了后端API接口大量暴露,前端和后端集成協(xié)同等大量問題。
在這種場景下如果IT系統(tǒng)劃分為5個微服務模塊,這5個微服務模塊之間的API接口設計和集成,才是我們真正需要去關注的問題點。
在當前微服務架構設計里面,微服務間的接口協(xié)同,微服務本身前后端接口協(xié)同全部混雜在一起,實際導致了后續(xù)整個微服務體系里面的API管控治理完全失控。
基于Saga分布式事務
前面談分布式事務的時候,更多談的是事物補償或者事務最終一致性保障,但是很多長周期的事務仍然是API接口的同步調用方式來實現(xiàn)。即當API接口同步調用的時候,微服務間并沒有徹底解耦。要解耦必須是基于事件或消息模式的異步調用方式。
Saga即是一種異步消息事件機制下的分布式事務解決方案。Saga是由一系列的本地事務構成。每一個本地事務在更新完數(shù)據(jù)庫之后,會發(fā)布一條消息或者一個事件來觸發(fā)Saga中的下一個本地事務的執(zhí)行。如果一個本地事務因為某些業(yè)務規(guī)則無法滿足而失敗,Saga會執(zhí)行在這個失敗的事務之前成功提交的所有事務的補償操作。
Saga的實現(xiàn)有很多種方式,其中最流行的兩種方式是:
基于事件的方式。這種方式沒有協(xié)調中心,整個模式的工作方式就像舞蹈一樣,各個舞蹈演員按照預先編排的動作和走位各自表演,最終形成一只舞蹈。
基于命令的方式。這種方式的工作形式就像一只樂隊,由一個指揮家(協(xié)調中心)來協(xié)調大家的工作。協(xié)調中心來告訴Saga的參與方應該執(zhí)行哪一個本地事務。
當重新來思考分布式事務的時候,再次印證我的一個觀點,即微服務拆分太細導致引入了很多完全沒有必要的分布式事務問題,增加了開發(fā),集成,測試和后續(xù)監(jiān)控運維的工作量和復雜度。
比如書里面談到的一個例子,對于一個在線訂餐系統(tǒng)提交一個訂單,這個本身是一個很容易實現(xiàn)的功能,但是在微服務化后,訂單提交涉及到用戶校驗,賬戶校驗,廚房工單生成多個獨立API服務接口調用。這個就涉及到這些API接口的事件編排,同時在編排中你還需要考慮出現(xiàn)異常時候的補償回退,考慮消息執(zhí)行順序等諸多問題。
這些急劇了增加了一個功能實現(xiàn)的復雜度。當你本身開發(fā)的系統(tǒng)就不存在海量并發(fā)和高性能需求的時候,我實在難以找到任何理由將微服務拆分的如此細,引入這么多的復雜度。
實際上以上的分布式事務場景應該更多地應用到大系統(tǒng)間的分布式事務協(xié)同上面,比如采購系統(tǒng)提交訂單的時候,同時涉及到工作流平臺中的工作流實例啟動,涉及到預算系統(tǒng)的預算校驗,這個時候才要分布式事務來實現(xiàn)是有必要的。
跨系統(tǒng)出現(xiàn)上面這種分布式事務處理和協(xié)同場景,這個量完全可控。但是你微服務拆分得太細,你在實現(xiàn)應用中任何一個功能都引入上面這種分布式事務問題,基于事件的編排和補償問題,可想而知已經不是簡單的復雜度問題,而是后期的系統(tǒng)可靠性和可運維問題。
如何徹底解耦?
前面談到了如果是CUD操作可以異步方式來實現(xiàn)解耦,那么對于查詢操作如何處理。如果一個查詢查找本身還涉及到多個微服務API接口的組合,這個時候如何實現(xiàn)解耦。
對于查詢操作一般來講都是同步操作,用戶端點擊查詢后會等待數(shù)據(jù)的返回,這個時候如果需要調用多個微服務API接口數(shù)據(jù)進行組合,那么底層某個微服務模塊如此異常,將直接導致整個查詢功能持續(xù)異常。
即如果存在查詢操作,那么微服務間仍然是緊耦合在一起的。我們希望的微服務間通過消息和異步來徹底解耦這個目標并沒有達到。
當然實際上可以考慮兩個方法來解決這個問題。
其一就是將常用數(shù)據(jù)緩存,查詢的時候直接訪問緩存庫。
其二就是將微服務底層數(shù)據(jù)庫需要共享數(shù)據(jù)進行同步,同步到一個大的共享ODS讀庫專門來提供查詢服務能力。
在一些項目的CQRS命令查詢職責分離實踐中,基本即采用的這個思路。通過這種方式來實現(xiàn)微服務間的徹底解耦。當然也引入了另外兩個依賴點,一個就是消息中間件,一個就是緩存庫或分布式ODS讀庫。這兩個點的穩(wěn)定性就直接影響到整個系統(tǒng)的穩(wěn)定性。
采用CQRS模式最大的一個問題點就是無法實現(xiàn)命令和查詢兩部分內容的強一致性保障,即很可能你界面上查詢到的數(shù)據(jù)不是最新的持久化數(shù)據(jù)庫里面的數(shù)據(jù),這個本身和消息管道異步寫入的實時性又關系。
其次在使用CQRS模式的時候,有一個重要假設就是在事件和命令發(fā)出后,無特殊情況在事件接收方都必須要能夠接收事件成功處理,否則就存在大量的異常錯誤消息的異步回寫,反而增加系統(tǒng)的復雜度。
實施CQRS引入的整體復雜度,也不是一般的小項目能夠玩得起的。
同時對于CQRS框架的實施,不是簡單的設計模式和開發(fā)復雜度問題,更加重要的仍然是是否能夠接受最終一致性要求,同時在該要求下將傳統(tǒng)的同步請求下業(yè)務功能和邏輯處理機制轉變?yōu)楫惒绞录r值下的事件鏈驅動模式。要實現(xiàn)這種轉變就必須能夠拆分出獨立,自治的命令和事件,同時確保這些事件在朝后端業(yè)務功能和邏輯模塊發(fā)送的時候能夠處理成功(即該做的校驗必須提前做完)。
微服務網關和API網關
對于API網關也可以先參考我頭條發(fā)布過的相關文章。
于API網關和微服務網關實際實現(xiàn)的核心功能基本一致,但是要注意到微服務網關一般是在微服務架構體系里面的內容。而API網關一般是可以獨立在微服務架構體系之外的內容。
也就是說API網關和微服務整體框架體系更加的松耦合。
API網關一般具備獨立的服務注冊接入,負載均衡和路由能力,而微服務網關一般則是通過和服務注冊中心的集成來實現(xiàn)服務注冊發(fā)現(xiàn),負載均衡和路由
應用范圍的區(qū)別
在這里重新強調下,微服務網關和API網關實際在應用范圍上有明顯的區(qū)別。即微服務網關一般應用在單個微服務應用內部,特別是在存在前后端分離情況下。而對于API網關則是應用在組織級,實現(xiàn)多個微服務應用之間的集成和協(xié)同能力。
單個微服務應用,完全沒有必要采用API網關,直接使用微服務網關。比如當你采用SpringCLoud框架體系的時候,你直接采用框架里面的Zuul或SpringCLoud Gateway即可。
但是當你在做組織級的多個微服務應用間集成的時候,這個時候需要的脫離單個微服務框架體系的的一個外部集成中間件,因此不能和微服務框架綁定太死。其次你會看到跨應用間的集成管控的粒度不是到微服務組件,而是要細化到一個個的API接口。因此這個時候啟用API網關就是必須的。
當你在組織級啟用API網關的時候發(fā)現(xiàn)一個新問題。
即對于整個組織級來說,遺留系統(tǒng)和單體應用的微服務化本身有個過程,在這個過程中一定是傳統(tǒng)架構和微服務架構共存。新的Rest API接口和老的SOAP接口和消息協(xié)議共存階段。而API網關本身對老的接口協(xié)議適配能力并不好。對于類似ESB總線來說雖然偏重,但是可以完全兼容和覆蓋API網關具備的能力。
因此在這種情況下API網關反而變得雞肋。
API網關是否該做類似服務編排組合的事情?
簡單來說API網關不應該做這件事情,傳統(tǒng)的ESB總線往往具備這個能力,但是API網關不適合來承載這個能力。如果做了服務編排的事情一個是讓API網關本身從單純的技術中間件變成了承載業(yè)務邏輯的中間件,其次就是該能力本身也將讓API網關變得更加重。
因此最好的方法仍然是獨立一個微服務來實現(xiàn)服務組合和編排,發(fā)布組合后的領域服務或組合服務能力,這個在我前面文章專門有談到過。
API網關本身的中心化問題
不要簡單地認為中心化架構就一定不好,中心化架構本身通過集中管控和攔截的方式,可以很方便地實現(xiàn)類似API接口安全,日志,路由,流控等各種管控能力。
在談微服務管控治理的時候,中心化的API網關是一個重要的實現(xiàn)思路和手段,當然在前面我也提到了整體的發(fā)展趨勢應該ServiceMesh化,實現(xiàn)完全的去中心化架構。這個ServiceMesh化本身又可以和DevOps,容器云等云原生技術形成一個完整的整體。
但是要意識到的點仍然是在于組織管控和標準化推進的力度,如果在組織級很多技術標準推進不一致,應用系統(tǒng)改造存在前后逐步演進,那么這個時候你很難簡單的去實現(xiàn)ServiceMesh化架構。簡單來說即是:
對于傳統(tǒng)IT架構的逐步微服務化,本身并不適合采用ServiceMesh。
在談微服務架構的時候,經常有人談到完全的去中心化,但是沒有搞清楚為啥要去中心化。即使異步消息集成,消息中間件仍然是中心點。當組織級標準規(guī)范,技術要求不統(tǒng)一的時候,管控能力達不到時候很難完全去中心化,這個時候中心化方式反而是一個好的選擇。
最后,做下簡單總結如下:
按照理想化的微服務方法來設計和實施微服務,對于大部分傳統(tǒng)企業(yè)信息化來說不適用,同樣照搬互聯(lián)網微服務架構化方法同樣不適用。企業(yè)IT架構在微服務轉型過程中更多應該基于業(yè)務場景和問題驅動,借鑒微服務拆分和解耦的思想,充分考慮敏捷交付,彈性擴展和性能,開發(fā)難度和資源成本投入,后期管控治理多方面的平衡。
來源:
https://www.toutiao.com/i6925193987997696516/