Python介绍RabbitMQ使用篇二WorkQueue
1. RabbitMQ WorkQueue基本工作模式介绍
上一篇我们使用C#语言讲解了单个消费者从消息队列中处理消息的模型,这一篇我们使用Python语言来讲解多个消费者同时工作从一个Queue处理消息的模型。

工作队列(又称:任务队列——Task Queues)是为了避免等待一些占用大量资源、时间的操作。当我们把任务(Task)当作消息发送到队列中,一个运行在后台的工作者(worker)进程就会取出任务然后处理。当你运行多个工作者(workers),任务就会在它们之间共享。这个概念在网络应用中是非常有用的,它可以在短暂的HTTP请求中处理一些复杂的任务,我么可以将耗时的请求放在任务队列,然后立马返回响应,接下来由多个worker去处理复杂的业务操作。(这种架构叫做"分布式异步队列",有时候用来方式D-DOS攻击,12306网站就是采用这种模式)
用Python操作Python模块首先要到如pika这个包,利用pip install pika去安装。
我们首先写一个new_task.py用来向任务队列中写入任务,已备用。
import pika
import sys with pika.BlockingConnection(pika.connection.ConnectionParameters(host="localhost")) as connection:
channel = connection.channel()
channel.queue_declare(queue = "hello")
for index in range(0,10):
channel.basic_publish(exchange="",
routing_key="hello",
body="["+str(index)+"]" + "Hello World")
connection.close()
接下来编写works.py程序,我们需要在works.py中创建消费者,让消费者从任务队列中提取任务去执行。
import pika
import sys
import time connection = pika.BlockingConnection(pika.connection.ConnectionParameters(host="localhost"))
channel = connection.channel()
channel.queue_declare(queue = "hello")
channel.basic_ack() def callback(ch, method, properties, body):
print(" [x] Received %r" % (body.decode('utf-8'),))
time.sleep(3) # 我们在这里利用线程休息来模拟一个比较耗时的任务处理
print(" [x] Done") channel.basic_consume(callback,
queue='hello',
no_ack= True) # 我们把no_ack标记为true用来屏蔽消息确认 channel.start_consuming()
connection.close()
在callback函数中让当前线程休息5秒用来模拟一个耗时的任务。
接下来首先打开两个Terminal窗口同时去运行works.py程序,然后运行new_task.py程序来查看效果。注意:在这里为了说明多个work能够同时分享任务队列中的队列,我们一定要先运行works.py,后运行new_task.py程序。具体原因后面在说明。

默认来说,RabbitMQ会按顺序得把消息发送给两个消费者(consumer),平均每个消费者都会收到同等数量得消息,这种发送消息的方式叫做——轮询(round-robin)。这样做的好处就是我们在处理相同数量的task所用的时间成倍的减少了。work越多,我们处理任务队列所用的时间就越少,这在高并发系统中会非常有用。
2.消息确认
当前的代码中,当消息被RabbitMQ发送给消费者(consumers)之后,马上就会在内存中移除。这种情况之下,假如其中一个工作者挂掉了,那么它正在处理的消息就会丢失,并且与此同时,后面所有发送到这个工作者的还没来得及处理的消息也都会丢失。这显然不是我们想看到的结果。我们不想丢失任何消息,如果一个工作者(worker)挂掉了,我们希望任务会重新发送给其他的工作者(worker)。
为了防止消息丢失,RabbitMQ提供了消息响应(acknowledgments)。消费者会通过一个ack(响应),告诉RabbitMQ已经收到并处理了某条消息,然后RabbitMQ才会释放并删除这条消息,而不是这条消息一发送出去马上就从内存中删除。
如果消费者(consumer)挂掉了,没有发送响应,RabbitMQ就会认为消息没有被完全处理,然后重新发送给其他消费者(consumer)。这样,及时工作者(workers)偶尔的挂掉,也不会丢失消息。消息是没有超时这个概念的;当工作者与它断开连的时候,RabbitMQ会重新发送消息。这样在处理一个耗时非常长的消息任务的时候就不会出问题了。消息响应默认是开启的。之前的例子中我们可以使用no_ack=True标识把它关闭。接下来我们移除这个标识,当工作者(worker)完成了任务,就发送一个响应。
对我们的workers.py稍微进行一下改动:
import pika
import sys
import time connection = pika.BlockingConnection(pika.connection.ConnectionParameters(host="localhost"))
channel = connection.channel()
channel.queue_declare(queue = "hello")
channel.basic_ack() def callback(ch, method, properties, body):
print(" [x] Received %r" % (body.decode('utf-8'),))
time.sleep(3)
print(" [x] Done")
ch.basic_ack(delivery_tag = method.delivery_tag) # 2. channel.basic_ack()方法用来执行消息确认操作 channel.basic_consume(callback,
queue='hello',
no_ack= False) # 1. no_ack告诉RabbitMQ开启消息确认机制,也就是说消息需要被确认 channel.start_consuming()
connection.close()
先开启两个Terinmal窗口执行workers.py然后执行new_task.py,当执行一半是利用ctrl+c关掉其中一个worker。可以看到RabbitMQ将已经关掉的worker的没来得及处理的消息,再一次发给worker2。以此保证消息不会丢失。

