详解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输出一个杨辉三角的例子
Jun 13 Python
跟老齐学Python之重回函数
Oct 10 Python
Python检测一个对象是否为字符串类的方法
May 21 Python
Python全局变量用法实例分析
Jul 19 Python
Python基于回溯法子集树模板解决m着色问题示例
Sep 07 Python
Python反射的用法实例分析
Feb 11 Python
Python 3.3实现计算两个日期间隔秒数/天数的方法示例
Jan 07 Python
Python生命游戏实现原理及过程解析(附源代码)
Aug 01 Python
python针对mysql数据库的连接、查询、更新、删除操作示例
Sep 11 Python
python连接PostgreSQL数据库的过程详解
Sep 18 Python
找Python安装目录,设置环境路径以及在命令行运行python脚本实例
Mar 09 Python
vscode配置anaconda3的方法步骤
Aug 08 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
2019年中国咖啡业现状与发展趋势
2021/03/04 咖啡文化
编写漂亮的代码 - 将后台程序与前端程序分开
2008/04/23 PHP
smarty基础之拼接字符串的详解
2013/06/18 PHP
解析关于java,php以及html的所有文件编码与乱码的处理方法汇总
2013/06/24 PHP
PHP下获取上个月、下个月、本月的日期(strtotime,date)
2014/02/02 PHP
PHP中使用break跳出多重循环代码实例
2015/01/21 PHP
joomla数据库操作示例代码
2016/01/06 PHP
基于Laravel实现的用户动态模块开发
2017/09/21 PHP
windows 2008r2+php5.6.28环境搭建详细过程
2019/06/18 PHP
JavaScript var声明变量背后的原理示例解析
2013/10/12 Javascript
JS+CSS设置img在DIV中只显示Img垂直居中的部分
2013/10/24 Javascript
JavaScript事件委托的技术原理探讨示例
2014/04/17 Javascript
JavaScript常用脚本汇总(一)
2015/03/04 Javascript
关于在Servelet中如何获取当前时间的操作方法
2016/06/28 Javascript
Extjs让combobox写起来简洁又漂亮
2017/01/05 Javascript
js通过keyCode值判断单击键盘上某个键,然后触发指定的事件方法
2017/02/19 Javascript
利用node实现一个批量重命名文件的函数
2017/12/21 Javascript
Vue render渲染时间戳转时间,时间转时间戳及渲染进度条效果
2018/07/27 Javascript
vue-cli项目配置多环境的详细操作过程
2018/10/30 Javascript
koa2实现登录注册功能的示例代码
2018/12/03 Javascript
vue使用localStorage保存登录信息 适用于移动端、PC端
2019/05/27 Javascript
微信小程序页面间跳转传参方式总结
2019/06/13 Javascript
python原始套接字编程示例分享
2014/02/21 Python
Python比较两个图片相似度的方法
2015/03/13 Python
举例讲解Python的lambda语句声明匿名函数的用法
2016/07/01 Python
python文本数据相似度的度量
2018/03/12 Python
python3利用Axes3D库画3D模型图
2020/03/25 Python
HTML5中drawImage用法分析
2014/12/01 HTML / CSS
美国成衣女装品牌:CHICO’S
2016/09/19 全球购物
洲际酒店集团美国官网:IHG美国
2017/11/16 全球购物
大学毕业典礼演讲稿
2014/09/09 职场文书
工程质检员岗位职责
2015/04/08 职场文书
2015年第31个教师节致辞
2015/07/31 职场文书
python爬不同图片分别保存在不同文件夹中的实现
2021/04/02 Python
python使用pygame创建精灵Sprite
2021/04/06 Python
gtx1650怎么样 gtx1650显卡相当于什么级别
2022/04/08 数码科技