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網關轉發請求給對應的服務。