系統架構演變概述

在公司業務初創時期,面對的主要問題是如何將一個想法變成實際的軟體實現,在這個時候整個軟體系統的架構並沒有搞得那麼複雜,為了快速迭代,整個軟體系統就是由“App+後臺服務”組成,而後臺服務也只是從工程角度將應用進行Jar包的拆分。此時軟體系統架構如下:

f28189ec7177122a0a943b820b31571a7abdf7b6

而此時整個軟體系統的功能也比較簡單,只有基本的使用者、訂單、支付等功能,並且由於業務流程沒有那麼複雜,這些功能基本耦合在一起。而隨著App的受歡迎程度(作者所在的公司正好處於網際網路熱點),所以App下載量在2017年迅猛增長,線上註冊人數此時也是蹭蹭往上漲。

隨著流量的迅猛增長,此時整個後臺服務的壓力變得非常大,為了抗住壓力只能不斷的加機器,平行擴充套件後臺服務節點。此時的部署架構如下:

473ec4085fe688c80bb187963446886a644d9aae

通過這種方式,整個軟體系統抗住了一波壓力,然而系統往往還是會偶爾出點事故,特別是因為api中的某個介面效能問題導致整個服務不可用,因為這些介面都在一個JVM程序中,雖然此時部署了多個節點,但因為底層資料庫、快取系統都是一套,所以還是會出現一掛全掛的情況。

另一方面,隨著業務的快速發展,以往相對簡單的功能變得複雜起來,這些功能除了有使用者看得見的、也會包括很多使用者看不見的,就好像百度搜索,使用者能看見的可能只是一個搜尋框,但是實際上後臺對應的服務可能是成百上千,如有些增長策略相關的功能:紅包、分享拉新等。還有些如廣告推薦相關的變現功能等。

此外,流量/業務的增長也意味著團隊人數的迅速增長,如果此時大家開發各自的業務功能還是用一套服務程式碼,很難想象百十來號人,在同一個工程在疊加功能會是一個什麼樣的場景。所以如何劃分業務邊界、合理的進行團隊配置也是一件十分迫切的事情了!

為了解決上述問題,適應業務、團隊發展,架構團隊決定進行微服務拆分。而要實施微服務架構,除了需要合理的劃分業務模組邊界外,也需要一整套完整的技術解決方案。

在技術方案的選擇上,服務拆分治理的框架也是有很多,早期的有如WebService,近期的則有各種Rpc框架(如Dubbo、Thirft、Grpc)。而Spring Cloud則是基於Springboot提供的一整套微服務解決方案,因為技術棧比較新,並且各類元件的支撐也非常全面,所以Spring Cloud就成為了首選。

經過一系列的重構+擴充套件,整個系統架構最終形成了以app為中心的一套微服務軟體系統,結構如下:

33894191e69fb09248dcb39859409021365b4afc

到這裡,整個軟體系統就基於SpringCloud初步完成了微服務體系的拆分。支付、訂單、使用者、廣告等核心功能抽離成獨立的微服務,與此同時各自微服務對應的資料庫也按照服務邊界進行了拆分。

在完成服務的拆分以後,原來功能邏輯之間的程式碼呼叫關係,轉換成了服務間網路的呼叫關係,而各個微服務需要根據各自所承載的功能提供相應的服務,此時服務如何被其他服務發現並呼叫,就成了整個微服務體系中比較關鍵的部分,使用過Dubbo框架的同學知道,在Dubbo中服務的註冊&發現是依賴於Zookeeper實現的,而在SpringCloud中我們是通過Consul來實現。另外在基於SpringCloud的架構體系中,提供了配置中心(ConfigServer)來幫助各個微服務管理配置檔案,而原本的api服務,隨著各個功能的抽離,逐步演變成前置閘道器服務了。

聊到這裡,基於SpringCloud我們進行了微服務拆分,而在這個體系結構中,分別提到了Consul、ConfigServer、閘道器服務這幾個關鍵元件,那麼這幾個關鍵元件具體是如何支撐這個龐大的服務體系的呢?

SpringCloud關鍵元件

Consul

Consul是一個開源的,使用go語言開發的註冊中心服務。它裡面內建了服務發現與註冊框架、分佈一致性協議實現、健康檢查、Key/Value儲存、多資料中心等多個方案。在SpringCloud框架中還可以選擇Eurke作為註冊中心,這裡之所以選擇Consul主要原因在於Consul對異構的服務的支援,如:grpc服務。

事實上,在後續的系統架構演進中,在某些服務模組進一步向子系統化拆分的過程中,就採用了grpc作為子系統服務間的呼叫方式。例如,支付模組的繼續擴張,對支付服務本身又進行了微服務架構的拆分,此時支付微服務內部就採用了grpc的方式進行呼叫,而服務註冊與發現本身則還是依賴於同一套Consul叢集。

