rabbitMQ消息队列
消息(Message)是指在应用间传输的数据
但是,如果你没有时间看英文文档,或者想看到一些总结性的内容,还是可以继续读下去的。
首先,来看一下整体的架构图,并介绍一些基本概念:
channel:信道是生产者,消费者和 RabbitMQ 通信的渠道,是建立在 TCP 连接上的虚拟连接。一个 TCP 连接上可以建立成百上千个信道,通过这种方式,可以减少系统开销,提高性能。
Broker:接收客户端连接,实现 AMQP 协议的消息队列和路由功能的进程。
Virtual Host:虚拟主机的概念,类似权限控制组,一个 Virtual Host 里可以有多个 Exchange 和 Queue,权限控制的最小粒度是 Virtual Host。
Exchange:交换机,接收生产者发送的消息,并根据 Routing Key 将消息路由到服务器中的队列 Queue。
ExchangeType:交换机类型决定了路由消息的行为,RabbitMQ 中有三种 Exchange 类型,分别是 direct、fanout、topic。
Message Queue:消息队列,用于存储还未被消费者消费的消息,由 Header 和 body 组成。Header 是由生产者添加的各种属性的集合,包括 Message 是否被持久化、优先级是多少、由哪个 Message Queue 接收等,body 是真正需要发送的数据内容。
BindingKey:绑定关键字,将一个特定的 Exchange 和一个特定的 Queue 绑定起来。
了解了基本概念之后,就开始写代码吧。本文使用 Python 开发,需要先安装 Pika,版本信息如下:
安装准备工具
1.下载Eralng,底下连接已提供otp_win64_20.2.exe链接:.提取码:3gpa
2.下载rabbitmq,底下链接已提供rabbitmq-server-3.7.4.exe链接:.提取码:vzar
安装步骤(图文)
1.第一步:安装otp_win64_20.2.exe右键以管理员身份运行
接着一直点击下一步傻瓜式安装2.第二步:安装rabbitmq-server-3.7.4.exe双击文件rabbitmq-server-3.7.4.exe,傻瓜式安装,(注意不要安装在包含中文和空格的目录下!安装后window服务中就存在rabbitMQ了,并且是启动状态。 )接着安装管理界面(插件)
进入rabbitMQ安装目录的sbin目录
点击上方的路径框输入cmd,按下回车键
输入命令点击回车
rabbitmq-plugins enable rabbitmq_management
第三步:1.重启服务,双击rabbitmq-server.bat(双击后可能需要等待一会)
2.打开浏览器,地址栏输入,即可看到管理界面的登陆页
输入用户名和密码,都为guest 进入主界面:
最上侧的导航以此是:概览、连接、信道、交换器、队列、用户管理
安装链接python的驱动
pip install pika
消费者生产者:
基本使用
说明:一对多模式,一个生产者,多个消费者,一个队列,每个消费者从队列中获取唯一的消息。
简单模式
生产者:
importpika
#1.连接rabbitmq服务器
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))#2.创建队列
channel =connection.channel()
#3.声明一个名为'hello'的队列
channel.queue_declare(queue='hello', durable=True)
#4.如果exchange为空,则是简单模式:向hello队列中插入字符串hello world!
channel.basic_publish(exchange='',
routing_key='hello',
body='hello world!',#消息持久化
properties=pika.BasicProperties(
delivery_mode=2,
))
print('send hello world!')#5.关闭连接
channel.close()
消费者:
importpika
#1.连接rabbitmq服务器
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))#2.创建队列
channel =connection.channel()
#3.声明一个名为'hello'的队列 谁先发的请求谁就先创建队列,队列不会重复创建
channel.queue_declare(queue='hello', durable=True)
#4.构建回调函数
defcallback(ch, method, properties, body):
print("[x] Received %r" %body)
#发送应答信号,表示数据已经处理完毕,可以删除
ch.basic_ack(delivery_tag=method.delivery_tag)
#5.确认监听队列queue:hello,一旦有值出现,则出发回调函数,callback#在订阅消息的时候可以指定应答模式,当auto_ack等于true的时候,表示当消费者一收到消息就表示消费者收到了消息,#消费者收到了消息就会立即从队列中删除。
channel.basic_consume(queue='hello',
auto_ack=False,
on_message_callback=callback)#6. 持续监听
print('[*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()
描述:将交换器、队列和消息都设置持久化之后就能保证数据不会被丢失吗?当然不是,多个方面:
消费者端: 消费者订阅队列将autoAck设置为true,虽然消费者接收到了消息,但是没有来得及处理就宕机了,那数据也会丢失,解决方案就是以为手动确认接收消息,待处理完消息之后,手动删除消息
解决方案:应答模式
自动ACK:消息一旦被接收,消费者自动发送ACK
手动ACK:消息接收后,不会发送ACK,需要手动调用
- 如果消息不太重要,丢失也没有影响,那么自动ACK会比较方便
- 如果消息非常重要,不容丢失。那么最好在消费完成后手动ACK,否则接收消息后就自动ACK,RabbitMQ就会把消息从队列中删除。如果此时消费者宕机,那么消息就丢失了。
#发送应答信号,表示数据已经处理完毕,可以删除#构建回调函数
defcallback(ch, method, properties, body):
print("[x] Received %r" %body)
#发送应答信号,表示数据已经处理完毕,可以删除
ch.basic_ack(delivery_tag=method.delivery_tag)#在订阅消息的时候可以指定应答模式,当auto_ack等于True的时候,表示自动,False表示手动,
channel.basic_consume(queue='hello',
auto_ack=False,
on_message_callback=callback)
在rabbitmq服务端,如果消息正确被发送,但是rabbitmq未来得及持久化,没有将数据写入磁盘,服务异常而导致数据丢失,解决方案,可以通过rabbitmq集群的方式实现消息中间件的高可用性
持久化
#持久化是为提高rabbitmq消息的可靠性,防止在异常情况(重启,关闭,宕机)下数据的丢失#durable:设置是否执行持久化。如果设置为true,即durable=true,持久化实现的重要参数
channel.queue_declare(queue='hello', durable=True)#消息持久化delivery_mode=2 2是存在在硬盘 1是默认存储在内存
properties=pika.BasicProperties(
delivery_mode=2,
)
发布/订阅模式
说明:生产者将消息发送给 broker,由交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接收到消息。消费者监听自己的队列并进行消费。
发布订阅模式图解:
代码:
发布者:
importpikaimportsys'''什么是发布/订阅:
在上一个实例中,我们搭建了一个工作队列,每个任务只分发给一个工作者(worker)。
在本实例中,我们要做的跟之前完全不一样 即:分发一个消息给多个消费者(consumers)。这种模式被称为“发布/订阅”。'''
#发布订阅--发布者#建立一个链接
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))#链接rabbitmq
channel =connection.channel()
#在调用queue_declare方法的时候,不提供queue参数就可以创建一个随机名的队列#result = channel.queue_declare()
#使用交换机,这里并没有使用指定的一个队列,而是使用交换机,这里使用的交换机是fanout交换机,并且指定交换机名为logs
channel.exchange_declare(exchange='logs',exchange_type='fanout')
#获取消息发布
message = "info: Hello World!"
#开始发布消息#你会注意到routing_key为空,这是因为,它的值会被扇型交换机(fanout exchange)忽略。
channel.basic_publish(exchange='logs',
routing_key='',
body=message)
print("[x] Sent %r"%message)#关闭链接
connection.close()
订阅者:
importpikaimporttime
connection= pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel=connection.channel()
channel.exchange_declare(exchange='logs', exchange_type='fanout')
#因为发布订阅用的是随机名队列,所以当与消费者断开连接的时候,这个队列应当被立即删除。exclusive标识符即可达到此目的。
result = channel.queue_declare('', exclusive=True)
#在之前的实例中我们都要事先知道,队列的名字,这里采用的就是默认获取
queue_name =result.method.queue
#开始绑定:将消费者绑定到和交换机同一个频道,这样logs交换机发来的消息,消费者就可以收到了
channel.queue_bind(exchange='logs', queue=queue_name)
print('[*] Waiting for logs. To exit press CTRL+C')
defcallback(ch, method, properties, body):print("[x] Received %r" %body.decode())
time.sleep(body.decode().count('.'))print("[x] Done")
channel.basic_consume(queue=queue_name,
on_message_callback=callback)
channel.start_consuming()#rabbitmqctl list_bindings 列出所有现存的绑定。#绑定的另外一种理解:#绑定(binding)是指交换机(exchange)和队列(queue)的关系。#可以简单理解为:这个队列(queue)对这个交换机(exchange)的消息感兴趣。
路由模式 Routing
说明:生产者将消息发送给 broker,由交换机根据routing_key分发到不同的消息队列,然后消费者同样根据routing_key来消费对应队列上的消息。
图解:
代码
生产者:
#!/usr/bin/env python
importpikaimportsys#连接rabbitmq
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!'#向交换机发数据 routing_key交换机向那个关键字队列发送数据
channel.basic_publish(
exchange='direct_logs', routing_key=severity, body=message)print("[x] Sent %r:%r" %(severity, message))
connection.close()
消费者:
#!/usr/bin/env python
importpikaimportsys#连接rabbitmq
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 notseverities:
sys.stderr.write("Usage: %s [info] [warning] [error]\n" %sys.argv[0])
sys.exit(1)
#routing_key = [info] [warning] [error] 其中一个#进行队列的绑定
for severity inseverities:
channel.queue_bind(
exchange='direct_logs', queue=queue_name, routing_key=severity)
print('[*] Waiting for logs. To exit press CTRL+C')
#回调函数
defcallback(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()
主题模式 Topics
图解
说明:其实,主题模式应该算是路由模式的一种,也是通过routing_key来分发,只不过是routing_key支持了正则表达式,更加灵活。
# 表示可以匹配0个或多个单词
* 表示只能匹配一个单词
代码:
生产者:
#!/usr/bin/env python
importpikaimportsys#连接
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()
消费者:
#!/usr/bin/env python
importpikaimportsys
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 notbinding_keys:
sys.stderr.write("Usage: %s [binding_key]...\n" %sys.argv[0])
sys.exit(1)
for binding_key inbinding_keys:
channel.queue_bind(
exchange='topic_logs', queue=queue_name, routing_key=binding_key)
print('[*] Waiting for logs. To exit press CTRL+C')
defcallback(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()
RPC 模式 RPC
图解
说明:通过消息队列来实现 RPC 功能,客户端发送消息到消费队列,消息内容其实就是服务端执行需要的参数,服务端消费消息内容,执行程序,然后将结果返回给客户端。
代码
生产者代码:
#!/usr/bin/env python
importpikaimportuuid
classFibonacciRpcClient(object):
def __init__(self):
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
self.channel.basic_consume(
queue=self.callback_queue,
on_message_callback=self.on_response,
auto_ack=True)
defon_response(self, ch, method, props, body):if self.corr_id ==props.correlation_id:
self.response=body
defcall(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 isNone:
self.connection.process_data_events()returnint(self.response)
fibonacci_rpc=FibonacciRpcClient()
print("[x] Requesting fib(30)")
response= fibonacci_rpc.call(30)print("[.] Got %r" % response)
消费者代码:
#!/usr/bin/env python
importpika
connection=pika.BlockingConnection(
pika.ConnectionParameters(host='localhost'))
channel=connection.channel()
channel.queue_declare(queue='rpc_queue')
deffib(n):if n ==0:return0elif n == 1:return 1
else:return fib(n - 1) + fib(n - 2)
defon_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(queue='rpc_queue', on_message_callback=on_request)
print("[x] Awaiting RPC requests")
channel.start_consuming()
总结
以上就是本文的全部内容,其中 Publish/Subscribe,Routing,Topics 三种模式可以统一归为 Exchange 模式,只是创建时交换机的类型不一样,分别是 fanout、direct、topic。
一般我们只会使用简单模式、发布订阅模式、路由模式,而主题模式和RPC模式很少使用了解即可。