真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

RPC遠(yuǎn)程調(diào)用該如何理解

本篇文章為大家展示了RPC遠(yuǎn)程調(diào)用該如何理解,內(nèi)容簡(jiǎn)明扼要并且容易理解,絕對(duì)能使你眼前一亮,通過(guò)這篇文章的詳細(xì)介紹希望你能有所收獲。

我們提供的服務(wù)有:做網(wǎng)站、成都網(wǎng)站制作、微信公眾號(hào)開(kāi)發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、隆陽(yáng)ssl等。為超過(guò)千家企事業(yè)單位解決了網(wǎng)站和推廣的問(wèn)題。提供周到的售前咨詢和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的隆陽(yáng)網(wǎng)站制作公司

今天我又給自己挖坑了,打算將 rpc 遠(yuǎn)程調(diào)用的知識(shí),好好地梳理一下,花了周末整整兩天的時(shí)間。

什么是RPC呢?

百度百科給出的解釋是這樣的:“RPC(Remote Procedure Call Protocol)——遠(yuǎn)程過(guò)程調(diào)用協(xié)議,它是一種通過(guò)網(wǎng)絡(luò)從遠(yuǎn)程計(jì)算機(jī)程序上請(qǐng)求服務(wù),而不需要了解底層網(wǎng)絡(luò)技術(shù)的協(xié)議”。這個(gè)概念聽(tīng)起來(lái)還是比較抽象,沒(méi)關(guān)系,繼續(xù)往后看,后面概念性的東西,我會(huì)講得足夠清楚,讓你完全掌握 RPC 的基礎(chǔ)內(nèi)容。在后面的篇章中還會(huì)結(jié)合其在 OpenStack 中實(shí)際應(yīng)用,一步一步揭開(kāi) rpc 的神秘面紗。

## 01\. 既 REST,何 RPC ?

在 OpenStack 里的進(jìn)程間通信方式主要有兩種,一種是基于HTTP協(xié)議的RESTFul API方式,另一種則是RPC調(diào)用。

那么這兩種方式在應(yīng)用場(chǎng)景上有何區(qū)別呢?

有使用經(jīng)驗(yàn)的人,就會(huì)知道:

*   前者(RESTful)主要用于**各組件之間**的通信(如nova與glance的通信),或者說(shuō)用于組件對(duì)外提供調(diào)用接口
*   而后者(RPC)則用于**同一組件中各個(gè)不同模塊之間**的通信(如nova組件中nova-compute與nova-scheduler的通信)。

關(guān)于OpenStack中基于RESTful API的通信方式主要是應(yīng)用了WSGI,這個(gè)知識(shí)點(diǎn),我在前一篇文章中,有深入地講解過(guò),你可以點(diǎn)擊查看。

對(duì)于不熟悉 OpenStack 的人,也別擔(dān)心聽(tīng)不懂,這樣吧,我給你提兩個(gè)問(wèn)題:

1.  RPC 和 REST 區(qū)別是什么?
2.  為什么要采用RPC呢?

**第一個(gè)問(wèn)題:RPC 和 REST 區(qū)別是什么?**

你一定會(huì)覺(jué)得這個(gè)問(wèn)題很奇怪,是的,包括我,但是你在網(wǎng)絡(luò)上一搜,會(huì)發(fā)現(xiàn)類似對(duì)比的文章比比皆是,我在想可能很多初學(xué)者由于基礎(chǔ)不牢固,才會(huì)將不相干的二者拿出來(lái)對(duì)比吧。既然是這樣,那為了讓你更加了解陌生的RPC,就從你熟悉得不能再熟悉的 REST 入手吧。

**01、所屬類別不同**

REST,是Representational State Transfer 的簡(jiǎn)寫(xiě),中文描述表述性狀態(tài)傳遞(是指某個(gè)瞬間狀態(tài)的資源數(shù)據(jù)的快照,包括資源數(shù)據(jù)的內(nèi)容、表述格式(XML、JSON)等信息。)

