RabbitMQ在Openstack中的使用

RabbitMQ在Openstack中的使用

1. AMQP協(xié)議

??RabbitMQ是Advanced Message Queuing Protocol (AMQP,高級消息隊(duì)列協(xié)議)開放標(biāo)準(zhǔn)的實(shí)現(xiàn),它支持符合標(biāo)準(zhǔn)的客戶端請求程序與符合標(biāo)準(zhǔn)的消息中間件代理進(jìn)行通信。
??AMQP中的核心概念:
??(1) Broker :消息中間件的服務(wù)節(jié)點(diǎn),對于RabbitMQ來說,一個RabbitMQ Broker可以簡單地看作一個RabbitMQ服務(wù)節(jié)點(diǎn),或者RabbitMQ服務(wù)實(shí)例;
??(2) Virtual Host: 虛擬主機(jī),表示一批交換器、消息隊(duì)列和相關(guān)對象。虛擬主機(jī)是共享相同的身份認(rèn)證和加密環(huán)境的獨(dú)立服務(wù)器域。每個 vhost 本質(zhì)上就是一個 mini 版的 RabbitMQ 服務(wù)器,擁有自己的隊(duì)列、交換器、綁定和權(quán)限機(jī)制;
??(3) Producer:生產(chǎn)者,消息投遞方,生產(chǎn)者創(chuàng)建消息,然后發(fā)布到RabbitMQ中;
??(4) Consumer :消費(fèi)者,就是接收消息的一方,消費(fèi)者連接到RabbitMQ服務(wù)器,并訂閱到隊(duì)列上;
??(5) Queue :隊(duì)列,RabbitMQ的內(nèi)部對象,用于存儲消息,RabbitMQ的生產(chǎn)者生產(chǎn)消息并最終投遞到隊(duì)列中,消費(fèi)者可以從隊(duì)列中獲取消息并消費(fèi);
??(6) Exchange:交換器,生產(chǎn)者將消息發(fā)送到Exchange,由交換器將消息路由到一個或者多個隊(duì)列中;
??(7) RoutingKey :路由鍵,生產(chǎn)者將消息發(fā)給交換器的時候,一般會指定一個RoutingKey,用來指定這個消息的路由規(guī)則,生產(chǎn)者可以在發(fā)送消息給交換器時,通過指定RoutingKey來決定消息流向哪里。
??AMQP模型:

圖1-1 AMQP模型

??AMQP協(xié)議消息發(fā)送接收流程:
??(1) 在Producer客戶端建立了Channel后,就建立了到Broker上Virtual Host的連接。接下來Producer就可以向這個Virtual Host中的Exchange發(fā)送消息了;
??(2) Exchange能夠處理消息的前提是:它至少已經(jīng)和某個queue或者另外的Exchange形成了綁定關(guān)系,并設(shè)置好了到這些queue和Excahnge的Routing(路由規(guī)則)。Excahnge中的Routing有四種模式。在Exchange收到消息后,會根據(jù)設(shè)置的Routing(路由規(guī)則),將消息發(fā)送到符合要求的queue或者Exchange中(路由規(guī)則還會和RoutingKey屬性配合使用);
??Excahnge中的Routing的四種模式:
??1)fanout路由模式,fanout路由模式不需要routingKey。當(dāng)設(shè)置為fanout模式的Exchange收到消息后,然后復(fù)制多份,分別發(fā)送到和自己綁定的各個queue中,相當(dāng)于廣播模式。
??2)direct路由模式:

圖1-2 direct路由模式

??以上圖的配置為例,以routingKey=”aa”發(fā)送消息到Exchange,則消息會路由到queueX和queueY,另外路由鍵與隊(duì)列名是完全匹配的,如果一個隊(duì)列綁定到交換機(jī)要求路由鍵為“aa”,不會轉(zhuǎn)發(fā)“aa.bb”,也不會轉(zhuǎn)發(fā)“aa.cc”,它是完全匹配、單播的模式;如果以routingKey=”bb”,則消息只會路由到queueY。
??3)topic路由模式

圖1-3 topic路由模式

