讀書筆記-微服務(wù)架構(gòu)中的進(jìn)程間通信-02

1 進(jìn)程間通信

和單體應(yīng)用不同的是,單體應(yīng)用部署在同一臺機(jī)器,屬于同一個進(jìn)程,各個模塊之間通過函數(shù)互相調(diào)用;但是微服務(wù)架構(gòu)中的服務(wù)實(shí)例通常是部署在多個機(jī)器上的,所以必須使用進(jìn)程間通信。
有很多進(jìn)程間通信技術(shù)可供參考。服務(wù)可以使用基于同步/異步的通信機(jī)制,同時服務(wù)通信的消息格式也是不相同的,服務(wù)可以使用具有可讀性的格式(JSON/XML),也可以使用更高效的二進(jìn)制格式。

1.1 交互方式

有多種客戶端和服務(wù)的交互方式,可以分兩個維度。
1、一對一和一對多

  • 一對一:每個客戶端請求由一個服務(wù)實(shí)例來處理
  • 一對多:每個客戶端請求由多個服務(wù)實(shí)例來處理
    2、同步和異步
  • 同步:客戶端調(diào)用服務(wù)端,必須等待服務(wù)端響應(yīng)
  • 異步:客戶端的請求不會阻塞進(jìn)程
    各種交互方式可以用兩個維度來表示:


    image.png

一對一:

  • 請求/響應(yīng):客戶端向服務(wù)端發(fā)起請求,并等待響應(yīng),等待的過程可能造成線程阻塞,并且會造成服務(wù)的緊耦合
  • 異步請求/響應(yīng):客戶端發(fā)送請求到服務(wù)器,服務(wù)器異步響應(yīng)請求,客戶端不會阻塞
  • 單向通知:客戶端的請求發(fā)送到服務(wù)端,不期望服務(wù)端作出任何響應(yīng)

一對多:
發(fā)布/訂閱:客戶端發(fā)出消息,發(fā)送給多個訂閱者
發(fā)布/異步響應(yīng):客戶端發(fā)布消息,并等待訂閱者的響應(yīng)。

1.2 API演化

API作為客戶端和服務(wù)端的通信橋梁,不可避免的會隨著功能的增加而發(fā)生變化。在單體應(yīng)用中,變更API是相對簡單并且不容易出錯的事,因為更改之后編譯器會對不兼容的調(diào)用提示編譯錯誤;但是基于微服務(wù)的API更改起來就比較麻煩了,你并不能保證客戶端和你的api保持一致,一般有兩種處理方法

  • 努力的進(jìn)行向后兼容的更改,對API附加更強(qiáng)能功能,包括添加可選屬性、向響應(yīng)添加屬性等,如果只是進(jìn)行這些類型的更改,那么老版本的客戶端依然可以繼續(xù)使用更新后的服務(wù),但是客戶端和服務(wù)端必須同時支持健壯性原則的請求響應(yīng)內(nèi)容
  • 另一種是無法對已有的api進(jìn)行更改,因此服務(wù)必須在一段時間內(nèi)同時支持新舊版本的API,如getV1和getV2,舊版本的API可以在下一個版本提出需求去掉

1.3 消息格式

如果使用了HTTP歇息,那么需要選擇具體的消息格式(JSON/XML);有些進(jìn)程間的通信機(jī)制已經(jīng)指定了消息格式(gRPC/Thrift)。我們不應(yīng)該使用類似Java序列化這類跟變成虛言強(qiáng)相關(guān)的消息格式,因為今后可能會擴(kuò)展到其他的語言。

2 使用斷路器保護(hù)系統(tǒng)

分布式系統(tǒng)中,當(dāng)服務(wù)試圖向另一個服務(wù)發(fā)送請求,隨時都會面臨著局部故障的風(fēng)險,因為二者是獨(dú)立的進(jìn)程,如下圖所示:


image.png

此時服務(wù)B和服務(wù)A將會無限期的阻塞,所以要通過合理的設(shè)計服務(wù)來防止整個應(yīng)用程序的故障傳導(dǎo)

  • 網(wǎng)絡(luò)超時:請求鏈接一定不要做成無限阻塞,而是要設(shè)定超時時間,指定時間內(nèi)沒有完成調(diào)用變返回超時錯誤
  • 限流:服務(wù)接收請求是有能力上限的,當(dāng)請求到達(dá)上線,讓請求立即失敗,可以保護(hù)服務(wù)端
  • 斷路器模式:服務(wù)端監(jiān)控請求的成功和失敗的數(shù)量,如果失敗率超過一定比例就啟動斷路器,讓后續(xù)的調(diào)用都失效。在經(jīng)過一定的時間客戶端應(yīng)該繼續(xù)嘗試,如果調(diào)用成功就解除斷路器。
  • 彈性擴(kuò)容/縮容:當(dāng)訪問量達(dá)到N時,增加一臺機(jī)器,當(dāng)訪問量減少到M時,減少一臺機(jī)器,可以提高系統(tǒng)處理請求的能力

