分布式系统与消息的投递
消息是一個(gè)非常有趣的概念,它是由來(lái)源發(fā)出一個(gè)離散的通信單元,被發(fā)送給一個(gè)或者一群接受者,無(wú)論是單體服務(wù)還是分布式系統(tǒng)中都有消息的概念,只是這兩種系統(tǒng)中傳輸消息的通道方法或者通道不同;單體服務(wù)中的消息往往可以通過(guò) IO、進(jìn)程間通信、方法調(diào)用的方式進(jìn)行通信,而分布式系統(tǒng)中的遠(yuǎn)程調(diào)用就需要通過(guò)網(wǎng)絡(luò),使用 UDP 或者 TCP 等協(xié)議進(jìn)行傳輸。
communication-reliability然而網(wǎng)絡(luò)在計(jì)算機(jī)的世界中是最不可控的,如果我們通過(guò)網(wǎng)絡(luò)請(qǐng)求調(diào)用其他服務(wù)的接口,可能就會(huì)由于種種原因沒(méi)有將消息送達(dá)至目標(biāo)的服務(wù),對(duì)于當(dāng)前服務(wù)我們并不能控制網(wǎng)絡(luò)的傳輸,在很多時(shí)候也很難控制網(wǎng)絡(luò)通信的質(zhì)量,這也就是為什么『網(wǎng)絡(luò)是穩(wěn)定、可信賴的』分布式系統(tǒng)中常見(jiàn)的謬論之一。
通信渠道的不可靠是造成構(gòu)建大規(guī)模分布式系統(tǒng)非常復(fù)雜并且困難的重要原因。
網(wǎng)絡(luò)請(qǐng)求
作為分布式系統(tǒng)之間各個(gè)節(jié)點(diǎn)的通信渠道,網(wǎng)絡(luò)其實(shí)是非常不可靠通信方式,如果我們想要保證節(jié)點(diǎn)狀態(tài)的一致性,這種通信方式的復(fù)雜性使得我們?cè)谶M(jìn)行跨服務(wù)調(diào)用時(shí)需要處理非常多的邊界條件,在之前的文章?分布式系統(tǒng) · 分布式事務(wù)的實(shí)現(xiàn)原理?中簡(jiǎn)單介紹過(guò),網(wǎng)絡(luò)通信可能會(huì)包含,成功、失敗以及超時(shí)三種情況。
network-communication每一次網(wǎng)絡(luò)請(qǐng)求其實(shí)都是一次信息的投遞,由于當(dāng)前的節(jié)點(diǎn)無(wú)法得知其他節(jié)點(diǎn)信息,只能通過(guò)網(wǎng)絡(luò)請(qǐng)求的響應(yīng)來(lái)得知這次信息投遞的結(jié)果。
成功與失敗
雖然網(wǎng)絡(luò)的情況比較不穩(wěn)定,但是我們?cè)诖蠖鄶?shù)時(shí)候通過(guò)網(wǎng)絡(luò)傳輸一些信息時(shí),無(wú)論是返回的結(jié)果是成功還是失敗,其實(shí)都能得到確定的結(jié)果:
network-success-and-failure每一次確定的響應(yīng)都需要這次請(qǐng)求在一個(gè)往返以及被調(diào)用節(jié)點(diǎn)中正確處理,流量既不能被中間代理丟包,也不能由于目標(biāo)節(jié)點(diǎn)的錯(cuò)誤導(dǎo)致無(wú)法發(fā)出響應(yīng),只有在同時(shí)滿足了這兩個(gè)條件的情況下,我們才能得到確定的響應(yīng)結(jié)果。對(duì)于節(jié)點(diǎn)來(lái)說(shuō),這次請(qǐng)求返回成功還是失敗都比較好處理,因?yàn)橹灰写_定的結(jié)果,網(wǎng)絡(luò)請(qǐng)求這種通信方式與進(jìn)程間通信或者方法調(diào)用這些更可靠的途徑在處理上都沒(méi)有太多的區(qū)別,但是在通信的過(guò)程中出現(xiàn)其他的問(wèn)題時(shí)就比較棘手了。
超時(shí)
在分布式系統(tǒng)中,不是任何的網(wǎng)絡(luò)請(qǐng)求都能夠得到確定的響應(yīng),如果網(wǎng)絡(luò)請(qǐng)求在往返以及被調(diào)用節(jié)點(diǎn)處理的過(guò)程中出現(xiàn)了丟包或者節(jié)點(diǎn)錯(cuò)誤,發(fā)出請(qǐng)求的節(jié)點(diǎn)就可能永遠(yuǎn)也無(wú)法得到這次請(qǐng)求的響應(yīng)。
network-timeout每一個(gè)節(jié)點(diǎn)在發(fā)出請(qǐng)求之后,都對(duì)這次請(qǐng)求如何路由以及被處理一無(wú)所知,所以節(jié)點(diǎn)需要設(shè)置一個(gè)合適的超時(shí)時(shí)間,如果請(qǐng)求沒(méi)有在規(guī)定的時(shí)間內(nèi)返回,就會(huì)認(rèn)為當(dāng)前請(qǐng)求已經(jīng)超時(shí),也就是網(wǎng)絡(luò)請(qǐng)求失敗了。
超時(shí)的網(wǎng)絡(luò)請(qǐng)求是導(dǎo)致分布式系統(tǒng)難以處理的根本原因之一,在這種問(wèn)題發(fā)生時(shí)節(jié)點(diǎn)并不知道目標(biāo)節(jié)點(diǎn)是否收到了當(dāng)前請(qǐng)求,對(duì)于冪等的網(wǎng)絡(luò)請(qǐng)求還好,一旦請(qǐng)求可能會(huì)改變目標(biāo)節(jié)點(diǎn)的狀態(tài)就非常棘手了,因?yàn)槲覀儾⒉荒艽_定上一次網(wǎng)絡(luò)請(qǐng)求是在哪一步失敗的,如果是響應(yīng)返回的過(guò)程中發(fā)生了故障,那么如果重試一些請(qǐng)求就會(huì)出現(xiàn)問(wèn)題,可能會(huì)觸發(fā)銀行的兩次轉(zhuǎn)賬,這是我們無(wú)論如何也無(wú)法接受的;總而言之,網(wǎng)絡(luò)通信的不穩(wěn)定迫使我們處理由于超時(shí)而出現(xiàn)的復(fù)雜問(wèn)題,這也是在開(kāi)發(fā)分布式系統(tǒng)時(shí)不得不考慮的。
消息投遞語(yǔ)義
在分布式系統(tǒng)中使用網(wǎng)絡(luò)進(jìn)行通信確實(shí)是一種不可靠的方式,消息的發(fā)送者只能知道掌控當(dāng)前節(jié)點(diǎn),所以沒(méi)有辦法保證傳輸渠道的可靠性,網(wǎng)絡(luò)超時(shí)這種常見(jiàn)的通信錯(cuò)誤極大地增加了分布式系統(tǒng)通信的復(fù)雜度,我們可以對(duì)網(wǎng)絡(luò)提供的基本傳輸能力進(jìn)行封裝,保證數(shù)據(jù)通信的可靠性。
message-delivery-problems網(wǎng)絡(luò)請(qǐng)求由于超時(shí)的問(wèn)題,消息的發(fā)送者只能通過(guò)重試的方式對(duì)消息進(jìn)行重發(fā),但是這就可能會(huì)導(dǎo)致消息的重復(fù)發(fā)送與處理,然而如果超時(shí)后不重新發(fā)送消息也可能導(dǎo)致消息的丟失,所以如何在不可靠的通信方式中,保證消息不重不漏是非常關(guān)鍵的。
message-delivery-qos我們一般都會(huì)認(rèn)為,消息的投遞語(yǔ)義有三種,分別是最多一次(At-Most Once)、最少一次(At-Least Once)以及正好一次(Exactly Once),我們分別會(huì)介紹這三種消息投遞語(yǔ)義究竟是如何工作的。
最多一次
最多一次其實(shí)非常容易保證的,UDP 這種傳輸層的協(xié)議其實(shí)保證的就是最多一次消息投遞,消息的發(fā)送者只會(huì)嘗試發(fā)送該消息一次,并不會(huì)關(guān)心該消息是否得到了遠(yuǎn)程節(jié)點(diǎn)的響應(yīng)。
at-most-once無(wú)論該請(qǐng)求是否發(fā)送給了接受者,發(fā)送者都不會(huì)重新發(fā)送這條消息;這其實(shí)就是最最基本的消息投遞語(yǔ)義,然而消息可能由于網(wǎng)絡(luò)或者節(jié)點(diǎn)的故障出現(xiàn)丟失。
最少一次
為了解決最多一次時(shí)的消息丟失問(wèn)題,消息的發(fā)送者需要在網(wǎng)絡(luò)出現(xiàn)超時(shí)重新發(fā)送相同的消息,也就是引入超時(shí)重試的機(jī)制,在發(fā)送者發(fā)出消息會(huì)監(jiān)聽(tīng)消息的響應(yīng),如果超過(guò)了一定時(shí)間也沒(méi)有得到響應(yīng)就會(huì)重新發(fā)送該消息,直到得到確定的響應(yīng)結(jié)果。
at-least-once對(duì)于最少一次的投遞語(yǔ)義,我們不僅需要引入超時(shí)重試機(jī)制,還需要關(guān)心每一次請(qǐng)求的響應(yīng),只有這樣才能確保消息不會(huì)丟失,但是卻可能會(huì)造成消息的重復(fù),這就是最少一次在解決消息丟失后引入的新問(wèn)題。
正好一次
雖然最少一次解決了最多一次的消息丟失問(wèn)題,但是由于重試卻帶來(lái)了另一個(gè)問(wèn)題 - 消息重復(fù),也就是接受者可能會(huì)多次收到同一條消息;從理論上來(lái)說(shuō),在分布式系統(tǒng)中想要解決消息重復(fù)的問(wèn)題是不可能的,很多消息服務(wù)提供了正好一次的 QoS 其實(shí)是在接收端進(jìn)行了去重。
exactly-once消息去重需要生產(chǎn)者生產(chǎn)消息時(shí)加入去重的?key,消費(fèi)者可以通過(guò)唯一的?key?來(lái)判斷當(dāng)前消息是否是重復(fù)消息,從消息發(fā)送者的角度來(lái)看,實(shí)現(xiàn)正好一次的投遞是不可能的,但是從整體來(lái)看,我們可以通過(guò)唯一?key或者重入冪等的方式對(duì)消息進(jìn)行『去重』。
消息的重復(fù)是不可能避免的,除非我們?cè)试S消息的丟失,然而相比于丟失消息,重復(fù)發(fā)送消息其實(shí)是一種更能讓人接受的處理方式,因?yàn)橐坏┫G失就無(wú)法找回,但是消息重復(fù)卻可以通過(guò)其他方法來(lái)避免副作用。
投遞順序
由于一些網(wǎng)絡(luò)的問(wèn)題,消息在投遞時(shí)可能會(huì)出現(xiàn)順序不一致性的情況,在網(wǎng)絡(luò)條件非常不穩(wěn)定時(shí),我們就可能會(huì)遇到接收方處理消息的順序和生產(chǎn)者投遞的不一致;想要滿足絕對(duì)的順序投遞,其實(shí)在生產(chǎn)者和消費(fèi)者的單線程運(yùn)行時(shí)是相對(duì)比較好解決的,但是在市面上比較主流的消息隊(duì)列中,都不會(huì)對(duì)消息的順序進(jìn)行保證,在這種大前提下,消費(fèi)者就需要對(duì)順序不一致的消息進(jìn)行處理,常見(jiàn)的兩種方式就是使用序列號(hào)或者狀態(tài)機(jī)。
序列號(hào)
使用序列號(hào)保證投遞順序的方式其實(shí)與 TCP 協(xié)議中使用的?SEQ?非常相似,因?yàn)榫W(wǎng)絡(luò)并不能保證所有數(shù)據(jù)包傳輸?shù)捻樞虿⑶颐總€(gè)棧幀的傳輸大小有限,所以 TCP 協(xié)議在發(fā)送數(shù)據(jù)包時(shí)加入?SEQ,接受方可以通過(guò)?SEQ?將多個(gè)數(shù)據(jù)包拼接起來(lái)并交由上層協(xié)議進(jìn)行處理。
message-delivery-sequence在投遞消息時(shí)加入序列號(hào)其實(shí)與 TCP 中的序列號(hào)非常類似,我們需要在數(shù)據(jù)之外增加消息的序列號(hào),對(duì)于消費(fèi)者就可以根據(jù)每一條消息附帶的序列號(hào)選擇如何處理順序不一致的消息,對(duì)于不同的業(yè)務(wù)來(lái)說(shuō),常見(jiàn)的處理方式就是用阻塞的方式保證序列號(hào)的遞增或者忽略部分『過(guò)期』的消息。
狀態(tài)機(jī)
使用序列號(hào)確實(shí)能夠保證消息狀態(tài)的一致,但是卻需要在消息投遞時(shí)額外增加字段,這樣消費(fèi)者才能在投遞出現(xiàn)問(wèn)題時(shí)進(jìn)行處理,除了這種方式之外,我們也可以通過(guò)狀態(tài)機(jī)的方式保證數(shù)據(jù)的一致性,每一個(gè)資源都有相應(yīng)的狀態(tài)遷移事件,這些事件其實(shí)就是一個(gè)個(gè)消息(或操作),它們能夠修改資源的狀態(tài):
message-delivery-statemachine在狀態(tài)機(jī)中我們可以規(guī)定,狀態(tài)的遷移方向,所有資源的狀態(tài)只能按照我們規(guī)定好的線路進(jìn)行改變,在這時(shí)只要對(duì)生產(chǎn)者投遞的消息狀態(tài)做一定的約束,例如:資源一旦?completed?就不會(huì)變成?failed,因?yàn)檫@兩個(gè)狀態(tài)都是業(yè)務(wù)邏輯中定義的最終狀態(tài),所以處于最終狀態(tài)的資源都不會(huì)繼續(xù)接受其他的消息。
假設(shè)我們有如下的兩條消息?active?和?complete,它們分別會(huì)改變當(dāng)前資源的狀態(tài),如果一個(gè)處于?pending狀態(tài)的資源先收到了?active?再收到?complete,那么狀態(tài)就會(huì)從?pending?遷移到?active?再到?completed;但是如果資源先收到?complete?后收到?active,那么當(dāng)前資源的狀態(tài)會(huì)直接從?pending?跳躍到?completed,對(duì)于另一條消息就會(huì)直接忽略;從總體來(lái)看,雖然消息投遞的順序是亂序的,但是資源最終還是通過(guò)狀態(tài)機(jī)達(dá)到了我們想要的正確狀態(tài),不會(huì)出現(xiàn)不一致的問(wèn)題。
協(xié)議
消息投遞其實(shí)有非常多相關(guān)的應(yīng)用,最常見(jiàn)的組件就是消息隊(duì)列了,作為一種在各個(gè) Web 項(xiàng)目中常用的組件,它提供了很多能力,包括消息的持久存儲(chǔ)、不同的投遞語(yǔ)義以及復(fù)雜的路由規(guī)則等等,能夠顯著地增加系統(tǒng)的可用性、起到比較比明顯的削峰效果。
在這里將介紹幾種比較常見(jiàn)的消息隊(duì)列協(xié)議,我們將簡(jiǎn)單說(shuō)明各個(gè)協(xié)議的作用以及它們的實(shí)現(xiàn)原理和關(guān)鍵特性,也會(huì)簡(jiǎn)單提及一些遵循這些協(xié)議實(shí)現(xiàn)的消息隊(duì)列中間件。
AMQP 協(xié)議
AMQP 協(xié)議的全稱是 Advanced Message Queuing Protocol,它是一個(gè)用于面向消息中間件的開(kāi)放標(biāo)準(zhǔn),協(xié)議中定義了隊(duì)列、路由、可用性以及安全性等方面的內(nèi)容。
amqp-protoco該協(xié)議目前能夠?yàn)橥ㄓ玫南㈥?duì)列架構(gòu)提供一系列的標(biāo)準(zhǔn),將發(fā)布訂閱、隊(duì)列、事務(wù)以及流數(shù)據(jù)等功能抽象成了用于解決消息投遞以及相關(guān)問(wèn)題的標(biāo)準(zhǔn),StormMQ、RabbitMQ 都是 AMQP 協(xié)議的一個(gè)實(shí)現(xiàn)。
在所有實(shí)現(xiàn) AMQP 協(xié)議的消息中間中,RabbitMQ 其實(shí)是最出名的一個(gè)實(shí)現(xiàn),在分布式系統(tǒng)中,它經(jīng)常用于存儲(chǔ)和轉(zhuǎn)發(fā)消息,當(dāng)生產(chǎn)者短時(shí)間內(nèi)創(chuàng)建了大量的消息,就會(huì)通過(guò)消息中間件對(duì)消息轉(zhuǎn)儲(chǔ),消費(fèi)者會(huì)按照當(dāng)前的資源對(duì)消息進(jìn)行消費(fèi)。
producer-and-consumeRabbitMQ 在消息投遞的過(guò)程中保證存儲(chǔ)在 RabbitMQ 中的全部消息不會(huì)丟失、推送者和訂閱者需要通過(guò)信號(hào)的方式確認(rèn)消息的投遞,它支持最多一次和最少一次的投遞語(yǔ)義,當(dāng)我們選擇最少一次時(shí),需要冪等或者重入機(jī)制保證消息重復(fù)不會(huì)出現(xiàn)問(wèn)題。
MQTT 協(xié)議
另一個(gè)用于處理發(fā)布訂閱功能的常見(jiàn)協(xié)議就是 MQTT 了,它建立在 TCP/IP 協(xié)議之上,能夠在硬件性能底下或者網(wǎng)絡(luò)狀態(tài)糟糕的情況下完成發(fā)布與訂閱的功能;與 AMQP 不同,MQTT 協(xié)議支持三種不同的服務(wù)質(zhì)量級(jí)別(QoS),也就是投遞語(yǔ)義,最多一次、最少一次和正好一次。
從理論上來(lái)看,在分布式系統(tǒng)中實(shí)現(xiàn)正好一次的投遞語(yǔ)義是不可能的,這里實(shí)現(xiàn)的正好一次其實(shí)是協(xié)議層做了重試和去重機(jī)制,消費(fèi)者在處理 MQTT 消息時(shí)就不需要關(guān)系消息是否重復(fù)這種問(wèn)題了。
總結(jié)
在分布式系統(tǒng)中想要保證消息的送達(dá)確實(shí)是一件比較復(fù)雜的事情,通信方式的不確定使得我們需要處理很多問(wèn)題,我們既需要在網(wǎng)絡(luò)錯(cuò)誤或者超時(shí)時(shí)進(jìn)行重試,還需要對(duì)一些請(qǐng)求支持重入和冪等,保證不會(huì)出現(xiàn)一致性的錯(cuò)誤;這其實(shí)都是因?yàn)樵诜植际较到y(tǒng)中,正好一次的消息投遞語(yǔ)義是不存在的,消息要么可能會(huì)丟失,要么就可能會(huì)重復(fù)。
總結(jié)
以上是生活随笔為你收集整理的分布式系统与消息的投递的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Spring Cloud Greenwi
- 下一篇: JVM 总体概述