微服務實戰(六):如何作好服務拆分?

原文連接:https://mp.weixin.qq.com/s/mcBdtqBRQbY4D5i6G7o-7g

 

服務拆分的前提

說到微服務,服務拆分是繞不過去的話題,可是微服務不是說拆就能拆的,有不少的前提條件,須要完成前面幾節所論述的部分。

首先要有一個持續集成的平臺,使得服務在拆分的過程當中,功能的一致性,這種一致性不能經過人的經驗來,而須要通過大量的迴歸測試集,而且持續的拆分,持續的演進,持續的集成,從而保證系統時刻處於能夠驗證交付的狀態,而非閉門拆分一段時間,最終誰也不知道功能最終究竟有沒有bug,於是須要另一個月的時間專門修改bug。

其次在接入層,API和UI要動靜分離,API由API網關統一的管理,這樣後端不管如何拆分,能夠保證對於前端來說,統一的入口,並且能夠實現拆分過程當中的灰度發佈,路由分發,流量切分,從而保證拆分的平滑進行。並且拆分後的微服務之間,爲了高性能,是不建議每次調用都進行認證鑑權的,而是在API網關上作統一的認證鑑權,一旦進入網關,服務之間的調用就是可信的。

其三對於數據庫,須要進行良好的設計,不該該有大量的聯合查詢,而是將數據庫當成一個簡單的key-value查詢,複雜的聯合查詢經過應用層,或者經過Elasticsearch進行。若是數據庫表之間耦合的很是嚴重,其實服務拆分是拆不出來的。

其四要作應用的無狀態化,只有無狀態的應用,才能橫向擴展,這樣拆分纔有意義。前端

服務拆分的時機

知足了服務拆分的前提以後,那先拆哪一個模塊,後拆哪一個模塊呢?什麼狀況下一個模塊應該拆分出來呢?

微服務拆分絕非一個大躍進運動,由高層發起,把一個應用拆分的七零八落的,最終大大增長運維成本,可是並不會帶來收益。

微服務拆分的過程,應該是一個由痛點驅動的,是業務真正遇到了快速迭代和高併發的問題,若是不拆分,將對於業務的發展帶來影響,只有這個時候,微服務的拆分是有肯定收益的,增長的運維成本纔是值得的。

微服務解決的問題之一,就是快速迭代。

互聯網產品的特色就是迭代速度快,通常一年半就能決出勝負,第一一統天下,第二被第一收購,其餘死翹翹。因此快速上線,快速迭代,就是生命線,並且一旦成功就是百億身家,因此不管付出多大運維成本,使用微服務架構都是值得的。

這也就是爲何大部分使用微服務架構的都是互聯網企業,由於對於這些企業來說收益明顯。而對於不少傳統的應用,半年更新一次,企業運營相對平穩,IT系統的好壞對於業務沒有關鍵性影響,在他們眼中,微服務化改造帶來的效果,還不如開發多加幾回班。數據庫

微服務拆分時機一:提交代碼頻繁出現大量衝突

微服務對於快速迭代的效果,首先是開發獨立,若是是一單體應用,幾百人開發一個模塊,若是使用Git作代碼管理,則常常會遇到的事情就是代碼提交衝突。

一樣一個模塊,你也改,他也改,幾百人根本沒辦法溝通。因此當你想提交一個代碼的時候,發現和別人提交的衝突了,因而由於你是後提交的人,你有責任去merge代碼,好不容易merge成功了,等再次提交的時候,發現又衝突了,你是否是很惱火。隨着團隊規模越大,衝突機率越大。

因此應該拆分紅不一樣的模塊,每十我的左右維護一個模塊,也即一個工程,首先代碼衝突的機率小多了,並且有了衝突,一個小組一吼,基本上問題就解決了。

每一個模塊對外提供接口,其餘依賴模塊能夠不用關注具體的實現細節,只須要保證接口正確就能夠。編程

微服務拆分時機二:小功能要積累到大版本才能上線,上線開總監級別大會

微服務對於快速迭代的效果,首先是上線獨立。若是沒有拆分微服務,每次上線都是一件很痛苦的事情。當你修改了一個邊角的小功能,可是你不敢立刻上線,由於你依賴的其餘模塊纔開發了一半,你要等他,等他好了,也不敢立刻上線,由於另外一個被依賴的模塊也開發了一半,當全部的模塊都耦合在一塊兒,互相依賴,誰也沒辦法獨立上線,而是須要總監協調各個團隊,你們開大會,約定一個時間點,不管大小功能,死活都要這天上線。

