前面简单介绍过Neutron消息中间件RabbitMQ的用法。学习过的人,相信对RabbitMQ不会再有陌生感。接下来将会继续介绍Neutron中消息通信模块oslo.messaging。它是OpenStack公共库oslo中的一个组件,封装了RPC调用,为RPC和事件通知提供了一套统一的接口。
使用过OpenStack的人都知道,目前原生OpenStack底层MQ驱动默认是kombu。那么为什么OpenStack又在此基础上做了一层封装?在编程领域,一个显而易见的原因是减少模板代码和对底层驱动提供更加灵活的选择权。
当访问数据库出现太多模板代码时,Java家族出现了Hibernate、MyBatis、JDBC等等驱动。类似的,OpenStack对RPC进行封装之后,对消息代理服务器的调用就会简化很多,同时屏蔽了底层驱动的差异性,随时可以更换底层驱动(kombu)而不需要修改任何上层代码。
下面介绍oslo.messaging在RPC通信过程中的几个主要对象:
-
Transport
Transport(传输层)主要实现了RPC底层的通信以及事件循环,多线程等功能,可以直接通过URL来获取tranport对象(Neutron默认从neutron.conf配置文件读取tranport_url)。
URL格式如下:
transport://user:password@host:5672,user:password@host:5672,...
实际上,RabbitMQ常常以集群方式部署。
目前支持Transport的MQ有RabbitMQ,Qpid和ZeroMQ。可使用oslo_messaging.get_transport方法获取Transport实例。
-
Target
Target封装了指定某个消息最终目的地的所有信息。可使用oslo_messaging.Target对象获取Target实例。Target对象初始化参数如下:
参数 说明 exchange=None 字符串类型,用于指定交换器名称,默认使用配置文件中的control_exchange topic=None 字符串类型,用于表示消费者提供的一组接口 namespace=None 字符串类型,表示服务器暴露的某个特定接口 version=None 字符串类型,消费者暴露的接口支持M.N类型的版本号 server=None 字符串类型,用于生产者指定消息的目的地址是某个特定的消费者,而不是某topic下的一组消费者 fanout=None 布尔类型,用于指定交换器类型 通过传入不同的参数,可以适应不同的场景需求。以neutron-l3-agent上报状态的RPC为例
target = oslo_messaging.Target(topic=topic, version='1.0',= namespace=n_const.RPC_NAMESPACE_STATE)
其Target的topic为”q-reports-plugin“,namespace默认None,exchange使用oslo.conf中的默认值”neutron“(从neutron/common/config.py文件oslo_messaging.set_transport_defaults(control_exchange='neutron')可知),fanout取默认值False。
-
获取生产者-client端
Neutron中使用neutron.common.rpc.get_client方法获取客户端,参数是Target。实际上是获取了一个BackingOffClient对象,该对象继承自oslo_messaging.RPCClient。
通过Neutron方式获取的客户端支持两个RPC模式:同步和异常。
-
-
同步
调用方法名call,有三个参数:一个字典对象指明上下文,调用的方法名称和传递给方法的参数
-
异步
调用方法名cast,参数传递方式与call相同。但是需要注意采用异步方式时,exchange需要指定为fanout类型。
-
-
获取消费者-server端
一个RPC服务器可以暴露多个endpoint,每个endpoint包含一组方法。创建server端时,需要指Transport、Target和一组endpoint。以下还是以l3-agent为例:
self.endpoints = [ rpc.RpcCallbacks(self.notifier, self.type_manager), securitygroups_rpc.SecurityGroupServerRpcCallback(), dvr_rpc.DVRServerRpcCallback(), dhcp_rpc.DhcpRpcCallback(), agents_db.AgentExtRpcCallback(), metadata_rpc.MetadataRpcCallback(), resources_rpc.ResourcesPullRpcCallback() ] self.conn.create_consumer(self.topic, self.endpoints, fanout=False)
下面以oslo.messaging方式实现一个RPC示例
-
生产者
from oslo_config import cfg import oslo_messaging transport_url = 'rabbit://guest:guest@192.168.90.26:5672/' transport = oslo_messaging.get_transport(cfg.CONF,transport_url) target = oslo_messaging.Target(topic='test',fanout=True,version='1.0',namespace=None) client = oslo_messaging.RPCClient(transport, target) r = client.cast({}, 'test',a=2) print r
-
消费者
from oslo_config import cfg import oslo_messaging import sys import time class TestEndpoint(object): def test(self, ctx, **kwargs): print "receive client access %s" % kwargs transport_url = 'rabbit://guest:guest@192.168.90.26:5672/' server = sys.argv[1] transport = oslo_messaging.get_transport(cfg.CONF,transport_url) target = oslo_messaging.Target(topic='test', server=server) endpoints = [ TestEndpoint(), ] server = oslo_messaging.get_rpc_server(transport, target, endpoints) try: server.start() while True: time.sleep(1) except KeyboardInterrupt: print("Stopping server") server.stop() server.wait()
-
分别执行server和client,观察输出
receive client access {u'a': 2}
小结
通过以上对oslo.messaging模块的介绍,相信大家对Neutron的通信方式有更进一步的认识。只需要简单的创建两个对象Transport和Target,然后分别通过工厂方法获取客户端和服务端,就可以创建一个完整的RPC通信。
如果对云计算感兴趣,可以关注我的微信公众号: