- 微服務架構

>> 微服務架構基礎

~ 微服務概念

微服務架構是一種架構概念,旨在通過將功能分解到各個離散的服務中,以實現對解決方案的解耦;它的主要作用是將功能分解離散到各個服務中,從而降低系統的耦合性,並添加更靈活的服務支持;

把一個大型的單體應用和服務拆分成數個微服務;

~ 微服務架構與傳統架構的區別

1、系統架構需要遵循3個標準:

  • 提高敏捷性:及時響應業務需求;
  • 提升用戶體驗:減少用戶流失;
  • 降低成本:降低增加產品、客戶、業務方案的成本;

2、傳統式開發(單體式開發):將所有的功能打包在一個war包裏,基本沒有外部依賴(除了容器),部署在一個JavaEE容器(Tomcat、JBoss、Weblogic)裏面,包含MVC的所有邏輯;

優點

  • 開發簡單,集中式管理;
  • 基本不會重複開發;
  • 功能都在本地(整個應用程序部署在一臺服務器),沒有分佈式的管理和調用的消耗;

缺點

  • 效率低:開發都在同一個項目裏改代碼,相互等待,衝突不斷;
  • 維護難:代碼功能耦合在一起;
  • 不靈活:構建時間長,任何小修改都要重構整個項目,耗時間;
  • 穩定性差:一個微小的問題都可能導致整個應用掛掉;
  • 擴展性不夠:無法滿足高併發下的業務需求;

3、微服務架構:有效的拆分應用,將應用拆分成多個微服務,實現敏捷開發和部署;

微服務特徵:

  • 一系列的獨立的服務共同組成系統;
  • 單獨部署;
  • 每個服務爲獨立的業務開發;
  • 分佈式管理;
  • 非常強調隔離性;

>> 微服務實戰 - 怎麼實現微服務

要實際的應用微服務,需要解決一下問題:

  • 客戶端如何訪問這些服務;
  • 每個服務之間如何通信;
  • 如此多的服務,怎麼實現;
  • 服務掛了,怎麼解決;(備份方案,應急處理機制)

在這裏插入圖片描述

1、客戶端如何訪問這些服務

通過API網關的模式來訪問

傳統的單體開發,所有的服務都是在本地的,UI可以直接調用;現在按照功能拆分成獨立的服務,每個服務都跑在獨立的虛擬機上的Java進程裏;UI要怎麼去訪問服務?

一個服務,一個應用,一個容器;每個服務都是一個獨立的計算機,這就表示每個計算機都有一個獨立的IP和端口;

後臺有N個服務,前臺就要去記住管理N個服務;用戶去訪問UI,UI去管理這麼多服務,怎麼管理是個問題;一個服務下線、更新、升級,前臺就要重新部署;這不符合拆分的理念,特別是前臺是移動應用的時候:PC訪問的話,後臺服務變了,把JSP頁面改改,重啓服務,重新部署一下,用戶再次訪問PC的時候,就自動變了;移動應用訪問的話,後臺服務變了,就得下載新的安裝包,麻煩;

一般微服務在系統的內部,通常是無狀態的,也就是說各個微服務之間的互相調用是無狀態的,不知道是誰在調用;用戶登錄信息和權限管理最好有一個統一的地方維護管理(OAuth),這樣就不用在每個服務都登錄一次;當用戶去訪問UI的時候,都應該去訪問OAuth服務器(授權服務器),OAuth再去訪問服務;這樣就由OAuth服務器統一管理後面微服務的授權(用戶的登錄狀態);

無狀態:Http的請求時無狀態的,不會記錄是誰來請求的;web應用爲了實現有狀態的效果,纔有了會話,用session來記錄狀態,而http請求本身是無狀態的;

一般在後臺N個服務和UI之間會有一個API Gateway,由網關實現OAuth的功能,它的作用包括:

  • 提供統一的服務入口,讓服務對前臺透明;
  • 聚合後臺的服務,節省流量,提升性能;
  • 提供安全,過濾,流量控制等API管理功能;

既然微服務內部是無狀態的,我們就需要有一個東西讓它有狀態,這個東西就是授權服務器,這個東西又稱爲SSO(單點登錄);

單點登錄:單點就是一個點(一個地方),在一個地方登錄了,就相當於在所有地方登錄了;