一定一定不要忘记消息确认
在回调方法中一定要记得调用channel.basic_ack()方法用来确认消息。原因很容易理解,消息如果不确认,任务就算是被callback函数处理成功了,RabbitMQ在内存中也不会删除这条任务,这条任务还会停留在内存中。这样无疑会带来一个比较大的bug。
3.消息持久化
RabbittMQ如果意外崩溃的话,就会丢失所有的“队列”和“消息”。因此为了确保信息不会丢失,有两个事情是需要注意的:我们必须把“队列”和“消息”设为持久化。下面的代码分别演示了如何进行队列持久化和消息持久化。
import pika
import sys with pika.BlockingConnection(pika.connection.ConnectionParameters(host="localhost")) as connection:
channel = connection.channel()
channel.queue_declare(queue = "hello",durable=True) # 1.Queue持久化
for index in range(0,10):
channel.basic_publish(exchange="",
routing_key="hello",
body="["+str(index)+"]" + "Hello World",
properties= pika.BasicProperties( # 2.消息持久化
delivery_mode= 2
))
connection.close()
4.公平调度/多劳多得
在实际生产中我们不一定所有的任务处理时都消耗同样多的时间,有的任务需要更长的时间,有的任务需要比较少的时间。这样就造成有的工作者比较繁忙,有的工作者比较轻松。然而RabbitMQ并不知道这些,它仍然一如既往的派发消息。这样无疑会造成资源的浪费。
这时因为RabbitMQ只管分发进入队列的消息,不会关心有多少消费者(consumer)没有作出响应。它盲目的把第n-th条消息发给第n-th个消费者。

