微服務化之服務拆分與服務發現

微服務化之服務拆分與服務發現

本文章為《互聯網高併發微服務化架構實踐》系列課程的第六篇。

目錄

  • 服務拆分的前提
  • 服務拆分的時機
  • 服務拆分的方法
  • 服務拆分的規範
  • 服務發現的選型

一、服務拆分的前提

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

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

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

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

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

二、服務拆分的時機

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

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

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

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

互聯網產品的特點就是迭代速度快,一般一年半就能決出勝負,第一一統天下,第二被第一收購,其他死翹翹。所以快速上線,快速迭代,就是生命線,而且一旦成功就是百億身家,所以無論付出多大運維成本,使用微服務架構都是值得的。

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

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

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

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

所以應該拆分成不同的模塊,每十個人左右維護一個模塊,也即一個工程,首先代碼衝突的概率小多了,而且有了衝突,一個小組一吼,基本上問題就解決了。

每個模塊對外提供接口,其他依賴模塊可以不用關注具體的實現細節,只需要保證接口正確就可以。

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

微服務對於快速迭代的效果,首先是上線獨立。如果沒有拆分微服務,每次上線都是一件很痛苦的事情。當你修改了一個邊角的小功能,但是你不敢馬上上線,因為你依賴的其他模塊才開發了一半,你要等他,等他好了,也不敢馬上上線,因為另一個被依賴的模塊也開發了一半,當所有的模塊都耦合在一起,互相依賴,誰也沒辦法獨立上線,而是需要總監協調各個團隊,大家開大會,約定一個時間點,無論大小功能,死活都要這天上線。

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

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

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

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

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

因而對於併發量不大的系統,進行微服務化的驅動力差一些,如果只有不多的用戶在線,多線程就能解決問題,最多做好無狀態化,前面部署個負載均衡,單體應用部署多份。

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

單體應用無狀態化之後,雖然通過部署多份,可以承載一定的併發量,但是資源非常浪費。因為有的業務是需要擴容的,例如下單和支付,有的業務是不需要擴容的,例如註冊。如果一起擴容,消耗的資源可能是拆分後的幾倍,成本可能多出幾個億。而且由於配置複雜,在同一個工程裡面,往往在配置文件中是這樣組織的,這一塊是這個模塊的,下一塊是另一個模塊的,這樣擴容的時候,一些邊角的業務,也是需要對配置進行詳細審核,否則不敢貿然擴容。

微服務拆分時機四:熔斷降級全靠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開發人員的首選。

微服務化之服務拆分與服務發現

第二種是springcloud,springcloud為微服務而生,在dubbo已經沒有人維護的情況下,推出了支撐微服務的成熟框架。

微服務化之服務拆分與服務發現

dubbo vs. springcloud的對比,dubbo更加註重服務治理,原生功能不夠全面,而springcloud注重整個微服務生態,工具鏈非常全面。

微服務化之服務拆分與服務發現

springcloud可定製性強,通過各種組件滿足各種微服務場景,使用springboot統一編程模型,能夠快速構建應用,基於註解,使用方便,但是學習門檻比較高。

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

第三是kubernetes,Kubernetes雖然是容器平臺,但是他設計出來,就是為了跑微服務的,因而提供了微服務運行的很多組件。

微服務化之服務拆分與服務發現

很多springcloud可以做的事情,kubernetes也有相應的機制,而且由於是容器平臺,相對比較通用,可以支持多語言,對於業務無侵入,但是也正因為是容器平臺,對於微服務的運行生命週期的維護比較全面,對於服務之間的調用和治理,比較弱,service只能滿足最最基本的服務發現需求。

因而實踐中使用的時候,往往是kubernetes和springcloud結合使用,kubernetes負責提供微服務的運行環境,服務之間的調用和治理,由springlcoud搞定。

微服務化之服務拆分與服務發現

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

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

微服務化之服務拆分與服務發現

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

關於Wise2C睿雲智合

深圳睿雲智合科技有限公司成立於2012年,總部位於深圳,並分別在成都、深圳設立了研發中心,北京、上海設立了分支機構,核心骨幹人員全部為來自金融、科技行業知名企業資深業務專家、技術專家。早期專注於為中國金融保險等大型企業提供創新技術、電子商務、CRM等領域專業諮詢服務。

自2016年始,在率先將容器技術引進到中國保險行業客戶後,公司組建了專業的容器技術產品研發和實施服務團隊,旨在幫助中國金融行業客戶將容器創新技術應用於企業信息技術支持業務發展的基礎能力改善與提升,成為中國金融保險行業容器技術服務領導品牌。

此外,憑藉多年來在呼叫中心領域的業務經驗與技術積累,睿雲智合率先在業界推出基於開源軟交換平臺FreeSwitch的微服務架構多媒體數字化業務平臺,將語音、視頻、webchat、微信、微博等多種客戶接觸渠道集成,實現客戶統一接入、精準識別、智能路由的CRM策略,並以容器化治理來支持平臺的全應用生命週期管理,顯著提升了數字化業務處理的靈活、高效、彈性、穩定等特性,為幫助傳統企業向“以客戶為中心”的數字化業務轉型提供完美的一站式整體解決方案。


分享到:


相關文章: