本次分享內(nèi)容由三個(gè)部分組成:
微服務(wù)架構(gòu)與MQ
RabbitMQ場(chǎng)景分析與優(yōu)化
RabbitMQ在網(wǎng)易蜂巢中的應(yīng)用和案例分享
1微服務(wù)架構(gòu)與MQ
微服務(wù)架構(gòu)是一種架構(gòu)模式,它將單體應(yīng)用劃分成一組微小的服務(wù),各服務(wù)之間使用輕量級(jí)的通信機(jī)制交互。
上圖左邊是單體架構(gòu)應(yīng)用,把所有業(yè)務(wù)功能放在單個(gè)進(jìn)程中,需要擴(kuò)展時(shí)以進(jìn)程為單位水平復(fù)制到多臺(tái)機(jī)器。
上圖右邊是微服務(wù)架構(gòu)應(yīng)用,將每個(gè)業(yè)務(wù)功能以獨(dú)立進(jìn)程(服務(wù))的方式部署,可以按需將微服務(wù)分別部署在多臺(tái)機(jī)器,實(shí)現(xiàn)水平擴(kuò)展。
微服務(wù)各服務(wù)之間使用“輕量級(jí)”的通信機(jī)制。所謂輕量級(jí),是指通信協(xié)議與語(yǔ)言無(wú)關(guān)、與平臺(tái)無(wú)關(guān)。
微服務(wù)通信方式:
同步:RPC,REST等
異步:消息隊(duì)列
同步通信方式
優(yōu)點(diǎn):
實(shí)現(xiàn)方便。
協(xié)議通用,比如HTTP大家都非常熟知。
系統(tǒng)架構(gòu)簡(jiǎn)單,無(wú)需中間件代理。
缺點(diǎn):
客戶端耦合服務(wù)方。
通信雙方必須同時(shí)在線,否則會(huì)造成阻塞。
客戶端需要知道服務(wù)方的Endpoint,或者需要支持服務(wù)發(fā)現(xiàn)機(jī)制。
異步通信方式
優(yōu)點(diǎn):
系統(tǒng)解耦和。
通信雙方可以互相對(duì)對(duì)方無(wú)感知。
缺點(diǎn):
額外的編程復(fù)雜性。比如需要考慮消息可靠傳輸、高性能,以及編程模型的變化等。
額外的運(yùn)維復(fù)雜性。比如消息中間件的穩(wěn)定性、高可用性、擴(kuò)展性等非功能特性。
今天的主題是消息隊(duì)列在微服務(wù)架構(gòu)中的應(yīng)用與實(shí)踐。
消息隊(duì)列中間件如何選型?主要會(huì)考慮以下幾點(diǎn):
協(xié)議:AMQP、STOMP、MQTT、私有協(xié)議等
消息是否需要持久化
吞吐量
高可用支持,是否單點(diǎn)
分布式擴(kuò)展能力
消息堆積能力和重放能力
開發(fā)便捷,易于維護(hù)
社區(qū)成熟度
選擇RabbitMQ的原因:
開源,跨平臺(tái)
靈活的消息路由策略
持久化,消息可靠傳輸
透明集群,HA支持
支持高性能高并發(fā)訪問
支持多種消息協(xié)議
豐富的客戶端、插件和平臺(tái)支持
支持RPC解決方案
2RabbitMQ場(chǎng)景分析與優(yōu)化
RabbitMQ是一個(gè)實(shí)現(xiàn)了AMQP(高級(jí)消息隊(duì)列協(xié)議)協(xié)議的消息隊(duì)列中間件。
AMQP基本模型:
1. Queue
2. Exchange: Direct, Fanout, Topic, Header
3. Binding: BindingKey, RouteKey
總結(jié):生產(chǎn)者將消息發(fā)送到Exchange,Exchange通過匹配BindingKey和消息中的RouteKey來將消息路由到隊(duì)列,最后隊(duì)列將消息投遞給消費(fèi)者。
消息可靠傳輸是業(yè)務(wù)系統(tǒng)接入MQ時(shí)首先要考慮的問題。一般消息可靠性有三個(gè)等級(jí):
At most once: 最多一次
At least once: 最少一次
Exactly once: 恰好一次
RabbitMQ支持其中的“最多一次”和“最少一次”兩種。其中“最少一次”投遞實(shí)現(xiàn)機(jī)制:
生產(chǎn)者confirm。如何開啟:使用confirm.select
消息持久化。
消費(fèi)者ack。如何開啟:使用basic.consume(…, no-ack=false)
這里說下RabbitMQ消息持久化(寫磁盤)的兩個(gè)場(chǎng)景:
顯式指定消息屬性:delivery-mode=2
內(nèi)存吃緊時(shí),消息(包括非持久化消息)轉(zhuǎn)存到磁盤,由memory_high_watermark_paging_ratio參數(shù)指定閾值。
RabbitMQ的消息持久化是通過以下機(jī)制實(shí)現(xiàn)的:
消息體寫文件
異步刷盤,合并請(qǐng)求,減少fsync系統(tǒng)調(diào)用次數(shù)
當(dāng)進(jìn)程mailbox沒有新消息時(shí),實(shí)時(shí)刷盤
confirm機(jī)制下,等f(wàn)sync系統(tǒng)調(diào)用完成后返回basic.ack確認(rèn)
RabbitMQ開啟消息可靠性參數(shù)需要注意:
unack消息在服務(wù)器端沒有超時(shí),只能等待客戶端連接斷開,重新入隊(duì)等待投遞。
消息存在重復(fù)投遞的情況,需客戶端去重:
a)基于業(yè)務(wù)層的MsgId。
b)基于RabbitMQ的Redelivered flag標(biāo)記: 不完全靠譜,仍舊可能收到重復(fù)消息。
保障性能:
a)批量publish, ack。
b)更快的磁盤(SSD,RAID等)。
c)少堆積。
消息亂序。
生產(chǎn)者confirm機(jī)制是三個(gè)可靠性參數(shù)中對(duì)性能影響最大的。一般來說有三種編程方式:
普通confirm模式。每發(fā)送一條消息后,調(diào)用waitForConfirms()方法,等待服務(wù)器端confirm。實(shí)際上是一種串行confirm。
批量confirm模式。每次發(fā)送一批消息后,調(diào)用waitForConfirms()方法,等待服務(wù)器端confirm。
異步confirm模式。注冊(cè)一個(gè)回調(diào)方法,服務(wù)器端confirm了一條(或多條)消息后SDK會(huì)回調(diào)這個(gè)方法。
下面是一些生產(chǎn)者confirm機(jī)制的專項(xiàng)性能測(cè)試數(shù)據(jù):
總結(jié):
遵循線程數(shù)越大,吞吐量越大的規(guī)律。當(dāng)線程數(shù)量達(dá)到一個(gè)閾值之后,吞吐量開始下降。
不論哪種confirm模式,通過調(diào)整客戶端線程數(shù)量,都可以達(dá)到一個(gè)最大吞吐量值。
異步和批量confirm模式兩者沒有明顯的性能差距。所以,只需從可編程性的角度選擇異步或批量或者兩者結(jié)合的模式即可。相比而言,普通confirm模式只剩編程簡(jiǎn)單這個(gè)理由了。
下面講下RabbitMQ的高可用機(jī)制。
官方提供的高可用方案:cluster + ha policy
cluster機(jī)制:多個(gè)全聯(lián)通節(jié)點(diǎn)之間元信息(exchange、queue、binding等)保持強(qiáng)一致,但是隊(duì)列消息只會(huì)存儲(chǔ)在其中一個(gè)節(jié)點(diǎn)。
優(yōu)點(diǎn):提高吞吐量,部分解決擴(kuò)展性問題。
缺點(diǎn):不能提升數(shù)據(jù)可靠性和系統(tǒng)可用性。
ha policy機(jī)制:在cluster機(jī)制基礎(chǔ)上可以指定集群內(nèi)任意數(shù)量隊(duì)列組成鏡像隊(duì)列,隊(duì)列消息會(huì)在多節(jié)點(diǎn)間復(fù)制。實(shí)現(xiàn)數(shù)據(jù)高可靠和系統(tǒng)高可用。
設(shè)置參數(shù):ha-mode和ha-params可以細(xì)粒度(哪些節(jié)點(diǎn),哪些隊(duì)列)設(shè)置鏡像隊(duì)列。
設(shè)置參數(shù):ha-sync-mode=manual(默認(rèn))/automatic可以指定集群中新節(jié)點(diǎn)的數(shù)據(jù)同步策略。
有一點(diǎn)需要注意:鏡像隊(duì)列對(duì)網(wǎng)絡(luò)抖動(dòng)非常敏感,默認(rèn)參數(shù)配置下,出現(xiàn)腦裂后RabbitMQ集群不會(huì)自我恢復(fù),需要人工介入恢復(fù),務(wù)必加好監(jiān)控和報(bào)警。
RabbitMQ流控機(jī)制 流控類型:
內(nèi)存流控:由vm_memory_high_watermark參數(shù)控制,默認(rèn)0.4
磁盤流控:由disk_free_limit參數(shù)控制,默認(rèn)50M
單條連接流控:觸發(fā)條件是下游進(jìn)程的處理速度跟不上上游進(jìn)程。
RabbitMQ內(nèi)部進(jìn)程關(guān)系調(diào)用圖
注意:當(dāng)觸發(fā)流控(全局內(nèi)存/磁盤流控,單條連接流控)時(shí),生產(chǎn)者端的publish方法會(huì)被阻塞,生產(chǎn)者需要做的是:
注冊(cè)block事件,被流控時(shí),會(huì)收到一個(gè)回調(diào)通知。
異步化處理生產(chǎn)者發(fā)送消息,不要阻塞主流程。
3RabbitMQ在網(wǎng)易蜂巢中的應(yīng)用和案例分享
網(wǎng)易蜂巢平臺(tái)的服務(wù)化架構(gòu)如下,服務(wù)間通過RabbitMQ實(shí)現(xiàn)通信:
網(wǎng)易蜂巢消息服務(wù)器設(shè)計(jì)目標(biāo):實(shí)現(xiàn)一個(gè)路由靈活、數(shù)據(jù)可靠傳輸、高可用、可擴(kuò)展的消息服務(wù)器。
設(shè)計(jì)要點(diǎn):
Exchange類型為topic。
BindingKey就是隊(duì)列名。
每個(gè)服務(wù)(圖例中的REPO/CTRL/WEB等)啟動(dòng)后會(huì)初始化一條AMQP連接,由3個(gè)channel復(fù)用:一個(gè)channel負(fù)責(zé)生產(chǎn)消息,一個(gè)channel從TYPE(REPO/CTRL/WEB等)類型的隊(duì)列消費(fèi)消息,一個(gè)channel從TYPE.${hostname}類型的隊(duì)列消費(fèi)消息。
應(yīng)用場(chǎng)景舉例:
點(diǎn)對(duì)點(diǎn)(P2P)或請(qǐng)求有狀態(tài)服務(wù):消息的RouteKey設(shè)置為TYPE.${HOSTNAME}。如host1上的WEB服務(wù)需要向host2上的REPO服務(wù)發(fā)送消息,只需將消息的RouteKey設(shè)置為REPO.host2投遞到Exchange即可。
請(qǐng)求無(wú)狀態(tài)服務(wù):如果服務(wù)提供方是無(wú)狀態(tài)服務(wù),服務(wù)調(diào)用方不關(guān)心由哪個(gè)服務(wù)進(jìn)行響應(yīng),那么只需將消息的RouteKey設(shè)置為TYPE。比如CTRL是無(wú)狀態(tài)服務(wù),host1上的WEB服務(wù)只需將消息的RouteKey設(shè)置為CTRL即可。CTRL隊(duì)列會(huì)以Round-Robin的均衡算法將消息投遞給其中的一個(gè)消費(fèi)者。
組播:如果服務(wù)調(diào)用方需要與某類服務(wù)的所有節(jié)點(diǎn)通信,可以將消息的RouteKey設(shè)置為TYPE.*,Exchange會(huì)將消息投遞到所有TYPE.${HOSTNAME}隊(duì)列。比如WEB服務(wù)需通知所有CTRL服務(wù)更新配置,只需將消息的RouteKey設(shè)置為CTRL.*。
廣播:如果服務(wù)調(diào)用方需要與所有服務(wù)的所有節(jié)點(diǎn)通信,也就是說對(duì)當(dāng)前系統(tǒng)內(nèi)所有節(jié)點(diǎn)廣播消息,可以將消息的RouteKey設(shè)置為*.*。
總結(jié):通過對(duì)RouteKey和BindingKey的精心設(shè)計(jì),可以滿足點(diǎn)對(duì)點(diǎn)(私信)、組播、廣播等業(yè)務(wù)場(chǎng)景的通信需求。
優(yōu)缺點(diǎn)分析:
優(yōu)點(diǎn):
路由策略靈活。
支持負(fù)載均衡。
支持高可用部署。
支持消息可靠傳輸(生產(chǎn)者confirm,消費(fèi)者ack,消息持久化)。
支持prefetch count,支持流控。
缺點(diǎn):
存在消息重復(fù)投遞的可能性。
超時(shí)管理,錯(cuò)誤處理等需業(yè)務(wù)層處理。
對(duì)于多服務(wù)協(xié)作的場(chǎng)景支持度有限。比如以下場(chǎng)景:WEB=> CTRL=>REPO=>CTRL=> WEB。這個(gè)時(shí)候就需要CTRL緩存WEB請(qǐng)求,直至REPO響應(yīng)。
實(shí)踐案例分享
案例一:GC引起的MQ crash
1.環(huán)境參數(shù):
11.png
2.現(xiàn)象:
RabbitMQ崩潰,產(chǎn)生的erl_crash.dump文件內(nèi)容如下:
12.png
3.直接原因:
從數(shù)據(jù)來看,虛擬機(jī)內(nèi)存共4G,Erlang虛擬機(jī)已占用約1.98G內(nèi)存(其中分配給Erlang進(jìn)程的占1.56G),此時(shí)仍向操作系統(tǒng)申請(qǐng)1.82G,因?yàn)椴僮飨到y(tǒng)本身以及其他服務(wù)也占用一些內(nèi)存,當(dāng)前系統(tǒng)已經(jīng)分不出足夠的內(nèi)存了,所以Erlang虛擬機(jī)崩潰。
4.分析:
本例有兩個(gè)不符合預(yù)期的數(shù)據(jù):
1. 內(nèi)存流控閾值控制在1.67G左右(4G*0.4),為什么崩潰時(shí)顯示占用了1.98G?
2. 為什么Erlang虛擬機(jī)會(huì)額外再申請(qǐng)1.82G內(nèi)存?
因?yàn)椋?/p>
RabbitMQ每個(gè)隊(duì)列設(shè)計(jì)為一個(gè)Erlang進(jìn)程,由于進(jìn)程GC也是采用分代策略,當(dāng)新老生代一起參與Major GC時(shí),Erlang虛擬機(jī)會(huì)新開內(nèi)存,根據(jù)root set將存活的對(duì)象拷貝至新空間,這個(gè)過程會(huì)造成新老內(nèi)存空間同時(shí)存在,極端情況下,一個(gè)隊(duì)列可能短期內(nèi)需要兩倍的內(nèi)存占用量。這就是RabbitMQ將內(nèi)存流控的安全閾值默認(rèn)設(shè)置為0.4的原因。
內(nèi)存流控參數(shù)vm_memory_high_watermark 為0.4的意思是,當(dāng)RabbitMQ的內(nèi)存使用量大于40%時(shí),開始進(jìn)行生產(chǎn)者流控,但是該參數(shù)并不承諾RabbitMQ的內(nèi)存使用率不大于40%。
5.如何解決(規(guī)避)?
RabbitMQ獨(dú)立部署,不與其他Server共享內(nèi)存資源。
進(jìn)一步降低vm_memory_high_watermark值,比如設(shè)置成0.3,但是這種方式會(huì)造成內(nèi)存資源利用率太低。
升級(jí)RabbitMQ至新版(3.4+),新版RabbitMQ對(duì)內(nèi)存管理有過改進(jìn)。
案例二:鏡像隊(duì)列的單節(jié)點(diǎn)磁盤故障造成消息丟失
1.環(huán)境參數(shù):RabbitMQ: 3.1.5 (ha policy, ha-mode:all)
2.現(xiàn)象:
a)節(jié)點(diǎn)A的數(shù)據(jù)盤故障(磁盤控制器故障、無(wú)法讀寫),所有原本A上的生產(chǎn)者消費(fèi)者failover到B節(jié)點(diǎn)。
b)從節(jié)點(diǎn)B的WebUI上看,所有隊(duì)列信息(包括隊(duì)列元信息和數(shù)據(jù))丟失,但是exchange、binding、vhost等依舊存在。
c)節(jié)點(diǎn)B的日志中出現(xiàn)大量關(guān)于消費(fèi)請(qǐng)求的錯(cuò)誤日志:
13.png
d)從生產(chǎn)者端看來一切正常,依舊會(huì)收到來自節(jié)點(diǎn)B的confirm消息(basic.ack amqp方法)。
3.分析:
上述現(xiàn)象實(shí)際上有兩個(gè)坑在里面:
在數(shù)據(jù)可靠傳輸方面,鏡像隊(duì)列也不完全可靠。這是一個(gè)Bug。RabbitMQ 3.5.1版本以下都存在這個(gè)問題。
要保證消息可靠性,生產(chǎn)者端僅僅采用confirm機(jī)制還不夠。對(duì)于那些路由不可達(dá)的消息(根據(jù)RouteKey匹配不到相應(yīng)隊(duì)列),RabbitMQ會(huì)直接丟棄消息,同時(shí)confirm客戶端。
4.如何解決(規(guī)避)?
升級(jí)RabbitMQ到新版,至少3.5.1及以上。
生產(chǎn)者basic.publish方法設(shè)置mandatory參數(shù),它的作用是:對(duì)于那些路由不可達(dá)的消息,RabbitMQ會(huì)先通過basic.return消息向生產(chǎn)者返回消息內(nèi)容,然后再發(fā)送basic.ack消息進(jìn)行確認(rèn)
Q & A
Q1:為保障消息隊(duì)列穩(wěn)定性,消息隊(duì)列監(jiān)控點(diǎn)主要有哪些?
A1:首先是服務(wù)器的基礎(chǔ)監(jiān)控是要的。CPU,內(nèi)存,磁盤IO都是敏感項(xiàng)。特別是內(nèi)存,報(bào)警閾值可能要設(shè)置在50%以下,原因前面也說過,極端情況下一個(gè)隊(duì)列的內(nèi)存占用量可能是兩倍當(dāng)前值。
然后MQ業(yè)務(wù)的監(jiān)控也需要加,比如消息堆積數(shù),unack的消息數(shù),連接數(shù),channel數(shù)等等。RabbitMQ有提供rest api可以拿到這些監(jiān)控。
還有因?yàn)镽abbitMQ對(duì)網(wǎng)絡(luò)分區(qū)非常敏感,所以日志監(jiān)控也要加。RabbitMQ出現(xiàn)網(wǎng)絡(luò)分區(qū)或者觸發(fā)流控都會(huì)在它的運(yùn)行日志中有輸出。
大致就是這么幾點(diǎn)。
Q2:rabbitmq有嘗試結(jié)合netty提高網(wǎng)絡(luò)處理能力?
A2:erlang是actor模型代表,我覺得它的網(wǎng)絡(luò)處理能力也不比netty差的。
聯(lián)系客服