微服務近年來可謂煊赫一時,合理的使用微服務架構能夠解耦系統、提供更好的軟件伸縮性以及提升組織的敏捷性。然而現實中較少有項目一開始就會選擇使用微服務架構,絕大多數新項目在最初都會務實地從更容易掌控的單體架構起步構建,若是最終發現單體架構複雜到影響了團隊的開發效率及軟件的伸縮性等方面時,纔會開始考慮逐步將系統往微服務架構作演進。web
現實中任何軟件架構都是諸多trade-off的結果,想要得到微服務架構所帶來的好處也就意味着有能力承擔它所帶來的的反作用。Martin Fowler就曾在MicroservicePrerequisites一文中指出實施微服務所須要的先決條件,他用「個子是否夠高」形象地比喻了微服務所需的技能門檻。redis
而對於既有系統,還須要一種務實的演進方法和實施策略,使得可以伴隨着恰當的代碼重構,逐步積累能力和完善基礎設施,最終平穩的將其演進到微服務架構。sql
本文總結了一些從既有系統到微服務演進之路上會遇到的問題和解決策略。文中使用「既有系統」而非「遺留系統」,是由於遺留系統給人一種即將退出生命週期、行將就木的感受,而咱們則但願把精力投入到還有長遠商業價值的系統上,經過合理的微服務演進讓其具備持續的生命力。數據庫
演進策略編程
本文推薦的從既有系統到微服務的一種務實安全的演進策略是:自上向下分析,自下向上重構,逐步完善配套。緩存
所謂「自上向下分析」,主要包含如下步驟:安全
1.總體演進路線規劃:性能優化
梳理既有系統的領域模型,設計合理的內部服務邊界,按照優先級和依賴關係規劃演進路線;網絡
2.服務治理方案設計:架構
按照優先級,爲新服務定義職責,接口,與既有系統的交互方式以及跨服務的集成測試方案;
定義新服務的打包、測試、發佈、部署、集成方式,目標是可以爲其構建獨立的代碼庫和持續交付流水線;
3.代碼解耦設計和重構:
分析屬於新服務的獨立代碼以及和既有系統耦合的代碼,從物理打包和邏輯代碼重構兩層面解決耦合問題;
針對不一樣的解耦策略,制定不一樣的測試策略,完善自動化測試以支撐對應的代碼重構工做。
所謂「自下向上重構」,指的是按照前面的分析設計結果,從代碼重構開始,自下向上按照優先級和必定節奏持續進行服務化改造。
而「逐步完善配套」,指的是隨着服務化的開展,逐步完善代碼庫管理,多流水線集成,並逐步按需引入服務治理框架,積累微服務須要的技術和工具能力。
上述過程是一個迭代的過程:經過適度的分析和設計,規劃出具體的落地工做,而後經過小步增量的實踐迅速得到成果和反饋,在過程當中逐步培養人的能力、完善支撐微服務架構的工程實踐。
自上向下設計
明確目標和約束
對既有系統作微服務化解耦,須要對不一樣解耦方向能得到的收益和存在的約束作到心中有數。見過一些組織在作微服務拆分時只強調能夠得到的片面好處,忽略了對組織更有益的其它潛在價值,或者低估了微服務化帶來的問題。這每每會致使不合理的服務邊界劃分或者錯誤的優先級排序。
沿着不一樣的邊界劃分,目的是爲了避免同的價值目標:
沿着系統內不一樣的變化緣由和變化頻率作服務劃分
經過隔離不一樣的變化方向,減小特性開發之間的干擾,使能小的獨立交付團隊。經過獨立代碼庫、獨立流水線,獨立的開發、測試、交付和運維過程,提升交付效率和響應速度。
沿着不一樣的資源使用邊界作服務劃分
經過將不一樣資源佔用特徵的服務進行隔離,使能獨立的水平彈縮,優化資源使用效率和提高業務響應能力。
沿着不一樣性能路徑邊界作服務劃分
經過將性能核心路徑做爲獨立服務進行隔離,能夠爲性能核心路徑使用不一樣的技術棧以及作各類極致的性能優化;另外一方面避免各類改動影響到關鍵路徑的性能降低(例如被動引入更多的異步交互等)。
因爲服務劃分會爲系統引入新內部邊界,因此必須考慮以下的約束:
數據一致性約束:服務劃分後可能帶來數據一致性變弱的問題,須要考慮是否能夠接受;
性能約束:服務劃分後帶來的潛在性能降低,須要考慮如何度量以及承受程度;
容錯性約束:服務劃分爲系統內部引入更多的分佈式故障點,須要可以爲其找到可接受的容錯設計;
耦合關係約束:服務劃分會放大系統的耦合問題,因此須要考慮沿着系統的鬆耦合邊界進行服務劃分,避免服務間複雜的交互或者聯動修改。
在開始能夠按照理想的價值目標去劃分微服務邊界,而後再接受每一項約束的挑戰,最終的服務劃分方案每每是一個在目標和約束之間逐漸平衡後的結果。
避免過分設計陷阱
對既有系統的微服務改造設計每每會陷入「架構設計陷阱」。過於詳盡的分析和設計反而經常會阻礙微服務的拆分,常常獲得一個「成本很大,困難不少」的論證。
對於這種狀況,建議採用 快速啓動、增量交付、大膽實驗、當心求證 的原則。即快速構建目標,經過敏捷和精益軟件開發的方式快速實踐,經過反饋進行快速學習,在行動中解決各類問題。
具體的實踐過程當中:
有了基本的分析後,快速成立試點團隊做爲探索者進行解耦驗證,儘早得到反饋;
雖然快速啓動,可是短時間目標要明確,經過迭代的增量交付來規避風險;
在實踐過程當中逐步按需對修改影響較大的特性補充和完善自動化測試;
對有較大風險的代碼修改,能夠先拷貝一份在新的服務內作實驗,得到足夠反饋後再擇機合入原代碼庫;
能夠藉助工具分析代碼的依賴關係。曾經在一個項目咱們經過部署doxygen和graphviz來可視化代碼的依賴關係和解耦進展,取得了不錯的效果。
微服務設計
關於微服務設計的方方面面已經有不少優秀的書和文章了,例如《微服務設計》就是一本不錯的教材。即使如此到任何一個具體的領域,仍有不少困難和挑戰,須要領域專家和軟件工程師們密切配合去解決。
使用領域驅動設計(DDD)方法能夠幫助全部參與者從新梳理業務並達成共識。經過識別業務的界定上下文和聚合根,能夠爲如何劃分服務提供參考。能夠嘗試組織DDD Workshop,可是要清楚這只是一項工具,並且有時成本並不小。DDD是一個演進式的過程,更多的工做須要隨着深刻業務和代碼,經過實踐收集反饋迭代式的進行。
現實狀況中,負責系統架構演進的人員都是對業務和設計現狀比較熟悉的專家,一種高效的作法是從當前的數據模型直接入手。分析每一張表和每項字段所支撐的業務,將業務按照數據的內聚性進行分類,而後以此做爲服務劃分的起點。能夠假設已經將數據按照新的服務邊界從新分庫分表,而後嘗試基於此從新構建每條業務流程,並在過程當中解決因爲數據拆分而出現的各類問題。該作法適合對微服務架構有經驗的人和領域專家合做完成,這樣可以對出現的各類問題找到不偏頗的解決方案。
天下沒有免費的午飯,有時爲了獲得微服務的好處,是須要作一些妥協的。例如數據模型中某一實體的不一樣屬性具備不一樣的業務內聚度,因此同一律唸的不一樣屬性數據被分到了不一樣服務中,可是這些數據在某些場景下要保持同步(例如須要被總體刪除或修改等)。最多見的解決方案是選擇一個穩定的服務做爲對該實體的權威擁有者,其它服務經過某種手段(例如消息隊列)和該服務對齊各類實例操做。這意味着業務要能接受最終一致性,還得接受在某些異常場景下數據一直沒有同步成功而上報的告警。
在設計服務的集成方式時,須要站在業務角度去識別誰是更穩定的服務。依據「向穩定方向依賴」的原則,咱們只會讓不穩定的服務去調用穩定服務的API,而反過來穩定的服務最好經過消息隊列發佈事件。那些須要跨越多個服務去獲取數據的服務,通常能夠經過監聽事件和緩存與系統解耦,但這並不是適合全部場景。在某些場景下因爲業務的一致性和性能限制,咱們確實須要往回退,把某些服務進行合併。這就是個不斷的頭腦風暴,而後再在各類選擇中作trade-off,最終得到平衡的過程。
對於缺少經驗的團隊能夠從較容易拆分的服務作起。例如一個web服務端能夠先把路由和基本鑑權拆分出來,交給API Gateway負責;而後再把各類報表和統計等一致性與性能要求相對低的拆分出來,最後再嘗試切分其它業務處理。
一旦服務拆分出來,就能夠根據業務特徵從新優化數據模型並選擇更適合的數據庫。另外服務的API設計也是有技巧的:應用接口隔離原則,須要API能獨立完成功能,又要粒度相對小能夠靈活組合。這方面亞馬遜各類AWS服務的API設計就是不錯的樣例。
自下向上重構
獲得了可行的服務劃分方案,接下來就須要實際操做代碼,將新服務的代碼與既有系統進行解耦,爲獨立的服務代碼庫和流水線作好準備。
目錄/包結構調整
軟件的包結構通常和構建軟件的組織結構以及建模方式有關。通常複雜系統同時存在着兩個大的變化方向:技術維度和業務維度,而軟件的包結構每每只能反映其中的一個維度。當組織結構以軟件的技術維度進行劃分,那麼系統的包結構也基本上會以此劃分,這時業務維度的變化每每會映射到系統的每個包上。反過來也是同樣!衡量哪一種包結構合理,每每是看當前哪一個是主要的變化方向。對主要的變化方向進行拆包隔離,能夠下降代碼變化之間的互相影響程度。
若是按照變化方向進行包的拆分,就會發現系統中應該存在不少小的包,最後每一個服務是一堆原子的小包組合。這本質上是將系統從新進行合理模塊化的過程。Adam Drake在文章Enough with the microservices中就直接指出微服務架構應該先從良好的模塊化重構作起,大多數時候當模塊化作好了甚至會發現不少問題已經獲得解決了。
然而既有系統的模塊合理化調整很難僅經過從新拆包達成!由於代碼是有邏輯的,模塊化的邏輯邊界不可能剛恰好落在代碼文件邊界。大多數狀況下都須要先對某一個代碼文件進行拆分,對某一個類或者函數進行重構,對某一段邏輯進行從新設計,而後才能從新獲得一個一致的邏輯和物理邊界,支撐繼續的拆包工做。
以前見過一個組織經過拆包進行系統解耦,他們把新服務和既有系統共享的全部代碼拆分紅不少小的共享包。這樣作後看似每一個服務在構建和流水線是獨立性的,可是問題在於那些共享包的代碼量並不小並且包含不少耦合的業務邏輯,新的修改常常致使新服務和既有系統一塊兒升級更新。
能夠先對新服務創建獨立的目錄,而後嘗試把屬於新服務的代碼逐漸往獨立目錄中遷移,在這一過程當中識別出新服務和既有系統耦合的代碼,而後一邊重構,再一邊繼續調整目錄和包結構,最後使得新服務和既有系統在物理和邏輯上同時解耦。
代碼重構
軟件重構目的是爲了解耦新服務和既有系統之間的共享代碼。共享代碼通常分爲以下幾種形式:
1.共同依賴的組件或者類,這又分爲以下幾種狀況:
穩定的基礎功能代碼。例如編解碼庫,加解密等等。這些代碼能夠按照功能發佈成獨立組件,供每一個服務自行決定使用。
服務間接口和消息定義。這類代碼能夠劃分到獨立的庫中,儘可能保持向前兼容,由接口的消費方自由選擇依賴的版本。服務間的API和消息定義在本質上是契約的共享,可使用契約描述文件代替共享代碼,使用時自動從契約描述生成代碼,這對於不一樣技術棧的服務會比較友好。
不合理編碼致使的耦合。例如耦合了全部功能的大而全的單例類,通常是一些全局配置類或者是「建立一切」的工廠類等。這種狀況須要對原有設計進行重構,對大而全的類進行拆分,將屬於不一樣服務的代碼拆分到不一樣的類中,由各個服務領回屬於本身的代碼。
2.共同繼承的接口或者類,這又分爲以下幾種狀況:
繼承是爲了組合:須要將繼承的公共處理重構爲支撐組件,由不一樣的服務根據須要自行選擇組合和使用方式。
繼承是爲了面向接口編程,這時接口每每是爲了配合某些公共業務處理而作的抽象。這些公共處理能夠按照如下幾種狀況進行重構:
接口背後的公共處理包含了複雜的業務邏輯,優先考慮將該公共處理變爲一個服務。這時須要將繼承接口上的同步調用變爲服務間的消息接口。
接口背後的公共處理簡單或者並不穩定,能夠考慮按照「Replication Over Reuse」的原則,由每一個服務自行實現,減小服務間的代碼共享。
接口背後的公共處理複雜,可是包含的業務邏輯相對穩定,若是不能將其獨立爲服務(例如因爲性能緣由),能夠將其打包成公共組件,由每一個服務自行組合使用。
從既有系統到微服務演進,在具體的落地中會發現最基礎的工做主要是代碼重構。而可否很好的實施代碼重構是一個體現團隊基本軟件技能素質的過程,須要團隊提高軟件設計、代碼重構、自動化測試方面的能力。
逐步完善配套
隨着自下向上的重構,新服務的代碼逐漸解耦到獨立的目錄或者包中,這時就能夠按需補齊服務化所欠缺的服務治理機制和各類工程實踐。在服務代碼不具有獨立性的時候開始嘗試搭建各類服務治理機制和工程流水線,每每會引入不少偶發複雜度,對工具提出一些不切實際的要求。
服務治理
微服務做爲面向服務架構當下可以流行,緣由之一在於隨着技術的進步各類服務治理工具均可以廉價得到。服務註冊發現、API網關、消息隊列、負載均衡、服務監控、集羣運維等每種需求均可以在網絡上找到一批的開源工具,而團隊則須要根據本身的現狀進行合理的選擇。有經驗的團隊能夠把各類不一樣的治理工做交給最合適的工具去作,而對於缺少經驗的團隊來講能夠先從某一工具入手累積經驗。曾經有一個團隊在開始不想引入過多工具複雜性,先選擇使用redis作緩存和消息隊列,隨後又使用redis作分佈式配置以及服務的註冊與發現等等。後來隨着能力提高,轉而使用etcd替代redis作服務的註冊發現,使用kafka作消息隊列。
對服務治理工具的選擇要避免陷入選擇困難症。每一個團隊都會以爲本身的業務特殊,開源工具老是不能知足本身的全部要求。帶着這種想法很容易裹足不前,一再浪費架構重構的合適時機。精益的作法是,先找到業界廣泛使用的工具,一邊使用一邊解決問題,一旦開始了不少問題在實踐中總能迎刃而解。對於一些注重性能的系統,不可避免的須要對開源組件在特定業務場景下進行優化定製,也最好先開始使用而後在實踐中肯定優化的方向。
持續交付
「服務有本身獨立代碼庫和交付流水線,能夠避免交付過程當中的互相干擾,提升交付速度和質量」,遺憾的是上述描述實際上是個僞命題!
真正減小團隊干擾、提升交付速度和質量的是「正確的解耦」自己。獨立的代碼庫和流水線會將架構約束顯示化,讓團隊成員難以犯錯。可是若是過早的對不成熟或者不穩定的架構邊界進行固化,反而會下降團隊的效率,讓後續架構調整變得困難。另外在系統沒有合理解耦的狀況下,獨立的代碼庫和流水線只會讓交互變得更復雜,致使對構建和發佈工具提出一堆不合理的要求。
可是若是服務之間確實已經正交拆分,代碼邊界和架構邊界一致而且是穩定的,這時獨立的代碼庫和流水線就能夠下降團隊在交付流水線上的互相干擾和排隊,此時就值得爲新的服務創建獨立的代碼庫和自動化流水線。考慮到服務之間的集成,每每須要多級流水線,這時選擇一款支持pipeline的持續集成工具是必要的。Jenkins2.x以及GoCD是此類產品的表明。
適應的組織結構和文化
康威定律常常被拿來講明組織結構和系統架構之間的互相做用關係。在對既有系統的服務化重構中,軟件架構和團隊結構同步進行調整會讓整個過程更加順暢。曾經有一個系統最初按照技術維度劃分團隊,後來爲了提升響應市場的速度把團隊按照不一樣的業務類型進行了調整。從新劃分後的團隊開始發現他們會常常修改同一公共組件,這時他們自發的對該組件進行了解耦,將其中和業務相關的邏輯各自領了回去,而後將剩下的穩定邏輯下沉到了基礎設施中。
除了匹配的組織結構,還須要團隊逐漸調整本身的文化。專門的測試人員和運維人員在微服務架構下必然成爲瓶頸,須要改變傳統的細分工的文化。團隊每一個成員都要有意願和能力承擔起服務的測試和運維工做,這須要組織從文化建設到考覈方式作對應的調整。
總結
對於既有系統作微服務演進,一旦第一個服務改形成功,後續的服務藉助前面的成功經驗和已有的基礎實施,會更加的容易拆分。不過第一個服務的拆分確實須要投入比較大的決心和精力,本文給出了一些建議,歸根結底總結起來就是:以精益的方式開展,以代碼解耦爲核心,以服務化技能作武裝,以組織結構和文化調整作基礎!
歡迎工做一到五年的Java工程師朋友們加入Java架構開發:744677563
羣內提供免費的Java架構學習資料(裏面有高可用、高併發、高性能及分佈式、Jvm性能調優、Spring源碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)合理利用本身每一分每一秒的時間來學習提高本身,不要再用"沒有時間「來掩飾本身思想上的懶惰!趁年輕,使勁拼,給將來的本身一個交代!