3 服務(wù)發(fā)現(xiàn)機(jī)制

服務(wù)發(fā)現(xiàn)和服務(wù)注冊是屬于服務(wù)治理的概念(治理肯定是有問題了才需要的),你的客戶端調(diào)用服務(wù)端,必須知道服務(wù)實(shí)例具體的ip地址和端口號,在單體應(yīng)用中,這些都是固定的,但是在微服務(wù)中,因為一個服務(wù)可能通過X軸或者Z軸擴(kuò)展為多個實(shí)例,并且服務(wù)實(shí)例的ip地址會變服務(wù)實(shí)例集群會動態(tài)更改因此微服務(wù)必須使用服務(wù)發(fā)現(xiàn)。

3.1 服務(wù)發(fā)現(xiàn)概念

其關(guān)鍵組件是服務(wù)注冊表,它是包含服務(wù)實(shí)例網(wǎng)絡(luò)位置信息的一個數(shù)據(jù)庫。當(dāng)服務(wù)實(shí)例啟動或者停止時,服務(wù)發(fā)現(xiàn)機(jī)制會更新服務(wù)注冊表。(采用服務(wù)發(fā)現(xiàn)需要給服務(wù)起一個為一個名字,以便于調(diào)用方可以調(diào)用)

3.2 應(yīng)用層服務(wù)發(fā)現(xiàn)模式

3.2.1 概念

客戶端會通過服務(wù)注冊表,獲取可用的服務(wù)列表,然后通過路由策略選擇要調(diào)用的實(shí)例發(fā)送請求。這個過程包括兩部分:

  • 自注冊:服務(wù)實(shí)例向服務(wù)注冊表中注冊自己的網(wǎng)絡(luò)位置,并且提供運(yùn)行狀況檢查URL以便于服務(wù)注冊表定期檢查服務(wù)是否正常(服務(wù)還可以通過“續(xù)命”的方式,定期調(diào)用心跳API防止注冊過期)
  • 客戶端發(fā)現(xiàn):客戶端獲取服務(wù)實(shí)例列表,并通過路由策略在它們之間進(jìn)行負(fù)載均衡。


    image.png

3.2.2 優(yōu)點(diǎn)

可以處理多平臺部署的問題

3.2.3 缺點(diǎn)

必須為每種編程語言提供API,并且必須由開發(fā)人員對服務(wù)發(fā)現(xiàn)進(jìn)行開發(fā)。

3.3 平臺層服務(wù)發(fā)現(xiàn)模式(服務(wù)治理平臺)

3.3.1 概念

部署平臺包括一個服務(wù)注冊表,用于跟蹤可用服務(wù)的IP地址,當(dāng)由請求時,平臺會根據(jù)指定的路由策略(同機(jī)房優(yōu)先,同城市優(yōu)先)在實(shí)例之間進(jìn)行負(fù)載均衡,包括兩部分:

  • 第三方注冊模式:注冊統(tǒng)一由第三方負(fù)責(zé)(治理平臺的一部分),而不是服務(wù)本身注冊自己到服務(wù)注冊表。
  • 服務(wù)端發(fā)現(xiàn)模式:客戶端不需要查詢服務(wù)注冊表,然后自己選擇實(shí)例,而是直接向治理平臺發(fā)送請求(帶上服務(wù)的標(biāo)識),平臺查詢服務(wù)注冊表后根據(jù)指定路由返回可用實(shí)例,客戶端再訪問這個實(shí)例。


    image.png

3.3.2 優(yōu)點(diǎn)

客戶端和服務(wù)端不需要包含服務(wù)發(fā)現(xiàn)相關(guān)的代碼

3.3.3 缺點(diǎn)

對平臺有限制

4 基于異步消息模式的通信

4.1 兩種類型的消息通道

  • 點(diǎn)對點(diǎn):服務(wù)使用點(diǎn)對點(diǎn)通道來實(shí)現(xiàn)一對一交互方式
  • 發(fā)布訂閱:將一條消息發(fā)送給所有訂閱的接收方,使用發(fā)布-訂閱模式實(shí)現(xiàn)一對多的交互方式。

4.2 無代理消息和有代理消息

image.png

如上圖所示,無代理的架構(gòu)中服務(wù)直接通信,有代理的架構(gòu)中服務(wù)通過代理通信

4.2.1 無代理消息

服務(wù)可以直接通信,典型的是ZMQ,雖然操作起來很簡單,但是客戶端和服務(wù)端還是強(qiáng)耦合的方式,雙方需要了解彼此的位置(也就是要使用服務(wù)發(fā)現(xiàn)機(jī)制),導(dǎo)致服務(wù)雙方必須同時存在。

4.2.2 基于代理的消息

