rabbitMQ五种消息模型

本文详细介绍了RabbitMQ的五种消息模型:一对一简单模式、work模式、发布/订阅模式、路由模式(direct模式)和Topic模式。重点讨论了每种模式的概念、工作原理、优缺点以及代码实现,帮助理解如何在不同场景下选择合适的消息模型。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

rabbitMQ提供了6种消息模型,但是第六种其实是RPC(远程过程调用),并不是MQ,所以是5种模式。

其中5种消息模型分为两类:

  • 第一种和第二种是点对点
  • 第三,四,五种是一对多,都属于订阅模型,只不过是进行路由的方式不同。
  • 第六种RPC远程调用也可不算做模式

本文参照网站:https://www.rabbitmq.com/getstarted.html

一,一对一简单模式

在这里插入图片描述

  1. 概念
    • 生产者将消息发送到“ hello”队列。使用者从该队列接收消息。
  2. 图解
    • “ P”是我们的生产者

    • “ C”是我们的消费者

    • 中间的框是一个队列-RabbitMQ代表使用者保留的消息缓冲区。

  3. 发送信息
    import pika
    
    #连接主机
    conn = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
    channel = connection.channel()
    #发送信息
    #如果队列不存在就先创建一个队列
    channel.queue_declare(queue = 'hello')
    #把信息放入指定队列
    channel.basic_publish(exchange = ``,
                          routing_key = 'hello',
                          body = “世界你好!” )
    print(“ [x]发送'Hello World!'”)
    connection.close()
    
    • exchange有几种交换类型可用:direct,topic,headers 和fanout。

      #比如fanout它只是将接收到的所有消息广播到它知道的所有队列中
      channel.exchange_declare(exchange='logs',exchange_type='fanout')
      #连接默认交换机
      #channel.exchange_declare(exchange='',exchange_type='fanout')
      
    • 查看服务器上的交换机:sudo rabbitmqctl list_exchanges

      • 其中有一些是默认创建的交换机
    • 查看队列绑定情况:rabbitmqctl list_bindings

    • 创建临时队列:

      res = channel.queue_declare(queue = '',Exclusive = True)
      #Exclusive = True一旦使用者连接关闭,则应删除队列。
      
    • 如果发送失败,可能是磁盘空间不足,默认情况下,rabbitmq需要200MB的可用空间,可以尝试检查代理日志文件以确认并减少限制,rabbitmq.conf配置具体配置可点此处

  4. 接收信息
    import pika
    
    #连接主机
    conn = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
    channel = connection.channel()
    #以防没有队列则重新创建
    channel.queue_declare(queue = 'hello')
    
    #接收消息通过将callback函数订阅到队列来工作
    #每当我们收到消息时,Pika库都会调用此callback函数
    def callback(ch, method, properties, body):
       print(" [x] Received %r" % body)
    #接下来要告诉rabbitMQ这个callback函数应该从hello队列接收消息
    channel.basic_consume(
       queue='hello', on_message_callback=callback, auto_ack=True)
    
    print(' [*] Waiting for messages. To exit press CTRL+C')
    channel.start_consuming()
    
    • 队列接收消息过程:
      • 通过将callback函数订阅到队列来工作
      • 每当我们收到消息时,Pika库都会调用此callback函数
      • 接下来要告诉rabbitMQ这个callback函数应该从hello队列接收消息
    • 查看RabbitMQ的队列以及队列中有多少条消息:sudo rabbltmqctl list_queues

二,work模式

