python 操作RabbitMQ

1 安装python rabbitMQ module 

pip install pika
# or
easy_install pika
# or
# 源码
  
https://pypi.python.org/pypi/pika

实现最简单的队列通信

 send端

import pika

# 生产者
credentials = pika.PlainCredentials('admin', 'admin')  # mq用户名和密码,用于认证
connection = pika.BlockingConnection(
    pika.ConnectionParameters('192.168.37.100', 5672, '/', credentials)
)
channel = connection.channel()  # 声明一个管道

# 声明queue
channel.queue_declare(queue='hello')

# n RabbitMQ a message can never be sent directly to the queue, it always needs to go through an exchange.
channel.basic_publish(exchange='',
                      routing_key='hello',  # queue名称
                      body='Hello World!')
print(" [x] Sent 'Hello World!'")
connection.close()

receive端

import pika
import time

# 消费者
credentials = pika.PlainCredentials('admin', 'admin')  # mq用户名和密码,用于认证
connection = pika.BlockingConnection(
    pika.ConnectionParameters('192.168.37.100', 5672, '/', credentials)
)
channel = connection.channel()

# You may ask why we declare the queue again ‒ we have already declared it in our previous code.
# We could avoid that if we were sure that the queue already exists. For example if send.py program
# was run before. But we're not yet sure which program to run first. In such cases it's a good
# practice to repeat declaring the queue in both programs.
channel.queue_declare(queue='hello')


def callback(ch, method, properties, body):
    print('-->', ch)    # 管道的内存对象的内存地址
    print('-->', method)
    print('-->', properties)
    time.sleep(5)
    print(" [x] Received %r" % body)


channel.basic_consume(  # 消费消息
            'hello',
            callback,  # 如果收到消息,就调用callback函数来处理消息
            auto_ack=True   # auto_ack:默认为False,auto_ack设置成 False,在调用callback函数时,未收到确认标识,消息会重回队列。True,无论调用callback成功与否,消息都被消费掉
)
print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()

2 RabbitMQ消息分发轮询

#测试目的:RabbitMQ消息分发轮询
#1. 启动两个receive端口,并在callbackup睡眠5秒。
#2. send端口发送数据。
#3. 当第一个receive端收到信息后,立刻第一个程序中止。
#4. 查看第二个receive端是否再收到(正常能收到)

#RabbitMQ 的队列信息,只会在客户端确认收到后才会取消,否则一直存在,
#并且体现了RabbitMQ的分发轮询机制,第一个收了,然后到第二个,或者第一个收不了,第二个收。

send端

import pika

# 生产者
credentials = pika.PlainCredentials('admin', 'admin')  # mq用户名和密码,用于认证
connection = pika.BlockingConnection(
    pika.ConnectionParameters('192.168.37.100', 5672, '/', credentials)
)
channel = connection.channel()  # 声明一个管道

# 声明queue
channel.queue_declare(queue='hello')

# n RabbitMQ a message can never be sent directly to the queue, it always needs to go through an exchange.
channel.basic_publish(exchange='',
                      routing_key='hello',  # queue名称
                      body='Hello World!')
print(" [x] Sent 'Hello World!'")
connection.close()

receive端

import pika
import time

# 消费者
credentials = pika.PlainCredentials('admin', 'admin')  # mq用户名和密码,用于认证
connection = pika.BlockingConnection(
    pika.ConnectionParameters('192.168.37.100', 5672, '/', credentials)
)
channel = connection.channel()

# You may ask why we declare the queue again ‒ we have already declared it in our previous code.
# We could avoid that if we were sure that the queue already exists. For example if send.py program
# was run before. But we're not yet sure which program to run first. In such cases it's a good
# practice to repeat declaring the queue in both programs.
channel.queue_declare(queue='hello')


def callback(ch, method, properties, body):
    print('-->', ch)  # 管道的内存对象的内存地址
    print('-->', method)
    print('-->', properties)
    time.sleep(5)
    print(" [x] Received %r" % body)
    ch.basic_ack(delivery_tag=method.delivery_tag)  # 确认消息被执行完毕,主动告知rabbitMQ


