远程过程调用(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消息队列(六)的更多相关文章

  1. 基于Python语言使用RabbitMQ消息队列(一)

    介绍 RabbitMQ 是一个消息中间人(broker): 它接收并且发送消息. 你可以把它想象成一个邮局: 当你把想要寄出的信放到邮筒里时, 你可以确定邮递员会把信件送到收信人那里. 在这个比喻中, ...

  2. 基于Python语言使用RabbitMQ消息队列(五)

    Topics 在前面教程中我们改进了日志系统,相比较于使用fanout类型交易所只能傻瓜一样地广播,我们用direct获得了选择性接收日志的能力. 虽然使用direct类型交易所改进了我们的系统,但它 ...

  3. 基于Python语言使用RabbitMQ消息队列(四)

    路由 在上一节我们构建了一个简单的日志系统.我们能够广播消息给很多接收者. 在本节我们将给它添加一些特性——我们让它只订阅所有消息的子集.例如,我们只把严重错误(critical error)导入到日 ...

  4. 基于Python语言使用RabbitMQ消息队列(三)

    发布/订阅 前面的教程中我们已经创建了一个工作队列.在一个工作队列背后的假设是每个任务恰好会传递给一个工人.在这一部分里我们会做一些完全不同的东西——我们会发送消息给多个消费者.这就是所谓的“发布/订 ...

  5. 基于Python语言使用RabbitMQ消息队列(二)

    工作队列 在第一节我们写了程序来向命名队列发送和接收消息 .在本节我们会创建一个工作队列(Work Queue)用来在多个工人(worker)中分发时间消耗型任务(time-consuming tas ...

  6. python学习之-- RabbitMQ 消息队列

    记录:异步网络框架:twisted学习参考:www.cnblogs.com/alex3714/articles/5248247.html RabbitMQ 模块 <消息队列> 先说明:py ...

  7. Python并发编程-RabbitMQ消息队列

    RabbitMQ队列 RabbitMQ是一个在AMQP基础上完整的,可复用的企业消息系统.他遵循Mozilla Public License开源协议. MQ全称为Message Queue, 消息队列 ...

  8. Python RabbitMQ消息队列

    python内的队列queue 线程 queue:不同线程交互,不能夸进程 进程 queue:只能用于父进程与子进程,或者同一父进程下的多个子进程,进行交互 注:不同的两个独立进程是不能交互的.   ...

  9. 基于ASP.NET Core 5.0使用RabbitMQ消息队列实现事件总线(EventBus)

    文章阅读请前先参考看一下 https://www.cnblogs.com/hudean/p/13858285.html 安装RabbitMQ消息队列软件与了解C#中如何使用RabbitMQ 和 htt ...

随机推荐

  1. Vmware ESXi 6.0 多Vlan部署,vSphere Client管理方法

    背景: 公司IT部门新购了两台服务器与一台存储,打算做虚拟化,并将存储分成两个部分,分别配给那两台服务器.在宿主机上要安装的虚拟机属于不同的网段,这就涉及了多VLAN,当然这并不是多么高深的技术,属于 ...

  2. Python多类继承中,子类默认继承哪个父类的构造函数__init__

    [1]python中如果子类有自己的构造函数,不会自动调用父类的构造函数,如果需要用到父类的构造函数,则需要在子类的构造函数中显式的调用. [2]如果子类没有自己的构造函数,则会直接从父类继承构造函数 ...

  3. vim终端复制_不开启xterm_clipboard的解决方式

    后来发现了另外的方法,比这个更好==> 完美解决vim在终端不能复制的问题 http://www.cnblogs.com/cheerupforyou/p/6958695.html 使用xsehl ...

  4. windchill相关功能操作

    1.创建产品   2.创建文件夹   3.创建文档   4.创建用户账号   5.创建组   6.创建更改请求   7.创建部件新视图版本   8.创建可重用属性和全局枚举   9.在组织内分配上下文 ...

  5. 关于分析web.xml的一篇博客,写的很详细

    http://blog.csdn.net/believejava/article/details/43229361

  6. 分步引导中,Js操作Cookie,实现判断用户是否第一次登陆网站

    上一篇介绍了分布引导插件IntroJs的使用,本篇介绍通过Js操作cookie的方法. 分步引导的功能只适合与第一次登陆网站的新用户,不能每次登陆都提示分布引导,那么如何判断用户是否第一次登录网站呢? ...

  7. OnTouch关于performClick的Warning

    OnTouch关于performClick的Warning 当你对一个控件(例如FloatingActionButton)使用setOnTouchListener() 或者是对你的自定义控件重写onT ...

  8. 英语每日阅读---3、VOA慢速英语(翻译+字幕+讲解):哈佛大学被控歧视亚裔学生

    英语每日阅读---3.VOA慢速英语(翻译+字幕+讲解):哈佛大学被控歧视亚裔学生 一.总结 一句话总结:Harvard Accused of Discriminating Against Asian ...

  9. 51nod-1259-分块+dp

    http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1259 1259 整数划分 V2 基准时间限制:1 秒 空间限制:1310 ...

  10. 【Python】词典

    词典 (dictionary).与列表相似,词典也可以储存多个元素.这种储存多个元素的对象称为容器(container) 基本概念 常见的创建词典的方法: >>>dic = {'to ...