??routingKey中可以存在兩種特殊字符“”與“#”,用于做模糊匹配,其中“”用于匹配一個單詞,“#”用于匹配多個單詞(可以是零個),用“.”隔開。以上圖中的配置為例,routingKey=”aa.bb.cc”的消息會同時路由到queueX與queueY,routingKey=”aa.cc.dd”的消息會路由到queueX,routingKey=”cc.dd.ff”的消息會路由到queueY,routingKey=”cc.bb.ee”的消息會路由到queueY(雖然與兩個routingKey都匹配,但只會投遞給一次)。
??4)headers,headers類型的Exchange不依賴于routing key與binding key的匹配規(guī)則來路由消息,而是根據(jù)發(fā)送的消息內(nèi)容中的headers屬性進(jìn)行匹配。(一般不用)
??(3) queue收到消息后,可能會進(jìn)行如下的處理:如果當(dāng)前沒有Consumer的Channel連接到這個queue,那么queue將會把這條消息進(jìn)行存儲直到有Channel被創(chuàng)建;如果已經(jīng)有Channel連接到這個queue,那么消息將會按順序被發(fā)送給這個Channel。
??(4) Consumer收到消息后,就可以進(jìn)行消息的處理了。

2. RabbitMQ實(shí)現(xiàn)RPC請求

??RabbitMQ的RPC的處理流程圖:

圖2-1 RabbitMQ的RPC的處理流程

??RabbitMQ的RPC的處理流程步驟:
??(1) 當(dāng)客戶端啟動時,創(chuàng)建一個匿名的回調(diào)隊(duì)列。
??(2) 客戶端為RPC請求設(shè)置2個屬性:replyTo,設(shè)置回調(diào)隊(duì)列名字;correlationId,標(biāo)記request。
??(3) 請求被發(fā)送到rpc_queue隊(duì)列中。
??(4) RPC服務(wù)器端監(jiān)聽rpc_queue隊(duì)列中的請求,當(dāng)請求到來時,服務(wù)器端會處理并且把帶有結(jié)果的消息發(fā)送給客戶端。接收的隊(duì)列就是replyTo設(shè)定的回調(diào)隊(duì)列。
??(5) 客戶端監(jiān)聽回調(diào)隊(duì)列,當(dāng)有消息時,檢查correlationId屬性,如果與request中匹配,就是結(jié)果了。

3. Oslo_message對RPC的封裝

??而Oslo.messaging庫為OpenStack各個組件使用RPC和事件通知(Event Notification)提供了一套統(tǒng)一的接口,它基于支持 AMQP(Advanced Message Queuing Protocol) 協(xié)議的為同一個項(xiàng)目內(nèi)的各個進(jìn)程之間的通信提供了 API,如 nova-api 和 nova-scheduler 的通信,cinder-api 和 cinder-volume 的通信等。
??下面通過實(shí)例看一下Oslo.messaging庫使用RPC提供的接口。首先,了解一下Oslo.messaging的Transport和Target,Transport(傳輸層)主要實(shí)現(xiàn)RPC底層的通信以及事件循環(huán)、多線程等其他功能,通過URL來獲得指向不同Transport實(shí)現(xiàn)的句柄。URL格式:Transport://user:pass@host1:port[,hostN:portN]/virtual_host,可通過oslo.messaging.get_transport函數(shù)來獲得transport對象實(shí)例的句柄;Target封裝了指定某一個消息最終目的地的所有信息,下表所示為其所具有的屬性:

參數(shù) 說明
exchange topic所屬的范圍,默認(rèn)使用配置文件中的control_exchange選項(xiàng)
topic 一個topic可以用來標(biāo)識服務(wù)器所暴露的一組接口(一個接口包含多個可被遠(yuǎn)程調(diào)用的方法)
namespace 用來標(biāo)識服務(wù)器所暴露的某個特定接口(多個可被遠(yuǎn)程調(diào)用的方法)
version 服務(wù)器所暴露的接口支持M.N類型的版本號
server 客戶端可以指定此參數(shù)來要求消息的目的地是某個特定的服務(wù)器

??在不同的應(yīng)用場景下,構(gòu)造Target對象需要不同的參數(shù):創(chuàng)建一個RPC服務(wù)器時,需要topic和server參數(shù),exchange參數(shù)可選; 指定一個endpoint時,namespace和version是可選的;客戶端發(fā)送消息時,需要topic參數(shù),其他可選。

class ServerControlEndpoint(object):
    target = messaging.Target(namespace='controle', version='2.0')
    
    def __init__(self, server):
        self.server = server

    def stop(self, ctx):
        if self.server:
            self.server.stop()
            
            
class TestEndpoint(object):
    def test(self, ctx, arg):
        return arg
    

transport_url = 'rabbit://user:pass@host1:port/virtual_host'
transport = messaging.get_transport(cfg.CONF, transport_url)
target = messaging.Target(topic='test', server='server1')
endpoints = [
    ServerControlEndpoint(None),
    TestEndpoint(),
]

