RabbitMQ原理构成分析以及实战应用

一、RabbitMQ使用场景

为什么要使用RabbitMQ,可以通过以下场景来说明
场景:
比如用户在购物的时候,选中一件物品下单了,这时候就会发一个请求给我们的订单系统(Order),此时订单系统再发一个请求给我们的库存系统(stock)去修改库存,然后修改库,这里,我们的每一个请求都是一条消息,但是此时我们客户端在等待订单系统的消息,是否下单成功,但是订单系统并不需要知道库存系统(stock)的执行情况,因为无论库存系统是否修改库存成功或者库存修改出现BUG,我这条订单已经下了,因为是先下完了订单(下成功了) 才去更改库存, 库存如果更改出BUG了 那是库存系统的问题, 这个BUG不会影响订单系统
如果你可以理解这里,那我们就可以明白用户下订单“这条消息”是需要同步,我们需要知道下订单是否成功,但是订单系统(order)发消息给库存系统这条消息可以是异步的,我不需要知道你库存是否修改成功(我知道这里通知你我下单成功,然后你去修改库存即可)
这里有两套方案实现这个过程(案例引用的博客地址https://blog.csdn.net/qq_40708830/article/details/89454188)
方案一:
用户下完订单,订单发消息给库存,库存修改成功后返回消息,订单下成功了
在这里插入图片描述
很明显第一套方案耗时是我们下订单-订单系统-库存系统时间总和,这样总共耗时20min
那么有的同学可能会说订单系统-库存系统这个过程在订单系统中开辟新的线程去处理
在这里插入图片描述
这里下完订单后订单系统开辟新的线程去调用库存系统修改库存并同时返回下单的信息,使用新的线程确实可以解决,但是我们要自己去管理线程池,代码耦合度比较高
所以这时候RabbitMQ就上场了
在这里插入图片描述
这时候RabbitMQ就起了一个消息中间件的作用,中间人传递消息

二、RabbitMQ的组成以及实际应用

RabbitMQ中各个信息传递过程
在这里插入图片描述
在这里插入图片描述

然后看一下多个交换器Exchange和多个队列queue的场景
在这里插入图片描述

三、 RabbitMQ的组成介绍

RabbitMQ 是实现了高级消息队列协议(AMQP)的开源消息代理软件
1.Message
消息,消息是不具名的,它由消息头,消息体(payload)组成。消息是不透明的,而消息头则由一系列可选属性组成,这些属性包括routeing-key(路由键),priority(相对于其它消息的优先权),delivery-mode(指出消息可能持久性存储)等。
2.Publisher
消息的生产者。也是一个向交换器发布消息的客户端应用程序
3.Consumer
消息的消费者。表示一个从消息队列中取得消息的客户端应用程序
4.Exchange
交换器。用来接收生产者发送的消息并将这些消息路由给服务器中的队列
三种常用的交换器类型
1>direct(发布与订阅 完全匹配)
2>topic(主题,规则匹配)
3>fanout(广播)
5.Binding
绑定。用于消息队列和交换器之间的关联。一个邦定就是基于路由键(Routeingkey)将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表
6.Queue
消息队列。用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息
可以投入一个或多个队列。消息一直在队列里面,等待消费者链接到这个队列将其取走
7.Routeing-Key
路由键。RabbitMQ决定消息该投递到哪个队列的规则
队列通过路由键绑定到交换器
消息发送到MQ服务器时,消息将拥有一个路由键,即便是空的,RabbitMQ也会将其和绑定使用的路由键进行匹配。
如果匹配,消息将会投递到该队列。
如果不匹配,消息将会进入黑洞
8.Connection
链接。值rabbit服务器和客户端建立的TCP链接
9.Channel
信道。
1.Channel中文叫信道,是TCP里面的虚拟链接。例如:电缆相当于TCP,信道是一个独立的光纤束,一条TCP链接上创建多条信道是没有问题的
2.TCP一旦打开,就会创建多条AMQP信道
3.无论是发布消息,接收消息,订阅队列,这些动作都是同各国信道完成的
10.Virtual Host
虚拟主机。表示一批交换器,消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个vhost本质上就是一个mini版的RabbitMQ
服务器,拥有自己的队列,交换器,绑定和权限机制。vhost是APMQ概念的基础,必须在建立连接是指定,RabbitMQ默认的vhost是/
11.Borker
表示消息队列服务器实体

四、RabbitMQ参数含义

autoDelete
Queue:当所有消费客户端连接断开后,是否自动删除队列 true:删除 false:不删除
Exchange:当所有绑定队列都不在使用时,是否自动删除交换器 true:删除 false:不删除

RabbitMQ中的消息确认ACK机制
1.什么是消息确认ACK
如果在处理消息的过程中,消费者的服务器在处理消息时出现异常,那可能这条正在处理的消息就没有完成消费,数据就会丢失。为了确保数据
不会丢失,RabbitMQ支持消息确认-ACK
2.ACK的消息确认机制
ACK机子时消费者从RabbitMQ收到消息并处理完成后,反馈给RabbitMQ
RabbitMQ收到反馈后才将此消息从队列中删除。
1>如果一个消费者在处理消息出现了网络不稳定,服务器异常等现象,那么久不会
有ACK反馈,RabbitMQ会认为这个消息没有正常消费,会将消息重新放入到队列中,
2>如果在集群的情况下:RabbitMQ会立即将这个消息推送给这个在线的其他消费者。
这种机制保证了在消费者服务端故障的时候,不会丢失任何消息和任务。
3>消息只有收到消费者正确发送的ACK反馈后,消息才会从RabbitMQ服务器的数据中删除。
4>消息的ACK确认机制默认时打开的
3.ACK机制的开发注意事项分发
如果忘记了ACK,那么后果很严重。当Consumer退出时,Message会一直重新,然后RabbitMQ会占用越来越多的
的内容,由于RabbitMQ会长时间运行,因此这个“内存泄漏”是致命的。

RabbitMQ中消费者处理消息机制
消费者通过两种方式从特定的队列接收消息:

basic.consume,这样会将信道置为接收模式,直到取消对队列的订阅;
basic.get,主动让消费者接收队列中的下一条消息;
basic.get会影响性能,推荐使用basic.consume来实现高吞吐量,因为其处理过程是先订阅消息,获取单条消息,再取取消订阅。

如果队列拥有多个消费者时,队列收到的消息将以循环的方式发给消费者,即多个消费者平均消费这些消息。
另外,消费者接收到的每一条消息都要进行确认,必须通过basic.ack命令向rabbitmq服务端发送一个确认。 也可以设置auto_ack为true,只要消费者接收到消息,就自动视为确认,不过不建议这样,因为接收到不代表业务逻辑处理成功。 服务端接收到确认后,会从队列中删除对应消息。

还有一种场景,在接收到消息后,如果不想处理,可以通过下面方式处理:

把消费者从RabbitMQ服务器断开连接,,这样RabbitMQ会自动将消息入队并发送给另外一个消费者;
如果不想发送给其他消费者处理,就是想忽略这个消息,可以发送basic.reject命令;
最后来介绍下如何创建队列,首先明确下是生成者还是消费者创建,关键点是:生产者能否承担起丢失消息,因为发出去的消息如果路由到了不存在的队列,Rabbit会忽略它们。所以,建议生成者和消费者都尝试去创建队列,可以通过设置queue.declare的passive选项设置为ture来判断队列是否存在,如果不存在会返回一个错误。

通过queue.declare命令来创建队列,有一些选项说明下:
exclusive:如果设置true的化,队列将变成私有的,只有创建队列的应用程序才能够消费队列消息;
auto-delete:当最后一个消费者取消订阅的时候,队列会自动移除;
durable:是否要持久化;

RabbitMQ的持久化策略
1、持久化消息
当RabbitMQ服务器发生宕机或者需要重启时,默认情况下消息、队列、交换器都会消失,为了避免消息、队列、交换器相关的数据丢失,需要将消息、交换器、队列都设置为持久化(durable属性)。

(1)消息持久化的原理
对交换器和队列来说,当被设置为持久化时其相关数据会在磁盘中做备份,这样RabbitMQ服务器重启时会从磁盘中读取备份的数据恢复重建交换器和队列;对于消息来说,要实现持久化,除了将 “投递模式” (delivery mode)设为2之外,还需要将消息发布到持久化的交换器中并且到达持久化队列中才完成持久化存储,其他情况下的消息为无法应对服务器宕机和重启的非持久化。
当消息经过持久化交换器到达持久化队列是,RabbitMQ会将消息写入磁盘上的持久化日志文件,当持久化队列中的持久化消息被消费时,RabbitMQ会将持久化日志中这条消息标记为等待垃圾回收,然后在某个时间点RabbitMQ将其从持久化日志中删除。

(2)消息持久化的缺点
消耗性能,大幅度降低MQ服务器吞吐率;在MQ内建集群中工作得并不好,一旦某个集群节点宕机,这个节点上的队列不可用,直到节点恢复,而且出于系统容量和性能考虑,持久化消息不会在集群其他节点上备份,这导致未出队消息无法被消费,未写入到磁盘的消息也会丢失。

2、AMQP事务
为了弥补消息丢失的风险,产生了AMQP事务,简单地说就是将消息的发布和提交到磁盘上视为一个整体事务(要么都成功,否则视为失败),如果你了解数据库中事务的概念,想必不难理解。
缺点
大幅度降低系统消息吞吐量;由于消息发布和提交磁盘存储视为一个事务,会导致生产者应用程序发生同步。

3、发送方确认模式
生产者发布消息时首先要将信道设置成confirm模式,当消息成功写入磁盘后,MQ会向生产者发送一条确认消息,生产者收到成功的确认消息才认定为消息发布成功,当生产者受到确认消息时就会触发回调处理确认消息。
优点
既保持了消息发布和写入磁盘的事务一致性,又通过异步实现避免性能瓶颈

以python为里编写Direct模式下生产者,RabbitMQ,消费者之间交互
Direct 直连交换机
当一个绑定了 routing_key = 1 的消息被发送给直连交换机时,交换机会把消息发送给绑定了routing_key = 1的队列。

直连交换机经常用来循环分发任务给多个消费者。然后消息的负载均衡是发生在消费者之间的,而不是队列之间。
如下例:

消息生产者

import pika
config = pika.ConnectionParameters(
    host='127.0.0.1',
    credentials=pika.PlainCredentials('test', 'test'),
)
#创建 MQ 连接
conn = pika.BlockingConnection(config)
channel = conn.channel()
#在频道中创建一个队列
channel.exchange_declare(exchange='ceshi', type='direct')
# 发送消息到指定队列
# exchange 指定交换器
# routing_key 设置路由键
# body 发送的内容
channel.basic_publish(
    exchange='ceshi',
    routing_key='1',  # 设置路由键
    body='Hello World!'
)
conn.close()

消息消费者

import pika

config = pika.ConnectionParameters(
    host='127.0.0.1',
    port=5672,
    credentials=pika.PlainCredentials('test', 'test'),
)
# 创建 MQ 连接
conn = pika.BlockingConnection(config)
channel = conn.channel()
# 如果使用exchange, 这里检测 exchange 是否存在,如不存在创建。存在检测是否正确且		 是否符合 exchange_type 类型
channel.exchange_declare(exchange='ceshi', exchange_type='direct')

# 在频道中创建一个队列
channel.queue_declare(queue='hello')

# 将队列绑定到指定的 exchange
# routing_key 类似密钥,只接收 routing_key 正确的信息
channel.queue_bind(exchange='ceshi', queue='hello', routing_key='1')

# 回调函数四个必须的参数 body 是传入的内容
# channel: BlockingChannel
# method: spec.Basic.Deliver
# properties: spec.BasicProperties
# body: str or unicode
def callback(channel, method, properties, body):
    print (channel,method,properties,body)

# 指定队列调用的函数
# no_ack 参数 True 时处理完成后没有返回信息。False 时在处理完后应答
channel.basic_consume(
    callback,
    queue='hello',
    no_ack=False
)
print('waiting ...')
channel.start_consuming()
:在修改消费者的routing_key后,需要重新创建队列

(注:文章中有部分内容引用的其他博客,已注明)

猜你喜欢

转载自blog.csdn.net/qq_42707967/article/details/108548623