Rabbitmq五大核心概念
其中,中间的Broker表示RabbitMQ服务,每个Broker里面至少有一个Virtual host虚拟主机,每个虚拟主机中有自己的Exchange交换机、Queue队列以及Exchange交换机与Queue队列之间的绑定关系Binding。producer(生产者)和consumer(消费者)通过与Broker建立Connection来保持连接,然后在Connection的基础上建立若干Channel信道,用来发送与接收消息
1.Connection连接:
每个producer(生产者)或者consumer(消费者)要通过RabbitMQ发送与消费消息,首先就要与RabbitMQ建立连接,这个连接就是Connection。Connection是一个TCP长连接。
2.Channel(信道)
Channel是在Connection的基础上建立的虚拟连接,RabbitMQ中大部分的操作都是使用Channel完成的,比如:声明Queue、声明Exchange、发布消息、消费消息等
看到此处,你是否有这样一个疑问:既然已经有了Connection,我们完全可以使用Connection完成Channel的工作,为什么还要引入Channel这样一个虚拟连接的概念呢?因为现在的程序都是支持多线程的,如果没有Channel,那么每个线程在访问RabbitMQ时都要建立一个Connection这样的TCP连接,对于操作系统来说,建立和销毁TCP连接是非常大的开销,在系统访问流量高峰时,会严重影响系统性能。
Channel就是为了解决这种问题,通常情况下,每个线程创建单独的Channel进行通讯,每个Channel都有自己的channel id帮助Broker和客户端识别Channel,所以Channel之间是完全隔离的。
Connection与Channel之间的关系可以比作光纤电缆,如果把Connection比作一条光纤电缆,那么Channel就相当于是电缆中的一束光纤。
3.Virtual host(虚拟主机)
Virtual host是一个虚拟主机的概念,一个Broker中可以有多个Virtual host,每个Virtual host都有一套自己的Exchange和Queue,同一个Virtual host中的Exchange和Queue不能重名,不同的Virtual host中的Exchange和Queue名字可以一样。这样,不同的用户在访问同一个RabbitMQ Broker时,可以创建自己单独的Virtual host,然后在自己的Virtual host中创建Exchange和Queue,很好地做到了不同用户之间相互隔离的效果。
5.Exchange(交换机)
Exchange是一个比较重要的概念,它是消息到达RabbitMQ的第一站,主要负责根据不同的分发规则将消息分发到不同的Queue,供订阅了相关Queue的消费者消费到指定的消息。那Exchange有哪些分发消息的规则呢?这就要说到Exchange的4种类型了:direct、fanout、topic、headers。
在介绍这4种类型的Exchange之前,我们先来了解一下另外一个比较重要的概念:Routing key,翻译成中文就是路由键。当我们创建好Exchange和Queue之后,需要使用Routing key(通常叫作Binding key)将它们绑定起来,producer在向Exchange发送一条消息的时候,必须指定一个Routing key,然后Exchange接收到这条消息之后,会解析Routing key,然后根据Exchange和Queue的绑定规则,将消息分发到符合规则的Queue中。
1).direct
direct的意思是直接的,direct类型的Exchange会将消息转发到指定Routing key的Queue上,Routing key的解析规则为精确匹配。也就是只有当producer发送的消息的Routing key与某个Binding key相等时,消息才会被分发到对应的Queue上。
2).fanout
fanout是扇形的意思,该类型通常叫作广播类型。fanout类型的Exchange不处理Routing key,而是会将发送给它的消息路由到所有与它绑定的Queue上。
3).topic
topic的意思是主题,topic类型的Exchange会根据通配符对Routing key进行匹配,只要Routing key满足某个通配符的条件,就会被路由到对应的Queue上。通配符的匹配规则如下:
a.Routing key必须是一串字符串,每个单词用“.”分隔
b.符号“#”表示匹配一个或多个单词
c.符号“*”表示匹配一个单词
eg:
“*.123” 能够匹配到 “abc.123”,但匹配不到 “abc.def.123”;“#.123” 既能够匹配到 “abc.123”,也能匹配到 “abc.def.123”
4.headers
6.Queue中Arguments参数详解
1.x-message-ttl
消息在队列中过期的时间
2.x-expires
队列在多长时间没有被使用后删除
3.x-max-length
加入queue中消息的条数。先进先出原则,超过多少条后面的消息会顶替前面的消息
4.x-max-length-bytes
加入queue中消息的容积
5.x-dead-letter-routing-key
延迟结束后指向队列(死信收容队列)
6.x-dead-letter-exchange
延迟结束后指向交换机(死信收容交换机)
7.x-max-priority
列队的优先级该参数会造成额外的CPU消耗
7.死信队列&死信交换器
DLX 全称(Dead-Letter-Exchange),称之为死信交换器,当消息变成一个死信之后,如果这个消息所在的队列存在x-dead-letter-exchange参数,那么它会被发送到x-dead-letter-exchange对应值的交换器上,这个交换器就称之为死信交换器,与这个死信交换器绑定的队列就是死信队列
8.死信消息
1.消息被拒绝(Basic.Reject或Basic.Nack)并且设置 requeue 参数的值为 false
2.消息过期了
3.队列达到最大的长度
9.代码示例
生产者:
# !/usr/bin/python3
# -*- encoding: utf-8 -*-
"""
@File : producer.py
@Time : 8/27/23 3:08 PM
@Author :
@Software : PyCharm
@Description : 生产者
"""
import json
import pika
# 用户认证
from pika.exchange_type import ExchangeType
credentials = pika.PlainCredentials(username="admin", password="****")
# 连接配置
conn_addr = pika.ConnectionParameters(
host="127.0.0.1",
port=5672,
virtual_host="/admin",
credentials=credentials
)
# 连接
conn = pika.BlockingConnection(conn_addr)
# 建立通道 保持tcp连接
channel = conn.channel()
queue = "queue_one"
# direct
def direct_mode():
exchange = "exchange_one"
routing_key = "one"
# 声明消息队列
# 消息10秒未处理将过期
queue_result = channel.queue_declare(queue=queue, arguments={"x-message-ttl": 1000 * 10})
print(queue_result)
# 声明交换机
exchange_result = channel.exchange_declare(exchange=exchange, exchange_type=ExchangeType.direct.value)
print(exchange_result)
# 绑定队列
result = channel.queue_bind(queue=queue, exchange=exchange, routing_key=routing_key)
print(result)
for i in range(1000):
message = json.dumps({'========OrderId+++++': "2000%s" % i})
# 发送消息
channel.basic_publish(exchange=exchange, routing_key=routing_key, body=message)
# fanout
def fanout_mode():
exchange = "exchange_two"
routing_key = ""
# 声明消息队列
channel.queue_declare(queue=queue, arguments={"x-message-ttl": 1000 * 10})
# 声明交换机
channel.exchange_declare(exchange=exchange, exchange_type=ExchangeType.fanout.value)
# 绑定队列
channel.queue_bind(queue=queue, exchange=exchange, routing_key=routing_key)
for i in range(1000):
message = json.dumps({'========OrderId+++++': "3000%s" % i})
# 发送消息
channel.basic_publish(exchange=exchange, routing_key=routing_key, body=message)
# topic
def topic_mode():
exchange = "exchange_three"
# routing_key = "one"
# routing_key = "*.two"
routing_key = "*.two.#"
# 声明消息队列
channel.queue_declare(queue=queue, arguments={"x-message-ttl": 1000 * 10})
# 声明交换机
channel.exchange_declare(exchange=exchange, exchange_type=ExchangeType.topic.value)
# 绑定队列
channel.queue_bind(queue=queue, exchange=exchange, routing_key=routing_key)
for i in range(1000):
message = json.dumps({'========OrderId+++++': "1000%s" % i})
# 发送消息
channel.basic_publish(exchange=exchange, routing_key="one.two.a.b.c", body=message)
# 死信队列
def dead_queue():
"""
模拟延时队列
1. 创建1个死信交换机(正常创建即可)和一个死信队列(正常创建),二者通过路由键绑定。
2. 创建1个业务交换机, 创建一个业务队列,队列关联一个死信交换机及与交换机绑定的一个死信队列路由键。
3. 最后将业务交换机与业务队列绑定。
4. 代码只需要对生产消息到业务队列,消费死信队列的消息就可以
:return:
"""
pro_exchange = "pro_ex"
pro_routing_key = "pro_route"
dead_exchange = "dead_ex"
dead_routing_key = "dead_route"
pro_queue = "pro_queue"
dead_queue = "dead_queue_pro"
# 正常队列
channel.queue_declare(
queue=pro_queue,
arguments={
"x-message-ttl": 1000 * 10,
"x-dead-letter-exchange": dead_exchange,
"x-dead-letter-routing-key": dead_routing_key,
})
# 死信队列
channel.queue_declare(queue=dead_queue)
# 正常交换机
channel.exchange_declare(exchange=pro_exchange, exchange_type=ExchangeType.direct.value)
# 死信交换机
channel.exchange_declare(exchange=dead_exchange, exchange_type=ExchangeType.direct.value)
# 绑定正常队列
channel.queue_bind(queue=pro_queue, exchange=pro_exchange, routing_key=pro_routing_key)
# 绑定死信队列
channel.queue_bind(queue=dead_queue, exchange=dead_exchange, routing_key=dead_routing_key)
for i in range(1000):
message = json.dumps({'========OrderId+++++': "5000%s" % i})
# 发送消息
channel.basic_publish(exchange=pro_exchange, routing_key=pro_routing_key, body=message)
if __name__ == "__main__":
# direct_mode()
# fanout_mode()
# topic_mode()
dead_queue()
# 关闭连接
conn.close()
消费者:
# !/usr/bin/python3
# -*- encoding: utf-8 -*-
"""
@File : client.py
@Time : 8/27/23 3:08 PM
@Author :
@Software : PyCharm
@Description : 消费者
"""
import pika
# 用户认证
credentials = pika.PlainCredentials(username="admin", password="****")
# 连接配置
conn_addr = pika.ConnectionParameters(
host="127.0.0.1",
port=5672,
virtual_host="/admin",
credentials=credentials
)
# 连接
conn = pika.BlockingConnection(conn_addr)
# 建立通道 保持tcp连接
channel = conn.channel()
def consume_one():
# 定义回调函数处理消息队列中的消息
def callback(ch, method, properties, body):
print(method)
print(properties)
print(body.decode())
# 确认消息
ch.basic_ack(delivery_tag=method.delivery_tag)
# 添加不按顺序分配消息的参数
channel.basic_qos(prefetch_count=1)
# 消费消息
channel.basic_consume(queue="queue_one", on_message_callback=callback)
# 开始接收消息,并进入阻塞状态, 队列里有信息才会进行处理
channel.start_consuming()
# 第二种方式消费数据
def consume_two():
for method, properties, body in channel.consume('queue_one'):
print(body.decode())
channel.basic_ack(method.delivery_tag)
# 消费死信队列消息
def consume_dead():
for method, properties, body in channel.consume('dead_queue_pro'):
print(body.decode())
channel.basic_ack(method.delivery_tag)
if __name__ == "__main__":
# consume_one()
# consume_two()
consume_dead()