在这里插入图片描述

  1. 概念
    • work模式是一个生产者,一个队列,多个消费者,每个消费者获取到的消息唯一,多个消费者只有一个队列
  2. 优点及作用
    • 一个生产者一个队列多个消费者模式能够并行化工作
    • 解决了消息积压问题
  3. 工作原理
    • 默认情况下,RabbitMQ将每个消息按顺序发送给下一个使用者。平均而言,每个消费者都会收到相同数量的消息。这种分发消息的方式称为循环。与三个或更多的工人一起尝试
  4. 如果工人工作中突然死亡或者断网:
    • 如果使用者在不发送确认的情况下死亡(其通道已关闭,连接已关闭或TCP连接丢失),RabbitMQ将了解消息未得到充分处理,并将重新排队发送。确认必须在收到交货的同一通道上发送。如果尝试使用其他通道进行确认将导致通道级协议异常。如果同时有其他消费者在线,它将很快将其重新分发给另一个消费者。这样,就可以确保即使工人偶尔死亡也不会丢失任何消息。如果未进行处理RabbitMQ将消耗越来越多的内存,因为它将无法释放任何未确认的消息。

    • 为了调试这种错误可以打印messages_unacknowledged字段:

      sudo rabbitmqctl list_queues name messages_ready messages_unacknowledged
      
  5. 消息及队列持久化
    • 解决办法: 将队列和消息都标记为持久性。

    • 解决步骤:

      • 确保该队列将在RabbitMQ节点重启后继续存在。为此,我们需要将其声明为持久的

        #如果设置持久化之前存在该队列则不起作用
        channel.queue_declare(queue='hello', durable=True)
        
      • 通过提供值为2的delivery_mode属性将消息标记为持久性

        channel.basic_publish(exchange = '',
                              routing_key = “ task_queue”,
                              body = message,
                              properties = pika.BasicProperties(
                                 delivery_mode = 2,#使消息持久化 
                              ))
        
        • 有关消息持久性的说明

          将消息标记为持久性并不能完全保证不会丢失消息。尽管它告诉RabbitMQ将消息保存到磁盘,但是RabbitMQ接受消息但尚未将其保存时,仍有很短的时间。另外,RabbitMQ不会对每条消息都执行fsync(2)-它可能只是保存到缓存中,而没有真正写入磁盘。持久性保证并不强,但是对于我们的简单任务队列而言,这已经绰绰有余了。如果您需要更强有力的保证,则可以使用 发布者确认

  6. 任务派遣
    • 工作有轻有重一个人忙碌一个工人几乎没有工作,是因为什么?
      在这里插入图片描述

      • 因为RabbitMQ在消息进入队列时才调度消息。它不会查看使用者的未确认消息数。它只是盲目地将每第n条消息发送给第n个使用者。

      • 解决办法:

        #在处理并确认上一条消息之前,不要将新消息发送给工作人员。而是将其分派给不忙的下一个工作程序。
        channel.basic_qos(prefetch_count = 1)
        
    • 派发任务代码

      import pika
      import sys
      
      connection = pika.BlockingConnection(
          pika.ConnectionParameters(host='localhost'))
      channel = connection.channel()
      
      channel.queue_declare(queue='task_queue', durable=True)
      
      message = ' '.join(sys.argv[1:]) or "Hello World!"
      channel.basic_publish(
          exchange='',
          routing_key='task_queue',
          body=message,
          properties=pika.BasicProperties(
              delivery_mode=2,  # make message persistent
          ))
      print(" [x] Sent %r" % message)
      connection.close()
      
    • work模式代码

      import pika
      import time
      
      connection = pika.BlockingConnection(
          pika.ConnectionParameters(host='localhost'))
      channel = connection.channel()
      
      channel.queue_declare(queue='task_queue', durable=True)
      print(' [*] Waiting for messages. To exit press CTRL+C')
      
      
      def callback(ch, method, properties, body):
         print(" [x] Received %r" % body)
         time.sleep(body.count(b'.'))
         print(" [x] Done")
         ch.basic_ack(delivery_tag=method.delivery_tag)
      
      
      channel.basic_qos(prefetch_count=1)
      channel.basic_consume(queue='task_queue', on_message_callback=callback)
      
      channel.start_consuming()
      

三,发布/订阅模式