REST 是一種軟件架構(gòu)風(fēng)格。 這種風(fēng)格的典型應(yīng)用,就是HTTP。其因?yàn)楹?jiǎn)單、擴(kuò)展性強(qiáng)的特點(diǎn)而廣受開(kāi)發(fā)者的青睞。

而RPC 呢,是 Remote Procedure Call Protocol 的簡(jiǎn)寫(xiě),中文描述是遠(yuǎn)程過(guò)程調(diào)用,它可以實(shí)現(xiàn)客戶端像調(diào)用本地服務(wù)(方法)一樣調(diào)用服務(wù)器的服務(wù)(方法)。

RPC 是一種基于 TCP 的通信協(xié)議,按理說(shuō)它和REST不是一個(gè)層面上的東西,不應(yīng)該放在一起討論,但是誰(shuí)讓REST這么流行呢,它是目前最流行的一套互聯(lián)網(wǎng)應(yīng)用程序的API設(shè)計(jì)標(biāo)準(zhǔn),某種意義下,我們說(shuō) REST 可以其實(shí)就是指代 HTTP 協(xié)議。

**02、使用方式不同**

**從使用上來(lái)看**,HTTP 接口只關(guān)注服務(wù)提供方,對(duì)于客戶端怎么調(diào)用并不關(guān)心。接口只要保證有客戶端調(diào)用時(shí),返回對(duì)應(yīng)的數(shù)據(jù)就行了。而RPC則要求客戶端接口保持和服務(wù)端的一致。

*   REST 是服務(wù)端把方法寫(xiě)好,客戶端并不知道具體方法。客戶端只想獲取資源,所以發(fā)起HTTP請(qǐng)求,而服務(wù)端接收到請(qǐng)求后根據(jù)URI經(jīng)過(guò)一系列的路由才定位到方法上面去
*   PRC是服務(wù)端提供好方法給客戶端調(diào)用,客戶端需要知道服務(wù)端的具體類,具體方法,然后像調(diào)用本地方法一樣直接調(diào)用它。

**03、面向?qū)ο蟛煌?*

從設(shè)計(jì)上來(lái)看,RPC,所謂的遠(yuǎn)程過(guò)程調(diào)用 ,是面向方法的 ,REST:所謂的 Representational state transfer ,是面向資源的,除此之外,還有一種叫做 SOA,所謂的面向服務(wù)的架構(gòu),它是面向消息的,這個(gè)接觸不多,就不多說(shuō)了。

**04、序列化協(xié)議不同**

接口調(diào)用通常包含兩個(gè)部分,序列化和通信協(xié)議。

通信協(xié)議,上面已經(jīng)提及了,REST 是 基于 HTTP 協(xié)議,而 RPC 可以基于 TCP/UDP,也可以基于 HTTP 協(xié)議進(jìn)行傳輸?shù)摹?/p>

常見(jiàn)的序列化協(xié)議,有:json、xml、hession、protobuf、thrift、text、bytes等,REST 通常使用的是 JSON或者XML,而 RPC 使用的是 JSON-RPC,或者 XML-RPC。

通過(guò)以上幾點(diǎn),我們知道了 REST 和 RPC 之間有很明顯的差異。

**第二個(gè)問(wèn)題:為什么要采用RPC呢?**

那到底為何要使用 RPC,單純的依靠RESTful API不可以嗎?為什么要搞這么多復(fù)雜的協(xié)議,渣渣表示真的學(xué)不過(guò)來(lái)了。

關(guān)于這一點(diǎn),以下幾點(diǎn)僅是我的個(gè)人猜想,僅供交流哈:

1.  RPC 和 REST 兩者的定位不同,REST 面向資源,更注重接口的規(guī)范,因?yàn)橐WC通用性更強(qiáng),所以對(duì)外最好通過(guò) REST。而 RPC 面向方法,主要用于函數(shù)方法的調(diào)用,可以適合更復(fù)雜通信需求的場(chǎng)景。
2.  RESTful API客戶端與服務(wù)端之間采用的是同步機(jī)制,當(dāng)發(fā)送HTTP請(qǐng)求時(shí),客戶端需要等待服務(wù)端的響應(yīng)。當(dāng)然對(duì)于這一點(diǎn)是可以通過(guò)一些技術(shù)來(lái)實(shí)現(xiàn)異步的機(jī)制的。
3.  采用RESTful API,客戶端與服務(wù)端之間雖然可以獨(dú)立開(kāi)發(fā),但還是存在耦合。比如,客戶端在發(fā)送請(qǐng)求的時(shí),必須知道服務(wù)器的地址,且必須保證服務(wù)器正常工作。而 rpc + ralbbimq中間件可以實(shí)現(xiàn)低耦合的分布式集群架構(gòu)。

說(shuō)了這么多,我們?cè)撊绾芜x擇這兩者呢?我總結(jié)了如下兩點(diǎn),供你參考:

*   REST 接口更加規(guī)范,通用適配性要求高,建議對(duì)外的接口都統(tǒng)一成 REST(也有例外,比如我接觸過(guò) zabbix,其 API 就是基于 JSON-RPC 2.0協(xié)議的)。而組件內(nèi)部的各個(gè)模塊,可以選擇 RPC,一個(gè)是不用耗費(fèi)太多精力去開(kāi)發(fā)和維護(hù)多套的HTTP接口,一個(gè)RPC的調(diào)用性能更高(見(jiàn)下條)
*   從性能角度看,由于HTTP本身提供了豐富的狀態(tài)功能與擴(kuò)展功能,但也正由于HTTP提供的功能過(guò)多,導(dǎo)致在網(wǎng)絡(luò)傳輸時(shí),需要攜帶的信息更多,從性能角度上講,較為低效。而RPC服務(wù)網(wǎng)絡(luò)傳輸上僅傳輸與業(yè)務(wù)內(nèi)容相關(guān)的數(shù)據(jù),傳輸數(shù)據(jù)更小,性能更高。

## 02\. 實(shí)現(xiàn)遠(yuǎn)程調(diào)用的三種方式

“遠(yuǎn)程調(diào)用”意思就是:被調(diào)用方法的具體實(shí)現(xiàn)不在程序運(yùn)行本地,而是在別的某個(gè)地方(分布到各個(gè)服務(wù)器),調(diào)用者只想要函數(shù)運(yùn)算的結(jié)果,卻不需要實(shí)現(xiàn)函數(shù)的具體細(xì)節(jié)。

**01、基于 xml-rpc**

Python實(shí)現(xiàn) rpc,可以使用標(biāo)準(zhǔn)庫(kù)里的 SimpleXMLRPCServer,它是基于XML-RPC 協(xié)議的。

有了這個(gè)模塊,開(kāi)啟一個(gè) rpc server,就變得相當(dāng)簡(jiǎn)單了。執(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))
# 將實(shí)例注冊(cè)給rpc server
server.register_instance(obj)

print "Listening on port 8088"
server.serve_forever()
```

有了 rpc server,接下來(lái)就是 rpc client,由于我們上面使用的是 XML-RPC,所以 rpc clinet 需要使用xmlrpclib 這個(gè)庫(kù)。

```
import xmlrpclib

server = xmlrpclib.ServerProxy("http://localhost:8088")
```

然后,我們通過(guò) server_proxy 對(duì)象就可以遠(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是一個(gè)單線程的服務(wù)器。這意味著,如果幾個(gè)客戶端同時(shí)發(fā)出多個(gè)請(qǐng)求,其它的請(qǐng)求就必須等待第一個(gè)請(qǐng)求完成以后才能繼續(xù)。

若非要使用 SimpleXMLRPCServer 實(shí)現(xiàn)多線程并發(fā),其實(shí)也不難。只要將代碼改成如下即可。

```
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 實(shí)現(xiàn)的遠(yuǎn)程調(diào)用,上面我們也提到 除了 xml-rpc 之外,還有 json-rpc 協(xié)議。