發(fā)送方將消息寫入消息代理,消息代理將消息發(fā)送給接受方(發(fā)送方并不需要知道接收方的位置,并且可以使用消息隊列進(jìn)行削峰,例如Kafka,kafka使用topic和消費(fèi)組的概念實(shí)現(xiàn)消息機(jī)制),可以理解發(fā)送方和接受方是松耦合,因為二者不需要知道彼此的位置,并且可以使用消息隊列進(jìn)行削峰;但是因為引入了消息代理,所以會引來消費(fèi)隊列造成的瓶頸問題。

4.2.3 kafka

4.2.3.1 名詞解釋
  • Broker:Kafka節(jié)點(diǎn),一個節(jié)點(diǎn)就是一個Broker,多個節(jié)點(diǎn)組成kafka集群
  • topic:消息主題,供消費(fèi)者或消費(fèi)組訂閱
  • Partition:分片,一個topic可以分為多個分片,從而提高性能
  • Producer:生產(chǎn)者,生產(chǎn)信息
  • Consumer:消費(fèi)者,訂閱topic
  • Consumer Group:消費(fèi)組,可以包含多個消費(fèi)者,一個消息只能被一個消費(fèi)組消費(fèi)一次。
4.2.3.2 消費(fèi)邏輯

當(dāng)啟動一個消費(fèi)組去消費(fèi)一個topic的時候,無論有多少個consumer,這個consumer group一定回去把這個topic下所有的partition都消費(fèi)了。當(dāng)consumer group里面的consumer數(shù)量小于這個topic下的partition數(shù)量的時候,如下圖groupA,groupB,就會出現(xiàn)一個conusmer thread消費(fèi)多個partition的情況,總之是這個topic下的partition都會被消費(fèi)。如果consumer group里面的consumer數(shù)量等于這個topic下的partition數(shù)量的時候,如下圖groupC,此時效率是最高的,每個partition都有一個consumer thread去消費(fèi)。當(dāng)consumer group里面的consumer數(shù)量大于這個topic下的partition數(shù)量的時候,如下圖GroupD,就會有一個consumer thread空閑。因此,我們在設(shè)定consumer group的時候,只需要指明里面有幾個consumer數(shù)量即可,無需指定對應(yīng)的消費(fèi)partition序號,consumer會自動進(jìn)行rebalance。


image.png
4.2.3.3 消息有序

有序從兩方面保證:kafka節(jié)點(diǎn)不僅要保存數(shù)據(jù)的時候有序,并且消費(fèi)者消費(fèi)也要有序。kafka是怎么保證單個分片有序的?

  • 生產(chǎn)者發(fā)送隊列時,通過加鎖保證發(fā)消息順序(有兩個問題,第一個是kafka節(jié)點(diǎn)因為網(wǎng)絡(luò)原因沒有及時ack,導(dǎo)致生產(chǎn)者重復(fù)發(fā)送;另一個是msg1發(fā)送失敗,msg2發(fā)送成功;msg1重新發(fā)送導(dǎo)致亂序)。。。為實(shí)現(xiàn)Producer的冪等性,Kafka引入了Producer ID(即PID)和Sequence Number。對于每個PID,該P(yáng)roducer發(fā)送消息的每個<Topic, Partition>都對應(yīng)一個單調(diào)遞增的Sequence Number。同樣,Broker端也會為每個<PID, Topic, Partition>維護(hù)一個序號,并且每Commit一條消息時將其對應(yīng)序號遞增。對于接收的每條消息,如果其序號比Broker維護(hù)的序號)大一,則Broker會接受它,否則將其丟棄:如果消息序號比Broker維護(hù)的序號差值比一大,說明中間有數(shù)據(jù)尚未寫入,即亂序,此時Broker拒絕該消息,Producer拋出InvalidSequenceNumber如果消息序號小于等于Broker維護(hù)的序號,說明該消息已被保存,即為重復(fù)消息,Broker直接丟棄該消息,Producer拋出DuplicateSequenceNumberSender發(fā)送失敗后會重試,這樣可以保證每個消息都被發(fā)送到broker。
  • Kafka Consumer端是需要維護(hù)這個partition當(dāng)前消費(fèi)到哪個message的offset信息的,這個offset信息,high level api是維護(hù)在Zookeeper上,low level api是自己的程序維護(hù)。(Kafka管理界面上只能顯示high level api的consumer部分,因為low level api的partition offset信息是程序自己維護(hù),kafka是不知道的,無法在管理界面上展示 )當(dāng)使用high level api的時候,先拿message處理,再定時自動commit offset+1(也可以改成手動), 并且kakfa處理message是沒有鎖操作的。因此如果處理message失敗,此時還沒有commit offset+1,當(dāng)consumer thread重啟后會重復(fù)消費(fèi)這個message。。。重復(fù)消費(fèi)通常會使邏輯錯誤,需要在代碼里進(jìn)行冪等處理(通過加鎖,然后檢查數(shù)據(jù)庫/redis的數(shù)據(jù))

參考:https://www.zhihu.com/question/266390197/answer/772404605

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容