在这里插入图片描述

  1. 概念:
    • 和模式二不同的是,模式三是一个生产者、一个交换机、多个队列、多个消费者
  2. 在模式二中提到过交换机的四种类型
    • direct:

      • 所有发送到Direct Exchange的消息被转发到routing_key指定的队列中

      • 消息传递时,routing_key必须完全匹配才会被队列接收,否则消息会被抛弃

      • direct模式可以使用RabbitMQ自带的默认交换机,所以不需要将交换机进行任何绑定操作

    • topic:

      • 使用通配符进行模糊匹配

        • 符号“#”匹配一个或多个词
          eg:" log.# "能够匹配到‘ log.info.oa ’
        • 符号“ * ” 匹配不多不少一个词
          eg:“ log.* ”只能匹配到“log.erro”
    • headers: 不常用,不予介绍

    • fanout : 将接收到的所有消息广播到它知道的所有队列中

      • 如果routing_key 有指定也不会生效
  3. 使用步骤
    #创建交换机
    channel.exchange_declare(exchange='logs',exchange_type='fanout')
    #创建临时队列
    result = channel.queue_declare(queue='', exclusive=True)
    #绑定队列
    channel.queue_bind(exchange='logs',queue=result.method.queue)
    
    
    • 如果要将日志保存到文件,只需打开控制台并键入:

      python receive_logs.py> logs_from_rabbit.log
      
  4. 与第二种work模式区别:
    • 第三种发布订阅将消息发布到有名的交换器,而不是无名默认的交换机上
    • 第三种需要交换机绑定队列
  5. 完整代码:
    import pika
    
    connection = pika.BlockingConnection(
        pika.ConnectionParameters(host='localhost'))
    channel = connection.channel()
    
    channel.exchange_declare(exchange='logs', exchange_type='fanout')
    
    result = channel.queue_declare(queue='', exclusive=True)
    queue_name = result.method.queue
    
    channel.queue_bind(exchange='logs', queue=queue_name)
    
    print(' [*] Waiting for logs. To exit press CTRL+C')
    
    def callback(ch, method, properties, body):
       print(" [x] %r" % body)
    
    channel.basic_consume(
       queue=queue_name, on_message_callback=callback, auto_ack=True)
    
    channel.start_consuming()
    

四,路由模式(自称direct模式)

在这里插入图片描述

  1. 概念:
    • 交换机连接接队列发送消息需要指定routing_key,所以模式四就是生产者发送消息到交换机并且要指定routing_key,消费者将队列绑定到交换机时需要指定路由key
    • 注意:rabbitMQ是可以同时
  2. 算法
    • 背后的路由算法很简单:消息进入他绑定密钥与消息的routing_key完全匹配的队列 。

    • 图片说明:
      在这里插入图片描述

    • 在direct模式中,第一个队列由绑定密钥为orange绑定,第二个队列由绑定密钥为black和green绑定

    • 如果有消息没有使用存在的绑定密钥的消息将会被丢弃。

    • 用相同的绑定密钥可以绑定多个队列
      在这里插入图片描述

  3. 使用方法:
    • 和前三种不同的是交换机的类型要换成direct

      channel.exchange_declare(exchange='direct_logs',
                               exchange_type='direct')
      
    • 将绑定的其中一个队列为severity,发送消息

      channel.basic_publish(exchange='direct_logs',
                            routing_key=severity,
                            body=message)
      
    • 接收消息需要创建routing_key绑定

      result = channel.queue_declare(queue='', exclusive=True)
      queue_name = result.method.queue
      
      for severity in severities:
          channel.queue_bind(exchange='direct_logs',
                             queue=queue_name,
                             routing_key=severity)
      
  4. 完整代码

    在这里插入图片描述

    #发送信息
    import pika
    import sys
    
    connection = pika.BlockingConnection(
        pika.ConnectionParameters(host='localhost'))
    channel = connection.channel()
    
    channel.exchange_declare(exchange='direct_logs', exchange_type='direct')
    
    severity = sys.argv[1] if len(sys.argv) > 1 else 'info'
    message = ' '.join(sys.argv[2:]) or 'Hello World!'
    channel.basic_publish(
        exchange='direct_logs', routing_key=severity, body=message)
    print(" [x] Sent %r:%r" % (severity, message))
    connection.close()
    
    #接收信息
    import pika
    import sys
    
    connection = pika.BlockingConnection(
        pika.ConnectionParameters(host='localhost'))
    channel = connection.channel()
    
    channel.exchange_declare(exchange='direct_logs', exchange_type='direct')
    
    result = channel.queue_declare(queue='', exclusive=True)
    queue_name = result.method.queue
    
    severities = sys.argv[1:]
    if not severities:
        sys.stderr.write("Usage: %s [info] [warning] [error]\n" % sys.argv[0])
        sys.exit(1)
    
    for severity in severities:
        channel.queue_bind(
            exchange='direct_logs', queue=queue_name, routing_key=severity)
    
    print(' [*] Waiting for logs. To exit press CTRL+C')
    
    
    def callback(ch, method, properties, body):
        print(" [x] %r:%r" % (method.routing_key, body))
    
    
    channel.basic_consume(
        queue=queue_name, on_message_callback=callback, auto_ack=True)
    
    channel.start_consuming()
    
  5. 缺点:
    • 在我们的日志记录系统中,我们可能不仅要根据严重性订阅日志,还要根据发出日志的源订阅日志;
    • 它仍然存在局限性,它不能基于多个条件进行路由。那么就产生了下面第五种模式。