用戶訪問UI,UI訪問API網關,API網關也是一個服務,這個服務去聚合後面的微服務;API網關又相當於後面服務的防火牆,因爲API一個服務,把後面整個隔離了,所有安全的東西都可以在API網關這裏來做;

在這裏插入圖片描述
不使用API網關的時候,UI的PC端需要記錄所有後臺服務的IP地址和端口,移動應用也需要記錄一份IP地址,這時候如果後臺有一個服務的IP改了一下,PC端在後臺改一下代碼配置就可以了,但是移動應用就需要更新一個整個的安裝包,就爲了後臺服務改了一個IP,不現實;
在這裏插入圖片描述
有了API網關,解決了這個問題;UI的PC和移動應用上只需要記錄一個API網關的地址,後臺服務的IP地址由API網關來管理,只要API網關的地址不變,後臺服務的IP隨便怎麼改,怎麼增加,怎麼減少都無所謂,因爲只需要修改API網關管理的服務IP列表即可;這時候安卓端的每次後臺修改就要更新整個安裝包的尷尬問題就沒有了;

用戶訪問某個服務的時候,只需要通過UI去訪問API網關,然後由網關去調用不同的服務;


API網關有很多實現辦法,可以是一個軟硬一體的盒子,可以是一個簡單的MVC框架,可以是一個Node.js的服務端;它最重要的作用是爲UI提供後臺服務的聚合,提供統一的服務出口,解除它們之間的耦合;但是API網關可能成爲單點故障,或性能的瓶頸;

單點故障:網關掛掉了,所有後臺服務都不能訪問了,即使後臺服務都能正常運行;
性能瓶頸:API網關只有一個服務器,所有流量都要經由這個網關服務器,然後由它分發到後臺服務上面去;

2、每個服務之間如何通信

所有的微服務都是獨立的Java進程運行在獨立的虛擬機/容器上,所以服務間的通信就是IPC(Inter Process Communication 進程間通信);

解決服務間通信,有兩種方案:

(1)同步調用:

  • REST(Spring Boot、SpringMVC):就是Http通信;
  • RPC(Thrift,Dubbo):遠程過程調用;使用的時候就和調用本地應用一樣;

對外REST,對內RPC;
對內RPC因爲RPC在內網中調用的速度非常快;而對外REST是因爲有防火牆,防火牆只能接收字符串,REST提供的是json格式數據,這個格式是由字符串組成的;網絡中只有字符串可以穿透防火牆,RPC是遠程過程調用,是穿透不了防火牆的;

同步調用比較簡單,一致性強,但是容易出現調用問題: 同步調用會出現阻塞,出現阻塞就會出現單點故障; 性能體驗也會差一些,特別是調用層次多的時候;

一般REST基於HTTP,更容易實現,服務端的實現技術也更靈活些,各種語言都支持,同時也能跨客戶端,對客戶端沒有特殊的要求,只要支持HTTP請求,就能拿到字符串,拿到字符串就能在自己的程序裏想怎麼樣就怎麼樣;

(2)異步消息調用:

  • kafka
  • Notify
  • MessageQueue

在這裏插入圖片描述

異步消息的方式在分佈式系統中有特別廣泛的應用,它既能降低調用服務之間的耦合,又能成爲調用之間的緩衝,確保消息積壓不會沖垮被調用方,同時能保證調用方的服務體驗,繼續幹自己該乾的活,不至於被後臺性能拖慢;不過需要付出的代價是一致性的減弱,需要接受數據 最終一致性;還有就是後臺服務一般要實現 冪等性,因爲消息送出於性能的考慮一般會有重複(保證消息的被收到且僅收到一次對性能是很大的考驗);最後就是必須引入一個獨立的 Broker;

  • 調用服務之間的耦合:用戶服務與產品服務之間存在調用,若RPC的方式,用戶服務就得依賴產品服務,產品服務在線,用戶服務才能上線;產品服務不在線,用戶服務無法啓動,因爲用戶服務無法遠程調用到產品服務,相當於沒有添加需要的依賴;這就形成了耦合度;將RPC換成Broker能降低耦合;

  • Broker是一個服務器,它能成爲用戶服務和產品服務之間的緩衝,用戶服務調用產品服務會傳遞消息過來,Broker是一個服務器,形成一個緩衝的效果,這樣就不會因爲用戶服務而導致產品服務掛掉;

  • 用戶服務調用產品服務的時候,會有大量的請求過來,這就已經在拖產品服務的速度、內存消耗、CPU等;以此同時用戶也在通過UI調用網關,在單獨的獨立的請求產品服務,這就分成了兩個業務線在調用產品服務:別的用戶在調用產品服務,用戶服務在調用產品服務;這時有Broker服務器緩衝一些後臺服務的調用,能降低一些壓力;

  • 一致性的減弱:比如用戶通過UI調用API網關下了一個訂單,這個時候是用HTTP請求,是同步的,下完訂單馬上就有反饋;這個時候如果是異步,有一個緩衝,下完訂單,可能不會立刻處理,這樣也就不會立刻得到反饋;這就是一致性的減弱;

  • 最終一致性:不是實時反饋,但是最終的結果是正確的就可以了;

  • 冪等性:無論多少次請求,返回的結果都是一樣的;

  • 獨立的Broker:稱之爲消息隊列的中間服務器;

