首頁 > 軟體

一. SpringCloud簡介與微服務架構

2021-01-20 08:30:09

1. 微服務架構

1.1 微服務架構理解

微服務架構(Microservice Architecture)是一種架構概念,旨在通過將功能分解到各個離散的服務中以實現對解決方案的解耦。你可以將其看作是在架構層次而非獲取服務的類上應用很多SOLID原則。微服務架構是個很有趣的概念,它的主要作用是將功能分解到離散的各個服務當中,從而降低系統的耦合性,並提供更加靈活的服務支援。

  • 概念:把一個大型的單個應用程式和服務拆分為數個甚至數十個的支援微服務,它可延伸單個元件而不是整個的應用程式堆疊,從而滿足服務等級協定。

  • 定義:圍繞業務領域元件來建立應用,這些應用可獨立地進行開發、管理和迭代。在分散的元件中使用雲架構和平臺式部署、管理和服務功能,使產品交付變得更加簡單。

  • 本質:用一些功能比較明確、業務比較精練的服務去解決更大、更實際的問題。

1.2 傳統開發模式和微服務的區別

傳統的web開發方式

通過對比比較容易理解什麼是Microservice Architecture。和Microservice相對應的,這種方式一般被稱為Monolithic(單體式開發)。所有的功能打包在一個 WAR包裡,基本沒有外部依賴(除了容器),部署在一個JEE容器(Tomcat,JBoss,WebLogic)裡,包含了 DO/DAO,Service,UI等所有邏輯。

優點:

  • 開發簡單,集中式管理

  • 基本不會重複開發

  • 功能都在本地,沒有分散式的管理和呼叫消耗

缺點:

  • 效率低:開發都在同一個專案改程式碼,相互等待,衝突不斷

  • 維護難:程式碼功功能耦合在一起,新人不知道何從下手

  • 不靈活:構建時間長,任何小修改都要重構整個專案,耗時

  • 穩定性差:一個微小的問題,都可能導致整個應用掛掉

  • 擴充套件性不夠:無法滿足高並行下的業務需求

常見的系統架構遵循的三個標準和業務驅動力:

  • 提高敏捷性:及時響應業務需求,促進企業發展

  • 提升使用者體驗:提升使用者體驗,減少使用者流失

  • 降低成本:降低增加產品,客戶或業務方案的成本

基於微服務架構的設計

目的:有效的拆分應用,實現敏捷開發和部署

關於微服務的一個形象表達

  • X軸:執行多個負載均衡器之後的執行範例
  • Y軸:將應用進一步分解為微服務(分庫)
  • Z軸:巨量資料量時,將服務分割區(分表)
1.3 微服務的具體特徵

官方定義

  • 一些列的獨立的服務共同組成系統
  • 單獨部署,跑在自己的程序中
  • 每個服務為獨立的業務開發
  • 分散式管理
  • 非常強調隔離性

大概的標準

  • 分散式服務組成的系統
  • 按照業務,而不是技術來劃分組織
  • 做有生命的產品而不是專案
  • 強服務個體和弱通訊( Smart endpoints and dumb pipes )
  • 自動化運維( DevOps )
  • 高度容錯性
  • 快速演化和迭代
1.4 怎麼具體實踐微服務

使用者端如何存取這些服務 - API Gateway

原來的單體開發,所有的服務都是原生的,UI可以直接呼叫,現在按功能拆分成獨立的服務,跑在獨立的一般都在獨立的虛擬機器器上的 Java程序了。使用者端UI如何存取他的?後臺有N個服務,前臺就需要記住管理N個服務,一個服務下線/更新/升級,前臺就要重新部署,這明顯不符合我們拆分的理念,特別當前臺是移動應用的時候,通常業務變化的節奏更快。另外,N個小服務的呼叫也是一個不小的網路開銷。還有一般微服務在系統內部,通常是無狀態的,使用者登入資訊和許可權管理最好有一個統一的地方維護管理(OAuth)。

所以一般在後臺N個服務和UI之間一般會一個代理或者叫 API Gateway,他的作用包括:

  • 提供統一服務入口,讓微服務對前臺透明

  • 聚合後臺的服務,節省流量,提升效能

  • 提供安全,過濾,流控等API管理功能

其實這個API Gateway可以有很多廣義的實現辦法,可以是一個軟硬一體的盒子,也可以是一個簡單的MVC框架,甚至是一個Node.js的伺服器端。他們最重要的作 用是為前臺(通常是移動應用)提供後臺服務的聚合,提供一個統一的服務出口,解除他們之間的耦合,不過API Gateway也有可能成為單點故障點或者效能的瓶頸。用過Taobao Open Platform(淘寶開放平臺)的就能很容易的體會,TAO就是這個API Gateway。

每個服務之間如何通訊 - IPC

所有的微服務都是獨立的Java程序跑在獨立的虛擬機器器上,所以服務間的通訊就是IPC(inter process communication),已經有很多成熟的方案。現在基本最通用的有兩種方式:

同步呼叫:① REST(JAX-RS,Spring Boot)② RPC(Thrift, Dubbo)

非同步訊息呼叫:(Kafka, Notify, MetaQ)

同步和非同步的區別:

一般同步呼叫比較簡單,一致性強,但是容易出呼叫問題,效能體驗上也會差些,特別是呼叫層次多的時候。RESTful和RPC的比較也是一個很有意思的話題。一般REST基於HTTP,更容易實現,更容易被接受,伺服器端實現技術也更靈活些,各個語言都能支援,同時能跨使用者端,對使用者端沒有特殊的要求,只要封裝了HTTP的SDK就能呼叫,所以相對使用的廣一些。RPC也有自己的優點,傳輸協定更高效,安全更可控,特別在一個公司內部,如果有統一個的開發規範和統一的服務架構時,他的開發效率優勢更明顯些。就看各自的技術積累實際條件自己的選擇了。

而非同步訊息的方式在分散式系統中有特別廣泛的應用,他既能減低呼叫服務之間的耦合,又能成為呼叫之間的緩衝,確保訊息積壓不會沖垮被呼叫方,同時能保證呼叫方的服務體驗,繼續幹自己該乾的活,不至於被後臺效能拖慢。不過需要付出的代價是一致性的減弱,需要接受資料最終一致性;還有就是後臺服務一般要 實現冪等性,因為訊息傳送出於效能的考慮一般會有重複(保證訊息的被收到且僅收到一次對效能是很大的考驗);最後就是必須引入一個獨立的broker,如果公司內部沒有技術積累,對broker分散式管理也是一個很大的挑戰。

如此多的服務如何實現?- 服務發現

在微服務架構中,一般每一個服務都是有多個拷貝來做負載均衡。一個服務隨時可能下線也可能應對臨時存取壓力增加新的服務節點。服務之間如何相互感知?服務如何管理?這就是服務發現的問題了。一般有兩類做法,也各有優缺點。基本都是通過zookeeper等類似技術做服務註冊資訊的分散式管理。當服務上線時,服務提供者將自己的服務資訊註冊到ZK(或類似框架),並通過心跳維持長連結,實時更新連結資訊。服務呼叫者通過ZK定址,根據可客製化演演算法找到一個服務,還可以將服務資訊快取在本地以提高效能。當服務下線時,ZK會發通知給服務使用者端。

使用者端做服務發現:優點是架構簡單,擴充套件靈活,只對服務註冊器依賴。缺點是使用者端要維護所有呼叫服務的地址有技術難度,一般大公司都有成熟的內部框架支援,比如Dubbo。

伺服器端做服務發現:優點是簡單,所有服務對於前臺呼叫方透明,一般在小公司在雲服務上部署的應用採用的比較多。

服務掛了如何解決 - 熔斷機制,限流,負載均衡...

前面提到,Monolithic方式開發一個很大的風險是把所有雞蛋放在一個籃子裡,一榮俱榮一損俱損。而分散式最大的特性就是網路是不可靠的。通過微服務拆分能降低這個風險,不過如果沒有特別的保障結局肯定是噩夢。所以當我們的系統是由一系列的服務呼叫鏈組成的時候,我們必須確保任一環節出問題都不至於影響整體鏈路。

相應的手段有很多:這些方法基本都很明確通用,比如Netflix的Hystrix:https://github.com/Netflix/Hystrix

  • 重試機制

  • 限流

  • 熔斷機制

  • 負載均衡

  • 降級(本地快取)

1.5 微服務的優缺點

微服務的優點:

