基于Python语言使用RabbitMQ消息队列(六)
远程过程调用(RPC)
在第二节里我们学会了如何使用工作队列在多个工人中分布时间消耗性任务。
但如果我们想要运行存在于远程计算机上的方法并等待返回结果该如何去做呢?这就不太一样了,这种模式就是常说的远程过程调用(RPC)。
在本节我们会
在本节我们会使用RabbitMQ创建一个RPC系统:一个客户端和一个可扩展(scalable)的RPC服务。由于我们没什么真正的时间消耗型任务去分配,我们就创建一个摆样子的RPC服务,它可以返回Fibonacci数。
客户端接口
为了阐释RPC服务如何使用,我们将创建一个简单的客户端类,这个类暴露一个叫做call的方法,来发送RPC请求,并等待返回答案:
fibonacci_rpc = FibonacciRpcClient()
result = fibonacci_rpc.call(4)
print("fib(4) is %r" % result)
- 1
- 2
- 3
RPC注意事项
虽然RPC在计算机科学中是一个相当常见的模式,但它也经常饱受批评。但一个程序员没有弄清楚一个方法调用是本地还是缓慢的RPC,问题就来了。像这种迷惑就会导致一个无法预测的系统并给调试带来不必要的复杂性,相比较简化软件的思想,乱用RPC会带来难维护的一团糟的代码
把上面这些问题记在脑里,考虑以下建议:确认哪个方法调用是本地哪个调用是远程能够很明了。 使你的系统文档化,组件之间的依赖很清晰。
应对错误情况,当RPC服务宕机很久时客户端应该如何反应? 什么时候考虑避免使用 RPC.
如果可以你应该使用异步管线-而不是RPC-把结果推到下一个计算阶段。
回调队列
通常在 RabbitMQ上用RPC都很容易。客户端发送请求消息,服务端用响应消息回应。为了接收响应,客户端需要用这个请求发送一个 ‘callback’ 队列的地址。我们试一下:
result = channel.queue_declare(exclusive=True)
callback_queue = result.method.queue
channel.basic_publish(exchange='',
routing_key='rpc_queue',
properties=pika.BasicProperties(
reply_to = callback_queue,
),
body=request)
# ... 此处为从callback_queue队列读取响应消息的代码 ...
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
Message properties
AMQP 0-9-1 协议为一个消息预定义了一组包含14个属性的集合,大多数属性都很少使用,除了以下几种:
delivery_mode: 标记一个消息为持久的(值为2)或者暂时的(任何其他值)。你可能记得这个属性在第二节中。
content_type: 常用来描述编码的mime-type。例如常用的JSON格式最好把这个属性设置为:application/json。
reply_to:通常用来命名一个回调队列。
correlation_id: 让请求(requests)关联到RPC响应(responses)时很有用。
Correlation id
在上面呈现的方法中,我们建议为每个RPC调用创建一个回调队列。那会相当没效率,幸运的是有一种更好的方法——我们每个客户端创建一个单一回调队列。
但又出现了一个新的问题,在这个队列中接收到一个响应,不清楚这个响应属于哪个请求。这时correlation_id 属性就派上用场了。我们将为每个请求设定一个唯一值。稍后当我们从回调队列收到消息我们会查看这个属性,基于此我们能把这个响应和一个请求匹配。如果我们发现了一个不认识的correlation_id值,我们可以平安无事地忽略掉这条消息。——它不属于我们的请求。
你可能会问,为什么我们要忽略回调队列中不认识的消息,而不是提出个错误?那是由于服务端会有竞争条件(race condition)的可能。RPC服务在把结果发回来后,但还没有发送请求的通知消息之前就死掉了,虽然不太可能,但如果发生了,重启的RPC服务会再次处理之前的请求。所以我们要在客户端优雅地处理重复的响应,并且RPC应该是等幂的。
总结
我们的RPC工作流程如下:
当客户端启动,创建一个匿名的专用的回调队列。对于一个RPC请求,客户端发送一个带有两个属性的消息:reply_to,这个设置给回调队列;correlation_id,这个为每个请求设置唯一值。
请求发送给 rpc_queue 队列。
RPC工人 (也叫做服务)等待那个队列上的请求。当一个请求到达,它完成任务并且发送一个带有结果的消息返回给客户端,使用源自reply_to域的队列。
整合
rpc_server.py完整代码:
#!/usr/bin/env python
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.queue_declare(queue='rpc_queue')
def fib(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fib(n-1) + fib(n-2)
def on_request(ch, method, props, body):
n = int(body)
print(" [.] fib(%s)" % n)
response = fib(n)
ch.basic_publish(exchange='',
routing_key=props.reply_to,
properties=pika.BasicProperties(correlation_id = \
props.correlation_id),
body=str(response))
ch.basic_ack(delivery_tag = method.delivery_tag)
channel.basic_qos(prefetch_count=1)
channel.basic_consume(on_request, queue='rpc_queue')
print(" [x] Awaiting RPC requests")
channel.start_consuming()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
rpc_client.py完整代码:
#!/usr/bin/env python
import pika
import uuid
class FibonacciRpcClient(object):
def __init__(self):
self.connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
self.channel = self.connection.channel()
result = self.channel.queue_declare(exclusive=True)
self.callback_queue = result.method.queue
self.channel.basic_consume(self.on_response, no_ack=True,
queue=self.callback_queue)
def on_response(self, ch, method, props, body):
if self.corr_id == props.correlation_id:
self.response = body
def call(self, n):
self.response = None
self.corr_id = str(uuid.uuid4())
self.channel.basic_publish(exchange='',
routing_key='rpc_queue',
properties=pika.BasicProperties(
reply_to = self.callback_queue,
correlation_id = self.corr_id,
),
body=str(n))
while self.response is None:
self.connection.process_data_events()
return int(self.response)
fibonacci_rpc = FibonacciRpcClient()
print(" [x] Requesting fib(30)")
response = fibonacci_rpc.call(30)
print(" [.] Got %r" % response)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
现在我们的RPC服务已经准备好了。我们可以启动服务:
python rpc_server.py
# => [x] Awaiting RPC requests
- 1
- 2
在客户端运行请求一个fibonacci数:
python rpc_client.py
# => [x] Requesting fib(30)
- 1
- 2
当前的设计并不是RPC服务的唯一实现,但它有一些重要优势:
如果服务太慢,你只需要再运行一个服务进行扩展。试着在新控制台再运行一个rpc_server.py。
在客户端,RPC只需要发送和接收一个消息,不需要像queue_declare这样的异步调用。这样RPC客户端的单个RPC请求只需要一次网络往返。
我们的代码已经相当简化,没有试图处理更复杂(但重要)的问题,如:
如果没有正在运行的服务客户端该如何反应?
客户端需要为RPC设置超时吗?
如果服务功能故障或抛出异常,它应给被返回给客户端么?
在处理之前防止无效的输入消息。
基于Python语言使用RabbitMQ消息队列(六)的更多相关文章
- 基于Python语言使用RabbitMQ消息队列(一)
介绍 RabbitMQ 是一个消息中间人(broker): 它接收并且发送消息. 你可以把它想象成一个邮局: 当你把想要寄出的信放到邮筒里时, 你可以确定邮递员会把信件送到收信人那里. 在这个比喻中, ...
- 基于Python语言使用RabbitMQ消息队列(五)
Topics 在前面教程中我们改进了日志系统,相比较于使用fanout类型交易所只能傻瓜一样地广播,我们用direct获得了选择性接收日志的能力. 虽然使用direct类型交易所改进了我们的系统,但它 ...
- 基于Python语言使用RabbitMQ消息队列(四)
路由 在上一节我们构建了一个简单的日志系统.我们能够广播消息给很多接收者. 在本节我们将给它添加一些特性——我们让它只订阅所有消息的子集.例如,我们只把严重错误(critical error)导入到日 ...
- 基于Python语言使用RabbitMQ消息队列(三)
发布/订阅 前面的教程中我们已经创建了一个工作队列.在一个工作队列背后的假设是每个任务恰好会传递给一个工人.在这一部分里我们会做一些完全不同的东西——我们会发送消息给多个消费者.这就是所谓的“发布/订 ...
- 基于Python语言使用RabbitMQ消息队列(二)
工作队列 在第一节我们写了程序来向命名队列发送和接收消息 .在本节我们会创建一个工作队列(Work Queue)用来在多个工人(worker)中分发时间消耗型任务(time-consuming tas ...
- python学习之-- RabbitMQ 消息队列
记录:异步网络框架:twisted学习参考:www.cnblogs.com/alex3714/articles/5248247.html RabbitMQ 模块 <消息队列> 先说明:py ...
- Python并发编程-RabbitMQ消息队列
RabbitMQ队列 RabbitMQ是一个在AMQP基础上完整的,可复用的企业消息系统.他遵循Mozilla Public License开源协议. MQ全称为Message Queue, 消息队列 ...
- Python RabbitMQ消息队列
python内的队列queue 线程 queue:不同线程交互,不能夸进程 进程 queue:只能用于父进程与子进程,或者同一父进程下的多个子进程,进行交互 注:不同的两个独立进程是不能交互的. ...
- 基于ASP.NET Core 5.0使用RabbitMQ消息队列实现事件总线(EventBus)
文章阅读请前先参考看一下 https://www.cnblogs.com/hudean/p/13858285.html 安装RabbitMQ消息队列软件与了解C#中如何使用RabbitMQ 和 htt ...
随机推荐
- C语言中 ln(以自然对数e为底) lg(以十为底) 以及logab(以a为底,b为真数)的相关知识
总所周知,我们在高中学过对数函数,记作y=logax.下面是百度百科关于对数函数的描述: 对数的定义:一般地,如果ax=N(a>0,且a≠1),那么数x叫做以a为底N的对数,记作x=logaN, ...
- [Android Studio系列(五)] Android Studio手动配置Gradle的方法
1 问题 (1) android sutdio第一次打开一个工程巨慢怎么办? (2) 手动配置Gradle Home为什么总是无效? (3) 明明已经下载了Gradle,配置了gradle home, ...
- 简单介绍java Enumeration(转)
Enumeration接口 Enumeration接口本身不是一个数据结构.但是,对其他数据结构非常重要. Enumeration接口定义了从一个数据结构得到连续数据的手段.例如,Enumeratio ...
- 混合开发的大趋势之一React Native之Image (脑动理解)
文章是宝宝自己写的,你可以转走,标明哪来的就行王亟亟的大牛之路 国庆这些天要么旅游要么WOW,感觉整个人都废了.. 直接从黄种人晒成了非洲大酋长..然而还是无橙,这礼拜要做7天,昨天把单元测试的东西整 ...
- Git如何进行分支管理?
Git如何进行分支管理? 1.创建分支 创建分支很简单:git branch <分支名> 2.切换分支 git checkout <分支名> ...
- SpringCloud之eureka服务注册和服务发现
服务注册中心 :eureka-server 作用:服务注册中心提供服务注册功能 服务提供方:eureka-client 作用:注册服务到服务注册中心 服务注册中心 :eureka-server 创建 ...
- PHP超级全局变量、魔术变量和魔术函数的区别和联系
PHP超级全局变量.魔术变量和魔术函数的区别和联系 一.总结 一句话总结:PHP超级全局变量主要用于web开发,魔术变量主要用于输出当前对象的信息,魔术函数则是对象的常用方法 相同点: 1.PHP超级 ...
- spring mvc : 中文传值(post/get)中文乱码
请将以下代码放入 web.xml文件中,注意存放顺序,否则会报错.filter应该放在context-param后面: <!-- 字符过滤器 --> <filter> < ...
- 委托,lambda,匿名方法
lambda表达式其实就是匿名方法的变体或者说简写. 原来我们用 delegate void Del(int x); Del d = delegate(int x) { return x + 1; } ...
- ArcGIS API For Silverlight使用在线地图的多种方法总结
引自:http://www.cnblogs.com/meimao5211/p/3283969.html ArcGIS API For Silverlight使用在线地图的多种方法总结 本人也正在学习A ...