消息隊列:就是一個設計模式 - 生產者消費者模式:生產者只管生產,消費者只管消費;

消息隊列分兩種:

  • 有Broker的:
    Broker是對消息的持久化;
    有Broker就說明有個服務器 ,做兩個服務之間的中間緩衝;
    有Broker是爲了做消息的持久化,

  • 無Broker的:
    無Broker的就是兩個服務之間直連;
    無Broker就是在對消息的完整型要求不高的情況下使用,kafka是代表;
    kafka:全球最快消息隊列;一般拿來做日誌;

在這裏插入圖片描述

比如創建一個訂單,由生產者進行生產,然後傳遞給消費者;生產者這個服務只管生產,而不管消費者服務怎麼處理、什麼時候處理這個訂單,只是一直生產一直髮,這就會導致消費者服務被拖垮;所以生產者和消費者這個兩個服務之間要有一個稱爲Broker的緩衝地帶;

生產者不管消費者什麼時候消費、怎麼消費,消費者可能不能處理那麼大的信息,這就會形成消息積壓;消費者發送的請求都發送都Broker,Broker先存着,然後由Broker一條一條發送給消費者,消費者性能跟不上了,Broker就會等待,等消費者能處理了再發送請求給消費者;

生產者往消費者發消息,若消費者掛掉了,而生產者還在一直髮,若沒有緩衝地帶,這些消息就會消失,影響一致性;中間有一個Broker,對消息做了持久化,當消費者下線以後,再次上線,消息還在Broker中;

若消息很重要,不能丟失,就不可以在兩個服務之間直連,必須要有Broker來做消息的持久化;但有些消息可以丟失,要的是異步、速度,對數據的完整性(一致性)不考慮,比如日誌傳輸,日誌不需要完整,丟幾條也沒有問題;這時候就可以使用kafka;

3、如此多的服務,如何實現

在這裏插入圖片描述

在微服務架構中,一般每個服務都有多個拷貝,來做負載均衡;一個服務隨時可能下線,也可能應對臨時訪問壓力增加新的服務節點;

一個容器一個服務,一個容器是一個節點;一個服務有N個節點的時候,用戶請求的時候,每個節點都有可能去,多個用戶請求這個服務,就把請求的壓力分散開了,這就叫負載均衡;一個服務下線了,還有其他服務節點繼續提供服務,這個效果就稱爲高可用

服務之間如何相互感知?服務如何管理?這就是服務發現的問題了;

服務發現:API網關怎麼知道服務的IP和端口?這些服務下線了怎麼辦?新增了一個服務節點06,網關怎麼知道06節點的存在?
在這裏插入圖片描述

服務發現一般有2種做法,基本都是通過Zookeeper等類似技術做服務註冊信息的分佈式管理;當服務上線時,服務提供者將自己的服務信息註冊到ZK(或類似框架),並通過心跳維持長鏈接(Tcp長鏈接),實時更新鏈接信息;調用者通過ZK尋址,根據可指定算法,找到一個服務,還可以將一個服務信息緩存在本地以提高性能;當服務下線時,ZK會通知給服務客戶端;

Zookeeper是一個框架,主要用來做 服務的註冊與發現;