五,Topic模式

  1. 概念
    • topic模式的routing_key使用通配符组成进行模糊匹配
      • 符号“#”匹配一个或多个词
        • eg:" log.# "能够匹配到‘ log.info.oa ’
      • 符号“ * ” 只匹配一个词
        • eg:“ log.* ”只能匹配到“log.erro”
  2. 示例

    在这里插入图片描述

    • 使用三个词(两个点)的routing_key 发送消息;
    • routing_key设置为“quick.orange.rabbit”的消息将传递到两个队列,消息“ lazy.orange.elephant ”也将发送给他们两个;
    • “quick.orange.fox ”只会进入Q1,而“ lazy.brown.fox ”只会进入Q2;
    • lazy.pink.rabbit ”将被传递到Q2只有一次,即使两个绑定都匹配;
    • “ quick.brown.fox ”与任何绑定都不匹配,因此将被丢弃;
    • 如果违反规则只发送一个或者三个以上单词;
      • 比如:‘orange’ 和‘quick.orange.male.rabbit’,则不会被匹配
    • “ lazy.orange.male.rabbit ”即使有四个单词,也将匹配最后一个绑定,并将其传送到Q2队列。
  3. 注意:
    • 当队列用‘#’绑定时它将接收所有消息,与routing_key无关,此时就像fanout模式一样
    • 当绑定中不使用‘*’和‘#’时,主题交换就像direct模式一样。
  4. 代码示例
    #生产者
    import pika
    import sys
    
    connection = pika.BlockingConnection(
        pika.ConnectionParameters(host='localhost'))
    channel = connection.channel()
    
    channel.exchange_declare(exchange='topic_logs', exchange_type='topic')
    
    routing_key = sys.argv[1] if len(sys.argv) > 2 else 'anonymous.info'
    message = ' '.join(sys.argv[2:]) or 'Hello World!'
    channel.basic_publish(
        exchange='topic_logs', routing_key=routing_key, body=message)
    print(" [x] Sent %r:%r" % (routing_key, message))
    connection.close()
    
    #消费者 receive_logs_topic.py
    import pika
    import sys
    
    connection = pika.BlockingConnection(
        pika.ConnectionParameters(host='localhost'))
    channel = connection.channel()
    
    channel.exchange_declare(exchange='topic_logs', exchange_type='topic')
    
    result = channel.queue_declare('', exclusive=True)
    queue_name = result.method.queue
    
    binding_keys = sys.argv[1:]
    if not binding_keys:
        sys.stderr.write("Usage: %s [binding_key]...\n" % sys.argv[0])
        sys.exit(1)
    
    for binding_key in binding_keys:
        channel.queue_bind(
            exchange='topic_logs', queue=queue_name, routing_key=binding_key)
    
    print(' [*] Waiting for logs. To exit press CTRL+C')
    
    
    def callback(ch, method, properties, body):
        print(" [x] %r:%r" % (method.routing_key, body))
    
    
    channel.basic_consume(
        queue=queue_name, on_message_callback=callback, auto_ack=True)
    
    channel.start_consuming()
    
    • 要接收所有日志运行:python receive_logs_topic.py “#”
    • 要从kern接收所有日志:python receive_logs_topic.py “kern.*”
    • 如果只想看“critical”日志:python receive_logs_topic.py “*.critical”
    • 可以 查看多个日志:python receive_logs_topic.py “kern." ".critical”
    • 查看带有routing_key为’kern.critical’的日志类型:python emit_log_topic.py “kern.critical” “A critical kernel error”