我们可以使用basic.qos方法,并设置prefetch_count=1。这样是告诉RabbitMQ,再同一时刻,不要发送超过1条消息给一个工作者(worker),直到它已经处理了上一条消息并且作出了响应。这样,RabbitMQ就会把消息分发给下一个空闲的工作者(worker)。这样能保证消息是一个一个发出去的,并且是一个处理完成了再发另一个,而不是一次性全部发分出去了。这样尽可能的保证了每个worker的工作时间相同(公平调度),并且在相同时间执行效率高的worker会分享到更多的消息(多劳多得)。
channe.basic_qos(prefetch_count=1)
当然,如果所有的worker都长时间处于繁忙状态,没有时间接收下一条消息,那么任务队列就有可能满了。我们可以增加worker的数量,或者想其他办法。
代码整合
import pika
import sys with pika.BlockingConnection(pika.connection.ConnectionParameters(host="localhost")) as connection:
channel = connection.channel()
channel.queue_declare(queue = "hello",durable = True) # 1.Queue持久化
for index in range(0,10):
channel.basic_publish(exchange="",
routing_key="hello",
body="["+str(index)+"]" + "Hello World",
properties= pika.BasicProperties( # 2.消息持久化
delivery_mode= 2
))
connection.close()
new_task.py
import pika
import sys
import time connection = pika.BlockingConnection(pika.connection.ConnectionParameters(host="localhost"))
channel = connection.channel()
channel.queue_declare(queue = "hello",durable = True) # 队列持久化 def callback(ch, method, properties, body):
print(" [x] Received %r" % (body.decode('utf-8'),))
time.sleep(5)
print(" [x] Done----%r" % time.strftime("%Y-%m-%d %X",time.localtime()))
ch.basic_ack(delivery_tag = method.delivery_tag) channel.basic_qos(prefetch_count = 1) # 用来告诉每个worker一次只能接受一条消息
channel.basic_consume(callback,
queue='hello',
no_ack = False)
channel.start_consuming()
connection.close()
workers_busy
import pika
import sys
import time connection = pika.BlockingConnection(pika.connection.ConnectionParameters(host="localhost"))
channel = connection.channel()
channel.queue_declare(queue = "hello") def callback(ch, method, properties, body):
print(" [x] Received %r" % (body.decode('utf-8'),))
time.sleep(1)
print(" [x] Done----%r" % time.strftime("%Y-%m-%d %X",time.localtime()))
ch.basic_ack(delivery_tag = method.delivery_tag) channel.basic_qos(prefetch_count = 1)
channel.basic_consume(callback,
queue='hello',
no_ack = False)
channel.start_consuming()
connection.close()
workers_relax.py
Python介绍RabbitMQ使用篇二WorkQueue的更多相关文章
- C#介绍RabbitMQ使用篇一HelloWorld
RabbitMQ官网官方介绍: 译文: RabbitMQ是目前部署最广泛的开源消息代理(何为代理?可以理解为一个提供功能服务的中间件). 在全球范围内的大小企业中的生产环境中,RabbitMQ的部署两 ...
- python 学习之 基础篇二 字符编码
声明: 博文参考1:字符编码发展历程(ASCII,Unicode,UTF-8) 博文参考2:Python常见字符编码间的转换 (1)为什么要用字符编码 早期的计算机使用的是通电与否的特性的真空管,如果 ...
- Python操作rabbitmq系列(二):多个接收端消费消息
今天,我们要逐步开始讨论rabbitmq稍微高级点的耍法了.了解这一步,对我们设计高并发的系统非常有用.当然,还可以使用kafka.不过还是算了,有几个硬性条件不支持,还是用rabbitmq吧. 循环 ...
- python剑指网络篇二
在socket编程中 AF_INET 对应 IPv4 SOCK_STREAM 对应 TCP SOCK_DGRAM 对应 UDP
- Python之路【第九篇】:Python操作 RabbitMQ、Redis、Memcache、SQLAlchemy
Python之路[第九篇]:Python操作 RabbitMQ.Redis.Memcache.SQLAlchemy Memcached Memcached 是一个高性能的分布式内存对象缓存系统,用 ...
- Python全栈开发之路 【第一篇】:Python 介绍
本节内容 一.Python介绍 python的创始人为荷兰人——吉多·范罗苏姆(Guido van Rossum).1989年的圣诞节期间,吉多·范罗苏姆为了在阿姆斯特丹打发时间,决心开发一个新的脚本 ...
- 二、python介绍
python第一篇-------python介绍 一.python介绍 python的创始人为吉多·范罗苏姆(Guido van Rossum).1989年的圣诞节期间,Guido开始写Python语 ...
- 【python自动化第一篇:python介绍与入门】
一.python介绍以及发展史 1.1 python的介绍: 简单点来说吧,python这玩意儿是一个叫做Guido van Rossum的程序猿在1989年的圣诞打发时间而决心去开发的一个脚本编程 ...
- python【第十一篇】消息队列RabbitMQ、缓存数据库Redis
大纲 1.RabbitMQ 2.Redis 1.RabbitMQ消息队列 1.1 RabbitMQ简介 AMQP,即Advanced Message Queuing Protocol,高级消息队列协议 ...
随机推荐
- javascript: Element.getBoundingClientRect() 获取元素在网页上的坐标位置
来自:https://blog.csdn.net/weixin_42895400/article/details/81811095?utm_source=blogxgwz1 Element.getBo ...
- DMA设计
目录 DMA设计 DMA框架 手册请看英文手册 芯片特性 请求来源 协议简述 基本时序 模式 协议 数据大小的描述 具体完整的实例时序 代码设计 驱动程序 测试程序 测试 参考链接 title: DM ...
- 基于USB网卡适配器劫持DHCP Server嗅探Windows NTLM Hash密码
catalogue . DHCP.WPAD工作过程 . python Responder . USB host/client adapter(USB Armory): 包含DHCP Server . ...
- CMDB服务器管理系统【s5day88】:采集资产-文件配置(一)
django中间件工作原理 整体流程: 在接受一个Http请求之前的准备 启动一个支持WSGI网关协议的服务器监听端口等待外界的Http请求,比如Django自带的开发者服务器或者uWSGI服务器. ...
- PYTHON语言之常用内置函数
一 写在开头本文列举了一些常用的python内置函数.完整详细的python内置函数列表请参见python文档的Built-in Functions章节. 二 python常用内置函数请注意,有关内置 ...
- ST表学习笔记
ST表是一种利用DP思想求解最值的倍增算法 ST表常用于解决RMQ问题,即求解区间最值问题 接下来以求最大值为例分步讲解一下ST表的建立过程: 1.定义 f[i][j]表示[i,i+2j-1]这个长度 ...
- Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC
解决Invalid character found in the request target. The valid characters are defined in RFC 7230 and RF ...
- MySql常见的数据类型
⒈整型 名称 字节数 tinyint 1 smallint 2 mediumint 3 int/integer 4 bigint 8 特点: 1.如果不设置无符号还是有符号,默认是有符号,如果想设置无 ...
- 用户态与内核态 & 文件流与文件描述符 简介
用户态和内核态 程序代码的依赖和调用关系如下图所示: Lib:标准ASCI C函数,几乎所有的平台都支持该库函数,因此依赖该库的程序可移植性好: System Function:系统调用函数,与系统内 ...
- 006_设置执行命令提示和unset shell function
一.unset不能unset只读变量 问题: [root@zb1-bdwaimai-inf-wfe-28 ~]# source ~/.bash_profile bash: PROMPT_COMMAND ...