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]\n" % 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]...\n" % 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

 

posted @ 2019-01-08 01:31  pointerC++  阅读(251)  评论(0编辑  收藏  举报