此時的系統架構演進如下:

81875a69bfb5c4b5ee51d05c06126bd4681278f5

原有微服務架構中的模組服務在規模達到一定程度或複雜性達到一定程度後,都會朝著獨立的體系發展,從而將整個微服務的呼叫鏈路變的非常長,而從Consul的角度看,所有的服務又都是扁平的。

隨著微服務規模的越來越大,Consul作為整個體系的核心服務元件,在整個體系中處於關鍵的位置,一旦Consul掛掉,所有的服務都將停止服務。那麼Consul到底是什麼樣服務?其容災機制又該如何設計呢?

要保證Consul服務的高可用,在生產環境Consul應該是一個叢集(關於Consul叢集的安裝與配置可以參考網路資料),這是毫無疑問的。而在Consul叢集中,存在兩種角色:Server、Client,這兩種角色與Consul叢集上執行的應用服務並沒有什麼關係,只是基於Consul層面的一種角色劃分。實際上,維持整個Consul叢集狀態資訊的還是Server節點,與Dubbo中使用Zookeeper實現註冊中心一樣,Consul叢集中的各個Server節點也需要通過選舉的方式(使用GOSSIP協議、Raft一致性演算法,這裡不做詳細展開,在後面的文章中可以和大家單獨討論)來選舉整個叢集中的Leader節點來負責處理所有查詢和事務,並向其他節點同步狀態資訊。

Client角色則是相對無狀態的,只是簡單的代理轉發RPC請求到Server節點,之所以存在Client節點主要是分擔Server節點的壓力,作一層緩衝而已,這主要是因為Server節點的數量不宜過多,因為Server節點越多也就意味著達成共識的過程越慢,節點間同步的代價也就越高。對於Server節點,一般建議3-5臺,而Client節點則沒有數量的限制,可以根據實際情況部署數千或數萬臺。事實上,這也只是一種策略,在現實的生產環境中,大部分應用只需要設定3~5臺Server節點就夠了,作者所在的公司一套生產叢集中的Consul叢集的節點配置就是5個Server節點,並沒有額外再設定Client節點。

另外,在Consul叢集中還有一個概念是Agent,事實上每個Server或Client都是一個consul agent,它是執行在Consul叢集中每個成員上的一個守護程序,主要的作用是執行DNS或HTTP介面,並負責執行時檢查和保持服務資訊同步。我們在啟動Consul叢集的節點(Server或Client)時,都是通過consul agent的方式啟動的。例如:

 

consul agent -server -bootstrap -syslog
-ui
-data-dir= /opt/consul/data
-dns-port=53
-recursor=10.211.55.3
-config-dir=/opt/consul/conf
-pid-file= /opt/consul/run/consul .pid
-client= 10.211 . 55.4
-bind= 10.211 . 55.4
-node=consul-server01
-disable-host-node-id &

以實際的生產環境為例,Consul叢集的部署結構示意圖如下:

ffe781adeaa65847499895fa531c5d30779c8fdc

實際生產案例中並沒有設定Client節點,而是通過5個Consul Server節點組成的叢集,來服務整套生產叢集的應用註冊&發現。這裡有細節需要了解下,實際上5個Consul Server節點的IP地址是不一樣的,具體的服務在連線Consul叢集進行服務註冊與查詢時應該連線Leader節點的IP,而問題是,如果Leader節點掛掉了,相應的應用服務節點,此時如何連線通過Raft選舉產生的新Leader節點呢?難道手工切換IP不成?

顯然手工切換IP的方式並不靠譜,而在生產實踐中,Consul叢集的各個節點實際上是在Consul Agent上執行DNS(如啟動引數中紅色字型部分),應用服務在連線Consul叢集時的IP地址為DNS的IP,DNS會將地址解析對映到Leader節點對應的IP上,如果Leader節點掛掉,選舉產生的新Leader節點會將自己的IP通知DNS服務,DNS更新對映關係,這一過程對各應用服務則是透明的。

通過以上分析,Consul是通過叢集設計、Raft選舉演算法,Gossip協議等機制來確保Consul服務的穩定與高可用的。如果需要更高的容災級別,也可以通過設計雙資料中心的方式,來異地搭建兩個Consul資料中心,組成一個異地災備Consul服務叢集,只是這樣成本會更高,這就看具體是否真的需要了。

ConfigServer(配置中心)

配置中心是對微服務應用配置進行管理的服務,例如資料庫的配置、某些外部介面地址的配置等等。在SpringCloud中ConfigServer是獨立的服務元件,它與Consul一樣也是整個微服務體系中比較關鍵的一個元件,所有的微服務應用都需要通過呼叫其服務,從而獲取應用所需的配置資訊。

隨著微服務應用規模的擴大,整個ConfigServer節點的訪問壓力也會逐步增加,與此同時,各個微服務的各類配置也會越來越多,如何管理好這些配置檔案以及它們的更新策略(確保不因生產配置隨意改動而造成線上故障風險),以及搭建高可用的ConfigServer叢集,也是確保微服務體系穩定很重要的一個方面。

在生產實踐中,因為像Consul、ConfigServer這樣的關鍵元件,需要搭建獨立的叢集,並且部署在物理機而不是容器裡。在上一節介紹Consul的時候,我們是獨立搭建了5個Consul Server節點。而ConfigServer因為主要是http配置檔案訪問服務,不涉及節點選舉、一致性同步這樣的操作,所以還是按照傳統的方式搭建高可用配置中心。具體結構示意圖如下:

c7100bafafc839b132650a91d23b7d2885593307

我們可以單獨通過git來管理應用配置檔案,正常來說由ConfigSeever直接通過網路拉取git倉庫的配置供服務獲取就可以了,這樣只要git倉庫配置更新,配置中心就能立刻感知到。但是這樣做的不穩定之處,就在於git本身是內網開發用的程式碼管理工具,如果讓線上實時服務直接讀取,很容易將git倉庫拉掛了,所以,我們在實際的運維過程中,是通過git進行配置檔案的版本控制,區分線上分支/master與功能開發分支/feature,並且在完成mr後還需要手工(通過釋出平臺觸發)同步一遍配置,過程是將新的master分支的配置同步一份到各個configserver節點所在主機的本地路徑,這樣configserver服務節點就可以通過其本地目錄獲取配置檔案,而不用多次呼叫網路獲取配置檔案了。

而另一方面,隨著微服務越來越多,git倉庫中的配置檔案數量也會越來越多。為了便於配置的管理,我們需要按照一定的組織方式來組織不同應用型別的配置。在早期所有的應用因為沒有分類,所以導致上百個微服務的配置檔案放在一個倉庫目錄,這樣一來導致配置檔案管理成本增加,另外一方面也會影響ConfigServer的效能,因為某個微服務用不到的配置也會被ConfigServer載入。

所以後期的實踐是,按照配置的層次關係進行組織,將公司全域性的專案配置抽象到頂層,由ConfigServer預設載入,而其他所有的微服務則按照應用型別進行分組(通過git專案空間的方式分組),相同的應用放在一個組,然後這個組下單獨設立一個名為config的git倉庫來存放這個組下相關微服務的配置檔案。層次結構如下:

a6b6738c9daa30ec30e09be47a29033a1a21417f

這樣應用載入配置的優先順序就是“本地配置->common配置->組公共配置->專案配置”這樣的順序。例如某服務A,在專案工程的預設配置檔案(“bootstrap.yml/application.yml”)中配置了引數A,同時也在本地專案配置“application-production.yml”配置了引數B,而與此同時,ConfigServer中的common倉庫下的配置檔案“application.yml/application-production.yml”又分別存在引數C、引數D,同時有個組叫“pay”,其下的預設配置檔案“application.yml/application-production.yml”存在引數E、引數F,具體專案pay-api又存在配置檔案“pay-api-production.yml”其覆蓋了common倉庫中引數C、引數D的值。那麼此時如果該應用以“spring.profiles.active=production”的方式啟動,那麼其能獲取到的配置引數(通過連結訪問:http://{spring.cloud.config.uri}/pay-api-production.yml)就是A、B、C、D、E、F,其中C、D的引數值為pay-api-production.yml中最後覆蓋的值。

而對於ConfigServer服務本身來說,需要按照這樣的組織方式進行配置型別匹配,例如上述的例子中,假設還存在finance的配置倉庫,而pay組下的服務訪問配置中心的時候,是不需要finance空間下的配置檔案的,所以ConfigServer可以不用載入。這裡就需要在ConfigServer服務配置中進行一些配置。具體如下:


spring:
application:
name: @project.[email protected]
version: @project.[email protected]
build: @buildNumber@
branch: @scmBranch@
cloud:
inetutils:
ignoredInterfaces:
- docker0
config:
server:
health.enabled: false
git:
uri: /opt/repos/config
searchPaths: 'common,{application}'
cloneOnStart: true
repos:
pay:
pattern: pay-*
cloneOnStart: true
uri: /opt/repos/example/config
searchPaths: 'common,{application}'
finance:
pattern: finance-*
cloneOnStart: true
uri: /opt/repos/finance/config
searchPaths: 'common,{application}'

通過在ConfigServer服務本身的application.yml本地配置中,設定其配置搜尋方式,來實現這樣的目的。

閘道器服務&服務熔斷&監控

通過上面兩小節的內容,我們相對詳細地介紹了基於SpringCloud體系中比較關鍵的兩個服務元件。然而在微服務架構體系中,還有很多關鍵的問題需要解決,例如,應用服務在Consul中部署了多個節點,那麼呼叫方如何實現負載均衡?

關於這個問題,在傳統的架構方案中是通過Nginx實現的,但是在前面介紹Consul的時候只提到了Consul的服務註冊&發現、選舉等機制,並沒有提到Consul如何在實現服務呼叫的負載均衡。難道基於SpringCloud的微服務體系中的應用服務都是單節點在提供服務,哪怕即使部署了多個服務節點?事實上,我們在服務消費方通過@EnableFeignClients註解開啟呼叫,通過@FeignClient("user")註解進行服務呼叫時,就已經實現了負載均衡,為什麼呢?因為,這個註解預設是會預設開啟Robbin代理的,而Robbin是實現客戶端負載均衡的一個元件,通過從Consul拉取服務節點資訊,從而以輪詢的方式轉發客戶端呼叫請求至不同的服務端節點來實現負載均衡。而這一切都是在消費端的程序內部通過程式碼的方式實現的。這種負載方式寄宿於消費端應用服務上,對消費端存在一定的程式碼侵入性,這是為什麼後面會出現Service Mesh(服務網格)概念的原因之一,這裡就不展開了,後面有機會再和大家交流。

另一需要解決的關鍵問題是服務熔斷、限流等機制的實現,SpringCloud通過整合Netflix的Hystrix框架來提供這種機制的支援,與負載均衡機制一樣也是在消費端實現。由於篇幅的關係,這裡也不展開了,在後面的文章中有機會再和大家交流。

此外還有Zuul元件來實現API閘道器服務,提供路由分發與過濾相關的功能。而其他輔助元件還有諸如Sleuth來實現分散式鏈路追蹤、Bus實現訊息匯流排、Dashboard實現監控儀表盤等。由於SpringCloud的開源社群比較活躍,還有很多新的元件在不斷的被整合進來,感興趣的朋友可以持續關注下!

微服務之運維形態

在微服務體系結構下,隨著服務數量大量的增長,線上的部署&維護的工作量會變得非常大,而如果還採用原有的運維模式的話,就能難滿足需要了。此時運維團隊需要實施Devops策略,開發自動化運維釋出平臺,打通產品、開發、測試、運維流程,關注研發效能。

另外一方面也需要推進容器化(Docker/Docker Swarm/k8s)策略,這樣才能快速對服務節點進行伸縮,這也是微服務體系下的必然要求。

微服務氾濫問題

這裡還需要注意一個問題,就是實施微服務架構後,如何在工程上管控微服務的問題。盲目的進行微服務的拆分也不是一件很合理的事情,因為這會導致整個服務呼叫鏈路變得深不可測,對問題排查造成難度,也浪費線上資源。

重構問題

在早期單體架構方式向微服務架構的轉變過程中,重構是一個非常好的方式,也是確保服務規範化,業務系統應用架構合理化的很重要的手段。但是,一般來說,在快速發展階段也就意味著團隊規模的迅速增長,短時間內如何讓新的團隊有事可做也是一件非常考驗管理水平的事情,因為如果招了很多人,並且他們之間呈現一種過渡的競爭狀態的話,就會出現讓重構這件事變得有些功利的情況,從而導致重構不徹底、避重就輕,導致表象上看是很高大上的微服務架構,而業務系統實際上比較爛的情況。

另外,重構是在一定階段後作出的重要決策,不僅僅是重新拆分,也是對業務系統的重新塑造,所以一定要考慮好應用軟體的系統結構以及實施它們所需要付出的成本,切不可盲目!

後記

基於SpringCloud的微服務架構體系,通過整合各種開源元件來為整個體系服務支援,但是在負載均衡、熔斷、流量控制的方面需要對服務消費端的業務程序進行侵入。所以很多人會認為這不是一件很好的事情,於是出現了Service Mesh(服務網格)的概念,Service Mesh的基本思路就是通過主機獨立Proxy進行的部署來解耦業務系統程序,這個Proxy除了負責服務發現和負載均衡(不在需要單獨的註冊元件,如Consul)外,還負責動態路由、容錯限流、監控度量和安全日誌等功能。


原文釋出時間為:2018-11-16
本文作者:無敵碼農
本文來自雲棲社群合作伙伴“ 程式設計師小灰”,瞭解相關資訊可以關注“ 程式設計師小灰”。