(1)基於客戶端的服務註冊與發現:

  • 優點是架構簡單,擴展靈活,只對服務註冊器依賴;
  • 缺點是客戶端要維護所有調用服務的地址,有技術難度,一般大公司都有成熟的內部框架支持,比如 Dubbo;

Dubbo是RPC遠程調用框架,Zookeeper是服務註冊與發現框架,兩者結合使用完成微服務的實現;

在這裏插入圖片描述
客戶端服務自己註冊到服務註冊中心(ZK)去,ZK維護這個IP列表,訂單服務要調用產品服務,就去註冊中心查找產品服務的IP、端口、服務名稱;
在這裏插入圖片描述
客戶端服務器啓動的時候,需要把自己的IP、端口、服務名稱告訴 服務註冊與發現 服務器,API網關想要調用什麼服務,就告訴服務註冊中心服務名稱,註冊中心反饋回一個對應的服務的IP與端口,網關就能直接調用這個服務了;

原來是由網關管理後臺服務的IP列表,現在由註冊中心來管理,服務器的信息,由服務自己註冊到服務中心;


(2)基於服務端的服務註冊與發現:
優點是簡單,所有服務對於前臺調用方透明,一般在小公司在雲服務上部署的應用採用的比較多;

在這裏插入圖片描述
由服務調用者去調用負載均衡服務器,負載均衡服務器去調用註冊中心,再去調用對應的服務;比基於客戶端的方式多個一個LB服務器;
在這裏插入圖片描述
UI調用負載均衡服務器,再由LB調用API網關,因爲網關也需要開闢多個節點,做負載均衡,以避免單點故障的出現,從而實現高可用;服務註冊中心也要做負載均衡;

所有的服務都要經過註冊服務註冊中心,有服務註冊中心統一管理IP、端口、服務名稱;

4、服務掛了怎麼辦

分佈式最大的特性就是網絡是不可靠的:ping的時候偶爾會有丟包;

解決辦法:

  • 重試機制:一次沒請求成功,再請求一次; ZK去請求後臺服務的時候,由於網絡原因,沒有連接上服務,那就在超時之後再試一次;

  • 限流:同時有一萬個併發過來請求訪問服務器2,壓力很大,因爲是同步請求,就會阻塞,阻塞就可能掛掉,就會出現單點故障;這時候就可以在客戶端調用ZK那裏進行限流,讓一部分請求停止在客戶端那裏,不會發送到後臺服務器上;

  • 熔斷機制:客戶端的請求全部通過ZK發送到了後臺,流量一上來,就開啓熔斷機制,在真正到達處理請求的服務器之前被阻斷,阻止請求發送到處理服務器;同時返回客戶端服務無法響應的提示;

  • 負載均衡

  • 降級(本地緩存):把服務下線,以保障系統最基本的功能能使用,表面看整個系統依然是高可用的;
    當大量請求發送到後臺的時候,沒有限流也沒有啓動熔斷機制,因爲確實需要提供服務,但是因爲流量過大,計算機承載不了這些流量以後,就要停止部分服務,以保障數據一致性問題;比如:訂單服務,訂單服務後面又要去訪問其他服務,這時如訂單服務承載不了那麼大的壓力了,一旦再往下傳遞請求,就可能出現數據不一致了,這時候若沒有更好的解決方案,那就停掉訂單服務器,讓整個訂單服務下線,不要再產生新的訂單了;但是網站的其他服務還可以繼續使用;

    在這裏插入圖片描述

微服務實現總結:

單體應用拆分成多個獨立的服務,每一個服務是一個應用程序,使用Docker的容器化部署將所有這些服務進行隔離;

服務與服務之間有一個通信問題要解決,有兩種方式:同步請求方式和異步請求方式;同步請求方式有2種方式:REST和RPC方式;異步請求方式只有一種方式:消息隊列方式;

這個時候因爲由一個API Gateway來統一的存儲所有服務的IP與端口,對維護起來就增加了難度,於是就要使用另一個機制:服務註冊與發現機制;由統一的調度中心(API網關)去請求服務註冊與發現機制 ,去獲取所需要的服務的對應的IP和端口;由服務註冊中心來保障下面服務的高可用與一致性;

