關於OpenStack中基於RESTFul API的通信方式主要是應用了WSGI,我會在以後的博文中詳細討論。這裏主要還是分享一下關於RPC調用的一些理解。
首先,什麼是RPC呢?百度百科給出的解釋是這樣的:“RPC(Remote Procedure Call Protocol)——遠程過程調用協議,它是一種通過網絡從遠程計算機程序上請求服務,而不需要了解底層網絡技術的協議”。這個概念聽起來還是比較抽象,下面我會結合OpenStack中RPC調用的具體應用來具體分析一下這個協議。
其次,爲什麼要採用RPC呢?單純的依靠RESTFul API不可以嗎?其實原因有下面這幾個:
1. 由於RESTFul API是基於HTTP協議的,因此客戶端與服務端之間所能傳輸的消息僅限於文本
2. RESTFul API客戶端與服務端之間採用的是同步機制,當發送HTTP請求時,客戶端需要等待服務端的響應。當然對於這一點是可以通過一些技術來實現異步的機制的
3. 採用RESTFul API,客戶端與服務端之間雖然可以獨立開發,但還是存在耦合。比如,客戶端在發送請求的時,必須知道服務器的地址,且必須保證服務器正常工作
基於上面這幾個原因,所以OpenStack才採用了另一種遠程通信機制,這就是我們今天要討論的鼎鼎大名的RPC。
要了解OpenStack中的RPC,有一個組件是必不可少的,那就是RabbitMQ(消息隊列)。OpenStack中,RPC採用AMQP協議實現進程間通信,而RabbitMQ正是AMQP的實現方式,所以可以說OpenStack中的RPC調用都是基於RabbitMq完成的(注:有關AMQP、RabbitMQ的一些知識,可以參看我上篇分享的文章——《兔子與兔子洞》)。
在Nova中,定義了兩種遠程調用方式——rpc.call和rpc.cast。其中rpc.call方式是指request/response(請求/響應)模式,即客戶端在發送請求後,繼續等待服務器端的響應結果,待響應結果到達後,才結束整個過程。rpc.cast方式是指客戶端發送RPC調用請求後,不等待服務器端的響應結果。不難看出,較rpc.cast模式,rpc.call更爲複雜。爲了處理rpc.call,Nova採用了Topic Exchange(主題交換器)和Direct Exchange(直接交換器)兩種消息交換器。其中Topic Exchange用於客戶端向服務器端rpc.call的發起,Direct Exchange用於服務器端向客戶端返回rpc.call。對應於這兩種交換機,Nova中定義了Topic/Direct消息消費者、Topic/Direct消息發佈者、Topic/Direct交換器。各部件如下圖所示:
需要說明的是一個主題交換器可以關聯多個隊列,而一個直接交換器只能關聯一個隊列。
結合rpc.call代碼實例,開始我們的分析吧,代碼結構如下:
client.py——RPC調用請求客戶端啓動腳本
dispatcher.py——將客戶端發佈的消息分發給相應的方法處理
impl_kombu.py——核心代碼,實現了消費者、生產者、建立連接等操作
manager.py——定義處理RPC請求的方法
rpc_amqp.py——發送RPC請求和發生RPC響應
rpc.py——爲外部提供訪問API
server.py——服務器端啓動腳本
service.py——創建和管理RPC服務
接下來看一下rpc.call執行的流程:
1). RPC服務器端定義並啓動RPC服務
2). RPC服務器端建立和RabbitMQ服務器的連接
3). RPC服務器端創建和激活主題消費者
4). RPC客戶端向主題交換器發送RPC請求
5). RPC服務器端接收和處理RPC請求
6). RPC客戶端創建和激活直接消費者
7). RPC服務器端向直接交換機發送RPC響應
1. 服務器端:定義和啓動RPC服務
server.py
import service
srv = service.Service() #創建RPC服務
srv.start() #啓動RPC服務
while True:
srv.drain_events() #監聽RPC請求
以上代碼的作用是創建Service對象,然後分別調用start和drain_events方法。Service類的定義在service.py中。
service.py
import rpc
import manager
import dispatcher
TOPIC = 'sendout_request'
class Service(object):
def __init__(self):
self.topic = TOPIC
self.manager = manager.Manager()
def start(self):
self.conn = rpc.create_connection()
rpc_dispatcher = dispatcher.RpcDispatcher(self.manager)
self.conn.create_consumer(self.topic, rpc_dispatcher)
self.conn.consume()
def drain_events(self):
self.conn.drain_events()
Service類中包含init、start、drain_events三個方法。
2. 服務器端:建立與RabbitMQ的連接
Service類調用rpc.crerate_connection方法來建立連接。該方法會返回一個Connection對象。Connection對象的定義在impl_kombu.py文件中。
impl_kombu.py
class Connection(object):
def __init__(self):
self.consumers = []
self.connection = None
self.reconnect()
def reconnect(self):
#初次重連的等待時間
sleep_time = conf.get('interval_start', 1)
#每次連接失敗後增加的等待時間
stepping = conf.get('interval_stepping', 2)
#重連的最大等待時間
interval_max = conf.get('interval_max', 30)
sleep_time -= stepping
while True:
try:
self._connect()
return
except Exception, e:
if 'timeout' not in str(e):
raise
sleep_time += stepping
sleep_time = min(sleep_time, interval_max)
print("AMQP Server is unreachable,"
"trying to connect %d seconds later\n" % sleep_time)
time.sleep(sleep_time)<pre name="code" class="python"> def _connect(self):
hostname = rabbit_params.get('hostname')
port = rabbit_params.get('port')
if self.connection: #如果已有連接,釋放原有連接
print("Reconnecting to AMQP Server on "
"%(hostname)s:%(port)d\n" % locals())
self.connection.release()
self.connection = None
self.connection = kombu.connection.BrokerConnection(**rabbit_params)
self.consumer_num = itertools.count(1) #消費者迭代器,產生消費者唯一的標識
self.connection.connect() #建立與RabbitMQ的連接
self.channel = self.connection.channel() #獲取連接的通道
for consumer in self.consumers:
consumer.reconnect(self.channel)
Connection類的初始化方法比較簡單,主要是調用reconnect方法。reconnect方法會不斷嘗試與RabbitMQ建立連接。_connect方法實現真正的連接的建立。
3. 服務器端:創建和激活主題消費者
(1)Service類通過Connection類的create_consumer方法創建消費者
impl_kombu.py
class Connection(object):
def create_consumer(self, topic, proxy):
proxy_cb = rpc_amqp.ProxyCallback(proxy)
self.declare_topic_consumer(topic, proxy_cb)
def declare_topic_consumer(self, topic, callback):
print('declaring topic consumer for topic %s...\n' % topic)
self.declare_consumer(TopicConsumer, topic, callback)
def declare_consumer(self, consumer_cls, topic, callback):
def _declare_consumer():
consumer = consumer_cls(self.channel,topic, callback,self.consumer_num.next())
self.consumers.append(consumer)
print('Succed declaring consumer for topic %s\n' % topic)
return consumer
return self.ensure(_declare_consumer, topic)
def ensure(self, method, topic):
while True:
try:
return method()
except Exception, e:
if 'timeout' not in str(e):
raise
print('Failed to declare consumer for topic %s: '
'%s\n' % (topic, str(e)))
self.reconnect()
ProxyCallback類是一個處理回調的代理類,後續會說明。主要來看declare_topic_consumer方法,這個方法創建了消費者。declare_topic_consumer方法調用declare_consumer,向declare_consumer方法中傳入TopicConsumer類。declare_consumer方法對TopicConsumer類進行實例化,並通過ensure方法保證消費者創建成功。來看一下TopicConsumer這個類。
impl_kombu.py
class TopicConsumer(ConsumerBase):
def __init__(self, channel, topic, callback, tag, **kwargs):
self.topic = topic
options = {'durable': False,
'auto_delete': False,
'exclusive': False}
options.update(kwargs)
exchange = kombu.entity.Exchange(name=topic,
type='topic',
durable=options['durable'],
auto_delete=options['auto_delete'])
super(TopicConsumer, self).__init__(channel,
callback,
tag,
name=topic,
exchange=exchange,
routing_key=topic,
**options)
options變量用來設置交換器的屬性。這裏交換器的名稱爲topic的值,tag爲消費者的唯一標識。可以看到TopicConsumer類繼承自ConsumerBase類,來看一下這個基類。
impl_kombu.py
class ConsumerBase(object):
def __init__(self, channel, callback, tag, **kwargs):
self.callback = callback
self.tag = str(tag)
self.kwargs = kwargs
self.queue = None
self.reconnect(channel)
def reconnect(self, channel):
self.channel = channel
self.kwargs['channel'] = channel
self.queue = kombu.entity.Queue(**self.kwargs)
self.queue.declare()
ConsumerBase類的初始化方法調用了reconnect方法來創建隊列。
(2)激活消費者
Service類通過Connection類的consume方法激活消費者。Connection類的consume方法最終會調用ConsumerBase類的consume方法。
impl_kombu.py
class ConsumerBase(object):
def consume(self, *args, **kwargs):
options = {'consumer_tag': self.tag}
options['nowait'] = False
def _callback(raw_message):
message = self.channel.message_to_python(raw_message)
try:
msg = message.payload #獲取消息體
self.callback(msg) #處理消息
message.ack() #通知交換器消息處理完畢
except Exception:
print("Failed to process message... skipping it.\n")
self.queue.consume(*args, callback=_callback, **options) #激活消費者
主要定義了_callback方法,分發和處理RPC請求。
至此服務器端的工作暫時告一段落,下一篇博文來我們再來分析下客戶端的工作和服務器端的後續接收和處理RPC請求的工作。