進程之間的通信在開發(fā)過程中十分常見塞弊,那么如何保證進程之間消息通信的可靠性浩村,下面分別從分布式系統(tǒng)(消息中間件RabbitMQ)和單機系統(tǒng)(ZeroMQ)來說明他們在消息傳輸中蜻韭,是如何保證消息的不丟失的募谎。
1、RabbitMQ的可靠性傳輸
1.1 RabbitMQ出現(xiàn)消息丟失的情況及其解決辦法
如圖所示缔逛,RabbitMQ丟失消息的情況可以發(fā)送在任何一個節(jié)點。
1.1.1 生產者沒有成功把消息發(fā)送到MQ
a、丟失的原因:因為網絡傳輸的不穩(wěn)定性褐奴,當生產者在向MQ發(fā)送消息的過程中按脚,MQ沒有成功接收到消息,但是生產者卻以為MQ成功接收到了消息敦冬,不會再次重復發(fā)送該消息辅搬,從而導致消息的丟失。
b脖旱、解決辦法: 有兩個解決辦法:事務機制和confirm機制堪遂,最常用的是confirm機制。
事務機制:
? ? ? RabbitMQ 提供了事務功能萌庆,生產者發(fā)送數據之前開啟 RabbitMQ 事務channel.txSelect溶褪,然后發(fā)送消息,如果消息沒有成功被 RabbitMQ 接收到践险,那么生產者會收到異常報錯猿妈,此時就可以回滾事務channel.txRollback,然后重試發(fā)送消息巍虫;如果收到了消息彭则,那么可以提交事務channel.txCommit。偽代碼如下:
confirm機制:
? ? ? RabbitMQ可以開啟 confirm 模式占遥,在生產者那里設置開啟 confirm 模式之后俯抖,生產者每次寫的消息都會分配一個唯一的 id,如果消息成功寫入 RabbitMQ 中瓦胎,RabbitMQ 會給生產者回傳一個 ack 消息蚌成,告訴你說這個消息 ok 了。如果 RabbitMQ 沒能處理這個消息凛捏,會回調你的一個 nack 接口担忧,告訴你這個消息接收失敗,生產者可以發(fā)送坯癣。而且你可以結合這個機制自己在內存里維護每個消息 id 的狀態(tài)瓶盛,如果超過一定時間還沒接收到這個消息的回調,那么可以重發(fā)示罗。
注意:RabbitMQ的事務機制是同步的惩猫,很耗型能,會降低RabbitMQ的吞吐量蚜点。confirm機制是異步的轧房,生成者發(fā)送完一個消息之后,不需要等待RabbitMQ的回調绍绘,就可以發(fā)送下一個消息奶镶,當RabbitMQ成功接收到消息之后會自動異步的回調生產者的一個接口返回成功與否的消息迟赃。
1.1.2 RabbitMQ接收到消息之后丟失了消息
a、丟失的原因:RabbitMQ接收到生產者發(fā)送過來的消息厂镇,是存在內存中的纤壁,如果沒有被消費完,此時RabbitMQ宕機了捺信,那么再次啟動的時候酌媒,原來內存中的那些消息都丟失了。
b迄靠、解決辦法:開啟RabbitMQ的持久化秒咨。當生產者把消息成功寫入RabbitMQ之后,RabbitMQ就把消息持久化到磁盤掌挚。結合上面的說到的confirm機制雨席,只有當消息成功持久化磁盤之后,才會回調生產者的接口返回ack消息疫诽,否則都算失敗,生產者會重新發(fā)送旦委。存入磁盤的消息不會丟失奇徒,就算RabbitMQ掛掉了,重啟之后缨硝,他會讀取磁盤中的消息摩钙,不會導致消息的丟失。
c查辩、持久化的配置:
第一點是創(chuàng)建 queue 的時候將其設置為持久化胖笛,這樣就可以保證 RabbitMQ 持久化 queue 的元數據,但是它是不會持久化 queue 里的數據的宜岛。
第二個是發(fā)送消息的時候將消息的 deliveryMode 設置為 2长踊,就是將消息設置為持久化的,此時 RabbitMQ 就會將消息持久化到磁盤上去萍倡。
注意:持久化要起作用必須同時設置這兩個持久化才行身弊,RabbitMQ 哪怕是掛了,再次重啟列敲,也會從磁盤上重啟恢復 queue阱佛,恢復這個 queue 里的數據。
1.1.3 消費者弄丟了消息
a戴而、丟失的原因:如果RabbitMQ成功的把消息發(fā)送給了消費者凑术,那么RabbitMQ的ack機制會自動的返回成功,表明發(fā)送消息成功所意,下次就不會發(fā)送這個消息淮逊。但如果就在此時催首,消費者還沒處理完該消息,然后宕機了壮莹,那么這個消息就丟失了翅帜。
b、解決的辦法:簡單來說命满,就是必須關閉 RabbitMQ 的自動 ack涝滴,可以通過一個 api 來調用就行,然后每次在自己代碼里確保處理完的時候胶台,再在程序里 ack 一把歼疮。這樣的話,如果你還沒處理完诈唬,不就沒有 ack了韩脏?那 RabbitMQ 就認為你還沒處理完,這個時候 RabbitMQ 會把這個消費分配給別的 consumer 去處理铸磅,消息是不會丟的赡矢。
1.2? 如何防止重復消費
先說為什么會重復消費:正常情況下,消費者在消費消息的時候阅仔,消費完畢后吹散,會發(fā)送一個確認消息給消息隊列,消息隊列就知道該消息被消費了八酒,就會將該消息從消息隊列中刪除空民;但是因為網絡傳輸等等故障,確認信息沒有傳送到消息隊列羞迷,導致消息隊列不知道自己已經消費過該消息了界轩,再次將消息分發(fā)給其他的消費者。
解決思路是:
? ? ? ? 保證消息的唯一性衔瓮,就算是多次傳輸浊猾,不要讓消息的多次消費帶來影響;保證消息等冪性热鞍;
? ? ? ? 在消息生產時与殃,MQ內部針對每條生產者發(fā)送的消息生成一個inner-msg-id,作為去重和冪等的依據(消息投遞失敗并重傳)碍现,避免重復的消息進入隊列幅疼;
? ? ? ? 在消息消費時,要求消息體中必須要有一個bizId(對于同一業(yè)務全局唯一昼接,如支付ID爽篷、訂單ID、帖子ID等)作為去重和冪等的依據慢睡,避免同一條消息被重復消費逐工。
? ? ? 這個問題針對業(yè)務場景來答分以下幾點:
1.? 如果消息是做數據庫的insert操作铡溪,給這個消息做一個唯一主鍵,那么就算出現(xiàn)重復消費的情況泪喊,就會導致主鍵沖突棕硫,避免數據庫出現(xiàn)臟數據。
2.? 如果消息是做redis的set的操作袒啼,不用解決哈扮,因為無論set幾次結果都是一樣的,set操作本來就算冪等操作蚓再。
3.? 如果以上兩種情況還不行滑肉,可以準備一個第三方介質,來做消費記錄。以redis為例摘仅,給消息分配一個全局id靶庙,只要消費過該消息,將<id,message>以K-V形式寫入redis娃属。那消費者開始消費前六荒,先去redis中查詢有沒消費記錄即可。
1.3 RabbitMQ的優(yōu)缺點
a矾端、優(yōu)點:支持集群部署掏击,支持消息的持久化,使用erlang語言開發(fā)须床,并發(fā)性能高铐料,吞吐量大渐裂。
b豺旬、缺點:重量級消息隊列,需要單獨部署服務柒凉。
2族阅、ZeroMQ的可靠性傳輸
2.1 ZeroMQ有什么不同
ZeroMQ(簡稱ZMQ)是一個基于消息隊列的多線程網絡庫,其對套接字類型膝捞、連接處理坦刀、幀、甚至路由的底層細節(jié)進行抽象蔬咬,提供跨越多種傳輸協(xié)議的套接字鲤遥。
ZMQ不是單獨的服務,而是一個嵌入式庫林艘,它封裝了網絡通信盖奈、消息隊列、線程調度等功能狐援,向上層提供簡潔的API钢坦,應用程序通過加載庫文件究孕,調用API函數來實現(xiàn)高性能網絡通信。
2.2 ZeroMQ如何保證消息的可靠性
ZMQ保證消息可靠性的原理跟RabbitMQ基本類似爹凹。除此之外還有其他的特點:
1. 它在后臺線程異步的處理I/O厨诸。這些后臺線程使用無鎖數據結構與程序線程交流,所以并發(fā)ZMQ程序不需要鎖禾酱、信號量微酬、或其它等待狀態(tài)。
2. 組件可以動態(tài)的來來去去宇植,而ZMQ會自動重連得封。這意味著你可以按任意順序啟動組件。你可以創(chuàng)建“面向服務架構”(SOAs)指郁,服務可以隨時加入和離開網絡忙上。
3. 當需要時它自動將消息排入隊列。以智能的方式闲坎,消息排入隊列前推送消息到盡可能靠近接收者疫粥。
4. 它有幾種辦法處理滿溢隊列(稱為“高水位線”)。當隊列填滿時腰懂,ZMQ自動阻塞發(fā)送者梗逮,或丟棄消息,取決于你用的消息傳遞方式(所謂的“模式”)绣溜。
5. 它讓你的程序用任意傳輸方式來相互交談:TCP慷彤、多播、進程內怖喻、進程間底哗。更改傳輸方式時無需更改代碼。
6. 安全處理低速/阻塞的讀者锚沸,使用的是取決于消息傳遞模式的不同策略跋选。
7. 它讓你路由消息使用各種模式如請求-應答和發(fā)布-訂閱。這些模式是你創(chuàng)建拓撲哗蜈、網絡結構的方式前标。
8. 它讓你用一個調用就能創(chuàng)建代理來做隊列、轉發(fā)距潘、或捕獲消息炼列。代理可以降低網絡的互聯(lián)復雜度。
9. 它使用簡單的線上組幀音比,轉發(fā)整個消息并精確重現(xiàn)其發(fā)送時的樣子俭尖。如果你寫入一個10K的消息,就能接收一個10K的消息硅确。
10. 它不在消息上強加任何格式目溉。消息就是零到千兆大小的二進制大對象明肮。想要描述數據時你可以在其上選擇一些其它產品,例如谷歌的協(xié)議緩沖(protocol buffers)缭付、外部數據表示法(XDR)柿估、或其它。
11. 它智能的處理網絡錯誤陷猫。有時它會重試秫舌,有時它告知你一個操作失敗了。
2.3 ZeroMQ的優(yōu)缺點
a绣檬、優(yōu)點:輕量級足陨,不需要單獨部署服務,速度快娇未,作為單機C++和JAVA進程之間可靠性通信的首選墨缘。
b、缺點:消息隊列不支持持久化零抬,不支持集群镊讼,不支持高可用。