服務掛了之後可以使用”重試機制“、”限流“、”熔斷機制“、”負載均衡“、”服務降級“等方式以保障整個服務還處於高可用狀態;


>> 單點故障與分佈式鎖

  • 單點故障:通俗講就是 由一個服務阻塞或掛機了,導致後面的服務都不可以使用了,這時候就稱爲單點故障;

解決單點故障:使用分佈式鎖;

Zookeeper:是一個服務註冊與發現的框架,同時也是一個分佈式協調技術; 最厲害的地方是它實現了分佈式鎖的問題;

  • 分佈式鎖:爲了防止分佈式系統中多個進程之間相互干擾,就需要一種分佈式協調技術,來對這些進程進行協調,而分佈式協調的核心就是來實現分佈式鎖;ZK就是這樣的一個實現了分佈式鎖的分佈式協調技術;

  • 分佈式系統中的單點故障:
    在分佈式鎖服務中,有一種典型的應用場景,就是通過對集羣進行master選舉,來解決分佈式系統中的單點故障問題;通常分佈式系統採用主從模式,就是一個主機控制多個處理節點,主節點負責分發任務,從節點負責處理任務,當主節點發生故障時,任務沒人分發,那麼整個系統就都掛掉了;這種故障就叫做單點故障;

在這裏插入圖片描述

~ 單點故障傳統解決方案:

在這裏插入圖片描述
採用一個備用節點,這個備用節點定期給主節點發送ping包,主節點收到ping包以後向備用節點發送回復Ack,備用節點收到回覆的時候,就會認爲當前主節點還活着,讓它繼續提供服務;

當主節點掛了,這個時候備用節點就收不到回覆了,然後它就認爲主節點掛了,就接替它成爲主節點;

但是這種方式存在一個隱患,就是網絡問題;主節點沒有掛掉,但是由於網絡震盪問題,導致備用節點向主節點發送ping包之後,主節點沒有收到包,或是收到了,但是在返回Ack字節碼的時候,網絡突然丟了一個包,將主節點回應給備用節點的Ack包丟了,這就一下,備用節點沒收到回覆,就認爲主節點掛了,然後備用節點就將它的Master實例啓動起來,這樣分佈式系統中就出現了兩個主節點 - 雙Master(雙主問題);

出現Master以後,從節點就會將它所做的事一部分彙報給了主節點,一部分彙報給了從節點,這樣服務就亂套了:當發送插入記錄的請求的時候,2個主節點都會收到這個請求,機會調用2此從節點處理這個請求,這樣就會插入兩次數據;

爲了防止雙主問題,就要使用ZK加入分佈式鎖的概念,鎖住主節點的資源;

~ Zookeeper 解決方案

(1)Master啓動:
在引入ZK以後,啓動兩個主節點A和B,主節點-A主節點-B啓動以後,都向ZK去註冊一個節點,註冊後就會有一個編號;假設主節點-A 鎖註冊的節點是master-00001主節點-B 鎖註冊的節點是master-00002,註冊以後進行選舉,編號最小的節點將會在選舉中獲得鎖稱爲主節點,這裏主節點-A獲勝;然後主節點-B將會被阻塞成爲一個備用節點;通過這種方式就完成了對兩個Master進程的調度;

在我們通過UI發送請求的時候,回去請求ZK,由ZK去請求需要的主節點進行消息的分發;因爲是分佈式系統,會有多個主節點的實例,誰來當主節點,由ZK自己來選舉;
在這裏插入圖片描述

(2)Master故障:
在這裏插入圖片描述
如果”主節點-A“掛了,這時候它在ZK中註冊的節點就會自動刪除,ZK會自動感知節點的變化,然後再次發出選舉,這時候主節點-B獲勝,替代A稱爲新的主節點;

ZK維護主節點們,也會向主節點們發送ping包,當ZK向主節點-A發送Ping包,沒有回覆之後,就會通知主節點-B重新選舉;如果主節點-A是因爲網絡震盪無法回覆ZK的話,ZK就會將它從 服務註冊與發現 列表中直接刪除;當A重新恢復上線以後,就會重新註冊到ZK中,這時候就會變成master-00003,而不是原來的00001;這時候ZK會感知節點的變化再次發動選舉,這時候主節點-B會再次獲勝繼續擔任主節點,節點A就會阻塞稱爲備用節點,等待下次選舉;這樣就不會出現之前的雙主e問題了;

