當前位置:
首頁 > 最新 > 架構之路:從單體走向微服務

架構之路:從單體走向微服務

前言

最近兩年微服務越來越火,很多人都在考慮把自己的系統微服務化。是不是所有的系統都適合微服務架構,以及系統微服務化的過程中需要注意什麼,微服務有哪些現成的框架或技術?本文將為大家一一道來。

首先,我們講一下單體架構。

如上圖所示,單體架構是把所有的功能都放在一起進程里,如服務層、數據訪問層、甚至還有前端代碼。

單體服務的優點就很明顯了。IDE都是為開發單個應用設計的,容易測試——在本地就可以啟動完整的系統,容易部署——直接打包為一個完整的包拷貝到web容器的某個目錄下即可運行。

單體應用已經很好地服務了我們,未來無疑還會繼續發揮重要作用。但是,不管如何模塊化,單體應用最終都會因為團隊壯大、成員變動、應用範圍擴展等出現問題。

比如:

1.

複雜性高

2.

交付效率低

3.

伸縮性差

4.

可靠性差

面對單體架構的這些問題,我們要坐以待斃束手就擒嗎?

顯然不會,微服務的大旗在向我們招手。

微服務的理論基礎之康威定律:

第一定律:

組織溝通方式決定系統設計。

第一定律可以理解為:溝通成本隨著項目或者組織的人員增加呈指數級增長。時間複雜度是O(n^2)。

第二定律:

時間再多一件事情也不可能做的完美,但總有時間做完一件事情。

第二定律的大意是:一口氣吃不成胖子,先搞定能搞定的。需求太複雜了?適當忽略一些細節,先抓主線。需求太多了?放棄一些功能。

第三定律:

線型系統和線型組織架構間有潛在的異質同態特性。

第三定律的大意是:定義好系統的邊界和介面,在一個團隊內全棧,讓團隊自治,原因就是因為如果團隊按照這樣的方式組建,將溝通的成本維持在系統內部,每個子系統就會更加內聚,彼此的依賴耦合能變弱,跨系統的溝通成本也就能降低。

第四定律:

大的系統組織總是比小系統更傾向於分解。

第四定律可以理解為:人是複雜的社會動物,人與人的溝通非常複雜。但是當我們面對複雜系統時,又往往只能通過增加人力來解決。這樣只會越來越糟糕。所以,一個大的組織因為溝通成本/管理問題,總為被拆分成一個個小團隊。

既然要對原有系統進行拆分,我們應該遵循哪些原則呢?

功能單一、前後端分離、Restful通信風格、無狀態服務等,是微服務拆分的四大原則。

拆分後的微服務跟拆分前就有明顯的優勢了。比如微服務相對小,易於理解,開發效率高;一個服務修改後不需要更改其它服務;每個微服務都能橫向與縱向擴展,可以實現按需擴容,比如剛從提到的磁碟擴容、內存擴容等;組織架構上,在小型代碼庫上工作的小團隊更加高效。

微服務架構可以很好的將架構與組織相匹配,避免出現過大的代碼庫;微服務還降低了嘗試新技術的成本,比如有個新的技術出來後,我們可以用在一些不太重要的微服務上練練手,等團隊熟練掌握了該技術,就可以把它用在其它的微服務上。

上圖就明顯的體現出微服務比單體服務在垂直與水平擴充上的優勢了。

左側是單體服務,把所有的功能都放在一個進程里,相對它進行擴容,只能整體擴容。

右側是微服務,它把每個功能點都做成一個微服務,對它進行擴容的時候,可以根據每個微服務的特點進行定向擴容。比如只擴容硬碟或者內存或者CPU等。

Spring Cloud 是一個成熟的微服務框架,它的思維導圖如下:

Spring Cloud全家桶東西太多了,我接下來會重點講一下服務註冊與發現的Eureka,熔斷機制Hystrix,負載均衡Ribbon,以及服務追蹤Sleuth。

一、服務的註冊與發現

微服務為什麼需要服務的註冊與發現呢?

服務一般採用生產者與消費者模式,比如生產者有三台伺服器,我們可以把這三台伺服器的地址配置到配置文件中,這樣消費者就可以通過讀取配置文件獲取生產者的信息,進而與生產者進行通信。

但是這種辦法很麻煩,比如隨著用戶量的增加,需要對生產者伺服器進行擴充,這時候運維必須手動更改配置文件,有時候甚至需要重啟系統才能使這種更改生效。