那 python 如何實(shí)現(xiàn)基于 json-rpc 協(xié)議呢?

答案是很多,很多web框架其自身都自己實(shí)現(xiàn)了json-rpc,但我們要獨(dú)立這些框架之外,要尋求一種較為干凈的解決方案,我查找到的選擇有兩種

第一種是 `jsonrpclib`

```
pip install jsonrpclib -i https://pypi.douban.com/simple
```

第二種是 `python-jsonrpc`

```
pip install python-jsonrpc -i https://pypi.douban.com/simple
```

先來(lái)看第一種 [jsonrpclib](https://github.com/joshmarshall/jsonrpclib/)

它與 Python 標(biāo)準(zhǔn)庫(kù)的 SimpleXMLRPCServer 很類似(因?yàn)樗念惷徒凶?SimpleJSONRPCServer ,不明真相的人真以為它們是親兄弟)?;蛟S可以說(shuō),jsonrpclib 就是仿照 SimpleXMLRPCServer 標(biāo)準(zhǔn)庫(kù)來(lái)進(jìn)行編寫(xiě)的。

它的導(dǎo)入與 SimpleXMLRPCServer 略有不同,因?yàn)镾impleJSONRPCServer分布在jsonrpclib庫(kù)中。

服務(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")
```

[圖片上傳中...(image-e0730d-1610601786095-15)]

再來(lái)看第二種python-jsonrpc,寫(xiě)起來(lái)貌似有些復(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"
)
```

[圖片上傳中...(image-a950f0-1610601786095-14)]

還記得上面我提到過(guò)的 zabbix API,因?yàn)槲矣薪佑|過(guò),所以也拎出來(lái)講講。zabbix API 也是基于 json-rpc 2.0協(xié)議實(shí)現(xiàn)的。

因?yàn)閮?nèi)容較多,這里只帶大家打個(gè),zabbix 是如何調(diào)用的:直接指明要調(diào)用 zabbix server 的哪個(gè)方法,要傳給這個(gè)方法的參數(shù)有哪些。

[圖片上傳中...(image-9bf5a6-1610601786095-13)]

**03、基于 zerorpc**

以上介紹的兩種rpc遠(yuǎn)程調(diào)用方式,如果你足夠細(xì)心,可以發(fā)現(xiàn)他們都是http+rpc 兩種協(xié)議結(jié)合實(shí)現(xiàn)的。

