详解Python操作RabbitMQ服务器消息队列的远程结果返回


Posted in Python onJune 30, 2016

先说一下笔者这里的测试环境:Ubuntu14.04 + Python 2.7.4
RabbitMQ服务器

sudo apt-get install rabbitmq-server

Python使用RabbitMQ需要Pika库

sudo pip install pika

远程结果返回
消息发送端发送消息出去后没有结果返回。如果只是单纯发送消息,当然没有问题了,但是在实际中,常常会需要接收端将收到的消息进行处理之后,返回给发送端。

处理方法描述:发送端在发送信息前,产生一个接收消息的临时队列,该队列用来接收返回的结果。其实在这里接收端、发送端的概念已经比较模糊了,因为发送端也同样要接收消息,接收端同样也要发送消息,所以这里笔者使用另外的示例来演示这一过程。

示例内容:假设有一个控制中心和一个计算节点,控制中心会将一个自然数N发送给计算节点,计算节点将N值加1后,返回给控制中心。这里用center.py模拟控制中心,compute.py模拟计算节点。

compute.py代码分析

#!/usr/bin/env python
#coding=utf8
import pika
 
#连接rabbitmq服务器
connection = pika.BlockingConnection(pika.ConnectionParameters(
    host='localhost'))
channel = connection.channel()
 
#定义队列
channel.queue_declare(queue='compute_queue')
print ' [*] Waiting for n'
 
#将n值加1
def increase(n):
  return n + 1
 
#定义接收到消息的处理方法
def request(ch, method, properties, body):
  print " [.] increase(%s)" % (body,)
 
  response = increase(int(body))
 
  #将计算结果发送回控制中心
  ch.basic_publish(exchange='',
           routing_key=properties.reply_to,
           body=str(response))
  ch.basic_ack(delivery_tag = method.delivery_tag)
 
channel.basic_qos(prefetch_count=1)
channel.basic_consume(request, queue='compute_queue')
 
channel.start_consuming()

计算节点的代码比较简单,值得一提的是,原来的接收方法都是直接将消息打印出来,这边进行了加一的计算,并将结果发送回控制中心。

center.py代码分析

#!/usr/bin/env python
#coding=utf8
import pika
 
class Center(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):
    self.response = body
   
   
  def request(self, n):
    self.response = None
    #发送计算请求,并声明返回队列
    self.channel.basic_publish(exchange='',
                  routing_key='compute_queue',
                  properties=pika.BasicProperties(
                     reply_to = self.callback_queue,
                     ),
                  body=str(n))
    #接收返回的数据
    while self.response is None:
      self.connection.process_data_events()
    return int(self.response)
 
center = Center()
 
print " [x] Requesting increase(30)"
response = center.request(30)
print " [.] Got %r" % (response,)

上例代码定义了接收返回数据的队列和处理方法,并且在发送请求的时候将该队列赋值给reply_to,在计算节点代码中就是通过这个参数来获取返回队列的。

打开两个终端,一个运行代码python compute.py,另外一个终端运行center.py,如果执行成功,应该就能看到效果了。

笔者在测试的时候,出了些小问题,就是在center.py发送消息时没有指明返回队列,结果compute.py那边在计算完结果要发回数据时报错,提示routing_key不存在,再次运行也报错。用rabbitmqctl list_queues查看队列,发现compute_queue队列有1条数据,每次重新运行compute.py的时候,都会重新处理这条数据。后来使用/etc/init.d/rabbitmq-server restart重新启动下rabbitmq就ok了。

相互关联编号correlation id
上一遍演示了远程结果返回的示例,但是有一个没有提到,就是correlation id,这个是个什么东东呢?

假设有多个计算节点,控制中心开启多个线程,往这些计算节点发送数字,要求计算结果并返回,但是控制中心只开启了一个队列,所有线程都是从这个队列里获取消息,每个线程如何确定收到的消息就是该线程对应的呢?这个就是correlation id的用处了。correlation翻译成中文就是相互关联,也表达了这个意思。