關鍵點:複雜度可控,獨立按需擴充套件,技術選型靈活,容錯,可用性高

  • 它解決了複雜性的問題。它會將一種怪異的整體應用程式分解成一組服務。雖然功能總量 不變,但應用程式已分解為可管理的塊或服務。每個服務都以RPC或訊息驅動的API的形式定義了一個明確的邊界;Microservice架構模式實現了一個模組化水平。

  • 這種架構使每個服務都能夠由專注於該服務的團隊獨立開發。開發人員可以自由選擇任何有用的技術,只要該服務符合API合同。當然大多陣列織都希望避免完全無政府狀態並限制技術選擇。然而這種自由意味著開發人員不再有義務使用在新專案開始時存在的可能過時的技術。在編寫新服務時,他們可以選擇使用當前的技術。此外由於服務相對較小,使用當前技術重寫舊服務變得可行。

  • Microservice架構模式使每個微服務都能獨立部署。開發人員不需要協調部署本地服務的變更。這些變化可以在測試後儘快部署。例如UI團隊可以執行A | B測試,並快速迭代UI更改。Microservice架構模式使連續部署成為可能。

  • Microservice架構模式使每個服務都可以獨立調整。您可以僅部署滿足其容量和可用性限制的每個服務的範例數。此外您可以使用最符合服務資源要求的硬體。

微服務的缺點

關鍵點(挑戰):多服務運維難度,系統部署依賴,服務間通訊成本,資料一致性,系統整合測試,重複工作,效能監控等

  • 一個缺點是名稱本身。術語microservice過度強調服務規模。但重要的是要記住,這是一種手段而不是主要目標。微服務的目標是充分分解應用程式以便於敏捷應用程式開發和部署。

  • 微伺服器的另一個主要缺點是分散式系統而產生的複雜性。開發人員需要選擇和實現基於訊息傳遞或RPC的程序間通訊機制。此外他們還必須編寫程式碼來處理部分故障,因為請求的目的地可能很慢或不可用。

  • 微伺服器的另一個挑戰是分割區資料庫架構。更新多個業務實體的業務交易是相當普遍的。但是在基於微伺服器的應用程式中,您需要更新不同服務所擁有的多個資料庫。使用分散式事務通常不是一個選擇,而不僅僅是因為CAP定理。許多今天高度可延伸的NoSQL資料庫都不支援它們。你最終不得不使用最終的一致性方法,這對開發人員來說更具挑戰性。

  • 測試微服務應用程式也更復雜。服務類似的測試類將需要啟動該服務及其所依賴的任何服務(或至少為這些服務設定存根)。再次,重要的是不要低估這樣做的複雜性。

  • Microservice架構模式的另一個主要挑戰是實現跨越多個服務的更改。例如我們假設您正在實施一個需要更改服務A,B和C的故事,其中A取決於B和B取決於C,在單片應用程式中您可以簡單地更改相應的模組,整合更改並一次性部署。相比之下,在Microservice架構模式中,您需要仔細規劃和協調對每個服務的更改。例如,您需要更新服務C,然後更新服務B,然後再維修A,幸運的是大多數更改通常僅影響一個服務,而需要協調的多服務變更相對較少。

  • 部署基於微服務的應用程式也更復雜。單一應用程式簡單地部署在傳統負載平衡器後面的一組相同的伺服器上。每個應用程式範例都設定有基礎架構服務(如資料庫和訊息代理)的位置(主機和埠)。相比之下,微服務應用通常由大量服務組成。例如每個服務將有多個執行時範例。更多的移動部件需要進行設定,部署,擴充套件和監控。此外您還需要實現服務發現機制,使服務能夠發現需要與之通訊的任何其他服務的位置(主機和埠)。傳統的基於故障單和手動操作的方法無法擴充套件到這種複雜程度。因此,成功部署微服務應用程式需要開發人員更好地控制部署方法,並實現高水平的自動化。

2. SpringCloud引入

SpringCloud並不是一個框架而是一個微服務整體架構,或者說SpringCloud是一個生態圈,裡面包含了很多的服務,每一個服務獨立存在,相互之間互不干擾,可以直接執行。

其實SpringCloud就是一個完整的微服務架構,提供了所有功能,整個開發專案中所需要的架構功能微服務都有,也就是說整個springcloud就是一個完整的專案,這個架構已經搭建完畢了,用到了直接獲取即可,只需要往架構中注入自己的業務程式碼就可以。

