网络编程之:RabbitMQ队列
RabbitMQ
1 安装使用RabbitMQ
1. 安装
https://github.com/rabbitmq/rabbitmq-server/releases/
pip install pika
2. 使用
C:\Program Files\RabbitMQ Server\rabbitmq_server-3.8.8\sbin 下
rabbitmq-server.bat
rabbitmqctl.bat list_queues # 列出当前队列信息
2 RabbitMQ实现模型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XGgziDEq-1604876772692)(C:\Users\63291\AppData\Roaming\Typora\typora-user-images\image-20200925105834784.png)]
3 远程连接RabbitMQ Server
1. rabbitmq server上创建一个用户
rabbitmqctl add_user user1 password
2. 配置权限,允许从外面访问
rabbitmqctl set_permissions -p / user1 ".*" ".*" ".*"
set_permissions [-p vhost] {user} {conf} {write} {read}
vhost: The name of the virtual host to which to grant the user access, defaulting to /.
user: The name of the user to grant access to the specified virtual host.
conf: A regular expression matching resource names for which the user is granted configure permissions.
write: A regular expression matching resource names for which the user is granted write permissions.
read: A regular expression matching resource names for which the user is granted read permissions.
3. 客户端配置认证参数
credentials = pika.PlainCredentials('user', 'password')
connection = pika.BlockingConnection(pika.ConnectionParameters('192.168.0.1', 5672, '/', credentials))
channel = connection.channel()
4 Message acknowledgments
def callback(ch, method, properties, body):
print(" [x] Received %r" % body)
# 配置完成auto_ack=False后,还需要配置以下脚本:客户端需要手动与服务端确认收到消息,服务端才会从内存中删除消息
ch.basic_ack(delivery_tag=method.delivery_tag)
# auto_ack=True时,当 RabbitMQ 发送完消息后,不管客户端是否收到消息,都会立刻从内存中删除该消息;
# auto_ack=False时,当消息发送过程中通讯中断,RabbitMQ 会重传给新的客户端,并确认消息被完整接收后,删除消息。
channel.basic_consume('hello', callback, False)
5 消息持久化
1. 队列持久化
# 声明 queue,为确保 RabbitMQ 重启后,队列不会丢失,需要对队列进行持久化
channel.queue_declare(queue='hello', durable=True)
2. 消息持久化
channel.basic_publish(exchange='',
routing_key='hello', # queue名字
body='Hello World!',
properties=pika.BasicProperties(
delivery_mode=2 # 消息持久化,确保 RabbitMQ 重启后消息仍然存在
)
)
6 消息公平分发
# RabbitMQ会默认把生产者发的消息依次分发给各个消费者。
# 告诉RabbitMQ在我这个消费者当前消息还没处理完的时候就不要再给我发新消息了。
channel.basic_qos(prefetch_count=1)
7 简单生产消费实例
生产者端
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
# 声明一个管道
channel = connection.channel()
# 声明queue
channel.queue_declare(queue='hello')
# RabbitMQ 的消息不能被直接发送给队列,必须先经过 exchange 转发。
# 使用给定的 exchange, 发布 channel
channel.basic_publish(exchange='',
routing_key='hello', # queue名字
body='Hello World!',
)
'''
:param str exchange: The exchange to publish to
:param str routing_key: The routing key to bind on
:param bytes body: The message body
:param pika.spec.BasicProperties properties: Basic.properties
:param bool mandatory: The mandatory flag
'''
print(" [x] Sent 'Hello World!'")
connection.close()
消费者端
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
# 在此处声明一个 queue 的原因是:我们无法确认生产者与消费者哪个先执行,如果没有事先声明 queue ,消费者先执行,则由于 queue 不存在产生报错。
# 建议在生产者与消费者两边同时声明 queue.
channel.queue_declare(queue='hello')
def callback(ch, method, properties, body):
'''
callback(channel, method, properties, body)
channel: pika.Channel
method: pika.spec.Basic.Return
properties: pika.spec.BasicProperties
body: bytes
'''
print("'ch: %s ' \n'method: %s' \n'properties: %s'" % (ch, method, properties))
print(" [x] Received %r" % body)
# 消费消息,如果收到消息,就调用 callback 函数来处理消息
channel.basic_consume('hello', callback, auto_ack=True)
# auto_ack=True 收到消息后提交给服务器确认。否则默认服务器没有收到确认消息会一直发送消息,每开一个consumer都会收到服务端发送过来的信息
print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()
8 RabbitMQ消息分发+消息持久化实例
- 先启动消息生产者,然后再分别启动3个消费者,通过生产者多发送几条消息,你会发现,这几条消息会被依次分配到各个消费者身上
生产者端
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
# 声明一个管道
channel = connection.channel()
# 声明 queue,为确保 RabbitMQ 重启后,队列不会丢失,需要对队列进行持久化
channel.queue_declare(queue='hello', durable=True)
channel.basic_publish(exchange='',
routing_key='hello', # queue名字
body='Hello World!',
properties=pika.BasicProperties(
delivery_mode=2 # 消息持久化,确保 RabbitMQ 重启后消息仍然存在
)
)
print(" [x] Sent 'Hello World!'")
connection.close()
消费者端
import pika
import time
connection = pika.BlockingConnection(pika.ConnectionParameters(
'localhost'))
channel = connection.channel()
channel.queue_declare(queue='hello', durable=True)
def callback(ch, method, properties, body):
print("'ch: %s ' \n 'method: %s' \n 'properties: %s'" % (ch, method, properties))
time.sleep(5)
print(" [x] Received %r" % body)
# 配置完成auto_ack=False后,还需要配置以下脚本:客户端需要手动与服务端确认收到消息,服务端才会从内存中删除消息
ch.basic_ack(delivery_tag=method.delivery_tag)
# 告诉RabbitMQ在我这个消费者当前消息还没处理完的时候就不要再给我发新消息了。
channel.basic_qos(prefetch_count=1)
# 消费消息,如果收到消息,就调用callback函数来处理消息
# auto_ack=True时,当 RabbitMQ 发送完消息后,不管客户端是否收到消息,都会立刻从内存中删除该消息;
# auto_ack=False时,当消息发送过程中通讯中断,RabbitMQ 会重传给新的客户端,并确认消息被完整接收后,删除消息。
channel.basic_consume('hello', callback, False)
print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()
9 Publish\Subscribe(消息发布\订阅)
Exchange在定义的时候是有类型的,以决定到底是哪些Queue符合条件,可以接收消息
direct: 通过routingKey和exchange决定的那个唯一的queue可以接收消息
fanout: 所有bind到此exchange的queue都可以接收消息
topic:所有符合routingKey(此时可以是一个表达式)的routingKey所bind的queue可以接收消息
表达式符号说明:
#代表一个或多个字符,*代表任何字符
注:使用RoutingKey为#,Exchange Type为topic的时候相当于使用fanout
headers: 通过headers 来决定把消息发给哪些queue
9.1 有选择的接收消息(exchange_type=‘direct’)
生产者端
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('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_name, callback, False)
channel.start_consuming()
9.2 topic消息过滤(exchange_type=‘topic’)
生产者端
import sys, pika
conn = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = conn.channel()
channel.exchange_declare(exchange='topic_logs', exchange_type='topic')
severity = sys.argv[1] if len(sys.argv) > 1 else 'anonymous.info'
msg = ' '.join(sys.argv[2:]) or 'Hello World!'
channel.basic_publish(exchange='topic_logs',
routing_key=severity,
body=msg)
print(" [x] Sent %r:%r" % (severity, msg))
conn.close()
消费者端
import sys, pika
conn = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = conn.channel()
channel.exchange_declare('topic_logs', 'topic')
result = channel.queue_declare(queue='', exclusive=True)
queue_name = result.method.queue
binding_keys = sys.argv[1:]
if not binding_keys:
sys.stderr.write("Usage: %s [info] [warning] [error]\n" % sys.argv[0])
exit(1)
for binding_key in binding_keys:
channel.queue_bind(queue_name, 'topic_logs', 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_name, callback, False)
channel.start_consuming()
9.3 绑定消息过滤(exchange_type=‘fanout’)
生产者端
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
channel.exchange_declare('logs', 'fanout')
message = "info: 'hello world'"
channel.basic_publish(exchange='logs',
routing_key='',
body=message,
properties=pika.BasicProperties(
delivery_mode=2
)
)
print("[*] Send 'hello world'")
connection.close()
消费者端
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
channel.exchange_declare('logs', 'fanout')
result = channel.queue_declare(queue='', exclusive=True)
# exclusive排他的,唯一的
# 不指定queue名字,rabbit会随机分配一个名字,exclusive=True会在使用此queue的消费者断开后,自动将queue删除
queue_name = result.method.queue
channel.queue_bind(exchange='logs', queue=queue_name)
print(' [*] Waiting for messages. To exit press CTRL+C')
def callback(ch, method, properties, body):
print(" [*] Received %r" % body)
channel.basic_consume(queue_name, callback, False )
channel.start_consuming()
10 Remote procedure call (RPC)实例
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RKY1LJeR-1604876772698)(C:\Users\63291\AppData\Roaming\Typora\typora-user-images\image-20200925140313211.png)]
RPC_Server
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)
channel.basic_qos(prefetch_count=1)
channel.basic_consume('rpc_queue', on_request)
print(" [x] Awaiting RPC requests")
channel.start_consuming()
RPC_Client
import pika
import uuid
class FibonacciRpcClient(object):
def __init__(self):
self.connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
self.channel = self.connection.channel()
result = self.channel.queue_declare(queue='', exclusive=True)
self.callback_queue = result.method.queue
self.channel.basic_consume(self.callback_queue, self.on_response)
def on_response(self, ch, method, props, body):
if self.corr_id == props.correlation_id:
self.response = body
def call(self, n):
self.response = None
self.corr_id = str(uuid.uuid4())
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))
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(5)
print(" [.] Got %r" % response)