~ 總結 - 面試:什麼是ZK

什麼是Zookeeper:服務註冊與發現中心;
ZK解決了什麼問題:分佈式鎖的問題;

先解釋單點故障:分佈式系統才採用主從模式,就是一個主服務器調用兩個從服務器的資源;當主服務器掛掉了,從服務器還在繼續提供服務,但是由於主服務器掛掉了,兩個從服務器訪問不到了,這就叫做單點故障;

傳統的解決單點故障的方式是有兩個節點:主節點和備用節點,備用節點會一直ping主節點,若沒有收到主節點的回覆,就認爲主節點掛掉了,備用節點就上線代替原來的主節點稱爲新的主節點提供服務;但是若之前的主節點沒有掛掉,而是由於網絡震盪問題沒有及時回覆備用節點,使得備用節點誤以爲主節點掛掉了,這就出現了雙主問題;一旦出現雙主問題,所有的請求都會出現多次、重複,數據就會出現問題;

爲了解決這個問題,就引入了分佈式鎖的問題,而ZK這個框架就是解決分佈式鎖的問題的;

ZK要求 所有服務啓動的時候都要想ZK進行註冊,這時候ZK就維護了一個節點列表;ZK會發發動選舉,決定誰會成爲主節點,選舉出一個主節點,剩下的節點就會阻塞稱爲備用節點;ZK會一直向主節點發送ping包,如沒有收到主節點的回覆,就會從它維護的節點列表中刪掉,而不是停掉,然後通知所有備用節點進行選舉,選出新的主節點繼續提供服務;若出現剛纔相同的網絡震盪的導致主節點無法回覆ZK的時候,主節點重新上線,就會重新到ZK去註冊,這個時候就會註冊成新的節點,變成備用節點,等待下次選舉;


~ 爲什麼要使用分佈式鎖

在這裏插入圖片描述

三個併發同時去訪問負載均衡服務器,負載均衡服務器會去調度後臺服務;後臺有3個服務,3個服務提供的是3個相同的應用程序;

部署了3套應用程序,就變成了有3個獨立的JVM進程運行在3臺不同的服務器上;但是這裏面我們可能要調用相同的變量A(因爲系統是一樣的,所以變量是一致的);

3個變量A在3個JVM內存中,變量A同時都會在JVM分配內存,3個請求發送過來同時對這個變量進行操作,顯然結果是不對的;

不是同時發送過來,三個請求分別操作3個不同JVM內存區域的數據,變量A之間不存在共享,也不具可見性,處理的結果也是不對的;

這個變量A主要體現是在一個類中的一個成員變量,是一個有狀態的對象;

如果業務中確實存在這個場景,就需要一種方法解決這個問題;

爲了保證一個方法或屬性在高併發情況下的同一時間只能被一個線程執行,在單機環境中,Java提供了很多併發處理相關的API:同步鎖;

由於分佈式系統多線程、多進程,並且分佈在不同機器上,這使得原單機部署情況下的併發控制鎖策略生效;

這就需要一種跨JVM的互斥機制來控制共享資源的訪問;分佈式鎖就是解決這個問題的;

Zookeeper就能做到跨進程協作;

分佈式鎖應該具備的條件:

  • 在分佈式系統環境下,一個方法在同一時間只能被一個機器的一個線程執行;
  • 高可用的獲取鎖和釋放鎖:獲取鎖、釋放鎖的服務器(ZK)本身也得高可用;
  • 高性能的獲取鎖和釋放鎖:獲取/釋放鎖的操作要快;
  • 具備可重入性:可理解爲重新進入(重新觸發這個事情的時候),由多於一個任務併發使用,而不必擔心數據錯誤;
  • 具備鎖失效機制,防止死鎖;
  • 具備非阻塞鎖特性,就是沒有獲取到鎖將直接返回獲取鎖失敗;類似熔斷機制,拿不到鎖直接返回結果,不能讓程序阻塞在這個地方;

在這裏插入圖片描述
在這裏插入圖片描述


>> 微服務架構設計模式

https://www.funtl.com/zh/micro-service-about/再談微服務-微服務架構設計模式.html#微服務架構需要考慮的問題

文章整理自此博客視頻教程

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章