事实上, 之前记录的是MQ,作为分布式架构,懂openstack不知道MQ是没办法进行下去的,但是说MQ的网上教程实在太多了,既然是记录的转化,实在是没必要抄一遍,
还是把一些自己的理解记下来吧。
在使用MQ(消息队列)之前,做过rest的项目,用NIO写过异步网络通信进行RPC调用,可能有的人还用过RMI或者其他的远程调用与通信。同事ZCF问我项目里面
到底是使用rest call还是MQ,我回答说,这两个感觉很像,但是放在一起说是不合适的,rest是一种提供服务的方式,让功能条例化,清晰化,很好的控制系统暴露的
功能与方式,用MQ(自己用NIO写RPC,越写越多就可能会成为一个MQ,当然MQ的东西水也挺深)更多的是解耦,我认为这句话是最合适的,一个大型分布式系统,
“大型”和“分布式”缺一不可,功能繁杂,并行开发使用MQ解耦系统功能是再好不过的,除了提升开发效率,对系统维护功能拓展也很好,作为基础架构云平台,
openstack使用MQ几乎是不二之选。
不看具体的MQ,我们要想实现自己的MQ,有哪些功能:
1. client端实现发送消息, 由于MQ的异步性,我们可能需要queue来让message排队
2. server端要能从queue里面把message拿出来,处理之后给出返回
3. server端响应可能是同步的,也可能是异步的
4. 一条消息可能需要被多个server端接收,也可能被某一个server接收,不该接收消息的绝对不能收到消息,该收到的不能丢失
5. 可能需要接收某一类消息,比如一个节点需要接收所有的报警消息
6. 如果属于新老系统交替时,新老系统的服务同时开启,一条消息可能是发给特定版本系统的,且消息格式只有该版本系统才能处理
7. 消息队列一旦down了,重启之后未成功发送的消息需要"复活",因此消息需要一定程度的持久化
等等。
当然了一个具体的MQ实现涉及面很多,IBM给银行使用的消息中间件以稳定诸城可不便宜。
在上面的功能基础上,我们能想到的function:
rpc.call(同步调用,处理完返回)
rpc.cast(异步,发出去让server处理即可)
call或者cast调用发出的message以什么方式发给什么server,需要router(exchanger),exchanger决定消息:
发给某个特定的server(direct模式)
发给某个topic的server(topic模式)
发给所有server(fanout模式)
消息经过router(exchanger)到某个server,消息很多时需要排队,需要queue
server端consume消息,进行处理。
到这里已经和openstack的MQ功能很像了,早先的openstack没有对消息队列进行很多的封装,现在MQ的功能放在oslo的messaging中,
看openstack源码我们常能看到这些概念:
target, publisher, consumer, transport, serializer等
分别来看看:
transport:
client和server均需指定,在nova/rpc.py中:
TRANSPORT = messaging.get_transport(conf, allowed_remote_exmods=exmods,aliases=TRANSPORT_ALIASES)
TRANSPORT_ALIASES = {
'nova.openstack.common.rpc.impl_kombu': 'rabbit',
'nova.openstack.common.rpc.impl_qpid': 'qpid',
'nova.openstack.common.rpc.impl_zmq': 'zmq',
'nova.rpc.impl_kombu': 'rabbit',
'nova.rpc.impl_qpid': 'qpid',
'nova.rpc.impl_zmq': 'zmq',
}
进入/usr/lib/python2.7/site-packages/oslo/messaging包,在transport.py中get_transport方法,我们看到:
kwargs = dict(default_exchange=conf.control_exchange,
allowed_remote_exmods=allowed_remote_exmods)
try:
mgr = driver.DriverManager('oslo.messaging.drivers',
url.transport,
invoke_on_load=True,
invoke_args=[conf, url],
invoke_kwds=kwargs)
except RuntimeError as ex:
raise DriverLoadFailure(url.transport, ex)
return Transport(mgr.driver)
看起来很眼熟,不错,在在/usr/lib/python2.7/site-packages/oslo.messaging-1.4.xxx中的entry_points中
[oslo.messaging.drivers]
qpid = oslo.messaging._drivers.impl_qpid:QpidDriver
amqp = oslo.messaging._drivers.protocols.amqp:ProtonDriver
kombu = oslo.messaging._drivers.impl_rabbit:RabbitDriver
rabbit = oslo.messaging._drivers.impl_rabbit:RabbitDriver
fake = oslo.messaging._drivers.impl_fake:FakeDriver
zmq = oslo.messaging._drivers.impl_zmq:ZmqDriver
这也就是我们在/etc/nova/nova.conf中配置“nova.openstack.common.rpc.impl_kombu‘使用rabbitMQ的原因
所以transport和backend类似,指明实际操作时使用的MQ具体实现方式。
target:
在sourcecode中常能看到这样的语句: target = messaging.Target(topic='%s.%s' % (topic_base, msg_type),server=CONF.host)
看oslo/message包中target.py中Target类,是个很简单的类 :
def __init__(self, exchange=None, topic=None, namespace=None,
version=None, server=None, fanout=None):
self.exchange = exchange
self.topic = topic
self.namespace = namespace
self.version = version
self.server = server
self.fanout = fanout
类__doc__中解释道:
:param version: Interfaces have a major.minor version number associated
with them. A minor number increment indicates a backwards compatible
change and an incompatible change is indicated by a major number bump.
Servers may implement multiple major versions and clients may require
indicate that their message requires a particular minimum minor version.
:type version: str
:param server: Clients can request that a message be directed to a specific
server, rather than just one of a pool of servers listening on the topic.
:type server: str
:param fanout: Clients may request that a message be directed to all
servers listening on a topic by setting fanout to ``True``, rather than
just one of them.
可以看出,指定了server,就是direct方式将message发到指定server,不指定server,但指定了topic就将message发到接受该topic的server,
制定了fanout=true就发到所有server
至于exchange,在nova/config.py的parse_args中:
rpc.set_defaults(control_exchange='nova')
指定nova使用exchange"nova",默认exchange是”openstack“
serializer:
MQ的message在网络上传输,既然网络传输就必然涉及到数据的序列化与反序列化,serializer就是致命这一过程时使用的方式,随便看一个:
serializer = RequestContextSerializer(JsonPayloadSerializer())
JsonPayloadSerializer调用了jsonutils.to_primitive序列化一个entity
publisher和consumer
封装了client和server发送与接收的部分,这个在K版里,J版使用的是rpc.call, cast, server.start(),但是理解起来不难,而且如果愿意,trace进去
publisher和consumer,使用的还是rpc的那些方法。
看nova的rpc.rst文档,有一句mark一下,fanout基本用在cells的部分,之后会说:
Nova uses direct, fanout, and topic-based exchanges
mark link
1.http://www.gaort.com/index.php/archives/366
2.http://blog.csdn.net/juvxiao/article/details/23016155
3.http://blog.csdn.net/juvxiao/article/details/23532617
4.http://www.cnblogs.com/sammyliu/p/4384470.html