更麻煩的是,如果某台生產者伺服器宕機了,消費者伺服器是不知道的。這時候只能把該生產者伺服器的信息從配置文件中刪除。所以這種方式效率特別低。分散式系統下,需要更高效的方式來實現上述功能。

服務的註冊與發現步驟:

如上圖,服務的註冊與發現需要有三部分組成:註冊中心、服務提供者、服務消費者。

註冊中心:存儲服務提供者的信息包括IP、port、服務提供者的註冊名,並與微服務保持心跳。如果心跳不能保持,則註銷該實例。

服務提供者:向註冊中心發送服務信息以及自己的健康狀態,保持自身的無狀態化。

服務消費者:定期向註冊中心發送查詢請求,以獲取服務提供者的信息,包括ip埠號與服務名。

獲取信息後服務消費者就可以通過服務調用向服務提供者發起服務請求。如果服務提供者註銷了,服務消費者會獲得來自註冊中心的變更通知(只有zk才有這個功能)。

Spring Cloud Eureka 實現服務的註冊與發現

上圖的Application Service是服務提供者;Application Client是服務消費者;Eureka Server 是註冊中心。

Eureka可以部署在不同的區域中,如上圖,劃分的三個區域,us-east-1c/us-east-1d/us-east-1e不同的地區有不同的Eureka Server集群,它們兩兩互相註冊,並且保持服務註冊表數據的同步(通過Replicate),以達到數據的最終一致性。

每次Eureka Client嵌入到各個應用中,服務提供者藉助Eureka Client來完成服務的註冊(register,服務啟動時發送)、續約(renew,服務運行時發送)、取消(cancel,服務註銷時發送)。續約的方式就是通過持續發送心跳。服務消費者通過獲取服務的註冊信息(Get Refistry)來發起服務的調用,即圖中的Make Remote Call。

所有的Eureka Server共享和同步同一份數據,所有數據都存在內存中,所以無論向誰發起註冊請求,都可以獲得整個服務的註冊信息。

二、負載均衡

負載均衡(Load Balance),在微服務或分散式集群中經常用的一種應用。負載均衡簡單的說就是將用戶的請求平攤到多個服務上,從達到系統的高可用性(High Available)。常見的負載均衡軟體有Nginx,LVS,硬體F5等。Spring Cloud中提供了可以自定義演算法的負載均衡Spring Cloud Ribbon。

Ribbon在工作時分兩步:

第一步先選擇Eureka Server,它優先選擇在同一個Zone且負載較少的server。

第二步再根據用戶制定的策略,再從server渠道服務註冊列表中選擇一個地址。

其中Ribbon提供了三種策略:輪詢、隨機和根據響應時間加權。

Spring Cloud Ribbon實現負載均衡的示例圖

三、故障隔離

服務並不是百分百可靠的,服務可能會出錯或產生延遲,如果應用不能對其依賴的故障進行容錯和隔離,那麼該應用本身就處於被拖垮的風險中。在一個高流量的網站中,某個單一後端發生延遲,可能在數秒內告知所有應用資源(線程/隊列等)被耗盡,造成雪崩效應,嚴重時可以使整個網站癱瘓。

雪崩

1.

服務均可用下,用戶請求狀態

2.

某個服務延遲或不可用時用戶請求狀態

3.

服務延遲且多用戶同時訪問造成系統阻塞

當服務I由於某些原因無法響應時,用戶請求就會卡在服務I上。如果加入超時失敗的事件設置為10秒,那麼在這10秒內,容器的當前線程就會一致堵塞在該調用中。

我們假設容器最大線程數為100,如果此時又有99個請求都要調用服務I,那麼這99個線程都會被阻塞。這樣會因為容器線程耗盡而導致該應用無法響應其它任何請求。因為一個服務掛掉,則整個應用不可用。

Spring Cloud Hystrix 避免雪崩效應

四、 服務鏈路追蹤

一個由微服務構成的應用系統通過服務來劃分問題域,通過REST請求服務API來連接服務來完成完整業務。對於入口的一個調用可能需要有多個後台服務協同完成,鏈路上任何一個調用超時或出錯都可能造成前端請求的失敗。服務的調用鏈也會越來越長,並形成一個樹形的調用鏈。