server = messaging.get_rpc_server(transport, target, endpoints, executor='blocking')

??從上面代碼可以看出,創(chuàng)建rpc server對象之前,需要先創(chuàng)建transport和target對象,使用get_transport()函數(shù)來獲得transport對象的句柄,此處構(gòu)建的Target對象是用來建立RPC Server的,所以需指明topic和server參數(shù),使用get_rpc_server()函數(shù)創(chuàng)建server對象,然后調(diào)用server對象的start方法開始接收遠(yuǎn)程調(diào)用,對于endpoint,一個RPC服務(wù)器可以暴露多個endpoint(本實(shí)例有兩個),每個endpoint包含一組方法,這組方法是可以被客戶端通過某種Transport對象遠(yuǎn)程調(diào)用的,executor是消息構(gòu)造對象,有blocking的阻塞模式和eventlet采用協(xié)程兩種方式,OpenStack采用eventlet。

transport_url = 'rabbit://user:pass@host1:port/virtual_host'
transport = messaging.get_transport(cfg.CONF, transport_url)
target = messaging.Target(topic='test')
client = messaging.RPCClient(transport, target)
client.call(ctxt, 'test', arg=arg)
cctxt = client.prepare(namespace='control', version ='2.0')

??上述代碼是通過RPC Client,遠(yuǎn)程調(diào)用RPC Sever上的方法,調(diào)用方式有cast和call兩種遠(yuǎn)程調(diào)用方式。通過cast方式遠(yuǎn)程調(diào)用,請求發(fā)送后就直接返回了;通過call方式調(diào)用, 需要等響應(yīng)從服務(wù)器返回。

4. RabbitMQ在Openstack應(yīng)用

??下面以創(chuàng)建虛擬機(jī)為例分析一下消息流程:


圖4-1 創(chuàng)建虛擬機(jī)為例分析一下消息流程

??從上圖能夠看出,以nova-api和nova-conductor之間的通信為例,nova-conductor服務(wù)在啟動時會注冊一個RPC server等待處理請求,nova-api發(fā)送創(chuàng)建虛擬機(jī)的rpc請求時會先創(chuàng)建一個topic publisher用于topic發(fā)布,method為build_instance,然后publisher將消息發(fā)送給exchange,exchange再根據(jù)routingkey轉(zhuǎn)發(fā)給綁定的queue中,最后由topic consumer接收并調(diào)用nova-conductor manager中的build_instance方法處理,對于nova-conductor和nova-scheduler之間的通信,多了一步把目標(biāo)主機(jī)作為返回結(jié)果信息返回到reply_xx隊(duì)列中,然后由nova-conductor接收以后向nova-compute發(fā)起rpc.cast的創(chuàng)建請求。
??OpenStack各個組件內(nèi)部的各個服務(wù)進(jìn)程之間則是通過基于AMPQ的RPC方式進(jìn)行通信,實(shí)現(xiàn)RPC通信需借助Rabbitmq消息隊(duì)列,RPC方式又分為兩種,rpc.cast和rpc.call,rpc.call為request/response方式,多用于同步場景;而使用rpc.cast方式發(fā)出請求后則無需一直等待響應(yīng),但之后需要定期查詢執(zhí)行結(jié)果,一般用于異步場景,OpenStack將其使用的通信方式都封裝在公有庫oslo_messaging中。

5. RabbitMQ在Openstack中應(yīng)用常見問題之連接數(shù)

??RabbitMQ每增加一個連接,erlang都會給這個連接分配三個erlang進(jìn)程,每個進(jìn)程都會分配一定大小內(nèi)存空間,所以隨著連接數(shù)的增長,內(nèi)存和erlang進(jìn)程數(shù)呈現(xiàn)有規(guī)律的增長,所以RabbitMQ連接數(shù)的無限增大會壓垮mq服務(wù),導(dǎo)致RabbitMQ服務(wù)崩潰。
??(1) Openstack連接數(shù)增長原因
客戶端與RabbitMQ建立的是長連接,而不是建立短連接,因?yàn)槿绻l繁的建立、銷毀connection,會增加額外的時間開銷,當(dāng)業(yè)務(wù)量比較大時,就會對系統(tǒng)性能產(chǎn)生比較大的影響。OpenStack組件與RabbitMQ的連接使用到了第三方庫oslo_message中的connection pool的概念,在不超過pool size的前提上,當(dāng)有并發(fā)業(yè)務(wù)的時候,如果發(fā)現(xiàn)pool中已有connection正被使用,那么就會在pool中繼續(xù)創(chuàng)建新的connection,直到創(chuàng)建的connection數(shù)量達(dá)到pool的最大值,之后如果再有業(yè)務(wù)需要,會等待之前創(chuàng)建的connection被重新放入connection pool,然后等待被繼續(xù)使用。這種情況下,就會出現(xiàn)connection一直增長的現(xiàn)象。
??(2) Openstack最大連接數(shù)計(jì)算方法:
OpenStack的Connection連接數(shù)包括m臺計(jì)算節(jié)點(diǎn)+n臺控制節(jié)點(diǎn)兩部分之和:

Connection連接數(shù)1(m臺計(jì)算節(jié)點(diǎn)) =    (64+30)* m;
(其中,“m”指的是m臺的計(jì)算節(jié)點(diǎn),“64”指的是每個Nova-Compute進(jìn)程中配置的
最大rpc_conn_pool_size連接數(shù)上限,“30”指的是每個Ovs-agent進(jìn)程中配置的最
大rpc_conn_pool_size連接數(shù)上限);  
Connection連接數(shù)2(n臺控制節(jié)點(diǎn))=     
(nova-api worker進(jìn)程數(shù) + nova-conductor worker進(jìn)程數(shù) )* n * 30 
+ (cinder-api worker進(jìn)程數(shù) + 1 + 1+backend進(jìn)程數(shù))* n * 30
+ (neutron worker進(jìn)程數(shù) + 1 + 1)* 3 * 30;
(其中,“n”指的是n臺控制節(jié)點(diǎn),“30”指的是“nova-api”、“nova-conductor”和“neutron”
三個進(jìn)程中配置的最大rpc_conn_pool_size連接數(shù)上限);  

??綜合上面兩部分,OpenStack的最大Connection連接數(shù) 等于:Connection連接數(shù)1(m臺計(jì)算節(jié)點(diǎn))與 Connection連接數(shù)2(n臺控制節(jié)點(diǎn))之和。此計(jì)算值必須小于RabbitMQ能夠承載的連接數(shù)最大值,才能保證RabbitMQ不被壓垮,正常提供服務(wù)。對此,對RabbitMQ集群(三個節(jié)點(diǎn))做壓力測試能夠得到RabbitMQ集群能夠承載的連接數(shù)最大值。
??使用的測試環(huán)境配置如下:

cpu 32 Intel(R) Xeon(R) CPU E5-2640 v3 @ 2.60GHz
Memory 250GB
OS centos-release-7-2.1511.el7.centos.2.10.x86_64
Kernel 3.10.0-514.1.el7.x86_64
RabbitMQ 3.6.5
Erlang Erlang R16B03-1
壓測工具 PerfTest(RabbitMQ官網(wǎng)推薦)
計(jì)算節(jié)點(diǎn)數(shù) 3
控制節(jié)點(diǎn)數(shù) 3

??測試目的及用例:
利用壓力測試工具PerfTest逐步增加集群的三個節(jié)點(diǎn)的連接數(shù),觀察記錄各節(jié)點(diǎn)的內(nèi)存、連接數(shù)、日志輸出,測試并記錄“批量創(chuàng)建虛擬機(jī)”和“批量刪除虛擬機(jī)”兩個典型的Openstack業(yè)務(wù)場景所需時間。找到當(dāng)虛擬機(jī)的創(chuàng)建/刪除操作受到影響(創(chuàng)建/刪除十分耗時或者不能成功)或者RabbitMQ集群出現(xiàn)異常Error的日志時,從而得到RabbitMQ集群所承受的連接數(shù)即為集群所能承受的最大連接數(shù)。
對三個節(jié)點(diǎn),通過壓力測試工具對每個節(jié)點(diǎn)增加2000個連接,等待5分鐘(等待集群運(yùn)行穩(wěn)定),記錄每個節(jié)點(diǎn)連接數(shù)、內(nèi)存、erlang進(jìn)程數(shù)、消息積壓情況。然后通過腳本批量創(chuàng)建60臺虛擬機(jī),記錄全部創(chuàng)建完成所需時間、全部刪除完成所需時間,記錄nova-compute的狀態(tài)、RabbitMQ日志有無異常輸出。重復(fù)上述步驟,每次各增加2000個連接,直到發(fā)現(xiàn)虛擬機(jī)的創(chuàng)建或刪除耗時超過30分鐘,或者虛擬機(jī)無法成功創(chuàng)建或刪除,則終止測試。然后觀察RabbitMQ集群情況和nova-compute服務(wù)30分鐘,看是否有RabbitMQ異常日志打印和nova-compute由up變?yōu)閐own。
??測試結(jié)果:
??各個節(jié)點(diǎn)狀態(tài)結(jié)果:

節(jié)點(diǎn) 連接數(shù) 集群總連接數(shù)總連接數(shù) 內(nèi)存(G) erlang進(jìn)程數(shù)
Node1 2421 7594 1.7 29293
Node1 4422 13598 2.7 51298
Node1 6423 19601 3.7 73309
Node1 8424 25605 4.7 95312
Node1 10425 31613 6 117321
Node1 11427 34624 6.6 128339
Node1 12483 37713 8 140018
Node2 2448 7594 1.2 29561
Node2 4451 13598 2.1 51587
Node2 6452 19601 3 73587
Node2 8454 25605 3.9 95605
Node2 10460 31613 4.9 117670
Node2 11465 34624 6.3 128721
Node2 12487 37713 7.2 139960
Node3 2724 7594 1.8 32882
Node3 4725 13598 2.7 54889
Node3 6726 19601 3.6 76896
Node3 8727 25605 4.5 98903
Node3 10728 31613 5.4 120910
Node3 11731 34624 5.8 131950
Node3 12743 37713 6.9 143078

??虛機(jī)創(chuàng)建和節(jié)點(diǎn)狀態(tài)相關(guān)結(jié)果:

總連接數(shù) 消息積壓 創(chuàng)60臺虛機(jī)時間 刪20臺虛機(jī)時間 nova-compute狀態(tài) MQ日志
7594 3min 1min20s up 未出現(xiàn)異常日志
13598 2min49S 1min10s up 未出現(xiàn)異常日志
19601 2min50s 1min30s up 未出現(xiàn)異常日志
25605 2min56s 1min31s up 未出現(xiàn)異常日志
31613 4min37s 2min up 未出現(xiàn)異常日志
34624 6min30s但是有4臺創(chuàng)建失敗,原因是"Build of instance was re-scheduled: Request to http://ip:port/v2.0/ports.json timed out (HTTP 408) 2min20s up 未出現(xiàn)異常日志
37713 14000左右 15分鐘后。30臺成功,30臺失敗 無法刪除 一段時間后變?yōu)閐own 出現(xiàn)大量missed heartbeats from client, timeout: 60s

??各個節(jié)點(diǎn)及集群統(tǒng)計(jì)圖的趨勢變化:

圖5-1 node1測試結(jié)果
圖5-2 node2測試結(jié)果
圖5-3 node3測試結(jié)果

圖5-4 集群測試結(jié)果

??測試結(jié)論:
??1) 在集群連接數(shù)到達(dá)2.5w以上的時候,創(chuàng)建虛機(jī)和刪除虛機(jī)的耗時出現(xiàn)了明顯的增長;
??2) 集群達(dá)到3.5w以上時,出現(xiàn)的大量的消息堆積,說明連接數(shù)的增加,影響了客戶端對消息的消費(fèi);
??3) 集群增加到3.7w連接數(shù)的時候,客戶端大量丟失心跳,嘗試重新連接失敗,nova-compute節(jié)點(diǎn)的狀態(tài)全部為down,反觀內(nèi)存,socket,erlang等指標(biāo)并不高,沒有達(dá)到上限,從這里可以說明集群承載的連接數(shù)的能力是有上限的,不能夠單純的從它占用的系統(tǒng)資源來判定;
??4)測試得出該集群連接數(shù)最好低于3.5w。

6. 小結(jié)

??本篇文章對AMQP協(xié)議的基本概念以及框架流程做了詳細(xì)的介紹,RabbitMQ使用此協(xié)議能夠很好的實(shí)現(xiàn)RPC請求,Oslo_message對RPC進(jìn)行的封裝能夠使用起來更加優(yōu)雅,然后以創(chuàng)建虛擬機(jī)為例分析了RabbitMQ在Openstack中如何應(yīng)用,針對諸多的RabbitMQ應(yīng)用問題,選取了連接數(shù)問題進(jìn)行了測試并得出結(jié)論,后續(xù)會針對RabbitMQ應(yīng)用的其他問題做出更深入的研究。

最后編輯于
?著作權(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ù)。

相關(guān)閱讀更多精彩內(nèi)容

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