01. 既 REST ,何 RPC ?
在 OpenStack 里的進(jìn)程間通信方式主要有兩種,一種是基于HTTP協(xié)議的RESTFul API方式,另一種則是RPC調(diào)用。
那么這兩種方式在應(yīng)用場景上有何區(qū)別呢?
有使用經(jīng)驗的人,就會知道:
- 前者(RESTful)主要用于各組件之間的通信(如nova與glance的通信),或者說用于組件對外提供調(diào)用接口
- 而后者(RPC)則用于同一組件中各個不同模塊之間的通信(如nova組件中nova-compute與nova-scheduler的通信)。
基于RESTful API的通信方式主要是應(yīng)用了WSGI,這個知識點,我在前一篇文章中,有深入地講解過,你可以點擊查看 。
按照慣例,我會先給你提兩個問題,帶著這兩個問題你再往下看:
1、RPC 和 REST 區(qū)別是什么?
2、為什么要采用RPC呢?
首先,第一個問題:RPC 和 REST 區(qū)別是什么?
你一定會覺得這個問題很奇怪,是的,包括我,但是你在網(wǎng)絡(luò)上一搜,會發(fā)現(xiàn)類似對比的文章比比皆是,我在想可能很多初學(xué)者由于基礎(chǔ)不牢固,才會將不相干的二者拿出來對比吧。既然是這樣,那為了讓你更加了解陌生的RPC,就從你熟悉得不能再熟悉的 REST 入手吧。
01、所屬類別不同
REST,是Representational State Transfer 的簡寫,中文描述表述性狀態(tài)傳遞(是指某個瞬間狀態(tài)的資源數(shù)據(jù)的快照,包括資源數(shù)據(jù)的內(nèi)容、表述格式(XML、JSON)等信息。)
REST 是一種軟件架構(gòu)風(fēng)格。這種風(fēng)格的典型應(yīng)用,就是HTTP。其因為簡單、擴(kuò)展性強(qiáng)的特點而廣受開發(fā)者的青睞。
而RPC 呢,是 Remote Procedure Call Protocol 的簡寫,中文描述是遠(yuǎn)程過程調(diào)用,它可以實現(xiàn)客戶端像調(diào)用本地服務(wù)(方法)一樣調(diào)用服務(wù)器的服務(wù)(方法)。
而 RPC 可以基于 TCP/UDP,也可以基于 HTTP 協(xié)議進(jìn)行傳輸?shù)模蠢碚f它和REST不是一個層面意義上的東西,不應(yīng)該放在一起討論,但是誰讓REST這么流行呢,它是目前最流行的一套互聯(lián)網(wǎng)應(yīng)用程序的API設(shè)計標(biāo)準(zhǔn),某種意義下,我們說 REST 可以其實就是指代 HTTP 協(xié)議。
02、使用方式不同
從使用上來看 ,HTTP 接口只關(guān)注服務(wù)提供方,對于客戶端怎么調(diào)用并不關(guān)心。接口只要保證有客戶端調(diào)用時,返回對應(yīng)的數(shù)據(jù)就行了。而RPC則要求客戶端接口保持和服務(wù)端的一致。
- REST 是服務(wù)端把方法寫好,客戶端并不知道具體方法。客戶端只想獲取資源,所以發(fā)起HTTP請求,而服務(wù)端接收到請求后根據(jù)URI經(jīng)過一系列的路由才定位到方法上面去
- RPC是服務(wù)端提供好方法給客戶端調(diào)用,客戶端需要知道服務(wù)端的具體類,具體方法,然后像調(diào)用本地方法一樣直接調(diào)用它。
03、面向?qū)ο蟛煌?/strong>
從設(shè)計上來看,RPC,所謂的遠(yuǎn)程過程調(diào)用 ,是面向方法的 ,REST:所謂的 Representational state transfer ,是面向資源的,除此之外,還有一種叫做 SOA,所謂的面向服務(wù)的架構(gòu),它是面向消息的,這個接觸不多,就不多說了。
04、序列化協(xié)議不同
接口調(diào)用通常包含兩個部分,序列化和通信協(xié)議。
通信協(xié)議,上面已經(jīng)提及了,REST 是 基于 HTTP 協(xié)議,而 RPC 可以基于 TCP/UDP,也可以基于 HTTP 協(xié)議進(jìn)行傳輸?shù)摹?/p>
常見的序列化協(xié)議,有:json、xml、hession、protobuf、thrift、text、bytes等,REST 通常使用的是 JSON或者XML,而 RPC 使用的是 JSON-RPC,或者 XML-RPC。
通過以上幾點,我們知道了 REST 和 RPC 之間有很明顯的差異。
然后第二個問題:為什么要采用RPC呢?
那到底為何要使用 RPC,單純的依靠RESTful API不可以嗎?為什么要搞這么多復(fù)雜的協(xié)議,渣渣表示真的學(xué)不過來了。
關(guān)于這一點,以下幾點僅是我的個人猜想,僅供交流哈:
- RPC 和 REST 兩者的定位不同,REST 面向資源,更注重接口的規(guī)范,因為要保證通用性更強(qiáng),所以對外最好通過 REST。而 RPC 面向方法,主要用于函數(shù)方法的調(diào)用,可以適合更復(fù)雜通信需求的場景。
- RESTful API客戶端與服務(wù)端之間采用的是同步機(jī)制,當(dāng)發(fā)送HTTP請求時,客戶端需要等待服務(wù)端的響應(yīng)。當(dāng)然對于這一點是可以通過一些技術(shù)來實現(xiàn)異步的機(jī)制的。
- 采用RESTful API,客戶端與服務(wù)端之間雖然可以獨立開發(fā),但還是存在耦合。比如,客戶端在發(fā)送請求的時,必須知道服務(wù)器的地址,且必須保證服務(wù)器正常工作。而 rpc + ralbbimq中間件可以實現(xiàn)低耦合的分布式集群架構(gòu)。
說了這么多,我們該如何選擇這兩者呢?我總結(jié)了如下兩點,供你參考:
- REST 接口更加規(guī)范,通用適配性要求高,建議對外的接口都統(tǒng)一成 REST(我接觸過 zabbix,其 API 是基于 JSON-RPC 2.0協(xié)議的)。而組件內(nèi)部的各個模塊,可以選擇 RPC,一個是不用耗費(fèi)太多精力去開發(fā)和維護(hù)多套的HTTP接口,一個RPC的調(diào)用性能更高(見下條)
- 從性能角度看,由于HTTP本身提供了豐富的狀態(tài)功能與擴(kuò)展功能,但也正由于HTTP提供的功能過多,導(dǎo)致在網(wǎng)絡(luò)傳輸時,需要攜帶的信息更多,從性能角度上講,較為低效。而RPC服務(wù)網(wǎng)絡(luò)傳輸上僅傳輸與業(yè)務(wù)內(nèi)容相關(guān)的數(shù)據(jù),傳輸數(shù)據(jù)更小,性能更高。
02. 實現(xiàn)遠(yuǎn)程調(diào)用的三種方式
“遠(yuǎn)程調(diào)用”意思就是:被調(diào)用方法的具體實現(xiàn)不在程序運(yùn)行本地,而是在別的某個地方(分布到各個服務(wù)器),調(diào)用者只想要函數(shù)運(yùn)算的結(jié)果,卻不需要實現(xiàn)函數(shù)的具體細(xì)節(jié)。
光說不練嘴把式,接下來,我將分別用三種不同的方式全面地讓你搞明白 rpc 遠(yuǎn)程調(diào)用是如何實現(xiàn)的。
01、基于 xml-rpc
Python實現(xiàn) rpc,可以使用標(biāo)準(zhǔn)庫里的 SimpleXMLRPCServer,它是基于XML-RPC 協(xié)議的。
有了這個模塊,開啟一個 rpc server,就變得相當(dāng)簡單了。執(zhí)行以下代碼:
import SimpleXMLRPCServer
class calculate:
def add(self, x, y):
return x + y
def multiply(self, x, y):
return x * y
def subtract(self, x, y):
return abs(x-y)
def divide(self, x, y):
return x/y
obj = calculate()
server = SimpleXMLRPCServer.SimpleXMLRPCServer(("localhost", 8088))
# 將實例注冊給rpc server
server.register_instance(obj)
print "Listening on port 8088"
server.serve_forever()
有了 rpc server,接下來就是 rpc client,由于我們上面使用的是 XML-RPC,所以 rpc clinet 需要使用xmlrpclib 這個庫。
import xmlrpclib
server = xmlrpclib.ServerProxy("http://localhost:8088")
然后,我們通過 server_proxy 對象就可以遠(yuǎn)程調(diào)用之前的rpc server的函數(shù)了。
> > server.add(2, 3)
5
>> > server.multiply(2, 3)
6
>> > server.subtract(2, 3)
1
>> > server.divide(2, 3)
0
SimpleXMLRPCServer是一個單線程的服務(wù)器。這意味著,如果幾個客戶端同時發(fā)出多個請求,其它的請求就必須等待第一個請求完成以后才能繼續(xù)。
若非要使用 SimpleXMLRPCServer 實現(xiàn)多線程并發(fā),其實也不難。只要將代碼改成如下即可。
from SimpleXMLRPCServer import SimpleXMLRPCServer
from SocketServer import ThreadingMixIn
class ThreadXMLRPCServer(ThreadingMixIn, SimpleXMLRPCServer):pass
class MyObject:
def hello(self):
return "hello xmlprc"
obj = MyObject()
server = ThreadXMLRPCServer(("localhost", 8088), allow_none=True)
server.register_instance(obj)
print "Listening on port 8088"
server.serve_forever()
02、基于json-rpc
SimpleXMLRPCServer 是基于 xml-rpc 實現(xiàn)的遠(yuǎn)程調(diào)用,上面我們也提到 除了 xml-rpc 之外,還有 json-rpc 協(xié)議。
那 python 如何實現(xiàn)基于 json-rpc 協(xié)議呢?
答案是很多,很多web框架其自身都自己實現(xiàn)了json-rpc,但我們要獨立這些框架之外,要尋求一種較為干凈的解決方案,我查找到的選擇有兩種
第一種是 jsonrpclib
pip install jsonrpclib -i https://pypi.douban.com/simple
第二種是 python-jsonrpc
pip install python-jsonrpc -i https://pypi.douban.com/simple
先來看第一種 jsonrpclib
它與 Python 標(biāo)準(zhǔn)庫的 SimpleXMLRPCServer 很類似(因為它的類名就叫做 SimpleJSONRPCServer ,不明真相的人真以為它們是親兄弟)。或許可以說,jsonrpclib 就是仿照 SimpleXMLRPCServer 標(biāo)準(zhǔn)庫來進(jìn)行編寫的。
它的導(dǎo)入與 SimpleXMLRPCServer 略有不同,因為SimpleJSONRPCServer分布在jsonrpclib庫中。
服務(wù)端
from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer
server = SimpleJSONRPCServer(('localhost', 8080))
server.register_function(lambda x,y: x+y, 'add')
server.serve_forever()
客戶端
import jsonrpclib
server = jsonrpclib.Server("http://localhost:8080")
再來看第二種python-jsonrpc,寫起來貌似有些復(fù)雜。
服務(wù)端
import pyjsonrpc
class RequestHandler(pyjsonrpc.HttpRequestHandler):
@pyjsonrpc.rpcmethod
def add(self, a, b):
"""Test method"""
return a + b
http_server = pyjsonrpc.ThreadingHttpServer(
server_address=('localhost', 8080),
RequestHandlerClass=RequestHandler
)
print "Starting HTTP server ..."
print "URL: http://localhost:8080"
http_server.serve_forever()
客戶端
import pyjsonrpc
http_client = pyjsonrpc.HttpClient(
url="http://localhost:8080/jsonrpc"
)
調(diào)用過程如下
還記得上面我提到過的 zabbix API,因為我有接觸過,所以也拎出來講講。zabbix API 也是基于 json-rpc 2.0協(xié)議實現(xiàn)的。
因為內(nèi)容較多,這里只帶大家打個,zabbix 是如何調(diào)用的:直接指明要調(diào)用 zabbix server 的哪個方法,要傳給這個方法的參數(shù)有哪些。
03、基于 zerorpc
以上介紹的兩種rpc遠(yuǎn)程調(diào)用方式,如果你足夠細(xì)心,可以發(fā)現(xiàn)他們都是http+rpc 兩種協(xié)議結(jié)合實現(xiàn)的。
接下來,我們要介紹的這種(zerorpc),就不再使用走 http 了。
zerorpc 這個第三方庫,它是基于TCP協(xié)議、 ZeroMQ 和 MessagePack的,速度相對快,響應(yīng)時間短,并發(fā)高。zerorpc 和 pyjsonrpc 一樣,需要額外安裝,雖然SimpleXMLRPCServer不需要額外安裝,但是SimpleXMLRPCServer性能相對差一些。
pip install zerorpc -i https://pypi.douban.com/simple
服務(wù)端代碼
import zerorpc
class caculate(object):
def hello(self, name):
return 'hello, {}'.format(name)
def add(self, x, y):
return x + y
def multiply(self, x, y):
return x * y
def subtract(self, x, y):
return abs(x-y)
def divide(self, x, y):
return x/y
s = zerorpc.Server(caculate())
s.bind("tcp://0.0.0.0:4242")
s.run()
客戶端
import zerorpc
c = zerorpc.Client()
c.connect("tcp://127.0.0.1:4242")
調(diào)用過程如下
客戶端除了可以使用zerorpc框架實現(xiàn)代碼調(diào)用之外,它還支持使用“命令行”的方式調(diào)用。
客戶端可以使用命令行,那服務(wù)端是不是也可以呢?
是的,通過 Github 上的文檔幾個 demo 可以體驗到這個第三方庫做真的是優(yōu)秀。
比如我們可以用下面這個命令,創(chuàng)建一個rpc server,后面這個 time
Python 標(biāo)準(zhǔn)庫中的 time 模塊,zerorpc 會將 time 注冊綁定以供client調(diào)用。
zerorpc --server --bind tcp://127.0.0.1:1234 time
在客戶端,就可以用這條命令來遠(yuǎn)程調(diào)用這個 time 函數(shù)。
zerorpc --client --connect tcp://127.0.0.1:1234 strftime %Y/%m/%d
調(diào)用過程如下
03. 往rpc中引入消息中間件
經(jīng)過了上面的學(xué)習(xí),我們已經(jīng)學(xué)會了如何使用多種方式實現(xiàn)rpc遠(yuǎn)程調(diào)用。
通過對比,zerorpc 可以說是脫穎而出,一支獨秀。
但為何在 OpenStack 中,rpc client 不直接 rpc 調(diào)用 rpc server ,而是先把 rpc 調(diào)用請求發(fā)給 RabbitMQ ,再由訂閱者(rpc server)來取消息,最終實現(xiàn)遠(yuǎn)程調(diào)用呢?
為此,我也做了一番思考:
OpenStack 組件繁多,在一個較大的集群內(nèi)部每個組件內(nèi)部通過rpc通信頻繁,如果都采用rpc直連調(diào)用的方式,連接數(shù)會非常地多,開銷大,若有些 server 是單線程的模式,超時會非常的嚴(yán)重。
OpenStack 是復(fù)雜的分布式集群架構(gòu),會有多個 rpc server 同時工作,假設(shè)有 server01,server02,server03 三個server,當(dāng) rpc client 要發(fā)出rpc請求時,發(fā)給哪個好呢?這是問題一。
你可能會說輪循或者隨機(jī),這樣對大家都公平。這樣的話還會引出另一個問題,倘若請求剛好發(fā)到server01,而server01剛好不湊巧,可能由于機(jī)器或者其他因為導(dǎo)致服務(wù)沒在工作,那這個rpc消息可就直接失敗了呀。要知道做為一個集群,高可用是基本要求,如果出現(xiàn)剛剛那樣的情況其實是很尷尬的。這是問題二。
集群有可能根據(jù)實際需要擴(kuò)充節(jié)點數(shù)量,如果使用直接調(diào)用,耦合度太高,不利于部署和生產(chǎn)。這是問題三。
引入消息中間件,可以很好的解決這些問題。
解決問題一 :消息只有一份,接收者由AMQP的負(fù)載算法決定,默認(rèn)為在所有Receiver中均勻發(fā)送(round robin)。
解決問題二 :有了消息中間件做緩沖站,client 可以任性隨意的發(fā),server 都掛掉了?沒有關(guān)系,等 server 正常工作后,自己來消息中間件取就行了。
解決問題三 :無論有多少節(jié)點,它們只要認(rèn)識消息中間件這一個中介就足夠了。
04. 消息隊列你應(yīng)該知道什么?
既然講到了消息隊列,如果你之前沒有接觸過這塊內(nèi)容,最好花幾分鐘的時間跟我好好過下關(guān)于消息隊列的幾個基礎(chǔ)概念。
首先,RPC只是定義了一個通信接口,其底層的實現(xiàn)可以各不相同,可以是 socket,也可以是今天要講的 AMQP。
AMQP(Advanced Message Queuing Protocol)是一種基于隊列的可靠消息服務(wù)協(xié)議,作為一種通信協(xié)議,AMQP同樣存在多個實現(xiàn),如Apache Qpid,RabbitMQ等。
以下是 AMQP 中的幾個必知的概念:
Publisher :消息發(fā)布者
Receiver :消息接收者,在RabbitMQ中叫訂閱者:Subscriber。
Queue :用來保存消息的存儲空間,消息沒有被receiver前,保存在隊列中。
Exchange :用來接收Publisher發(fā)出的消息,根據(jù)Routing key 轉(zhuǎn)發(fā)消息到對應(yīng)的Message Queue中,至于轉(zhuǎn)到哪個隊列里,這個路由算法又由exchange type決定的。
Exchange type :主要四種描述exchange的類型。
direct:消息路由到滿足此條件的隊列中(queue,可以有多個):routing key = binding key
topic:消息路由到滿足此條件的隊列中(queue,可以有多個):routing key 匹配 binding pattern. binding pattern是類似正則表達(dá)式的字符串,可以滿足復(fù)雜的路由條件。
fanout:消息路由到多有綁定到該exchange的隊列中。
**binding ** :binding是用來描述exchange和queue之間的關(guān)系的概念,一個exchang可以綁定多個隊列,這些關(guān)系由binding建立。前面說的binding key /binding pattern也是在binding中給出。
在網(wǎng)上找了個圖,可以很清晰地描述幾個名詞的關(guān)系。
關(guān)于AMQP,有幾下幾點值得注意:
- 每個receiver/subscriber 在接收消息前都需要創(chuàng)建binding。
- 一個隊列可以有多個receiver,隊列里的一個消息只能發(fā)給一個receiver。
- 一個消息可以被發(fā)送到一個隊列中,也可以被發(fā)送到多個多列中。多隊列情況下,一個消息可以被多個receiver收到并處理。Openstack RPC中這兩種情況都會用到。
05. 生產(chǎn)中是如何使用RPC的?
前面鋪墊了那么久,終于到了講真實應(yīng)用的場景。在生產(chǎn)中RPC是如何應(yīng)用的呢?
其他模型我不太清楚,在 OpenStack 中的應(yīng)用模型是這樣的
至于為什么要如此設(shè)計,前面我已經(jīng)給出了自己的觀點。
接下來,就是源碼解讀 OpenStack ,看看其是如何通過rpc進(jìn)行遠(yuǎn)程調(diào)用的。如若你對此沒有興趣( 我知道很多人對此都沒有興趣,所以不浪費(fèi)大家時間 ), 可以直接跳過這一節(jié),進(jìn)入下一節(jié) 。
目前Openstack中有兩種RPC實現(xiàn),一種是在oslo messaging,一種是在openstack.common.rpc。
openstack.common.rpc是舊的實現(xiàn),oslo messaging是對openstack.common.rpc的重構(gòu)。openstack.common.rpc在每個項目中都存在一份拷貝,oslo messaging即將這些公共代碼抽取出來,形成一個新的項目。oslo messaging也對RPC API 進(jìn)行了重新設(shè)計,對多種 transport 做了進(jìn)一步封裝,底層也是用到了kombu這個AMQP庫。(注:Kombu 是Python中的messaging庫。Kombu旨在通過為AMQ協(xié)議提供慣用的高級接口,使Python中的消息傳遞盡可能簡單,并為常見的消息傳遞問題提供經(jīng)過驗證和測試的解決方案。)
關(guān)于oslo_messaging庫,主要提供了兩種獨立的API:
- oslo.messaging.rpc(實現(xiàn)了客戶端-服務(wù)器遠(yuǎn)程過程調(diào)用)
- oslo.messaging.notify(實現(xiàn)了事件的通知機(jī)制)
因為 notify 實現(xiàn)是太簡單了,所以這里我就不多說了,如果有人想要看這方面內(nèi)容,可以收藏我的博客(http://python-online.cn) ,我會更新補(bǔ)充 notify 的內(nèi)容。
OpenStack RPC 模塊提供了 rpc.call,rpc.cast, rpc.fanout_cast 三種 RPC 調(diào)用方法,發(fā)送和接收 RPC 請求。
- rpc.call 發(fā)送 RPC 同步請求并返回請求處理結(jié)果。
- rpc.cast 發(fā)送 RPC 異步請求,與 rpc.call 不同之處在于,不需要請求處理結(jié)果的返回。
rpc.call 和 .rpc.cast 從實現(xiàn)代碼上看,他們的區(qū)別很小,就是call調(diào)用時候會帶有wait_for_reply=True參數(shù),而cast不帶。
要了解 rpc 的調(diào)用機(jī)制呢,首先要知道 oslo_messaging 的幾個概念主要方法有四個:
transport :RPC功能的底層實現(xiàn)方法,這里是rabbitmq的消息隊列的訪問路徑
transport 就是定義你如何訪連接消息中間件,比如你使用的是 Rabbitmq,那在 nova.conf中應(yīng)該有一行transport_url
的配置,可以很清楚地看出指定了 rabbitmq 為消息中間件,并配置了連接rabbitmq的user,passwd,主機(jī),端口。
transport_url=rabbit://user:passwd@host:5672
def get_transport(conf, url=None, allowed_remote_exmods=None):
return _get_transport(conf, url, allowed_remote_exmods,
transport_cls=RPCTransport)
target :指定RPC topic交換機(jī)的匹配信息和綁定主機(jī)。
target用來表述 RPC 服務(wù)器監(jiān)聽topic,server名稱和server監(jiān)聽的exchange,是否廣播fanout。
class Target(object):
def __init__(self, exchange=None, topic=None, namespace=None,
version=None, server=None, fanout=None,
legacy_namespaces=None):
self.exchange = exchange
self.topic = topic
self.namespace = namespace
self.version = version
self.server = server
self.fanout = fanout
self.accepted_namespaces = [namespace] + (legacy_namespaces or [])
rpc server 要獲取消息,需要定義target,就像一個門牌號一樣。
rpc client 要發(fā)送消息,也需要有target,說明消息要發(fā)到哪去。
endpoints:是可供別人遠(yuǎn)程調(diào)用的對象
RPC服務(wù)器暴露出endpoint,每個 endpoint 包涵一系列的可被遠(yuǎn)程客戶端通過 transport 調(diào)用的方法。直觀理解,可以參考nova-conductor創(chuàng)建rpc server的代碼,這邊的endpoints就是 nova/manager.py:ConductorManager()
dispatcher :分發(fā)器,這是 rpc server 才有的概念
只有通過它 server 端才知道接收到的rpc請求,要交給誰處理,怎么處理?
在client端,是這樣指定要調(diào)用哪個方法的。
而在server端,是如何知道要執(zhí)行這個方法的呢?這就是dispatcher 要干的事,它從 endpoint 里找到這個方法,然后執(zhí)行,最后返回。
Serializer :在 python 對象和message(notification) 之間數(shù)據(jù)做序列化或是反序列化的基類。
主要方法有四個:
1. deserialize_context(ctxt) :對字典變成 request contenxt.
2. deserialize_entity(ctxt, entity) :對entity做反序列化,其中ctxt是已經(jīng)deserialize過的,entity是要處理的。
3. serialize_context(ctxt) :將Request context變成字典類型
4. serialize_entity(ctxt, entity) :對entity做序列化,其中ctxt是已經(jīng)deserialize過的,entity是要處理的。
executor :服務(wù)的運(yùn)行方式,單線程或者多線程
每個notification listener都和一個executor綁定,來控制收到的notification如何分配。默認(rèn)情況下,使用的是blocking executor(具體特性參加executor一節(jié))
oslo_messaging.get_notification_listener(transport, targets, endpoints, executor=’blocking’, serializer=None, allow_requeue=False, pool=None)
rpc server 和rpc client 的四個重要方法
reset()
:Reset service.start()
:該方法調(diào)用后,server開始poll,從transport中接收message,然后轉(zhuǎn)發(fā)給dispatcher.該message處理過程一直進(jìn)行,直到stop方法被調(diào)用。executor決定server的IO處理策略。可能會是用一個新進(jìn)程、新協(xié)程來做poll操作,或是直接簡單的在一個循環(huán)中注冊一個回調(diào)。同樣,executor也決定分配message的方式,是在一個新線程中dispatch或是…..stop()
:當(dāng)調(diào)用stop之后,新的message不會被處理。但是,server可能還在處理一些之前沒有處理完的message,并且底層driver資源也還一直沒有釋放。wait()
:在stop調(diào)用之后,可能還有message正在被處理,使用wait方法來阻塞當(dāng)前進(jìn)程,直到所有的message都處理完成。之后,底層的driver資源會釋放。
06. 模仿OpenStack寫 rpc 調(diào)用
模仿是一種很高效的學(xué)習(xí)方法,我這里根據(jù) OpenStack 的調(diào)用方式,抽取出核心內(nèi)容,寫成一個簡單的 demo,有對 OpenStack 感興趣的可以了解一下, 大部分人也可以直接跳過這章節(jié) 。
注意以下代碼不能直接運(yùn)行,你還需要配置 rabbitmq 的連接方式,你可以寫在配置文件中,通過 get_transport 從cfg.CONF 中讀取,也可以直接將其寫成url的格式做成參數(shù),傳給 get_transport 。而且還要nova或者其他openstack組件的環(huán)境中運(yùn)行(因為需要有ctxt這個環(huán)境變量)
簡單的 rpc client
#coding=utf-8
import oslo_messaging
from oslo_config import cfg
# 創(chuàng)建 rpc client
transport = oslo_messaging.get_transport(cfg.CONF, url="")
target = oslo_messaging.Target(topic='test', version='2.0')
client = oslo_messaging.RPCClient(transport, target)
# rpc同步調(diào)用
client.call(ctxt, 'test', arg=arg)
簡單的 rpc server
#coding=utf-8
from oslo_config import cfg
import oslo_messaging
import time
# 定義endpoint類
class ServerControlEndpoint(object):
target = oslo_messaging.Target(namespace='control',
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
# 創(chuàng)建rpc server
transport = oslo_messaging.get_transport(cfg.CONF, url="")
target = oslo_messaging.Target(topic='test', server='server1')
endpoints = [
ServerControlEndpoint(None),
TestEndpoint(),
]
server = oslo_messaging.get_rpc_server(transport, target,endpoints,executor='blocking')
try:
server.start()
while True:
time.sleep(1)
except KeyboardInterrupt:
print("Stopping server")
server.stop()
server.wait()
以上,就是本期推送的全部內(nèi)容,周末兩天沒有出門,都在寫這篇文章。真的快掏空了我自己,不過寫完后真的很暢快。
-
接口
+關(guān)注
關(guān)注
33文章
8696瀏覽量
151934 -
數(shù)據(jù)
+關(guān)注
關(guān)注
8文章
7145瀏覽量
89596 -
RPC
+關(guān)注
關(guān)注
0文章
111瀏覽量
11578 -
REST
+關(guān)注
關(guān)注
0文章
33瀏覽量
9448
發(fā)布評論請先 登錄
相關(guān)推薦
評論