如上圖所示,用戶發起的請求RequestX到A,A依賴B與C的返回結果,C又依賴D與E的返回結果,然後才能把ReplyX返回給用戶。如果ReplyX返回失敗或者響應延遲很長,怎麼樣才能快速定位問題呢?

Spring Cloud Sleuth來為我們解決上述問題。

要了解Sleuth,我們需要先了解幾個概念:

Trace(鏈路) :一次服務調用追蹤鏈路。

Span(跨度) :追蹤服務調基本結構,表示跨服務的一次調用; 多span形成樹形結構,組合成一次Trace追蹤記錄。

Annotation(標註) :在span中的標註點,記錄整個span時間段內發生的事件。

上圖是數據鏈路的追蹤過程:

用戶通過上圖的「REQUEST」發起一個請求,到「SERVICE 1」接收到請求後生成Root Span。這時候Trace Id與Span Id是一致的(雖然上面 Trace Id=x;Span Id=A,但是它們是相等的)。然後繼續往「SERVICE 2」發送請求,這時候會生成新的Span Id=B,同時Trace Id仍然為x。然後「SERVICE 2」可以自己創建一個任務,比如上面的Span Id=C 。「SERVICE 2」還可以給圖中的「SERVICE 3」/「SERVICE 4」發請求,接收響應,然後把結果返回給「SERVICE 1」,最終把結果返回給用戶。

上圖是上上圖的簡易版,我們只看SpanId的變化。最開始請求初始化的時候,是root Span,此時ParentId為null。然後把請求發送給下行服務,這時候的Span Id就會生成新的Span Id=B,ParentId為A。繼續服務下行轉發,這時候Span Id為C,ParentId 為B……由此可見,ParentId是為了維繫調用鏈路的順序。

下面讓我們看看Spring Cloud Sleuth的前端界面:

在上面標紅圈的地方,輸入TraceId,就可以把一個請求從發送到應答的所有過程以圖形化的形式展示出來。這樣開發再定位問題的時候,就一目了然了。該請求經歷了哪些服務,在每個微服務上的處理時間,以及微服務與微服務之間的網路延遲等信息,都清清楚楚的展示出來。

微服務的部署之藍綠部署

第一步:部署版本1的應用(一開始的狀態)所有外部請求的流量都打到這個版本上。

第二步:部署版本2的應用。版本2的代碼與版本1不同(新功能、Bug修復等)。

第三步:將流量從版本1切換到版本2。

微服務部署方式之灰度部署(金絲雀部署)

金絲雀發布是指通過將部分生產流量引流到新部署的系統(如上圖的金絲雀)來驗證系統是否按照預期運行。如果新版本沒有達到預期,可以迅速恢復到舊版本。如果達到了預期,就可以引導更多的流量到新版本(知道所有的服務都變成金絲雀)。

金絲雀發布與藍綠髮布的不同之處在於,新舊版本共存時間更長,而且經常會調整流量。

微服務的缺點

1.

運維要求高

更多的服務意味著更多的運維投入。在單體架構中,只需要保證一個應用的正常運行;而在微服務中,需要保證幾十甚至幾百個服務的正常運行與協作,這給項目的運維帶來了很大的挑戰。

2.

分散式固有的複雜性

使用微服務構建的是分散式系統。對於一個分散式系統,系統容錯、網路延遲、分散式事務等都給我們帶來了很大的挑戰。

3.

強一致性變為最終一致性

不同的服務經常使用不同的資料庫。基於微服務的應用一般都使用 SQL 和 NoSQL 結合的模式。而這些非關係型數據大多數並不支持 2PC。所以分散式事務不適用於微服務系統。

4.

重複勞動

很多服務可能都會使用到相同的功能,而這個功能並沒有達到分解為一個微服務的程度,這個時候,可能各個服務都會開發這一功能,從而導致代碼重複。

總結

上圖是微服務的生產力與單體服務生產力的對比圖,橫坐標是系統複雜度,縱坐標是系統的生產力。

從上面我們可以總結以下幾點:

1. 對於不複雜的系統,對微服務的額外管理會降低生產力。

2. 隨著系統複雜性增加,單體架構的生產力也在下降。

3.微服務的應用減少了隨著系統複雜的提高造成的生產力的衰減。

4. 團隊的技能比選擇單體架構還是微服務架構更加重要。


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 蘇研IT大學堂 的精彩文章:

TAG:蘇研IT大學堂 |