這種模式致使上線的時候,單次上線的需求列表很是長,這樣風險比較大,可能小功能的錯誤會致使大功能的上線不正常,將如此長的功能,須要一點點check,很是當心,這樣上線時間長,影響範圍大。於是這種的迭代速度快不了,頂多一個月一次就不錯了。

服務拆分後,在接口穩定的狀況下,不一樣的模塊能夠獨立上線。這樣上線的次數增多,單次上線的需求列表變小,能夠隨時回滾,風險變小,時間變短,影響面小,從而迭代速度加快。

對於接口要升級部分,保證灰度,先作接口新增,而非原接口變動,當註冊中心中監控到的調用狀況,發現接口已經不用了,再刪除。

微服務解決的問題之二,就是高併發。

互聯網一個產品的特色就是在短時間內要積累大量的用戶,這甚至比營收和利潤還重要,若是沒有大量的用戶基數,融資都會有問題。

於是對於併發量不大的系統,進行微服務化的驅動力差一些,若是隻有很少的用戶在線,多線程就能解決問題,最多作好無狀態化,前面部署個負載均衡,單體應用部署多份。後端

微服務拆分時機三:橫向擴展流程複雜,主要業務和次要業務耦合

單體應用無狀態化以後,雖然經過部署多份,能夠承載必定的併發量,可是資源很是浪費。由於有的業務是須要擴容的,例以下單和支付,有的業務是不須要擴容的,例如註冊。若是一塊兒擴容,消耗的資源多是拆分後的幾倍,成本可能多出幾個億。並且因爲配置複雜,在同一個工程裏面,每每在配置文件中是這樣組織的,這一塊是這個模塊的,下一塊是另外一個模塊的,這樣擴容的時候,一些邊角的業務,也是須要對配置進行詳細審覈,不然不敢貿然擴容。api

微服務拆分時機四:熔斷降級全靠if-else

在高併發場景下,咱們但願一個請求若是不成功,不要佔用資源,應該儘快失敗,儘快返回,並且但願當一些邊角的業務不正常的狀況下,主要業務流程不受影響。這就須要熔斷策略,也即當A調用B,而B老是不正常的時候,爲了讓B不要波及到A,能夠對B的調用進行熔斷,也即A不調用B,而是返回暫時的fallback數據,當B正常的時候,再放開熔斷,進行正常的調用。

有時候爲了保證核心業務流程,邊角的業務流程,如評論,庫存數目等,人工設置爲降級的狀態,也即默認不調用,將全部的資源用於大促的下單和支付流程。

若是核心業務流程和邊角業務流程在同一個進程中,就須要使用大量的if-else語句,根據下發的配置來判斷是否熔斷或者降級,這會使得配置異常複雜,難以維護。

若是核心業務和邊角業務分紅兩個進程,就可使用標準的熔斷降級策略,配置在某種狀況下,放棄對另外一個進程的調用,能夠進行統一的維護。緩存

服務拆分的方法

好了,當你以爲要將一個程序的某個部分拆分出來的時候,有什麼方法能夠保障平滑嗎?

首先要作的,就是原有工程代碼的標準化,咱們常稱爲「任何人接手任何一個模塊都能看到熟悉的面孔」

例如打開一個Java工程,應該有如下的package:數據結構

  • API接口包:全部的接口定義都在這裏,對於內部的調用,也要實現接口,這樣一旦要拆分出去,對於本地的接口調用,就能夠變爲遠程的接口調用。
  • 訪問外部服務包:若是這個進程要訪問其餘進程,對於外部訪問的封裝都在這裏,對於單元測試來說,對於這部分的Mock,可使得不用依賴第三方,就能進行功能測試。對於服務拆分,調用其餘的服務,也是在這裏。
  • 數據庫DTO:若是要訪問數據庫,在這裏定義原子的數據結構。
  • 訪問數據庫包:訪問數據庫的邏輯所有在這個包裏面。
  • 服務與商務邏輯:這裏實現主要的商業邏輯,拆分也是從這裏拆分出來。
  • 外部服務:對外提供服務的邏輯在這裏,對於接口的提供方,要實如今這裏。


另外是測試文件夾,每一個類都應該有單元測試,要審覈單元測試覆蓋率,模塊內部應該經過Mock的方法實現集成測試。

