• RabbitMQ


    一、 RabbitMQ安装及设置

    • 服务端安装在Linux(Centos 6.5)上,客户端通过Python API来访问:
    安装配置epel源
    $ rpm -ivh http://dl.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm
    
    安装erlang
    $ yum -y install erlang
    
    安装RabbitMQ
    $ yum -y install rabbitmq-server
    
    • 客户端(Windows)安装 python API:
    pip install pika
    • 添加用户并分配权限:

    远程访问RabbitMQ在服务器上自己增加一个用户(用户为“xue”,密码为“xue123”),步骤如下:
    (1) 创建一个name用户及为用户添加密码pass:sudo rabbitmqctl add_user name pass
    (2) 设置该用户为administrator角色:sudo rabbitmqctl set_user_tags name administrator
    (3) 设置权限:sudo rabbitmqctl set_permissions -p '/' name '.' '.' '.'
    (4) 重启rabbitmq服务:service rabbitmq-server restart 或者 /etc/init.d/rabbitmq-server restart

    • 查看队列数据:

    rabbitmqctl list_queues

    • 角色

    management
    policymaker
    monitoring
    administrator

    • 配置文件

    /etc/rabbitmq.config

    • 日志路径

    /var/log/rabbitmq

    • 注意事项

    注意要关闭防火墙(centos):
    1) 永久性生效,重启后不会复原
    开启: chkconfig iptables on
    关闭: chkconfig iptables off
    2) 即时生效,重启后复原
    开启: service iptables start
    关闭: service iptables stop

    二、 RabbitMQ基础知识

        AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。 AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。 RabbitMQ是一个由erlang开发的AMQP(Advanced Message Queuing Protocol)的开源实现。支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。 下面将重点介绍RabbitMQ中的一些基础概念,了解了这些概念,是使用好RabbitMQ的基础。

     RabbitMQ的结构图如下:

    说明:

        通过RabbitMQ的结构图能够清晰的看清楚整体的send Message到Receive Message的一个大致的流程。其中RabbitMQ Server部分可以看做是一个队列(Queue),工作过程就是 生产者(左边的Client A和Client B)产生数据 ----->  队列(Queue) ------> 消费者(右边的Client 1, Client 2和Client 3)处理数据 的一个过程,简化之后如下图所示。

    • Message (消息):RabbitMQ 转发的二进制对象,包括Headers(头)、Properties (属性)和 Data (数据),其中数据部分不是必要的。Producer(生产者): 消息的生产者,负责产生消息并把消息发到交换机
    • Exhange的应用

      • Consumer (消费者):使用队列 Queue 从 Exchange 中获取消息的应用。
      • Exchange (交换机):负责接收生产者的消息并把它转到到合适的队列
      • Queue (队列):一个存储Exchange 发来的消息的缓冲,并将消息主动发送给Consumer,或者 Consumer 主动来获取消息。

      • Binding (绑定):队列 和 交换机 之间的关系。Exchange 根据消息的属性和 Binding 的属性来转发消息。绑定的一个重要属性是 binding_key。

      • Connection (连接)和 Channel (通道):生产者和消费者需要和 RabbitMQ 建立 TCP 连接。一些应用需要多个connection,为了节省TCP 连接,可以使用 Channel,它可以被认为是一种轻型的共享 TCP 连接的连接。连接需要用户认证,并且支持 TLS (SSL)。连接需要显式关闭。

    补充说明:

        ConnectionFactory、Connection、Channel
        ConnectionFactory、Connection、Channel都是RabbitMQ对外提供的API中最基本的对象。
        Connection是RabbitMQ的socket链接,它封装了socket协议相关部分逻辑。
        ConnectionFactory为Connection的制造工厂。 Channel是我们与RabbitMQ打交道的最重要的一个接口,我们大部分的业务操作是在Channel这个接口中完成的,包括定义      Queue、定义Exchange、绑定Queue与Exchange、发布消息等。

    三、操作RabbitMQ

    1. 基于python的Queue实现生产者消费者模型:

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-
     3 import Queue
     4 import threading
     5 import time
     6 
     7 message = Queue.Queue(10)
     8 
     9 def producer(i):
    10     while True:
    11         time.sleep(1)
    12         print("producer --> %s" % i)
    13         message.put(i)
    14 
    15 def consumer(i):
    16     while True:
    17         time.sleep(1)
    18         msg = message.get()
    19         print("consumer --> %s" % msg)
    20 
    21 for i in range(15):
    22     t = threading.Thread(target=producer, args=(i,))
    23     t.start()
    24 
    25 for i in range(10):
    26     t = threading.Thread(target=consumer, args=(i,))
    27     t.start()
    python队列实现生产者消费者模型

    2. 使用RabbitMQ实现的简单消息队列

    Producer向queue发送消息,一个Consumer从该queue接收消息并打印

    • 生产者 Producer
     1 import pika
     2 
     3 
     4 credentials = pika.PlainCredentials('xue', 'xue123')
     5 connection = pika.BlockingConnection(pika.ConnectionParameters(
     6     '192.168.30.128',credentials=credentials))
     7 
     8 channel = connection.channel() #建立了rabbit 协议的通道
     9 
    10 # 声明queue
    11 channel.queue_declare(queue='test')
    12 
    13 # n RabbitMQ a message can never be sent directly to the queue, it always needs to go through an exchange.
    14 channel.basic_publish(exchange='',
    15                       routing_key='test',
    16                       body='Hello World!')
    17 print(" [x] Sent 'Hello World!'")
    18 connection.close()
    生产者
    • 消费者 Consumer
     1 import pika
     2 
     3 credentials = pika.PlainCredentials('xue', 'xue123')
     4 connection = pika.BlockingConnection(pika.ConnectionParameters(
     5     '192.168.30.128',credentials=credentials))
     6 
     7 channel = connection.channel()
     8 
     9 # You may ask why we declare the queue again ‒ we have already declared it in our previous code.
    10 # We could avoid that if we were sure that the queue already exists. For example if send.py program
    11 # was run before. But we're not yet sure which program to run first. In such cases it's a good
    12 # practice to repeat declaring the queue in both programs.
    13 channel.queue_declare(queue='test')
    14 
    15 
    16 def callback(ch, method, properties, body):
    17     print("received msg...start processing....",body)
    18     print(" [x] msg process done....",body)
    19 
    20 
    21 channel.basic_consume(callback,
    22                       queue='test',
    23                       no_ack=True)
    24 
    25 print(' [*] Waiting for messages. To exit press CTRL+C')
    26 channel.start_consuming()
    消费者
    • 执行结果
    1 #Producer端:
    2 [x] Sent 'Hello World!'
    3 
    4 #Consumer端:
    5 [*] Waiting for messages. To exit press CTRL+C
    6 received msg...start processing.... b'Hello World!'
    7  [x] msg process done.... b'Hello World!'
    执行结果

    3.  acknowledgment 消息确认不丢失

        1)数据丢失: 当设置no-ack = True,默认为False。当消费者遇到情况(its channel is closed, connection is closed, or TCP connection is lost)挂掉了,那么数据就会丢失,因为rabbitmq默认会把此消息标记为已完成,然后从队列中移除。

        2)数据不丢失:当设置no-ack = False,默认为False。那么当rabbitmq收到消费者的获取消息请求之后,标记为处理中,当再次收到ack之后,才会标记为已完成,然后从队列中将消息删除。当rabbitmq检测到消费者(its channel is closed, connection is closed, or TCP connection is lost)挂掉了,还没收到ack,则会重新将消息放回消息队列,交给下一个消费者处理,保证消息不丢失,也就是说,RabbitMQ给了消费者足够长的时间来做数据处理。

    • 设置no-ack = True

    生产者代码不变,消费者代码如下:

     1 import pika
     2 
     3 credentials = pika.PlainCredentials('xue', 'xue123')
     4 connection = pika.BlockingConnection(pika.ConnectionParameters(
     5     '192.168.30.128',credentials=credentials))
     6 
     7 channel = connection.channel()
     8 
     9 # You may ask why we declare the queue again ‒ we have already declared it in our previous code.
    10 # We could avoid that if we were sure that the queue already exists. For example if send.py program
    11 # was run before. But we're not yet sure which program to run first. In such cases it's a good
    12 # practice to repeat declaring the queue in both programs.
    13 channel.queue_declare(queue='test')
    14 
    15 def callback(ch, method, properties, body):
    16     print("received msg...start processing.... ", body)
    17     print(" [x] msg process done.... ", body)
    18 
    19 channel.basic_consume(callback,
    20                       queue='test',
    21                       no_ack=True)  # 不进行应答
    22 
    23 print(' [*] Waiting for messages. To exit press CTRL+C')
    24 channel.start_consuming()
    消费者
    • 执行结果(生产者发消息,消费者1和消费者2收消息,当断开消费者1,此时消费者2没有收到消息)
     1 #Producer端:
     2 [x] Sent 'Hello World!'
     3 
     4 #Consumer端1:
     5 [*] Waiting for messages. To exit press CTRL+C
     6 received msg...start processing.... b'Hello World!'
     7  [x] msg process done.... b'Hello World!'
     8 
     9 #当断开Consumer端1
    10 #Consumer端2:
    11 [*] Waiting for messages. To exit press CTRL+C
    执行结果
    • 设置no-ack = False

    生产者代码不变,消费者代码如下:

     1 import pika
     2 
     3 credentials = pika.PlainCredentials('xue', 'xue123')
     4 connection = pika.BlockingConnection(pika.ConnectionParameters(
     5     '192.168.30.128',credentials=credentials))
     6 
     7 channel = connection.channel()
     8 
     9 # You may ask why we declare the queue again ‒ we have already declared it in our previous code.
    10 # We could avoid that if we were sure that the queue already exists. For example if send.py program
    11 # was run before. But we're not yet sure which program to run first. In such cases it's a good
    12 # practice to repeat declaring the queue in both programs.
    13 channel.queue_declare(queue='test')
    14 
    15 def callback(ch, method, properties, body):
    16     print("received msg...start processing.... ", body)
    17     print(" [x] msg process done.... ", body)
    18 
    19 channel.basic_consume(callback,
    20                       queue='test',
    21                       no_ack=False)
    22 
    23 print(' [*] Waiting for messages. To exit press CTRL+C')
    24 channel.start_consuming()
    消费者
    • 执行结果(生产者发消息,消费者1和消费者2收消息,当断开消费者1,此时消费者2收到消息)
     1 #Producer端:
     2 [x] Sent 'Hello World!'
     3 
     4 #Consumer端1:
     5 [*] Waiting for messages. To exit press CTRL+C
     6 received msg...start processing.... b'Hello World!'
     7  [x] msg process done.... b'Hello World!'
     8 
     9 #当断开Consumer端1
    10 #Consumer端2:
    11 [*] Waiting for messages. To exit press CTRL+C
    12 received msg...start processing.... b'Hello World!'
    13  [x] msg process done.... b'Hello World!'
    执行结果
    • 设置no-ack = False 并应答
     1 import pika
     2 
     3 credentials = pika.PlainCredentials('xue', 'xue123')
     4 connection = pika.BlockingConnection(pika.ConnectionParameters(
     5     '192.168.30.128',credentials=credentials))
     6 
     7 channel = connection.channel()
     8 
     9 # You may ask why we declare the queue again ‒ we have already declared it in our previous code.
    10 # We could avoid that if we were sure that the queue already exists. For example if send.py program
    11 # was run before. But we're not yet sure which program to run first. In such cases it's a good
    12 # practice to repeat declaring the queue in both programs.
    13 channel.queue_declare(queue='test')
    14 
    15 def callback(ch, method, properties, body):
    16     print("received msg...start processing.... ", body)
    17     print(" [x] msg process done.... ", body)
    18     ch.basic_ack(delivery_tag=method.delivery_tag)  # 发送ack消息
    19 
    20 channel.basic_consume(callback,
    21                       queue='test',
    22                       no_ack=False) 
    23 
    24 print(' [*] Waiting for messages. To exit press CTRL+C')
    25 channel.start_consuming()
    消费者
    • 执行结果(生产者发消息,消费者1和消费者2收消息,当断开消费者1,此时消费者2未收到消息,此时已应答)
     1 #Producer端:
     2 [x] Sent 'Hello World!'
     3 
     4 #Consumer端1:
     5 [*] Waiting for messages. To exit press CTRL+C
     6 received msg...start processing.... b'Hello World!'
     7  [x] msg process done.... b'Hello World!'
     8 
     9 #当断开Consumer端1
    10 #Consumer端2:
    11 [*] Waiting for messages. To exit press CTRL+C
    执行结果

    4. durable   消息不丢失

    消息确认机制使得消费者端在崩溃的时候,RabbitMQ服务端消息不丢失,但是如果rabbitmq奔溃了呢?该如何保证队列中的消息不丢失? 此就需要product在往队列中push消息的时候,告诉rabbitmq,此队列中的消息需要持久化,用到的参数:durable=True。通过设置 durable=True 参数可以当RabbitMQ重启等操作时,生产者端的消息队列数据依然存在。(有人发现消费者端也需要设置 channel.queue_declare(queue='task_queue',durable=True),但是我在测试过程中去掉也是可以正常运行。)

    • 生产者
     1 import pika
     2 import time
     3 import sys
     4 
     5 credentials = pika.PlainCredentials('xue', 'xue123')
     6 connection = pika.BlockingConnection(pika.ConnectionParameters(
     7     '192.168.30.128',credentials=credentials))
     8 
     9 channel = connection.channel()
    10 # 声明queue make message persistent
    11 channel.queue_declare(queue='task_queue',durable=True)
    12 
    13 message = ' '.join(sys.argv[1:]) or "Hello World! %s" % time.time()
    14 
    15 # n RabbitMQ a message can never be sent directly to the queue, it always needs to go through an exchange.
    16 channel.basic_publish(exchange='',
    17                       routing_key='task_queue',
    18                       body=message,
    19                       properties=pika.BasicProperties(
    20                       delivery_mode=2,  # make message persistent
    21                       )
    22                 )
    23 print(" [x] Sent ", message)
    24 connection.close()
    生产者
    • 消费者
     1 import pika
     2 import time
     3 
     4 credentials = pika.PlainCredentials('xue', 'xue123')
     5 connection = pika.BlockingConnection(pika.ConnectionParameters(
     6     '192.168.30.128',credentials=credentials))
     7 
     8 channel = connection.channel()
     9 # make message persistent
    10 channel.queue_declare(queue='task_queue',durable=True)
    11 
    12 def callback(ch, method, properties, body):
    13     print(" [x] Received ", body)
    14     time.sleep(2)
    15     print(" [x] Done")
    16     ch.basic_ack(delivery_tag=method.delivery_tag)
    17 
    18 channel.basic_consume(callback,
    19                       queue='task_queue',
    20                       no_ack=False
    21                       )
    22 
    23 print(' [*] Waiting for messages. To exit press CTRL+C')
    24 channel.start_consuming()
    消费者
    • 消息队列查看结果
     1 #未运行生产者端查看消息队列
     2 [root@centos ~]# rabbitmqctl list_queues
     3 Listing queues ...
     4 task_queue      0
     5 ...done.
     6 #运行生产者端并查看消息队列 task_queue 存在
     7 [root@centos ~]# rabbitmqctl list_queues
     8 Listing queues ...
     9 task_queue      1
    10 ...done.
    11 #重启 rabbitmq服务
    12 [root@centos ~]# service rabbitmq-server restart
    13 Restarting rabbitmq-server: SUCCESS
    14 rabbitmq-server.
    15 #重启 rabbitmq 服务查看消息队列 task_queue 依然存在
    16 [root@centos ~]# rabbitmqctl list_queues
    17 Listing queues ...
    18 task_queue      1
    19 ...done.
    20 [root@centos ~]#
    消息列表查看结果
    • 运行结果
    1 #运行生产者端
    2 [x] Sent  Hello World! 1547381295.894814
    3 
    4 #重启RabbitMQ后 运行消费者端
    5 [*] Waiting for messages. To exit press CTRL+C
    6  [x] Received  b'Hello World! 1547381576.0062277'
    7  [x] Done
    运行结果

    5. 消息获取顺序

    默认消息队列里的数据是按照顺序被消费者拿走,例如:消费者1 去队列中获取 奇数 序列的任务,消费者2去队列中获取 偶数 序列的任务。

    通过 channel.basic_qos(prefetch_count=1) 表示谁来谁取,不再按照奇偶数排列。设置prefetchCount=1,则Queue每次给每个消费者发送一条消息;消费者处理完这条消息后Queue会再给该消费者发送一条消息。

    • 未加 channel.basic_qos(prefetch_count=1) 
    • 生产者
     1 import pika
     2 
     3 credentials = pika.PlainCredentials('xue', 'xue123')
     4 connection = pika.BlockingConnection(pika.ConnectionParameters(
     5     '192.168.30.128',credentials=credentials))
     6 
     7 channel = connection.channel()
     8 # 声明queue make message persistent
     9 channel.queue_declare(queue='task_queue',durable=True)
    10 
    11 # n RabbitMQ a message can never be sent directly to the queue, it always needs to go through an exchange.
    12 for i in range(10):
    13     channel.basic_publish(exchange='',
    14                           routing_key='task_queue',
    15                           body="send %s" % str(i),
    16                           properties=pika.BasicProperties(
    17                           delivery_mode=2,  # make message persistent
    18                           )
    19                     )
    20     print(" [x] Sent ", str(i))
    21 connection.close()
    生产者
    • 消费者
     1 import pika
     2 import time
     3 
     4 credentials = pika.PlainCredentials('xue', 'xue123')
     5 connection = pika.BlockingConnection(pika.ConnectionParameters(
     6     '192.168.30.128',credentials=credentials))
     7 
     8 channel = connection.channel()
     9 # make message persistent
    10 #channel.queue_declare(queue='task_queue',durable=True)
    11 
    12 def callback(ch, method, properties, body):
    13     print(" [x] Received ", body)
    14     time.sleep(2)
    15     print(" [x] Done")
    16     ch.basic_ack(delivery_tag=method.delivery_tag)
    17 
    18 # channel.basic_qos(prefetch_count=1)
    19 
    20 channel.basic_consume(callback,
    21                       queue='task_queue',
    22                       no_ack=False
    23                       )
    24 
    25 print(' [*] Waiting for messages. To exit press CTRL+C')
    26 channel.start_consuming()
    消费者
    • 执行结果
     1 #运行一个生产者,两个消费者(先运行两个消费者,然后运行生产者)
     2 #生产者端
     3 [x] Sent  0
     4  [x] Sent  1
     5  [x] Sent  2
     6  [x] Sent  3
     7  [x] Sent  4
     8  [x] Sent  5
     9  [x] Sent  6
    10  [x] Sent  7
    11  [x] Sent  8
    12  [x] Sent  9
    13 
    14 #消费者端1
    15  [*] Waiting for messages. To exit press CTRL+C
    16  [x] Received  b'send 1'
    17  [x] Done
    18  [x] Received  b'send 3'
    19  [x] Done
    20  [x] Received  b'send 5'
    21  [x] Done
    22  [x] Received  b'send 7'
    23  [x] Done
    24  [x] Received  b'send 9'
    25  [x] Done
    26 
    27 #消费者端2
    28 [*] Waiting for messages. To exit press CTRL+C
    29  [x] Received  b'send 0'
    30  [x] Done
    31  [x] Received  b'send 2'
    32  [x] Done
    33  [x] Received  b'send 4'
    34  [x] Done
    35  [x] Received  b'send 6'
    36  [x] Done
    37  [x] Received  b'send 8'
    38  [x] Done
    执行结果
    • 加 channel.basic_qos(prefetch_count=1)  
    • 消费者端1
     1 import pika
     2 
     3 credentials = pika.PlainCredentials('xue', 'xue123')
     4 connection = pika.BlockingConnection(pika.ConnectionParameters(
     5     '192.168.30.128',credentials=credentials))
     6 
     7 channel = connection.channel()
     8 # make message persistent
     9 #channel.queue_declare(queue='task_queue',durable=True)
    10 
    11 def callback(ch, method, properties, body):
    12     print(" [x] Received ", body)
    13     print(" [x] Done")
    14     ch.basic_ack(delivery_tag=method.delivery_tag)
    15 
    16 channel.basic_qos(prefetch_count=1)
    17 
    18 channel.basic_consume(callback,
    19                       queue='task_queue',
    20                       no_ack=False
    21                       )
    22 
    23 print(' [*] Waiting for messages. To exit press CTRL+C')
    24 channel.start_consuming()
    消费者端1
    • 消费者端2(sleep(2))
     1 import pika
     2 import time
     3 
     4 credentials = pika.PlainCredentials('xue', 'xue123')
     5 connection = pika.BlockingConnection(pika.ConnectionParameters(
     6     '192.168.30.128',credentials=credentials))
     7 
     8 channel = connection.channel()
     9 # make message persistent
    10 #channel.queue_declare(queue='task_queue',durable=True)
    11 
    12 def callback(ch, method, properties, body):
    13     print(" [x] Received ", body)
    14     time.sleep(2)
    15     print(" [x] Done")
    16     ch.basic_ack(delivery_tag=method.delivery_tag)
    17 
    18 channel.basic_qos(prefetch_count=1)
    19 
    20 channel.basic_consume(callback,
    21                       queue='task_queue',
    22                       no_ack=False
    23                       )
    24 
    25 print(' [*] Waiting for messages. To exit press CTRL+C')
    26 channel.start_consuming()
    消费者而端2
    • 执行结果
     1 #先运行消费者端1和消费者端2,再运行生产者端
     2 #生产者端
     3  [x] Sent  0
     4  [x] Sent  1
     5  [x] Sent  2
     6  [x] Sent  3
     7  [x] Sent  4
     8  [x] Sent  5
     9  [x] Sent  6
    10  [x] Sent  7
    11  [x] Sent  8
    12  [x] Sent  9
    13 
    14 #消费者端1
    15  [*] Waiting for messages. To exit press CTRL+C
    16  [x] Received  b'send 1'
    17  [x] Done
    18  [x] Received  b'send 2'
    19  [x] Done
    20  [x] Received  b'send 3'
    21  [x] Done
    22  [x] Received  b'send 4'
    23  [x] Done
    24  [x] Received  b'send 5'
    25  [x] Done
    26  [x] Received  b'send 6'
    27  [x] Done
    28  [x] Received  b'send 7'
    29  [x] Done
    30  [x] Received  b'send 8'
    31  [x] Done
    32  [x] Received  b'send 9'
    33  [x] Done
    34 
    35 
    36 #消费者端2
    37  [*] Waiting for messages. To exit press CTRL+C
    38  [x] Received  b'send 0'
    39  [x] Done
    执行结果

     6. 使用Exchanges

        上面讨论的生产者将消息直接投递到Queue中,实际上这在RabbitMQ中这种事情永远都不会发生。实际的情况是,生产者将消息发送到Exchange(交换器,下图中的X),由Exchange将消息路由到一个或多个Queue中(或者丢弃)。 

         RabbitMQ中的Exchange有四种类型(fanout、direct、topic、headers),不同的类型有着不同的路由策略。

    (1)首先介绍 routing key, Binding,Binding key作用及之间的关系。

    • routing key

        生产者在将消息发送给Exchange的时候,一般会指定一个routing key,来指定这个消息的路由规则,而这个routing key需要与Exchange Type及binding key联合使用才能最终生效。 在Exchange Type与binding key固定的情况下(在正常使用时一般这些内容都是固定配置好的),我们的生产者就可以在发送消息给Exchange时,通过指定routing key来决定消息流向哪里。 RabbitMQ为routing key设定的长度限制为255 bytes。

    • Binding

        RabbitMQ中通过Binding将Exchange与Queue关联起来,这样RabbitMQ就知道如何正确地将消息路由到指定的Queue了。

    • Binding key

        在绑定(Binding)Exchange与Queue的同时,一般会指定一个binding key;消费者将消息发送给Exchange时,一般会指定一个routing key;当binding key与routing key相匹配时,消息将会被路由到对应的Queue中。这个将在Exchange Types章节会列举实际的例子加以说明。 在绑定多个Queue到同一个Exchange的时候,这些Binding允许使用相同的binding key。 binding key 并不是在所有情况下都生效,它依赖于Exchange Type,比如fanout类型的Exchange就会无视binding key,而是将消息路由到所有绑定到该Exchange的Queue。

    (2)Exchange

        RabbitMQ常用的Exchange Type有fanout、direct、topic、headers这四种(AMQP规范里还提到两种Exchange Type,分别为system与自定义),下面分别进行介绍。

    1)fanout(广播类型)

    fanout类型的Exchange路由规则非常简单,它会把所有发送到该Exchange的消息路由到所有与它绑定的Queue中。 

    上图中,生产者(P)发送到Exchange(X)的所有消息都会路由到图中的两个Queue,并最终被两个消费者(C1与C2)消费。

    注意:由于fanout是广播类型,因此需要先启动消费者端,否则先启动生产者端再启动消费者端会接收不到数据(生产者已发送消息,但是生产者端错过消息接收)

    • 生产者
     1 import pika
     2 import sys
     3 
     4 credentials = pika.PlainCredentials('xue', 'xue123')
     5 connection = pika.BlockingConnection(pika.ConnectionParameters(
     6     '192.168.30.128',credentials=credentials))
     7 channel = connection.channel()
     8 
     9 message = ' '.join(sys.argv[1:]) or "info: Hello World!"
    10 
    11 channel.basic_publish(exchange='logs',
    12                       routing_key='',
    13                       body=message)
    14 print(" [x] Sent ", message)
    15 connection.close()
    fanout生产者
    • 消费者
     1 import pika
     2 
     3 credentials = pika.PlainCredentials('xue', 'xue123')
     4 connection = pika.BlockingConnection(pika.ConnectionParameters(
     5     '192.168.30.128',credentials=credentials))
     6 channel = connection.channel()
     7 
     8 channel.exchange_declare(exchange='logs', exchange_type='fanout')
     9 
    10 result = channel.queue_declare(exclusive=True)  # 不指定queue名字,rabbit会随机分配一个名字,exclusive=True会在使用此queue的消费者断开后,自动将queue删除
    11 queue_name = result.method.queue
    12 channel.queue_bind(exchange='logs', queue=queue_name)
    13 
    14 print(' [*] Waiting for logs. To exit press CTRL+C')
    15 
    16 def callback(ch, method, properties, body):
    17     print(" [x] receive ", body)
    18     ch.basic_ack(delivery_tag=method.delivery_tag)
    19 
    20 channel.basic_qos(prefetch_count=1)
    21 channel.basic_consume(callback, queue=queue_name,no_ack=False)
    22 channel.start_consuming()
    fanout消费者
    • 执行结果
     1 #生产者端(执行两次)
     2 [x] Sent  info: Hello World!
     3 
     4 #消费者端1
     5 [*] Waiting for logs. To exit press CTRL+C
     6  [x] receive  b'info: Hello World!'
     7  [x] receive  b'info: Hello World!'
     8 
     9 #消费者端2
    10 [*] Waiting for logs. To exit press CTRL+C
    11  [x] receive  b'info: Hello World!'
    12  [x] receive  b'info: Hello World!'
    fanout执行结果

    2)direct(关键字类型)

    direct类型的Exchange路由规则也很简单,它会把消息路由到那些binding key与routing key完全匹配的Queue中。 

    以上图的配置为例,我们以routingKey=”error”发送消息到Exchange,则消息会路由到Queue1(amqp.gen-S9b…,这是由RabbitMQ自动生成的Queue名称)和Queue2(amqp.gen-Agl…);如果我们以routingKey=”info”或routingKey=”warning”来发送消息,则消息只会路由到Queue2。如果我们以其他routingKey发送消息,则消息不会路由到这两个Queue中。

    注意:先启动消费者端,否则先启动生产者端再启动消费者端会接收不到数据(生产者已发送消息,但是生产者端错过消息接收)

    • 生产者
     1 import pika
     2 import sys
     3 credentials = pika.PlainCredentials('xue', 'xue123')
     4 connection = pika.BlockingConnection(pika.ConnectionParameters(
     5     '192.168.30.128',credentials=credentials))
     6 channel = connection.channel()
     7 
     8 channel.exchange_declare(exchange='direct_logs',exchange_type='direct')
     9 
    10 severity = sys.argv[1] if len(sys.argv) > 1 else 'info' #严重程度,级别
    11 
    12 message = ' '.join(sys.argv[2:]) or 'Hello World!'
    13 
    14 channel.basic_publish(exchange='direct_logs',
    15                       routing_key=severity,
    16                       body=message)
    17 print(" [x] Sent %r:%r" % (severity, message))
    18 connection.close()
    Direct生产者
    • 消费者
     1 import pika
     2 import sys
     3 
     4 credentials = pika.PlainCredentials('xue', 'xue123')
     5 connection = pika.BlockingConnection(pika.ConnectionParameters(
     6     '192.168.30.128',credentials=credentials))
     7 channel = connection.channel()
     8 channel.exchange_declare(exchange='direct_logs',exchange_type='direct')
     9 
    10 result = channel.queue_declare(exclusive=True)
    11 queue_name = result.method.queue
    12 
    13 severities = sys.argv[1:]
    14 if not severities:
    15     sys.stderr.write("Usage: %s [info] [warning] [error]
    " % sys.argv[0])
    16     sys.exit(1)
    17 
    18 for severity in severities:
    19     channel.queue_bind(exchange='direct_logs',queue=queue_name,routing_key=severity)
    20 
    21 print(' [*] Waiting for logs. To exit press CTRL+C')
    22 
    23 def callback(ch, method, properties, body):
    24     print(" [x] receive %r:%r" % (method.routing_key, body))
    25     ch.basic_ack(delivery_tag=method.delivery_tag)
    26 
    27 channel.basic_qos(prefetch_count=1)
    28 channel.basic_consume(callback,
    29                       queue=queue_name,
    30                       no_ack=False)
    31 
    32 channel.start_consuming()
    Direct消费者
    • 执行结果

    3)topic(模糊匹配类型)

    前面讲到direct类型的Exchange路由规则是完全匹配binding key与routing key,但这种严格的匹配方式在很多情况下不能满足实际业务需求。topic类型的Exchange在匹配规则上进行了扩展,它与direct类型的Exchage相似,也是将消息路由到binding key与routing key相匹配的Queue中,但这里的匹配规则有些不同,它约定:

    • routing key为一个句点号“. ”分隔的字符串(我们将被句点号“. ”分隔开的每一段独立的字符串称为一个单词),如“stock.usd.nyse”、“nyse.vmw”、“quick.orange.rabbit”
    • binding key与routing key一样也是句点号“. ”分隔的字符串
    • binding key中可以存在两种特殊字符“*”与“#”,用于做模糊匹配,其中“*”用于匹配一个单词,“#”用于匹配多个单词(可以是零个)

    以上图中的配置为例,routingKey=”quick.orange.rabbit”的消息会同时路由到Q1与Q2,routingKey=”lazy.orange.fox”的消息会路由到Q1与Q2,routingKey=”lazy.brown.fox”的消息会路由到Q2,routingKey=”lazy.pink.rabbit”的消息会路由到Q2(只会投递给Q2一次,虽然这个routingKey与Q2的两个bindingKey都匹配);routingKey=”quick.brown.fox”、routingKey=”orange”、routingKey=”quick.orange.male.rabbit”的消息将会被丢弃,因为它们没有匹配任何bindingKey。

    注意:先启动消费者端,否则先启动生产者端再启动消费者端会接收不到数据(生产者已发送消息,但是生产者端错过消息接收)

    • 生产者
     1 import pika
     2 import sys
     3 
     4 credentials = pika.PlainCredentials('xue', 'xue123')
     5 connection = pika.BlockingConnection(pika.ConnectionParameters(
     6     '192.168.30.128',credentials=credentials))
     7 channel = connection.channel()
     8 
     9 
    10 channel.exchange_declare(exchange='topic_logs',exchange_type='topic')
    11 
    12 routing_key = sys.argv[1] if len(sys.argv) > 1 else 'anonymous.info'
    13 message = ' '.join(sys.argv[2:]) or 'Hello World!'
    14 
    15 channel.basic_publish(exchange='topic_logs',routing_key=routing_key,body=message)
    16 print(" [x] Sent %r:%r" % (routing_key, message))
    17 connection.close()
    topic生产者
    • 消费者
     1 import pika
     2 import sys
     3 
     4 credentials = pika.PlainCredentials('xue', 'xue123')
     5 connection = pika.BlockingConnection(pika.ConnectionParameters(
     6     '192.168.30.128',credentials=credentials))
     7 channel = connection.channel()
     8 
     9 channel.exchange_declare(exchange='topic_logs', exchange_type='topic')
    10 
    11 result = channel.queue_declare(exclusive=True)
    12 queue_name = result.method.queue
    13 
    14 binding_keys = sys.argv[1:]
    15 if not binding_keys:
    16     sys.stderr.write("Usage: %s [binding_key]...
    " % sys.argv[0])
    17     sys.exit(1)
    18 
    19 for binding_key in binding_keys:
    20     channel.queue_bind(exchange='topic_logs',
    21                        queue=queue_name,
    22                        routing_key=binding_key)
    23 
    24 print(' [*] Waiting for logs. To exit press CTRL+C')
    25 
    26 def callback(ch, method, properties, body):
    27     print(" [x] receive %r:%r" % (method.routing_key, body))
    28     ch.basic_ack(delivery_tag=method.delivery_tag)
    29 
    30 channel.basic_qos(prefetch_count=1)
    31 channel.basic_consume(callback,
    32                       queue=queue_name,
    33                       no_ack=False)
    34 
    35 channel.start_consuming()
    topic消费者
    • 执行结果

    4)headers

    headers类型的Exchange不依赖于routing key与binding key的匹配规则来路由消息,而是根据发送的消息内容中的headers属性进行匹配。 在绑定Queue与Exchange时指定一组键值对;当消息发送到Exchange时,RabbitMQ会取到该消息的headers(也是一个键值对的形式),对比其中的键值对是否完全匹配Queue与Exchange绑定时指定的键值对;如果完全匹配则消息会路由到该Queue,否则不会路由到该Queue。 该类型的Exchange没有用到过(不过也应该很有用武之地),所以不做介绍。

    RPC

    MQ本身是基于异步的消息处理,前面的示例中所有的生产者(P)将消息发送到RabbitMQ后不会知道消费者(C)处理成功或者失败(甚至连有没有消费者来处理这条消息都不知道)。 但实际的应用场景中,我们很可能需要一些同步处理,需要同步等待服务端将我的消息处理完成后再进行下一步处理。这相当于RPC(Remote Procedure Call,远程过程调用)。在RabbitMQ中也支持RPC。

    • 实现过程
    • 客户端发送请求(消息)时,在消息的属性(MessageProperties ,在AMQP 协议中定义了properties ,这些属性会随着消息一起发送)中设置两个值replyTo (一个Queue 名称,用于告诉服务器处理完成后将通知我的消息发送到这个Queue 中)和correlationId (此次请求的标识号,服务器处理完成后需要将此属性返还,客户端将根据这个id了解哪条请求被成功执行了或执行失败)。
      self.channel.basic_publish(exchange='',
      routing_key='rpc_queue',
      properties=pika.BasicProperties(
      reply_to=self.callback_queue,
      correlation_id=self.corr_id,
      ),
      body=str(n))
    • 服务器端收到消息并处理
    • 服务器端处理完消息后,将生成一条应答消息到replyTo 指定的Queue ,同时带上correlationId 属性
      ch.basic_publish(exchange='',
      routing_key=props.reply_to,
      properties=pika.BasicProperties(correlation_id= props.correlation_id),
      body=str(response))
    • 客户端之前已订阅replyTo 指定的Queue ,从中收到服务器的应答消息后,根据其中的correlationId属性分析哪条请求被执行了,根据执行结果进行后续业务处理

    计算斐波拉切数列例子:

    • 生产者
     1 import pika
     2 import uuid
     3 
     4 class FibonacciRpcClient(object):
     5     def __init__(self):
     6         credentials = pika.PlainCredentials('xue', 'xue123')
     7         self.connection = pika.BlockingConnection(pika.ConnectionParameters(
     8             '192.168.30.128', credentials=credentials))
     9         self.channel = self.connection.channel()
    10 
    11         #生成随机队列名称 reply_to=self.callback_queue
    12         result = self.channel.queue_declare(exclusive=True)
    13         self.callback_queue = result.method.queue
    14 
    15         self.channel.basic_consume(self.on_response, no_ack=False, #准备接受命令结果
    16                                    queue=self.callback_queue)
    17 
    18     def on_response(self, ch, method, props, body):
    19         """"callback方法"""
    20         if self.corr_id == props.correlation_id:
    21             self.response = body
    22 
    23     def call(self, n):
    24         self.response = None
    25         self.corr_id = str(uuid.uuid4()) #唯一标识符
    26         self.channel.basic_publish(exchange='',
    27                                    routing_key='rpc_queue',
    28                                    properties=pika.BasicProperties(
    29                                        reply_to=self.callback_queue,
    30                                        correlation_id=self.corr_id,
    31                                    ),
    32                                    body=str(n))
    33 
    34         count = 0
    35         while self.response is None:
    36             self.connection.process_data_events() #检查队列里有没有新消息,但不会阻塞
    37             count += 1
    38             print("check...",count)
    39 
    40         return int(self.response)
    41 
    42 fibonacci_rpc = FibonacciRpcClient()
    43 
    44 print(" [x] Requesting fib(30)")
    45 
    46 response = fibonacci_rpc.call(10)
    47 print(" [.] Got ", response)
    计算Fibonacci sequence生产者端
    • 消费者
     1 import pika
     2 import time
     3 
     4 credentials = pika.PlainCredentials('xue', 'xue123')
     5 connection = pika.BlockingConnection(pika.ConnectionParameters(
     6     '192.168.30.128',credentials=credentials))
     7 
     8 channel = connection.channel()
     9 channel.queue_declare(queue='rpc_queue')
    10 
    11 def fib(n):
    12     if n == 0:
    13         return 0
    14     elif n == 1:
    15         return 1
    16     else:
    17         return fib(n - 1) + fib(n - 2)
    18 
    19 def on_request(ch, method, props, body):
    20     n = int(body)
    21 
    22     print(" [.] fib(%s)" % n)
    23     response = fib(n)
    24 
    25     ch.basic_publish(exchange='',
    26                      routing_key=props.reply_to,
    27                      properties=pika.BasicProperties(correlation_id= props.correlation_id),
    28                      body=str(response))
    29     ch.basic_ack(delivery_tag=method.delivery_tag)
    30 
    31 
    32 channel.basic_qos(prefetch_count=1)
    33 channel.basic_consume(on_request, queue='rpc_queue')
    34 
    35 print(" [x] Awaiting RPC requests")
    36 channel.start_consuming()
    计算Fibonacci sequence消费者端
    • 计算结果
    #消费者端
    [x] Awaiting RPC requests
     [.] fib(10)
    
    #生产者端
    [x] Requesting fib(30)
    check... 1
    check... 2
    check... 3
    check... 4
    check... 5
    check... 6
    check... 7
    check... 8
    check... 9
    check... 10
     [.] Got  55
    执行结果

    参考资料:

    http://www.rabbitmq.com/getstarted.html(官网)

    https://www.cnblogs.com/dwlsxj/p/RabbitMQ.html (RabbitMQ基础知识)

    https://www.cnblogs.com/pycode/p/RabbitMQ.html 

    http://www.cnblogs.com/wupeiqi/articles/5132791.html (武sir)

    https://www.sojson.com/blog/48.html (为什么要选择RabbitMQ ,RabbitMQ简介,各种MQ选型对比)

    https://www.cnblogs.com/enjoyall/p/7767462.html?utm_source=debugrun&utm_medium=referral

  • 相关阅读:
    构建前端第8篇之---Webstom搭建ES6运行环境
    方法重写
    继承的成员变量的访问特定
    继承的理解
    总结与新的开始
    python 小案例demo07
    python 小案例demo06
    python 小案例demo05
    python 小案例demo05 升级版石头剪刀布
    python 小案例demo04
  • 原文地址:https://www.cnblogs.com/xuejiale/p/10236795.html
Copyright © 2020-2023  润新知