rabbitmq的5种模式详解

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)。

消息持久化配置
  1. 队列持久化

    channel.queue_declare(queue='task_queue', durable=True)
    
  2. 消息持久化

    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)

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值