接下來是配置文件夾,配置profile,配置分爲幾類:多線程

  • 內部配置項(啓動後不變,改變須要重啓)
  • 集中配置項(配置中心,可動態下發)
  • 外部配置項(外部依賴,和環境相關)


當一個工程的結構很是標準化以後,接下來在原有服務中,先獨立功能模塊 ,規範輸入輸出,造成服務內部的分離。在分離出新的進程以前,先分離出新的jar,只要可以分離出新的jar,基本也就實現了鬆耦合。

接下來,應該新建工程,新啓動一個進程,儘早的註冊到註冊中心,開始提供服務,這個時候,新的工程中的代碼邏輯能夠先沒有,只是轉調用原來的進程接口。

爲何要越早獨立越好呢?哪怕還沒實現邏輯先獨立呢?由於服務拆分的過程是漸進的,伴隨着新功能的開發,新需求的引入,這個時候,對於原來的接口,也會有新的需求進行修改,若是你想把業務邏輯獨立出來,獨立了一半,新需求來了,改舊的,改新的都不合適,新的還沒獨立提供服務,舊的若是改了,會形成從舊工程遷移到新工程,邊遷移邊改變,合併更加困難。若是儘早獨立,全部的新需求都進入新的工程,全部調用方更新的時候,都改成調用新的進程,對於老進程的調用會愈來愈少,最終新進程將老進程所有代理。

接下來就能夠將老工程中的邏輯逐漸遷移到新工程,因爲代碼遷移不能保證邏輯的徹底正確,於是須要持續集成,灰度發佈,微服務框架可以在新老接口之間切換。

最終當新工程穩定運行,而且在調用監控中,已經沒有對於老工程的調用的時候,就能夠將老工程下線了。架構

服務拆分的規範

微服務拆分以後,工程會比較的多,若是沒有必定的規範,將會很是混亂,難以維護。

首先人們常常問的一個問題是,服務拆分以後,原來都在一個進程裏面的函數調用,如今變成了A調用B調用C調用D調用E,會不會由於調用鏈路過長而使得相應變慢呢?併發

服務拆分的規範一:服務拆分最多三層,兩次調用

服務拆分是爲了橫向擴展,於是應該橫向拆分,而非縱向拆成一串的。也即應該將商品和訂單拆分,而非下單的十個步驟拆分,而後一個調用一個。

縱向的拆分最多三層:

  • 基礎服務層:用於屏蔽數據庫,緩存層,提供原子的對象查詢接口,有這一層,爲了數據層作必定改變的時候,例如分庫分表,數據庫擴容,緩存替換等,對於上層透明,上層僅僅調用這一層的接口,不直接訪問數據庫和緩存。
  • 組合服務層:這一層調用基礎服務層,完成較爲複雜的業務邏輯,實現分佈式事務也多在這一層
  • Controller層:接口層,調用組合服務層對外

 

服務拆分的規範二:僅僅單向調用,嚴禁循環調用

微服務拆分後,服務之間的依賴關係複雜,若是循環調用,升級的時候就很頭疼,不知道應該先升級哪一個,後升級哪一個,難以維護。

於是層次之間的調用規定以下:

  • 基礎服務層主要作數據庫的操做和一些簡單的業務邏輯,不容許調用其餘任何服務。
  • 組合服務層,能夠調用基礎服務層,完成複雜的業務邏輯,能夠調用組合服務層,不容許循環調用,不容許調用Controller層服務
  • Controller層,能夠調用組合業務層服務,不容許被其餘服務調用


若是出現循環調用,例如A調用B,B也調用A,則分紅Controller層和組合服務層兩層,A調用B的下層,B調用A的下層。也可使用消息隊列,將同步調用,改成異步調用。

服務拆分的規範三:將串行調用改成並行調用,或者異步化

若是有的組合服務處理流程的確很長,須要調用多個外部服務,應該考慮如何經過消息隊列,實現異步化和解耦。

例以下單以後,要刷新緩存,要通知倉庫等,這些都不須要再下單成功的時候就要作完,而是能夠發一個消息給消息隊列,異步通知其餘服務。

並且使用消息隊列的好處是,你只要發送一個消息,不管下游依賴方有一個,仍是有十個,都是一條消息搞定,只不過多幾個下游監聽消息便可。

對於下單必須同時作完的,例如扣減庫存和優惠券等,能夠進行並行調用,這樣處理時間會大大縮短,不是屢次調用的時間之和,而是最長的那個系統調用時間。

服務拆分的規範四:接口應該實現冪等

