AMQP协议
AMQP全称高级消息队列协议
AMQP中的概念
-
Publisher:消息发送者,将消息发送到Exchange并指定RoutingKey,以便queue可以接收到指定的消息。
-
Consumer:消息消费者,从queue获取消息,一个Consumer可以订阅多个queue以从多个queue中接收消息。
-
Server:一个具体的MQ服务实例,也称为Broker。
-
Virtual host:虚拟主机,一个Server下可以有多个虚拟主机,用于隔离不同项目,一个Virtual host通常包含多个Exchange、Message Queue。
-
Exchange:交换器,接收Producer发送来的消息,把消息转发到对应的Message Queue中。
-
Routing key:路由键,用于指定消息路由规则(Exchange将消息路由到具体的queue中),通常需要和具体的Exchange类型、Binding的Routing key结合起来使用。
-
Bindings:指定了Exchange和Queue之间的绑定关系。Exchange根据消息的Routing key和Binding配置(绑定关系、Binding、Routing key等)来决定把消息分派到哪些具体的queue中。这依赖于Exchange类型。
-
Message Queue:实际存储消息的容器,并把消息传递给最终的Consumer。
-
Channel: 消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务
RabbitMQ使用amqp协议,JMS规范仅对于Java的使用作出的规定,跟其他语言无关,协议是语言无关的,只要语言实现了该协议,就可以做客户端。如此,则不同语言之间互操作性得以保证。
RabbitMQ
异步处理、流量削峰、限流、缓冲、排队、最终一致性、消息驱动等需求的场景都可以使用消息中间件。
灵活的路由配置,RabbitMQ中,在生产者和队列之间有一个交换器模块。根据配置的路由规则,生产者发送的消息可以发送到不同的队列中。路由规则很灵活,还可以自己实现。
RabbitMQ工作流程
生产者发送消息的流程
-
1.生产者连接RabbitMQ,建立TCP连接(Connection),开启信道(Channel)
-
2.生产者声明一个Exchange(交换器),并设置相关属性,比如交换器类型、是否持久化等
-
3.生产者声明一个队列井设置相关属性,比如是否排他、是否持久化、是否自动删除等
-
4.生产者通过 bindingKey (绑定Key)将交换器和队列绑定( binding )起来
-
5.生产者发送消息至RabbitMQ Broker,其中包含 routingKey (路由键)、交换器等信息
-
6.相应的交换器根据接收到的 routingKey 查找相匹配的队列。
-
7.如果找到,则将从生产者发送过来的消息存入相应的队列中。
-
8.如果没有找到,则根据生产者配置的属性选择丢弃还是回退给生产者
-
9.关闭信道。
-
10.关闭连接。
消费者接收消息的过程
-
1.消费者连接到Rabbit MQBroker,建立一个连接(Connection),开启一个信道(Channel) 。
-
2.消费者向RabbitMQ Broker 请求消费相应队列中的消息,可能会设置相应的回调函数, 以及做一些准备工作
-
3.等待RabbitMQ Broker 回应并投递相应队列中的消息, 消费者接收消息。
-
4.消费者确认( ack) 接收到的消息。
-
5.RabbitMQ 从队列中删除相应己经被确认的消息。
-
6.关闭信道。
-
7.关闭连接
RabbitMQ Exchange类型
RabbitMQ常用的交换器类型有: fanout,direct,topic,headers 四种。
-
Fanout:会把所有发送到该交换器的消息路由到所有与该交换器绑定的队列中
-
Direct:direct类型的交换器路由规则很简单,它会把消息路由到那些BindingKey和RoutingKey完全匹配的队列中
-
Topic:topic类型的交换器在direct匹配规则上进行了扩展,也是将消息路由到BindingKey和RoutingKey 相匹配的队列中,这里的匹配规则稍微不同,它约定: BindingKey和RoutingKey一样都是由"."分隔的字符串;BindingKey中可以存在两种特殊字符“”和 “#”,用于模糊匹配,其中""用于匹配一个单词,"#"用于匹配多个单词(可以是0个)。
-
Headers:headers类型的交换器不依赖于路由键的匹配规则来路由信息,而是根据发送的消息内容中的headers属性进行匹配。性能很差,不实用
RabbitMQ的工作模式
简单模式
生产者直接发送消息给RabbitMQ,另一端消费。未定义和指定Exchange的情况下,使用的是AMQP default这个内置的Exchange。
消息的消费者(consumer) 监听 消息队列,如果队列中有消息,就消费掉,消息被拿走后,自动从队列中删除(隐患 消息可能没有被消费者正确处理,已经从队列中消失了,造成消息的丢失,这里可以设置成手动的ack,但如果设置成手动ack,处理完后要及时发送ack消息给队列,否则会造成内存溢出)。
工作队列模式
消息产生者将消息放入队列消费者可以有多个,消费者1,消费者2同时监听同一个队列,消息被消费。C1 C2共同争抢当前的消息队列内容,谁先拿到谁负责消费消息(隐患:高并发情况下,默认会产生某一个消息被多个消费者共同使用,可以设置一个开关(syncronize) 保证一条消息只能被一个消费者使用)。
发布订阅模式
使用fanout类型交换器,routingKey忽略。每个消费者定义生成一个队列并绑定到同一个 Exchange,每个消费者都可以消费到完整的消息。
消息广播给所有订阅该消息的消费者。
在RabbitMQ中,生产者不是将消息直接发送给消息队列,实际上生产者根本不知道一个消息被发送到哪个队列。
生产者将消息发送给交换器。交换器非常简单,从生产者接收消息,将消息推送给消息队列。交换器必须清楚地知道要怎么处理接收到的消息。应该是追加到一个指定的队列,还是追加到多个队列,还是丢弃。规则就是交换器类型。
路由模式
使用 direct 类型的Exchange,发N条消费并使用不同的 routingKey ,消费者定义队列并将队列、 routingKey 、Exchange绑定。此时使用 direct 模式Exchagne必须要 routingKey 完全匹配的情况下消息才会转发到对应的队列中被消费。
上一个模式中,可以将消息广播到很多接收者。
现在我们想让接收者只接收部分消息,如,我们通过直接模式的交换器将关键的错误信息记录到log文件,同时在控制台正常打印所有的日志信息。
topic 主题模式(路由模式的一种)
使用 topic 类型的交换器,队列绑定到交换器、 bindingKey 时使用通配符,交换器将消息路由转 发到具体队列时会根据消息 routingKey 模糊匹配,比较灵活。
总结工作模式
简单模式和工作队列模式:就是一对一和一对多,没有定义交换器,使用的默认的交换器,生产者发送消息。消息者都是通过生产者定义的队列名称监听,消费。
发布订阅模式:生产者需要定义一个fanout类型的交换器,消费者通过交换器的名称进行绑定,消息广播给所有订阅该消息的消费者。
路由模式:生产者需要定义一个direct类型的交换器,同时需要定义不同的routingKey来发送不同的消息,消费者通过交换器的名称和routingKey来接收消息
topic主题模式:在路由模式的基础上采用通配符来定义routingKey,消费者接收消息更加灵活
如何保证消息不被重复消费?
-
先说为什么会重复消费:正常情况下,消费者在消费消息的时候,消费完毕后,会发送一个确认消息给消息队列,消息队列就知道该消息被消费了,就会将该消息从消息队列中删除;但是因为网络传输等等故障,确认信息没有传送到消息队列,导致消息队列不知道自己已经消费过该消息了,再次将消息分发给其他的消费者。
-
一个解决思路是:保证消息的唯一性,就算是多次传输,不要让消息的多次消费带来影响;保证消息等幂性;
-
在我们系统中,发送的消息会封装成一个对象,有个messageId是唯一的,接收消息的时候先查redis是否有这个id,有的话就是重复消息,没有的话,就消费这个消息,然后会往redis存id。数据多了可以设置个比较长的有效期 或者持久化到 其他地方 或者定期清理下数据
如何确保消息正确地发送至RabbitMQ?如何确保消息接收方消费了消息?
发送方确认模式:一旦消息被投递到目的队列后,或者消息被写入磁盘后(可持久化的消息),信道会发送一个确认给生产者(包含消息唯一 ID)。
接收方确认机制:消费者接收每一条消息后都必须进行确认,只有消费者确认了消息,RabbitMQ才能安全地把消息从队列中删除。
怎么避免消息丢失?
丢失又分为:生产者丢失消息、消息列表丢失消息、消费者丢失消息;
生产者丢失消息
-
事务机制:发送消息前,开启事务(channel.txSelect()),然后发送消息,如果发送过程中出现什么异常,事务就会回滚(channel.txRollback()),如果发送成功则提交事务(channel.txCommit())。然而,这种方式有个缺点:吞吐量下降;
-
发送端确认机制:
- 一旦channel进入confirm模式,所有在该信道上发布的消息都将会被指派一个唯一的ID(从1开始)
- 一旦消息被投递到所有匹配的队列之后;rabbitMQ就会发送一个ACK给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了;
- 如果rabbitMQ没能处理该消息,则会发送一个Nack消息给你,你可以进行重试操作。
消息队列丢失消息
可以进行 Exchange的持久化,Queue的持久化,消息的持久化
如果消息和队列是持久化的,那么确认消息会在消息持久化后发出,这样,如果消息持久化磁盘之前,rabbitMQ阵亡了,那么生产者收不到Ack信号,生产者会自动重发。
将queue的持久化标识durable设置为true,则代表是一个持久的队列
发送消息的时候将deliveryMode=2,消息的持久化
消费者丢失消息
消费者丢数据一般是因为采用了自动确认消息模式,改为手动确认消息即可!