channel.basic_consume(  # 消费消息
    'hello',
    callback,  # 如果收到消息,就调用callback函数来处理消息
    # auto_ack=False   # auto_ack设置成 False,在调用callback函数时,未收到确认标识,消息会重回队列。True,无论调用callback成功与否,消息都被消费掉
)
print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()

 3 RabbitMQ消息持久化

 非持久声明的queue,在服务端宕机后,消息队列queue和消息都不复存在了

3.1 RabbitMQ消息持久化

发送端:

①队列持久化很简单,只需要在服务端(produce)声明queue的时候添加一个参数:

channel.queue_declare(queue='hello', durable=True)  # durable=True 持久化

②仅仅持久化队列是没有意义的,还需要多消息进行持久化

channel.basic_publish(exchange="",
                      routing_key="hello",  #queue的名字
                      body="hello world",   #body是要发送的内容
                      properties=pika.BasicProperties(delivery_mode=2,) # make message persistent=>使消息持久化的特性
                      )

接收端:(在服务端队列消息都持久化了之后需要在客户端声明queue的时候也持久化)

channel.queue_declare(queue='hello', durable=True)

发送端代码

import pika

# 生产者
credentials = pika.PlainCredentials('admin', 'admin')  # mq用户名和密码,用于认证
connection = pika.BlockingConnection(
    pika.ConnectionParameters('192.168.37.100', 5672, '/', credentials)
)
channel = connection.channel()  # 声明一个管道

# 声明queue
channel.queue_declare(queue='hello', durable=True)

# n RabbitMQ a message can never be sent directly to the queue, it always needs to go through an exchange.
channel.basic_publish(exchange='',
                      routing_key='hello',  # queue名称
                      body='Hello World!',
                      properties=pika.BasicProperties(delivery_mode=2, )  # make message persistent=>使消息持久化的特性
                      )
print(" [x] Sent 'Hello World!'")
connection.close()

接收端代码:

import pika
import time

# 消费者
credentials = pika.PlainCredentials('admin', 'admin')  # mq用户名和密码,用于认证
connection = pika.BlockingConnection(
    pika.ConnectionParameters('192.168.37.100', 5672, '/', credentials)
)
channel = connection.channel()

# You may ask why we declare the queue again ‒ we have already declared it in our previous code.
# We could avoid that if we were sure that the queue already exists. For example if send.py program
# was run before. But we're not yet sure which program to run first. In such cases it's a good
# practice to repeat declaring the queue in both programs.
channel.queue_declare(queue='hello', durable=True)


def callback(ch, method, properties, body):
    print('-->', ch)  # 管道的内存对象的内存地址
    print('-->', method)
    print('-->', properties)
    time.sleep(5)
    print(" [x] Received %r" % body)
    ch.basic_ack(delivery_tag=method.delivery_tag)  # 确认消息被执行完毕,主动告知rabbitMQ


channel.basic_consume(  # 消费消息
    'hello',
    callback,  # 如果收到消息,就调用callback函数来处理消息
    # auto_ack=False   # auto_ack设置成 False,在调用callback函数时,未收到确认标识,消息会重回队列。True,无论调用callback成功与否,消息都被消费掉
)
print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()

3.2 队列消息持久化+公平分发示列:

发送端:

import pika

# 生产者
credentials = pika.PlainCredentials('admin', 'admin')  # mq用户名和密码,用于认证
connection = pika.BlockingConnection(
    pika.ConnectionParameters('192.168.37.100', 5672, '/', credentials)
)
channel = connection.channel()  # 声明一个管道

# 声明queue
channel.queue_declare(queue='hello', durable=True)

# n RabbitMQ a message can never be sent directly to the queue, it always needs to go through an exchange.
channel.basic_publish(exchange='',
                      routing_key='hello',  # queue名称
                      body='Hello World!',
                      properties=pika.BasicProperties(delivery_mode=2, )  # make message persistent=>使消息持久化的特性
                      )
print(" [x] Sent 'Hello World!'")
connection.close()

接收端:

import pika
import time

# 消费者
credentials = pika.PlainCredentials('admin', 'admin')  # mq用户名和密码,用于认证
connection = pika.BlockingConnection(
    pika.ConnectionParameters('192.168.37.100', 5672, '/', credentials)
)
channel = connection.channel()
channel.basic_qos(prefetch_count=1)  # 在消息消费之前加上消息处理配置
channel.queue_declare(queue='hello', durable=True)


def callback(ch, method, properties, body):
    print('-->', ch)  # 管道的内存对象的内存地址
    print('-->', method)
    print('-->', properties)
    time.sleep(5)
    print(" [x] Received %r" % body)
    ch.basic_ack(delivery_tag=method.delivery_tag)  # 确认消息被执行完毕,主动告知rabbitMQ


channel.basic_consume(  # 消费消息
    'hello',
    callback,  # 如果收到消息,就调用callback函数来处理消息
    # auto_ack=False   # auto_ack设置成 False,在调用callback函数时,未收到确认标识,消息会重回队列。True,无论调用callback成功与否,消息都被消费掉
)
print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()

4 RabbitMQ广播模式(演示时用的pika版本==0.10.0)

# pip3 install pika==0.10.0
Exchange
  在RabbitMQ下进行广播模式需要用到,exchange这个参数,它会把发送的消息推送到queues队列中,exchange必须要知道,它接下来收到的消息要分给谁,是要发给一个queue还是发给多个queue,还是要删除,这些动作都取决于exchange的传入参数。
  Exchange在定义的时候是有类型的,以决定到底是哪些Queue符合条件,可以接收消息。
  Exchange:在RabbitMQ中相当于中间件负责转发消息。

 

 注:如上图生产端到消费端,是通过exchange转发到队列内的,消费端在队列中取的数据,并不是直接在exchange到消费端。

  • fanout: 所有bind到此exchange的queue都可以接收消息
  •               订阅发布:fanout 广播消息只能发给以存活的消费端,实时发送,并不能存储数据。

  • direct: 通过routingKey和exchange决定的那个唯一的queue可以接收消息
  •         direct广播 可指定级别接收端进行广播。

  • topic:所有符合routingKey(此时可以是一个表达式)的routingKey所bind的queue可以接收消息
  •           可同过发出去的多个消息设置 多个级别,消息结尾加入级别。

表达式符号说明:#代表一个或多个字符,*代表任何字符
#.a会匹配a.a,aa.a,aaa.a等
*.a会匹配a.a,b.a,c.a等

注:使用RoutingKey为#,Exchange Type为topic的时候相当于使用fanout

注:headers: 通过headers 来决定把消息发给哪些queue

4.1 RabbitMQ fanout广播模式(实例)

send 发送端

import pika
import sys

credentials = pika.PlainCredentials('admin', 'admin')  # mq用户名和密码,用于认证
connection = pika.BlockingConnection(
    pika.ConnectionParameters('192.168.37.100', 5672, '/', credentials)
)
channel = connection.channel()

# exchange=“自定义名字”
# type = 'fanout' 定义exchange发送类型,广播类型
# exchange_type type报错就使用这个
channel.exchange_declare(exchange='logs',
                         exchange_type='fanout')

# 下面这条命令是 可通过命令行输入定义的消息 or 如果没输入就是后面这段话。
# message = ' '.join(sys.argv[1:]) or "info: Hello World!"

# 发送的内容
message = "info: Hello World!"

# routing_key 传入queue 由于是广播,不填
channel.basic_publish(exchange='logs',
                      routing_key='',
                      body=message)
print(" [x] Sent %r" % message)
connection.close()

# 注:由于是广播类型所以不需要写queue。

receive接收端:

import pika

credentials = pika.PlainCredentials('admin', 'admin')  # mq用户名和密码,用于认证
connection = pika.BlockingConnection(
    pika.ConnectionParameters('192.168.37.100', 5672, '/', credentials)
)
channel = connection.channel()

channel.exchange_declare(exchange='logs', exchange_type='fanout')
# exclusive=True 唯一的
# 不指定queue名字,rabbit会随机分配一个名字,
# exclusive=True会在使用此queue的消费者断开后,自动将queue删除
result = channel.queue_declare(exclusive=True)

# 随机取queue名字。
queue_name = result.method.queue
print("random queuename", queue_name)

# channel.queue_bind 绑定exchange转发器
# exchange=logs 由于rabbitMQ下不知一个exchange需要绑定。
#  queue_name 对列名
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(callback,
                      queue=queue_name,
                      no_ack=True)

channel.start_consuming()

 4.2 direct广播模式