它具有微服務的以下幾大優勢:

  • 複雜度可控

    • 在將應用分解的同時,規避了原本複雜度無止境的積累。每一個微服務專注於單一功能,並通過定義良好的介面清晰表述服務邊界。由於體積小、複雜度低,每個微服務可由一個小規模開發團隊完全掌控,易於保持高可維護性和開發效率
  • 獨立部署

    • 具備獨立的執行程序,所以每個微服務也可以獨立部署。
    • 當某個服務發生變更時無需編譯、部署整個應用。
    • 由微服務組成的應用相當於具備一系列可並行的釋出流程,使得釋出更加高效,同時降低對生產環境所造成的風險,最終縮短應用交付週期
  • 技術選型靈活

    • 微服務架構下,技術選型是去中心化的。每個團隊可以根據自身服務的需求和行業發展的現狀,自由選擇最適合的技術棧。
    • 由於每個微服務相對簡單,故需要對技術棧進行升級時所面臨的風險就較低,甚至完全重構一個微服務也是可行的
  • 容錯能力

    • 在微服務架構下,故障會被隔離在單個服務中。若設計良好,其他服務可通過 重試、平穩退化等機制實現應用層面的容錯
  • 擴充套件性

    • 每個服務可以根據實際需求獨立進行擴充套件

3. SpringCloud五大元件淺析

3.1 舉例業務場景

如上圖,假設現在開發一個電商網站,要實現支付訂單功能流程如下

  • 建立一個訂單後,如果使用者立刻支付了這個訂單,我們需要將這個訂單狀態更新為已支付
  • 扣減相對應的商品庫存
  • 通知倉儲中心進行發貨
  • 給使用者這次購物新增加相對應的積分

針對上述流程我們需要有訂單服務、庫存服務、倉儲服務、積分服務,整個流程的大體思路如下:

  • 使用者針對一個訂單完成支付後,就會去找訂單服務,更新訂單狀態

  • 訂單服務呼叫庫存服務,完成相應的功能

  • 訂單服務呼叫倉儲服務,完成相應的功能

  • 訂單服務呼叫積分服務,完成相應的功能

3.2 服務發現 - Netflix Eureka(類似zookeeper)

首先考慮一個問題,訂單服務要呼叫庫存服務、倉儲服務、積分服務,如何呼叫呢?

訂單服務根本不知道上述服務在哪臺伺服器上,所以沒法呼叫,而Eureka的作用就是來告訴訂單服務它想呼叫的服務在哪臺伺服器上,Eureka有使用者端和伺服器端,每一個服務上面都有Eureka使用者端,可以把本服務的相關資訊註冊到Eureka伺服器端上,那麼我們的訂單服務就可以就可以找到庫存服務、倉儲服務、積分服務了

我們上述的業務使用Eureka後如下圖:

總結:

  • Eurake使用者端:負責將這個服務的資訊註冊到Eureka伺服器端中
  • Eureka伺服器端:相當於一個註冊中心,裡面有登入檔,登入檔中儲存了各個服務所在的機器和埠號,可通過Eureka伺服器端找到各個服務
3.3 WebService使用者端Feign(類似Dubbo)

通過上面的Eureka,現在訂單服務確實知道庫存服務、積分服務、倉儲服務在哪了,但是我們如何去呼叫這些服務呢,如果我們自己去寫很多程式碼呼叫那就太麻煩了,而SpringCloud已經為我們準備好了一個核心元件:Feign,接下來看如何通過Feign讓訂單服務呼叫庫存服務,注意Feign也是用在消費者端的。

訂單服務與倉庫服務Service

沒有底層的建立連線、構造請求、解析響應的程式碼,直接就是用註解定義一個 FeignClient介面,然後呼叫那個介面就可以了。人家Feign Client會在底層根據你的註解,跟你指定的服務建立連線、構造請求、發起靕求、獲取響應、解析響應,等等。這一系列髒活累活,人家Feign全給你幹了。

問題來了,Feign是如何做到的呢?其實Feign的一個機制就是使用了動態代理:

  • 首先,如果你對某個介面定義了@FeignClient註解,Feign就會針對這個介面建立一個動態代理
  • 接著你要是呼叫那個介面,本質就是會呼叫 Feign建立的動態代理,這是核心中的核心
  • Feign的動態代理會根據你在介面上的@RequestMapping等註解,來動態構造出你要請求的服務的地址
  • 最後針對這個地址,發起請求、解析響應
3.4 客服端負載均衡 - Netflix Ribbon

上面可以通過Eureka可以找到服務,然後通過Feign去呼叫服務,但是如果有多臺機器上面都部署了庫存服務,我應該使用Feign去呼叫哪一臺上面的服務呢,這個時候就需要Ribbon了,它在服務消費者端設定和使用,作用就是負載均衡,預設使用的負載均衡演演算法是輪詢演演算法,Ribbon會從Eureka伺服器端中獲取到對應的服務登入檔,然後就知道相應服務的位置,然後Ribbon根據設計的負載均衡演演算法去選擇一臺機器,Feigin就會針對這些機器構造並行送請求。

3.5 斷路器 - Netflix Hystrix

在微服務架構裡一個系統會有多個服務,以本文的業務場景為例:訂單服務在一個業務流程裡需要呼叫三個服務,現在假設訂單服務自己最多隻有100個執行緒可以處理請求,如果積分服務出錯,每次訂單服務呼叫積分服務的時候,都會卡住幾秒鐘,然後丟擲—個超時異常。

分析下這樣會導致什麼問題呢?如果系統在高並行的情況下,大量請求湧過來的時候,訂單服務的100個執行緒會卡在積分服務這塊,導致訂單服務沒有一個多餘的執行緒可以處理請求,這種問題就是微服務架構中恐怖的伺服器雪崩問題,這麼多的服務互相呼叫要是不做任何保護的話,某一個服務掛掉會引起連鎖反應導致別的服務掛掉。

服務也不應該掛掉啊,我們只要讓儲存服務和倉儲服務正常工作就可以了,至於積分服務我們後期可以手動給使用者加上積分,這個時候就輪到Hystrix了,Hystrix是隔離、熔斷以及降級的一個框架,說白了就是Hystrix會搞很多小執行緒池然後讓這些小執行緒池去請求服務,返回結果,Hystrix相當於是個中間過濾區,如果我們的積分服務掛了,那我們請求積分服務直接就返回了,不需要等待超時時間結束丟擲異常,這就是所謂的熔斷,但是也不能啥都不幹就返回啊,不然我們之後手動加積分咋整啊,那我們每次呼叫積分服務就在資料庫裡記錄一條訊息,這就是所謂的降級,Hystrix隔離、熔斷和降級的全流程如下:

3.6 服務閘道器 - Netflix Zuul (類似於伺服器端的Nginx)

該元件是負責網路路由的,假設你後臺部署了幾百個服務,現在有個前端兄弟,人家請求是直接從瀏覽器那兒發過來的。打個比方:人家要請求一下庫存服務,你難道還讓人家記著這服務的名字叫做inventory-service,並且部署在5臺機器上,就算人家肯記住這一個,那你後臺可有幾百個服務的名稱和地址呢?難不成人家請求一個,就得記住一個?

上面這種情況,壓根兒是不現實的。所以一般微服務架構中都必然會設計一個閘道器在裡面,像android、ios、pc前端、微信小程式、H5等等,不用去關心後端有幾百個服務,就知道有一個閘道器,所有請求都往閘道器走,閘道器會根據請求中的一些特徵,將請求轉發給後端的各個服務。

3.7 總結
  • Eureka:服務啟動的時候,服務上的Eureka使用者端會把自身註冊到Eureka伺服器端,並且可以通過Eureka伺服器端知道其他註冊的服務。
  • Ribbon:服務間發起請求的時候,服務消費者方基於Ribbon服務做到負載均衡,從服務提供者儲存的多臺機器中選擇一臺,如果一個服務只在一臺機器上面,那就用不到Ribbon選擇機器了,如果有多臺機器,那就需要使用Ribbon選擇之後再去使用。
  • Feign:Feign使用的時候會整合Ribbon,Ribbon去Eureka伺服器端中找到服務提供者的所在的伺服器資訊,然後根據隨機策略選擇一個,拼接Url地址後發起請求。
  • Hystrix:發起的請求是通過Hystrix的執行緒池去存取服務,不同的服務通過不同的執行緒池,實現了不同的服務排程隔離,如果服務出現故障,通過服務熔斷,避免服務雪崩的問題 ,並且通過服務降級,保證可以手動實現服務正常功能。
  • Zuul:如果前端呼叫後臺系統,統一走zull閘道器進入,通過zull閘道器轉發請求給對應的服務。

IT145.com E-mail:sddin#qq.com