微服務拆分以後,服務之間的調用當出現錯誤的時候,必定會重試,可是爲了避免要下兩次單,支付兩次,須要全部的接口實現冪等。

冪等通常須要設計一個冪等表來實現,冪等表中的主鍵或者惟一鍵能夠是transaction id,或者business id,能夠經過這個id的惟一性標識一個惟一的操做。

也有冪等操做使用狀態機,當一個調用到來的時候,每每觸發一個狀態的變化,當下次調用到來的時候,發現已經不是這個狀態,就說明上次已經調用過了。

狀態的變化須要是一個原子操做,也即併發調用的時候,只有一次能夠執行。可使用分佈式鎖,或者樂觀鎖CAS操做實現。

服務拆分的規範五:接口數據定義嚴禁內嵌,透傳

微服務接口之間傳遞數據,每每經過數據結構,若是數據結構透傳,從底層一直到上層使用同一個數據結構,或者上層的數據結構內嵌底層的數據結構,當數據結構中添加或者刪除一個字段的時候,波及的面會很是大。

於是接口數據定義,在每兩個接口之間約定,嚴禁內嵌和透傳,即使差很少,也應該從新定義,這樣接口數據定義的改變,影響面僅僅在調用方和被調用方,當接口須要更新的時候,比較可控,也容易升級。

服務拆分的規範六:規範化工程名

微服務拆分後,工程名很是多,開發人員,開發團隊也很是多,如何讓一個開發人員看到一個工程名,或者jar的名稱,就大概知道是幹什麼的,須要一個規範化的約定。

例如出現pay就是支付,出現order就是下單,出現account就是用戶。

再如出現compose就是組合層,controller就是接口層,basic就是基礎服務層。

出現api就是接口定義,impl就是實現。

pay-compose-api就是支付組合層接口定義。

account-basic-impl就是用戶基礎服務層的實現。

服務發現的選型

微服務拆分後,服務之間的調用須要服務發現和註冊中心進行維護。也能主流的有幾種方法。

第一是Dubbo,Dubbo是SOA架構的微服務框架的標準,已經被大量使用,雖然中間中斷維護過一段時間,可是隨着微服務的興起,從新進行了維護,是不少熟悉Dubbo RPC開發人員的首選。

1.jpg


第二種是Spring Cloud,Spring Cloud爲微服務而生,在Dubbo已經沒有人維護的狀況下,推出了支撐微服務的成熟框架。

2.jpg


Dubbo vs. Spring Cloud的對比,Dubbo更加註重服務治理,原生功能不夠全面,而Spring Cloud注重整個微服務生態,工具鏈很是全面。

3.jpg


Spring Cloud可定製性強,經過各類組件知足各類微服務場景,使用Spring Boot統一編程模型,可以快速構建應用,基於註解,使用方便,可是學習門檻比較高。

Dubbo註冊到ZooKeeper裏面的是接口,而Spring Cloud註冊到Eureka或者Consul裏面的是實例,在規模比較小的狀況下沒有分別,可是規模一旦大了,例如實例數目萬級別,接口數據就算十萬級別,對於ZooKeeper中的樹規模比較大,並且ZooKeeper是強一致性的,當一個節點掛了的時候,節點之間的數據同步會影響線上使用,而Spring Cloud就好不少,實例級別少一個量級,另外Consul也非強一致的。

第三是Kubernetes,Kubernetes雖然是容器平臺,可是他設計出來,就是爲了跑微服務的,於是提供了微服務運行的不少組件。

4.jpg


不少Spring Cloud能夠作的事情,Kubernetes也有相應的機制,並且因爲是容器平臺,相對比較通用,能夠支持多語言,對於業務無侵入,可是也正由於是容器平臺,對於微服務的運行生命週期的維護比較全面,對於服務之間的調用和治理,比較弱,Service只能知足最最基本的服務發現需求。

於是實踐中使用的時候,每每是Kubernetes和Spring Cloud結合使用,Kubernetes負責提供微服務的運行環境,服務之間的調用和治理,由Spring Cloud搞定。

5.jpg


第四是Service Mesh,Service Mesh必定程度上彌補了kubernetes對於服務治理方面的不足,對業務代碼0侵入,將服務治理下沉到平臺層,是服務治理的一個趨勢。

然而Service Mesh須要使用單獨的進程進行請求轉發,性能還不能讓人滿意,另外社區比較新,成熟度不足,暫時沒有達到大規模生產使用的標準。

6.png
相關文章
相關標籤/搜索