1. Hello World (直连模式)
# producer.py
import pika
# 1. 创建连接
credentials = pika.PlainCredentials('root', 'root123') # 需要连接对象的用户名和密码
parameters = pika.ConnectionParameters(host='192.168.100.37', port=5672, virtual_host='/ok',
credentials=credentials) # 连接参数,ip地址,端口,虚拟host,身份信息
connection = pika.BlockingConnection(parameters) # 创建连接
# 2. 创建通道
channel = connection.channel()
# 3. 声明/创建队列,有则使用,无则创建
channel.queue_declare(queue='hello')
# 4. 发送消息,由空字符串标识的默认交换,指定队列名称,指定消息体
channel.basic_publish(exchange='', routing_key='hello', body='Hello World!')
print(" [x] Sent 'Hello World!'")
# 5. 关闭连接
connection.close()
# consumer1.py
import pika, sys, os
def main():
# 1. 创建连接
credentials = pika.PlainCredentials('root', 'root123')
parameters = pika.ConnectionParameters(host='192.168.100.37', port=5672, virtual_host='/ok', credentials=credentials)
connection = pika.BlockingConnection(parameters)
# 2. 建立通道
channel = connection.channel()
# 3. 声明队列
channel.queue_declare(queue='hello')
# 4. 定义回调函数,便于队列中获取数据后,进行后续操作
def callback(ch, method, properties, body):
print(" [x] Received %r" % body)
# 5. 接收消息
channel.basic_consume(queue='hello', on_message_callback=callback, auto_ack=True)
print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
print('Interrupted')
try:
sys.exit(0)
except SystemExit:
os._exit(0)
2. work(任务队列模型)
实现最简单的任务模型代码参考如下
# producer.py
import pika
# 1. 创建连接
credentials = pika.PlainCredentials('root', 'root123') # 需要连接对象的用户名和密码
parameters = pika.ConnectionParameters(host='192.168.100.37', port=5672, virtual_host='/ok',
credentials=credentials) # 连接参数,ip地址,端口,虚拟host,身份信息
connection = pika.BlockingConnection(parameters) # 创建连接
# 2. 创建通道
channel = connection.channel()
# 3. 声明/创建队列,有则使用,无则创建
channel.queue_declare(queue='hello')
# 4. 发送消息,由空字符串标识的默认交换,指定队列名称,指定消息体
for i in range(0, 20):
msg = "Hello World!---" + str(i)
channel.basic_publish(exchange='', routing_key='hello', body=msg)
print(msg)
# 5. 关闭连接
connection.close()
#counsumer1.py 和 counsumer2.py
import pika, sys, os
def main():
# 1. 创建连接
credentials = pika.PlainCredentials('root', 'root123')
parameters = pika.ConnectionParameters(host='192.168.100.37', port=5672, virtual_host='/ok', credentials=credentials)
connection = pika.BlockingConnection(parameters)
# 2. 建立通道
channel = connection.channel()
# 3. 声明队列
channel.queue_declare(queue='hello')
# 4. 定义回调函数,便于队列中获取数据后,进行后续操作
def callback(ch, method, properties, body):
print(" [x] Received %r" % body)
# 5. 接收消息
channel.basic_consume(queue='hello', on_message_callback=callback, auto_ack=True)
print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
print('Interrupted')
try:
sys.exit(0)
except SystemExit:
os._exit(0)
运行结果如下
默认情况下,RabbitMQ将按顺序将每个消息发送给下一个使用者。平均而言,每个消费者都会收到相同数量的消息。这种分发消息的方式称为循环。
消息确认
完成一项任务可能需要几秒钟。您可能想知道,如果其中一个使用者开始一项漫长的任务并仅部分完成而死掉,会发生什么情况。使用我们当前的代码,RabbitMQ一旦将消息传递给使用者,便立即将其标记为删除。在这种情况下,如果您杀死一个worker,我们将丢失正在处理的消息。我们还将丢失所有发送给该特定worker但尚未处理的消息。
但是我们不想丢失任何任务。如果一个worker死亡,我们希望将任务交付给另一个worker。
为了确保消息永不丢失,RabbitMQ支持消息确认。消费者发送回一个确认(acknowledgement),以告知RabbitMQ已经接收,处理了特定的消息,并且RabbitMQ可以自由删除它。
如果使用者死了(其通道已关闭,连接已关闭或TCP连接丢失)而没有发送确认,RabbitMQ将了解消息未完全处理,并将重新排队。如果同时有其他消费者在线,它将很快将其重新分发给另一个消费者。这样,您可以确保即使worker偶尔死亡也不会丢失任何消息。
没有任何消息超时;消费者死亡时,RabbitMQ将重新传递消息。即使处理消息需要非常非常长的时间也没关系。
默认情况下,手动消息确认处于打开状态。在前面的示例中,我们通过auto_ack = True标志显式关闭了它们。我们完成任务后,是时候删除此标志并从worker发送适当的确认了(默认auto_ack 为False)。
消息持久化配置
-
队列持久化
channel.queue_declare(queue='task_queue', durable=True)
-
消息持久化
channel.basic_publish(exchange='', routing_key="task_queue", body=message, properties=pika.BasicProperties( delivery_mode = 2, # 消息持久化 ))
有关消息持久性的说明
将消息标记为持久性并不能完全保证不会丢失消息。尽管它告诉RabbitMQ将消息保存到磁盘,但是RabbitMQ接受消息但尚未将其保存时,仍有很短的时间。而且,RabbitMQ不会对每条消息都执行fsync(2)-它可能只是保存到缓存中,而没有真正写入磁盘。持久性保证并不强,但是对于我们的简单任务队列而言,这已经绰绰有余了。如果您需要更强有力的保证,则可以使用 发布者确认。
公平派遣(循环机制)
多个消费者绑定一个队列,默认情况下,队列中的消息是平均分配给每一个消费者的。
发生这种情况是因为RabbitMQ在消息进入队列时才调度消息。它不会查看消费者的未确认消息数。它只是盲目地将每第n条消息发送给第n个使用者。
为了解决这个问题,我们可以将Channel的basic_qos通道方法与 prefetch_count = 1设置一起使用。这使用basic.qos协议方法来告诉RabbitMQ一次不向worker发送多条消息。换句话说,在处理并确认上一条消息之前,不要将新消息发送给worker。而是将其分派给不忙的下一个工作程序。
channel.basic_qos(prefetch_count=1)
注意关于队列大小
如果所有worker都忙,则您的队列可以填满。您将需要注意这一点,并可能增加更多的worker,或使用消息TTL。
实现【能者多劳】,【任务持久化】,【消息手动确认】的要求完整代码如下:
# produser.py
import pika
# 1. 创建连接
credentials = pika.PlainCredentials('root', 'root123') # 需要连接对象的用户名和密码
parameters = pika.ConnectionParameters(host='192.168.100.37', port=5672, virtual_host='/ok',
credentials=credentials) # 连接参数,ip地址,端口,虚拟host,身份信息
connection = pika.BlockingConnection(parameters) # 创建连接
# 2. 创建通道
channel = connection.channel()
# 3. 声明/创建队列,有则使用,无则创建,修改队列属性的话,需要重新新建一个队列(比如这里定义队列的持久化,就不能用之前的hello,改为new_hello)
channel.queue_declare(queue='new_hello', durable=True)
# 4. 发送消息,由空字符串标识的默认交换,指定队列名称,指定消息体
for i in range(0, 20):
msg = "Hello World!---" + str(i)
channel.basic_publish(exchange='', routing_key='new_hello', body=msg, properties=pika.BasicProperties(
delivery_mode=2, # 消息持久化
))
print(msg)
# 5. 关闭连接
connection.close()
#consumer1.py
import time
import pika, sys, os
def main():
# 1. 创建连接
credentials = pika.PlainCredentials('root', 'root123')
parameters = pika.ConnectionParameters(host='192.168.100.37', port=5672, virtual_host='/ok',
credentials=credentials)
connection = pika.BlockingConnection(parameters)
# 2. 建立通道
channel = connection.channel()
# 3. 声明队列
channel.queue_declare(queue='new_hello', durable=True)
# 4. 定义回调函数,便于队列中获取数据后,进行后续操作
def callback(ch, method, properties, body):
time.sleep(0.1)
print(" [x] Received %r" % body)
ch.basic_ack(delivery_tag=method.delivery_tag)
# 5. 接收消息
channel.basic_qos(prefetch_count=1) # 队列消息一个一个接收
channel.basic_consume(queue='new_hello', on_message_callback=callback, auto_ack=False)
print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
print('Interrupted')
try:
sys.exit(0)
except SystemExit:
os._exit(0)
3. Publish/Subscribe(广播模型)
在开始代码之前,需要先介绍一下exchanges(交换机),简单理解来说,就是接收生产者发送的消息,并把消息发送给队列。
在上面两个模型中,我们代码中并没有定义交换机这一部分,其实实际上是用了一个默认的交换机,exchange=''
。
交换机有几种模式: direct, topic, headers,fanout.广播模型中,我们先介绍和使用fanout(扇形)
1. 交换机声明
交换机声明包括交换机的名称和其对应的类型,比如,下面的代码
channel.exchange_declare(exchange='logs',exchange_type='fanout')
2. 临时队列
在广播模式中,我们是需要获取当前时间的所有生产者给出的数据,不像上面的两个案例中,我们需要不同的数据,所以需要指定队列,所以不在这里,我们可以直接生成临时的随机队列供使用。当然,当消费者断开当前的临时队列,我们就需要即可进行销毁;
result = channel.queue_declare(queue='', exclusive=True)
获取当前队列名称
queue_name = result.method.queue
3. 队列和交换机进行绑定
channel.queue_bind(exchange='logs', queue=result.method.queue)
# producers.py
import pika
# 1. 创建连接
credentials = pika.PlainCredentials('root', 'root123') # 需要连接对象的用户名和密码
parameters = pika.ConnectionParameters(host='192.168.100.37', port=5672, virtual_host='/ok',
credentials=credentials) # 连接参数,ip地址,端口,虚拟host,身份信息
connection = pika.BlockingConnection(parameters) # 创建连接
# 2. 创建通道
channel = connection.channel()
# 3. 声明交换机
channel.exchange_declare(exchange="logs", exchange_type="fanout")
# 4. 发送消息,由空字符串标识的默认交换,指定队列名称,指定消息体
for i in range(0, 20):
msg = "Hello World!---" + str(i)
channel.basic_publish(exchange='logs', routing_key='', body=msg, properties=pika.BasicProperties(
delivery_mode=2, # 消息持久化
))
print(msg)
# 5. 关闭连接
connection.close()
#consumers1.py
import pika, sys, os
def main():
# 1. 创建连接
credentials = pika.PlainCredentials('root', 'root123')
parameters = pika.ConnectionParameters(host='192.168.100.37', port=5672, virtual_host='/ok',
credentials=credentials)
connection = pika.BlockingConnection(parameters)
# 2. 建立通道
channel = connection.channel()
# 3. 声明交换机
channel.exchange_declare(exchange="logs", exchange_type="fanout")
# 4. 队列声明
result = channel.queue_declare(queue="", exclusive=True)
queue_name = result.method.queue
# 5. 交换机和通道进行绑定
channel.queue_bind(exchange="logs", queue=queue_name)
# 6. 定义回调函数,便于队列中获取数据后,进行后续操作
def callback(ch, method, properties, body):
print(" [x] Received %r" % body)
# print(method.delivery_tag,"---")
ch.basic_ack(delivery_tag=method.delivery_tag)
# 7. 接收消息
channel.basic_qos(prefetch_count=1) # 队列消息一个一个发送
channel.basic_consume(queue=queue_name, on_message_callback=callback, auto_ack=False)
print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
print('Interrupted')
try:
sys.exit(0)
except SystemExit:
os._exit(0)
关于队列的声明,随机或者自定义都是可以的,下面的代码,我把两个消费者的队列分别起名为111和222,其实是完全没有影响的。
4. Routing(路由模型)
我们上面说的广播模型,是将生产者的所有信息都进行接收,不区分是哪个消费者;这里的路由模型,实现的是,针对不同类型的数据,可以给某一个消费者,或者多个,实现‘分类’;类似日志系统,只希望将error等级以上的日志内容记录到磁盘,但是像info,warning等低级别的日志,只在终端进行打印即可,那么类似这种需求就可以使用路由模型进行处理。
当然,路由模型中,你也可以将同一个路由绑定到多个队列,这样实现的效果就和广播是一样的了。
在路由模型中,交换机的类型,选择为direct,类似直接匹配的意思。
1.定义交换机名称和类型
channel.exchange_declare(exchange='direct_logs',exchange_type='direct')
2. 发送者绑定路由
channel.basic_publish(exchange='direct_logs',
routing_key="error",
body=message)
# producer.py
import random
import pika
# 1. 创建连接
credentials = pika.PlainCredentials('root', 'root123') # 需要连接对象的用户名和密码
parameters = pika.ConnectionParameters(host='192.168.100.37', port=5672, virtual_host='/ok',
credentials=credentials) # 连接参数,ip地址,端口,虚拟host,身份信息
connection = pika.BlockingConnection(parameters) # 创建连接
# 2. 创建通道
channel = connection.channel()
# 3. 声明交换机
channel.exchange_declare(exchange="direct_logs", exchange_type="direct")
# 4. 发送消息,由空字符串标识的默认交换,指定队列名称,指定消息体
for i in range(0, 20):
m = random.choice(["error", "info", "warning"])
msg = "Hello World!---" + str(m)
# route_key绑定路由
channel.basic_publish(exchange='direct_logs', routing_key=m, body=msg, properties=pika.BasicProperties(
delivery_mode=2, # 消息持久化
))
print(msg)
# 5. 关闭连接
connection.close()
# consumer.py
import pika, sys, os
def main():
# 1. 创建连接
credentials = pika.PlainCredentials('root', 'root123')
parameters = pika.ConnectionParameters(host='192.168.100.37', port=5672, virtual_host='/ok',
credentials=credentials)
connection = pika.BlockingConnection(parameters)
# 2. 建立通道
channel = connection.channel()
# 3. 声明交换机
channel.exchange_declare(exchange="direct_logs", exchange_type="direct")
# 4. 队列声明
result = channel.queue_declare(queue="222", exclusive=True)
# queue_name = result.method.queue
# 5. 交换机和通道进行绑定,路由绑定
channel.queue_bind(exchange="direct_logs", queue="222", routing_key="error")
# 6. 定义回调函数,便于队列中获取数据后,进行后续操作
def callback(ch, method, properties, body):
print(" [x] Received %r" % body)
# print(method.delivery_tag, "---")
ch.basic_ack(delivery_tag=method.delivery_tag)
# 7. 接收消息
channel.basic_qos(prefetch_count=1) # 队列消息一个一个发送
channel.basic_consume(queue="222", on_message_callback=callback, auto_ack=False)
print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
print('Interrupted')
try:
sys.exit(0)
except SystemExit:
os._exit(0)
5. Topics(动态路由模型)
Topics,我这里翻译过来称为动态路由,也就是说,其实其实现的功能和Routing类似,只不过在匹配的时候,增加了【模糊匹配】的功能。也就是说,对于多个条件的路由,可以使用动态路由模型进行实现。
1. 动态路由格式
动态路由之间以【.】分隔,类似aa.bb.cc这样的
2. 动态路由匹配符
*(星号)可以代替一个单词。
#(井号)可以替代零个或多个单词。
当队列用“ # ”(哈希)绑定键绑定时,它将接收所有消息,而与路由键无关,就像在扇出(fanout)交换中一样。
当在绑定中不使用特殊字符“ * ”(星号)和“ # ”(井号)时,主题交换的行为就像直接(direct)的一样。
# producer.py
import random
import pika
# 1. 创建连接
credentials = pika.PlainCredentials('root', 'root123') # 需要连接对象的用户名和密码
parameters = pika.ConnectionParameters(host='192.168.100.37', port=5672, virtual_host='/ok',
credentials=credentials) # 连接参数,ip地址,端口,虚拟host,身份信息
connection = pika.BlockingConnection(parameters) # 创建连接
# 2. 创建通道
channel = connection.channel()
# 3. 声明交换机
channel.exchange_declare(exchange="topics_logs", exchange_type="topic")
# 4. 发送消息,由空字符串标识的默认交换,指定队列名称,指定消息体
for i in range(0, 20):
m = random.choice(["error", "info", "warning"]) + ".name"
msg = "Hello World!---" + str(m)
channel.basic_publish(exchange='topics_logs', routing_key=m, body=msg, properties=pika.BasicProperties(
delivery_mode=2, # 消息持久化
))
print(msg)
# 5. 关闭连接
connection.close()
# consumer1.py
import pika, sys, os
def main():
# 1. 创建连接
credentials = pika.PlainCredentials('root', 'root123')
parameters = pika.ConnectionParameters(host='192.168.100.37', port=5672, virtual_host='/ok',
credentials=credentials)
connection = pika.BlockingConnection(parameters)
# 2. 建立通道
channel = connection.channel()
# 3. 声明交换机
channel.exchange_declare(exchange="topics_logs", exchange_type="topic")
# 4. 队列声明
result = channel.queue_declare(queue="111", exclusive=True)
# queue_name = result.method.queue
# 5. 交换机和通道进行绑定
channel.queue_bind(exchange="topics_logs", queue="111", routing_key="*.*")
# 6. 定义回调函数,便于队列中获取数据后,进行后续操作
def callback(ch, method, properties, body):
print(" [x] Received %r" % body)
# print(method.delivery_tag,"---")
ch.basic_ack(delivery_tag=method.delivery_tag)
# 7. 接收消息
channel.basic_qos(prefetch_count=1) # 队列消息一个一个发送
channel.basic_consume(queue="111", on_message_callback=callback, auto_ack=False)
print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
print('Interrupted')
try:
sys.exit(0)
except SystemExit:
os._exit(0)
# consumer2.py
import pika, sys, os
def main():
# 1. 创建连接
credentials = pika.PlainCredentials('root', 'root123')
parameters = pika.ConnectionParameters(host='192.168.100.37', port=5672, virtual_host='/ok',
credentials=credentials)
connection = pika.BlockingConnection(parameters)
# 2. 建立通道
channel = connection.channel()
# 3. 声明交换机
channel.exchange_declare(exchange="topics_logs", exchange_type="topic")
# 4. 队列声明
result = channel.queue_declare(queue="222", exclusive=True)
# queue_name = result.method.queue
# 5. 交换机和通道进行绑定
channel.queue_bind(exchange="topics_logs", queue="222", routing_key="#")
# 6. 定义回调函数,便于队列中获取数据后,进行后续操作
def callback(ch, method, properties, body):
print(" [x] Received %r" % body)
# print(method.delivery_tag, "---")
ch.basic_ack(delivery_tag=method.delivery_tag)
# 7. 接收消息
channel.basic_qos(prefetch_count=1) # 队列消息一个一个发送
channel.basic_consume(queue="222", on_message_callback=callback, auto_ack=False)
print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
print('Interrupted')
try:
sys.exit(0)
except SystemExit:
os._exit(0)