correlation id运行原理:控制中心发送计算请求时设置correlation id,而后计算节点将计算结果,连同接收到的correlation id一起返回,这样控制中心就能通过correlation id来标识请求。其实correlation id也可以理解为请求的唯一标识码。

示例内容:控制中心开启多个线程,每个线程都发起一次计算请求,通过correlation id,每个线程都能准确收到相应的计算结果。

compute.py代码分析

和上面一篇相比,只需修改一个地方:将计算结果发送回控制中心时,增加参数correlation_id的设定,该参数的值其实是从控制中心发送过来的,这里只是再次发送回去。代码如下:

#!/usr/bin/env python
#coding=utf8
import pika
 
#连接rabbitmq服务器
connection = pika.BlockingConnection(pika.ConnectionParameters(
    host='localhost'))
channel = connection.channel()
 
#定义队列
channel.queue_declare(queue='compute_queue')
print ' [*] Waiting for n'
 
#将n值加1
def increase(n):
  return n + 1
 
#定义接收到消息的处理方法
def request(ch, method, props, body):
  print " [.] increase(%s)" % (body,)
 
  response = increase(int(body))
 
  #将计算结果发送回控制中心,增加correlation_id的设定
  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(request, queue='compute_queue')
 
channel.start_consuming()

center.py代码分析

控制中心代码稍微复杂些,其中比较关键的有三个地方:

使用python的uuid来产生唯一的correlation_id。
发送计算请求时,设定参数correlation_id。
定义一个字典来保存返回的数据,并且键值为相应线程产生的correlation_id。
代码如下:

#!/usr/bin/env python
#coding=utf8
import pika, threading, uuid
 
#自定义线程类,继承threading.Thread
class MyThread(threading.Thread):
  def __init__(self, func, num):
    super(MyThread, self).__init__()
    self.func = func
    self.num = num
 
  def run(self):
    print " [x] Requesting increase(%d)" % self.num
    response = self.func(self.num)
    print " [.] increase(%d)=%d" % (self.num, response)
 
#控制中心类
class Center(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)
 
    #返回的结果都会存储在该字典里
    self.response = {}
 
  #定义接收到返回消息的处理方法
  def on_response(self, ch, method, props, body):
    self.response[props.correlation_id] = body
 
  def request(self, n):
    corr_id = str(uuid.uuid4())
    self.response[corr_id] = None
 
    #发送计算请求,并设定返回队列和correlation_id
    self.channel.basic_publish(exchange='',
                  routing_key='compute_queue',
                  properties=pika.BasicProperties(
                     reply_to = self.callback_queue,
                     correlation_id = corr_id,
                     ),
                  body=str(n))
    #接收返回的数据
    while self.response[corr_id] is None:
      self.connection.process_data_events()
    return int(self.response[corr_id])
 
center = Center()
#发起5次计算请求
nums= [10, 20, 30, 40 ,50]
threads = []
for num in nums:
  threads.append(MyThread(center.request, num))
for thread in threads:
  thread.start()
for thread in threads:
  thread.join()

笔者开启了两个终端,来运行compute.py,开启一个终端来运行center.py,最后结果输出截图如下:

详解Python操作RabbitMQ服务器消息队列的远程结果返回

可以看到虽然获取的结果不是顺序输出,但是结果和源数据都是对应的。

这边示例的做法就是创建一个队列,使用correlation id来标识每次请求。也有做法可以不使用correlation id,就是每请求一次,就创建一个临时队列,不过这样太消耗性能了,官方也不推荐这么做。

Python 相关文章推荐
极简的Python入门指引
Apr 01 Python
python基于BeautifulSoup实现抓取网页指定内容的方法
Jul 09 Python
Python实现更改图片尺寸大小的方法(基于Pillow包)
Sep 19 Python
NumPy.npy与pandas DataFrame的实例讲解
Jul 09 Python
Linux下Pycharm、Anaconda环境配置及使用踩坑
Dec 19 Python
python识别文字(基于tesseract)代码实例
Aug 24 Python
如何基于Python + requests实现发送HTTP请求
Jan 13 Python
让Django的BooleanField支持字符串形式的输入方式
May 20 Python
通过实例简单了解python yield使用方法
Aug 06 Python
Numpy数组的广播机制的实现
Nov 03 Python
解决jupyter notebook图片显示模糊和保存清晰图片的操作
Apr 24 Python
用python画城市轮播地图
May 28 Python
Python操作RabbitMQ服务器实现消息队列的路由功能
Jun 29 #Python
Python通过RabbitMQ服务器实现交换机功能的实例教程
Jun 29 #Python
Python+Pika+RabbitMQ环境部署及实现工作队列的实例教程
Jun 29 #Python
Python的消息队列包SnakeMQ使用初探
Jun 29 #Python
Python中线程的MQ消息队列实现以及消息队列的优点解析
Jun 29 #Python
深入理解Python中装饰器的用法
Jun 28 #Python
Python中的迭代器与生成器高级用法解析
Jun 28 #Python
You might like
[FAQ]PHP中的一些常识:类篇
2006/10/09 PHP
如何获知PHP程序占用多少内存(memory_get_usage)
2012/09/23 PHP
Windows上php5.6操作mongodb数据库示例【配置、连接、获取实例】
2019/02/13 PHP
php array_map()函数实例用法
2021/03/03 PHP
HR vs ForZe BO3 第一场 2.13
2021/03/10 DOTA
jquery的颜色选择插件实例代码
2008/10/02 Javascript
jQuery中filter()和find()的区别深入了解
2013/09/25 Javascript
javascript实现回车键提交表单方法总结
2015/01/10 Javascript
javascript文本框内输入文字倒计数的方法
2015/02/24 Javascript
详解JavaScript对Date对象的操作问题(生成一个倒数7天的数组)
2015/10/01 Javascript
JavaScript实现简单生成随机颜色的方法
2017/09/21 Javascript
Vue2.0 http请求以及loading展示实例
2018/03/06 Javascript
bing Map 在vue项目中的使用详解
2018/04/09 Javascript
vue生命周期的探索
2019/04/03 Javascript
基于 Vue 的 Electron 项目搭建过程图文详解
2020/07/22 Javascript
[01:18:36]LGD vs VP Supermajor 败者组决赛 BO3 第一场 6.10
2018/07/04 DOTA
使用Python的Treq on Twisted来进行HTTP压力测试
2015/04/16 Python
Python的Django框架中的Context使用
2015/07/15 Python
Python 列表(List) 的三种遍历方法实例 详解
2017/04/15 Python
Python中生成器和迭代器的区别详解
2018/02/10 Python
Python使用dict.fromkeys()快速生成一个字典示例
2019/04/24 Python
Linux下通过python获取本机ip方法示例
2019/09/06 Python
Python程序控制语句用法实例分析
2020/01/14 Python
简述python Scrapy框架
2020/08/17 Python
Django如何实现密码错误报错提醒
2020/09/04 Python
pytorch __init__、forward与__call__的用法小结
2021/02/27 Python
基于注解实现 SpringBoot 接口防刷的方法
2021/03/02 Python
2014两会学习心得:榜样精神伴我行
2014/03/17 职场文书
请假条标准格式规范
2014/04/10 职场文书
企业党的群众路线教育实践活动学习心得体会
2014/10/31 职场文书
2014年学习委员工作总结
2014/11/14 职场文书
六年级小学生评语
2014/12/26 职场文书
体育部部长竞选稿
2015/11/21 职场文书
2016党员干部廉政准则学习心得体会
2016/01/20 职场文书
python学习之panda数据分析核心支持库
2021/05/07 Python
CSS精灵图的原理与使用方法介绍
2022/03/17 HTML / CSS