- 微服务架构
>> 微服務架構基礎
~ 微服務概念
微服務架構是一種架構概念,旨在通過將功能分解到各個離散的服務中,以實現對解決方案的解耦;它的主要作用是將功能分解離散到各個服務中,從而降低系統的耦合性,并添加更靈活的服務支持;
把一個大型的單體應用和服務拆分成數個微服務;
~ 微服務架構與傳統架構的區別
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#微服務架構需要考慮的問題
文章整理自此博客視頻教程
總結
- 上一篇: 设置linux的自动关机和windows
- 下一篇: Group Normalization(