六,RPC(远程过程调用)

  1. 概念
    • 有时我们需要在远程计算机上运行功能并且等待结果,这种模式叫做(RPC)远程过程调用
  2. 使用方法
    • 创建一个客户端,并且通过调用call方法去调用我们的服务器,然后实时接收我们服务端处理后返回的结果,显示在控制台。

      fibonacci_rpc = FibonacciRpcClient()
      结果= fibonacci_rpc.call(4print(“ fib(4is%r”%result)
      #这个方法发送RPC请求并阻塞,直到收到回答为止
      
  3. RabbitMQ的RPC模式工作流程

    在这里插入图片描述

    • 生产端和消费端共同约定消费队列和回复队列
    • 生产端每次发送消息时指定一个唯一ID,携带回复队列名称和消息发送给消费队列
    • 消费者从消费队列获取消息,处理后,将结果和生产端发送过来的唯一ID发送给回复队列
    • 生产端从回复队列获取消息和唯一ID,判断ID是否匹配,匹配,则此消息为回复消息。否则就丢弃。
  4. RPC缺点
    • rpc调用的方法容易造成系统混乱

    • 滥用RPC可能导致无法维护代码

  5. 示例代码
    • rpc_server.py

      import pika
      
      connection = pika.BlockingConnection(
          pika.ConnectionParameters(host='localhost'))
      
      channel = connection.channel()
      
      channel.queue_declare(queue='rpc_queue')
      
      def fib(n):
          if n == 0:
              return 0
          elif n == 1:
              return 1
          else:
              return fib(n - 1) + fib(n - 2)
      
      def on_request(ch, method, props, body):
          n = int(body)
      
          print(" [.] fib(%s)" % n)
          response = fib(n)
      
          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)
      #我们可能要运行多个服务器进程。为了将负载平均分配给多个服务器,我们需要设置 prefetch_count设置。
      channel.basic_qos(prefetch_count=1)
      #该回调是RPC服务器的核心。收到请求后执行,完成工作并将响应发送回去
      channel.basic_consume(queue='rpc_queue', on_message_callback=on_request)
      
      print(" [x] Awaiting RPC requests")
      channel.start_consuming()
      
    • rpc_client.py

      import pika
      import uuid
      
      class FibonacciRpcClient(object):
      
         def __init__(self):
      #我们建立连接,建立频道并声明一个专用的“callback回调队列”以进行答复。
             self.connection = pika.BlockingConnection(
                 pika.ConnectionParameters(host='localhost'))
             self.channel = self.connection.channel()
             result = self.channel.queue_declare(queue='', exclusive=True)
             self.callback_queue = result.method.queue
      #我们订阅“回调”队列,以便我们可以接收RPC响应。
             self.channel.basic_consume(
                 queue=self.callback_queue,
                 on_message_callback=self.on_response,
                 auto_ack=True)
      #对于每个响应消息,它都会检查correlation_id(唯一ID)是否是我们要查找的那个。
         def on_response(self, ch, method, props, body):
             if self.corr_id == props.correlation_id:
                 self.response = body  #如果是这样,它将响应保存在self.response中并中断使用循环。
      #定义call方法,执行RPC请求
         def call(self, n):
             self.response = None
             self.corr_id = str(uuid.uuid4()) #生成唯一ID,on_response”回调函数将使用该值来捕获适当的响应。
             self.channel.basic_publish( 
                 exchange='',
                 routing_key='rpc_queue',
      #发布具有两个属性的请求消息: reply_to和correlation_id
                 properties=pika.BasicProperties(
                     reply_to=self.callback_queue,  
                     correlation_id=self.corr_id,
                 ),
                 body=str(n))
      
             while self.response is None:
                 self.connection.process_data_events()
             return int(self.response)
      
      
      fibonacci_rpc = FibonacciRpcClient()
      
      print(" [x] Requesting fib(30)")
      #最后,我们将响应返回给用户。
      response = fibonacci_rpc.call(30)
      print(" [.] Got %r" % response)
      
      
      - 如果RPC服务器太慢就运行另一台RPC服务器运行rcp_server.py
      - 在客户端,RPC只需要发送和接收一条消息,不需要诸如创建队列的调用,所以RPC客户端只需要一个网络往返就可以处理单个RPC请求。
                     correlation_id=self.corr_id,
                 ),
                 body=str(n))
      
             while self.response is None:
                 self.connection.process_data_events()
             return int(self.response)
      
      
      fibonacci_rpc = FibonacciRpcClient()
      
      print(" [x] Requesting fib(30)")
      #最后,我们将响应返回给用户。
      response = fibonacci_rpc.call(30)
      print(" [.] Got %r" % response)
      
      • 如果RPC服务器太慢就运行另一台RPC服务器运行rcp_server.py
      • 在客户端,RPC只需要发送和接收一条消息,不需要诸如创建队列的调用,所以RPC客户端只需要一个网络往返就可以处理单个RPC请求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值