有选择的接收消息(exchange type=direct)

RabbitMQ还支持根据关键字发送,即:队列绑定关键字,发送者将数据根据关键字发送到消息exchange,exchange根据 关键字 判定应该将数据发送至指定队列。

send 生产端

终端:python 脚本名 允许传入级别  发送消息
例:python direct_p.py info Hello
import pika
import sys

credentials = pika.PlainCredentials('admin', 'admin')  # mq用户名和密码,用于认证
connection = pika.BlockingConnection(
    pika.ConnectionParameters('192.168.37.100', 5672, '/', credentials)
)
channel = connection.channel()

# exchange=“自定义名字”
# type = 'direct' 定义exchange发送类型,广播类型
# exchange_type type报错就使用这个
channel.exchange_declare(exchange='direct_logs',
                         exchange_type='direct')

# 级别:默认取执行脚本传入参数,如果取不到执行info
severity = sys.argv[1] if len(sys.argv) > 1 else 'info'

# 下面这条命令是 可通过命令行输入定义的消息 or 如果没输入就是后面这段话。
message = ' '.join(sys.argv[2:]) or 'Hello World!'

# routing_key=severity 消息发送到指定级别
channel.basic_publish(exchange='direct_logs',
                      routing_key=severity,
                      body=message)
print(" [x] Sent %r:%r" % (severity, message))
connection.close()

recv 消费端

终端:python 脚本文件 启动级别

例:python direct_c.py info

import pika
import sys

credentials = pika.PlainCredentials('admin', 'admin')  # mq用户名和密码,用于认证
connection = pika.BlockingConnection(
    pika.ConnectionParameters('192.168.37.100', 5672, '/', credentials)
)
channel = connection.channel()

channel.exchange_declare(exchange='direct_logs',
                         exchange_type='direct')

result = channel.queue_declare(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)

print(severities)
# 循环severities这个列表进行绑定
# routing_key=severity 接收端就是severity
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(callback,
                      queue=queue_name,
                      no_ack=True)

channel.start_consuming()

 4.3 topic广播模式

更细致的消息过滤

Although using the direct exchange improved our system, it still has limitations - it can't do routing based on multiple criteria.

In our logging system we might want to subscribe to not only logs based on severity, but also based on the source which emitted the log. You might know this concept from the syslog unix tool, which routes logs based on both severity (info/warn/crit...) and facility (auth/cron/kern...).

That would give us a lot of flexibility - we may want to listen to just critical errors coming from 'cron' but also all logs from 'kern'.

send 生产端
终端:python 执行脚本 消息.级别名称 消息.自定以级别
例:python topic_p Hello.info Hi.mysql
import pika
import sys

credentials = pika.PlainCredentials('admin', 'admin')  # mq用户名和密码,用于认证
connection = pika.BlockingConnection(
    pika.ConnectionParameters('192.168.37.100', 5672, '/', credentials)
)

channel = connection.channel()

channel.exchange_declare(exchange='topic_logs',
                         exchange_type='topic')

# 级别:默认取执行脚本传入参数,如果取不到执行info
# 发送消息结尾需要加入级别。
routing_key = sys.argv[1] if len(sys.argv) > 1 else 'anonymous.info'

# 下面这条命令是 可通过命令行输入定义的消息 or 如果没输入就是后面这段话。
message = ' '.join(sys.argv[2:]) or 'Hello World!'

# routing_key=severity 消息发送到指定级别
channel.basic_publish(exchange='topic_logs',
                      routing_key=routing_key,
                      body=message)
print(" [x] Sent %r:%r" % (routing_key, message))
connection.close()
recv 消费端
终端:python 执行脚本 *.级别 *.定义级别
例:python topic_c *.info *.mysql 
注:“#”代表可收所有。
import pika
import sys

credentials = pika.PlainCredentials('admin', 'admin')  # mq用户名和密码,用于认证
connection = pika.BlockingConnection(
    pika.ConnectionParameters('192.168.37.100', 5672, '/', credentials)
)

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)

# 循环severities这个列表进行绑定
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(callback,
                      queue=queue_name,
                      no_ack=True)

channel.start_consuming()

 

posted @ 2020-08-22 10:47  耗油炒白菜  阅读(116)  评论(0编辑  收藏  举报