接下來(lái),我們要介紹的這種([zerorpc](https://github.com/0rpc/zerorpc-python)),就不再使用走 http 了。

[zerorpc](https://github.com/0rpc/zerorpc-python) 這個(gè)第三方庫(kù),它是基于TCP協(xié)議、 ZeroMQ 和 MessagePack的,速度相對(duì)快,響應(yīng)時(shí)間短,并發(fā)高。zerorpc 和 pyjsonrpc 一樣,需要額外安裝,雖然SimpleXMLRPCServer不需要額外安裝,但是SimpleXMLRPCServer性能相對(duì)差一些。

```
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")
```

[圖片上傳中...(image-47df9-1610601786095-12)]

客戶端除了可以使用zerorpc框架實(shí)現(xiàn)代碼調(diào)用之外,它還支持使用“命令行”的方式調(diào)用。

[圖片上傳中...(image-df586b-1610601786095-11)]

客戶端可以使用命令行,那服務(wù)端是不是也可以呢?

是的,通過(guò) Github 上的文檔幾個(gè) demo 可以體驗(yàn)到這個(gè)第三方庫(kù)做真的是優(yōu)秀。

比如我們可以用下面這個(gè)命令,創(chuàng)建一個(gè)rpc server,后面這個(gè) `time` Python 標(biāo)準(zhǔn)庫(kù)中的 time 模塊,zerorpc 會(huì)將 time 注冊(cè)綁定以供client調(diào)用。

```
zerorpc --server --bind tcp://127.0.0.1:1234 time
```

在客戶端,就可以用這條命令來(lái)遠(yuǎn)程調(diào)用這個(gè) time 函數(shù)。

```
zerorpc --client --connect tcp://127.0.0.1:1234 strftime %Y/%m/%d
```

[圖片上傳中...(image-6cb105-1610601786095-10)]

## 03\. 往rpc中引入消息中間件

經(jīng)過(guò)了上面的學(xué)習(xí),我們已經(jīng)學(xué)會(huì)了如何使用多種方式實(shí)現(xiàn)rpc遠(yuǎn)程調(diào)用。

通過(guò)對(duì)比,zerorpc 可以說(shuō)是脫穎而出,一支獨(dú)秀。

但為何在 OpenStack 中,rpc client 不直接 rpc 調(diào)用 rpc server ,而是先把 rpc 調(diào)用請(qǐng)求發(fā)給 RabbitMQ ,再由訂閱者(rpc server)來(lái)取消息,最終實(shí)現(xiàn)遠(yuǎn)程調(diào)用呢?

為此,我也做了一番思考:

OpenStack 組件繁多,在一個(gè)較大的集群內(nèi)部每個(gè)組件內(nèi)部通過(guò)rpc通信頻繁,如果都采用rpc直連調(diào)用的方式,連接數(shù)會(huì)非常地多,開(kāi)銷大,若有些 server 是單線程的模式,超時(shí)會(huì)非常的嚴(yán)重。

OpenStack 是復(fù)雜的分布式集群架構(gòu),會(huì)有多個(gè) rpc server 同時(shí)工作,假設(shè)有 server01,server02,server03 三個(gè)server,當(dāng) rpc client 要發(fā)出rpc請(qǐng)求時(shí),發(fā)給哪個(gè)好呢?這是問(wèn)題一。

你可能會(huì)說(shuō)輪循或者隨機(jī),這樣對(duì)大家都公平。這樣的話還會(huì)引出另一個(gè)問(wèn)題,倘若請(qǐng)求剛好發(fā)到server01,而server01剛好不湊巧,可能由于機(jī)器或者其他因?yàn)閷?dǎo)致服務(wù)沒(méi)在工作,那這個(gè)rpc消息可就直接失敗了呀。要知道做為一個(gè)集群,高可用是基本要求,如果出現(xiàn)剛剛那樣的情況其實(shí)是很尷尬的。這是問(wèn)題二。

集群有可能根據(jù)實(shí)際需要擴(kuò)充節(jié)點(diǎn)數(shù)量,如果使用直接調(diào)用,耦合度太高,不利于部署和生產(chǎn)。這是問(wèn)題三。

引入消息中間件,可以很好的解決這些問(wèn)題。

**解決問(wèn)題一**:消息只有一份,接收者由AMQP的負(fù)載算法決定,默認(rèn)為在所有Receiver中均勻發(fā)送(round robin)。

**解決問(wèn)題二**:有了消息中間件做緩沖站,client 可以任性隨意的發(fā),server 都掛掉了?沒(méi)有關(guān)系,等 server 正常工作后,自己來(lái)消息中間件取就行了。

**解決問(wèn)題三**:無(wú)論有多少節(jié)點(diǎn),它們只要認(rèn)識(shí)消息中間件這一個(gè)中介就足夠了。

## 04\. 消息隊(duì)列你應(yīng)該知道什么?

由于后面,我將實(shí)例講解 OpenStack 中如何將 rpc 和 mq broker 結(jié)合使用。

而在此之前,你必須對(duì)消息隊(duì)列的一些基本知識(shí)有個(gè)概念。

首先,RPC只是定義了一個(gè)通信接口,其底層的實(shí)現(xiàn)可以各不相同,可以是 socket,也可以是今天要講的 AMQP。

AMQP(Advanced Message Queuing Protocol)是一種基于隊(duì)列的可靠消息服務(wù)協(xié)議,作為一種通信協(xié)議,AMQP同樣存在多個(gè)實(shí)現(xiàn),如Apache Qpid,RabbitMQ等。

以下是 AMQP 中的幾個(gè)必知的概念:

*   Publisher:消息發(fā)布者

*   Receiver:消息接收者,在RabbitMQ中叫訂閱者:Subscriber。

*   Queue:用來(lái)保存消息的存儲(chǔ)空間,消息沒(méi)有被receiver前,保存在隊(duì)列中。

*   Exchange:用來(lái)接收Publisher發(fā)出的消息,根據(jù)Routing key 轉(zhuǎn)發(fā)消息到對(duì)應(yīng)的Message Queue中,至于轉(zhuǎn)到哪個(gè)隊(duì)列里,這個(gè)路由算法又由exchange type決定的。

    exchange type:主要四種描述exchange的類型。

    direct:消息路由到滿足此條件的隊(duì)列中(queue,可以有多個(gè)): routing key = binding key

    topic:消息路由到滿足此條件的隊(duì)列中(queue,可以有多個(gè)):routing key 匹配 binding pattern. binding pattern是類似正則表達(dá)式的字符串,可以滿足復(fù)雜的路由條件。

    fanout:消息路由到多有綁定到該exchange的隊(duì)列中。

*   binding :binding是用來(lái)描述exchange和queue之間的關(guān)系的概念,一個(gè)exchang可以綁定多個(gè)隊(duì)列,這些關(guān)系由binding建立。前面說(shuō)的binding key /binding pattern也是在binding中給出。

在網(wǎng)上找了個(gè)圖,可以很清晰地描述幾個(gè)名詞的關(guān)系。

[圖片上傳中...(image-3f97d6-1610601786095-9)]

關(guān)于AMQP,有幾下幾點(diǎn)值得注意:

1.  每個(gè)receiver/subscriber 在接收消息前都需要?jiǎng)?chuàng)建binding。
2.  一個(gè)隊(duì)列可以有多個(gè)receiver,隊(duì)列里的一個(gè)消息只能發(fā)給一個(gè)receiver。
3.  一個(gè)消息可以被發(fā)送到一個(gè)隊(duì)列中,也可以被發(fā)送到多個(gè)多列中。多隊(duì)列情況下,一個(gè)消息可以被多個(gè)receiver收到并處理。Openstack RPC中這兩種情況都會(huì)用到。

## 05\. OpenStack中如何使用RPC?

前面鋪墊了那么久,終于到了講真實(shí)應(yīng)用的場(chǎng)景。在生產(chǎn)中RPC是如何應(yīng)用的呢?

其他模型我不太清楚,在 OpenStack 中的應(yīng)用模型是這樣的

[圖片上傳中...(image-ecd481-1610601786095-8)]

至于為什么要如此設(shè)計(jì),前面我已經(jīng)給出了自己的觀點(diǎn)。

接下來(lái),就是源碼解讀 OpenStack ,看看其是如何通過(guò)rpc進(jìn)行遠(yuǎn)程調(diào)用的。如若你對(duì)此沒(méi)有興趣(我知道很多人對(duì)此都沒(méi)有興趣,所以不浪費(fèi)大家時(shí)間),可以直接跳過(guò)這一節(jié),進(jìn)入下一節(jié)。

目前Openstack中有兩種RPC實(shí)現(xiàn),一種是在oslo messaging,一種是在openstack.common.rpc。

openstack.common.rpc是舊的實(shí)現(xiàn),oslo messaging是對(duì)openstack.common.rpc的重構(gòu)。openstack.common.rpc在每個(gè)項(xiàng)目中都存在一份拷貝,oslo messaging即將這些公共代碼抽取出來(lái),形成一個(gè)新的項(xiàng)目。oslo messaging也對(duì)RPC API 進(jìn)行了重新設(shè)計(jì),對(duì)多種 transport 做了進(jìn)一步封裝,底層也是用到了kombu這個(gè)AMQP庫(kù)。(注:Kombu 是Python中的messaging庫(kù)。Kombu旨在通過(guò)為AMQ協(xié)議提供慣用的高級(jí)接口,使Python中的消息傳遞盡可能簡(jiǎn)單,并為常見(jiàn)的消息傳遞問(wèn)題提供經(jīng)過(guò)驗(yàn)證和測(cè)試的解決方案。)

關(guān)于oslo_messaging庫(kù),主要提供了兩種獨(dú)立的API:

1.  oslo.messaging.rpc(實(shí)現(xiàn)了客戶端-服務(wù)器遠(yuǎn)程過(guò)程調(diào)用)
2.  oslo.messaging.notify(實(shí)現(xiàn)了事件的通知機(jī)制)

因?yàn)?notify 實(shí)現(xiàn)是太簡(jiǎn)單了,所以這里我就不多說(shuō)了,如果有人想要看這方面內(nèi)容,可以收藏我的博客([python-online.cn](http://python-online.cn)) ,我會(huì)更新補(bǔ)充 notify 的內(nèi)容。

OpenStack RPC 模塊提供了 rpc.call,rpc.cast, rpc.fanout_cast 三種 RPC 調(diào)用方法,發(fā)送和接收 RPC 請(qǐng)求。

*   rpc.call 發(fā)送 RPC **同步請(qǐng)求**并返回請(qǐng)求處理結(jié)果。
*   rpc.cast 發(fā)送 RPC **異步請(qǐng)求**,與 rpc.call 不同之處在于,不需要請(qǐng)求處理結(jié)果的返回。
*   rpc.fanout_cast 用于發(fā)送 RPC 廣播信息無(wú)返回結(jié)果

rpc.call 和 rpc.rpc.cast 從實(shí)現(xiàn)代碼上看,他們的區(qū)別很小,就是call調(diào)用時(shí)候會(huì)帶有wait_for_reply=True參數(shù),而cast不帶。

要了解 rpc 的調(diào)用機(jī)制呢,首先要知道 oslo_messaging 的幾個(gè)概念

*   transport:RPC功能的底層實(shí)現(xiàn)方法,這里是rabbitmq的消息隊(duì)列的訪問(wèn)路徑

    transport 就是定義你如何訪連接消息中間件,比如你使用的是 Rabbitmq,那在 nova.conf中應(yīng)該有一行`transport_url`的配置,可以很清楚地看出指定了 rabbitmq 為消息中間件,并配置了連接rabbitmq的user,passwd,主機(jī),端口。

    ```
    transport_url=rabbit://user:passwd@host:5672
    ```

    [圖片上傳中...(image-cefa55-1610601786090-6)]

   

    ```
    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用來(lái)表述 RPC 服務(wù)器監(jiān)聽(tīng)topic,server名稱和server監(jiān)聽(tīng)的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,就像一個(gè)門牌號(hào)一樣。

    [圖片上傳中...(image-dbec91-1610601786090-5)]

   

    rpc client 要發(fā)送消息,也需要有target,說(shuō)明消息要發(fā)到哪去。

    [圖片上傳中...(image-c46d56-1610601786090-4)]

   

*   endpoints:是可供別人遠(yuǎn)程調(diào)用的對(duì)象

    RPC服務(wù)器暴露出endpoint,每個(gè) endpoint 包涵一系列的可被遠(yuǎn)程客戶端通過(guò) transport 調(diào)用的方法。直觀理解,可以參考nova-conductor創(chuàng)建rpc server的代碼,這邊的endpoints就是 `nova/manager.py:ConductorManager()`

    [圖片上傳中...(image-80ed4a-1610601786090-3)]

   

*   dispatcher:分發(fā)器,這是 rpc server 才有的概念

    [圖片上傳中...(image-78c3ac-1610601786089-2)]

   

    只有通過(guò)它 server 端才知道接收到的rpc請(qǐng)求,要交給誰(shuí)處理,怎么處理?

    在client端,是這樣指定要調(diào)用哪個(gè)方法的。

    [圖片上傳中...(image-ca81ed-1610601786089-1)]

   

    而在server端,是如何知道要執(zhí)行這個(gè)方法的呢?這就是dispatcher 要干的事,它從 endpoint 里找到這個(gè)方法,然后執(zhí)行,最后返回。

    [圖片上傳中...(image-9c965e-1610601786089-0)]

   

*   Serializer:在 python 對(duì)象和message(notification) 之間數(shù)據(jù)做序列化或是反序列化的基類。

    主要方法有四個(gè):

    1.  deserialize_context(ctxt) :對(duì)字典變成 request contenxt.
    2.  deserialize_entity(ctxt, entity) :對(duì)entity做反序列化,其中ctxt是已經(jīng)deserialize過(guò)的,entity是要處理的。
    3.  serialize_context(ctxt) :將Request context變成字典類型
    4.  serialize_entity(ctxt, entity) :對(duì)entity做序列化,其中ctxt是已經(jīng)deserialize過(guò)的,entity是要處理的。
*   executor:服務(wù)的運(yùn)行方式,單線程或者多線程

    每個(gè)notification listener都和一個(gè)executor綁定,來(lái)控制收到的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 的四個(gè)重要方法

1.  `reset()`:Reset service.
2.  `start()`:該方法調(diào)用后,server開(kāi)始poll,從transport中接收message,然后轉(zhuǎn)發(fā)給dispatcher.該message處理過(guò)程一直進(jìn)行,直到stop方法被調(diào)用。executor決定server的IO處理策略??赡軙?huì)是用一個(gè)新進(jìn)程、新協(xié)程來(lái)做poll操作,或是直接簡(jiǎn)單的在一個(gè)循環(huán)中注冊(cè)一個(gè)回調(diào)。同樣,executor也決定分配message的方式,是在一個(gè)新線程中dispatch或是..... *
3.  `stop()`:當(dāng)調(diào)用stop之后,新的message不會(huì)被處理。但是,server可能還在處理一些之前沒(méi)有處理完的message,并且底層driver資源也還一直沒(méi)有釋放。
4.  `wait()`:在stop調(diào)用之后,可能還有message正在被處理,使用wait方法來(lái)阻塞當(dāng)前進(jìn)程,直到所有的message都處理完成。之后,底層的driver資源會(huì)釋放。

## 06\. 模仿OpenStack寫(xiě)rpc調(diào)用

模仿是一種很高效的學(xué)習(xí)方法,我這里根據(jù) OpenStack 的調(diào)用方式,抽取出核心內(nèi)容,寫(xiě)成一個(gè)簡(jiǎn)單的 demo,有對(duì) OpenStack 感興趣的可以了解一下,**大部分人也可以直接跳過(guò)這章節(jié)**。

以下代碼不能直接運(yùn)行,你還需要配置 rabbitmq 的連接方式,你可以寫(xiě)在配置文件中,通過(guò) get_transport 從cfg.CONF 中讀取,也可以直接將其寫(xiě)成url的格式做成參數(shù),傳給 get_transport 。

**簡(jiǎ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)
```

**簡(jiǎn)單的 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)容就是RPC遠(yuǎn)程調(diào)用該如何理解,你們學(xué)到知識(shí)或技能了嗎?如果還想學(xué)到更多技能或者豐富自己的知識(shí)儲(chǔ)備,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。


文章題目:RPC遠(yuǎn)程調(diào)用該如何理解
網(wǎng)頁(yè)鏈接:http://weahome.cn/article/pghehe.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部