RabbitMq篇

消息队列(Message Queue)是一种应用间的通信方式,消息发送后可以立即返回,由消息系统来确保消息的可靠传递。消息发布者只管把消息发布到 MQ 中而不用管谁来取,消息使用者只管从 MQ 中取消息而不管是谁发布的。这样发布者和使用者都不用知道对方的存在。

RabbitMq是使用Erlang语言开发的开源消息队列系统,基于AMQP协议来实现。

1、特点

  • 可靠性:RabbitMq使用一些机制来保证可靠性,如持久化、传输确认及发布确认等。

  • 灵活的路由:在消息进入队列之前,通过交换器来路由消息。对于典型的路由功能,RabbitMq己经提供了一些内置的交换器来实现。针对更复杂的路由功能,可以将多个 交换器绑定在一起,也可以通过插件机制来实现自己的交换器。

  • 扩展性:多个RabbitMq节点可以组成一个集群,也可以根据实际业务情况动态地扩展 集群中节点。

  • 高可用性:队列可以在集群中的机器上设置镜像,使得在部分节点出现问题的情况下队 列仍然可用。

  • 多种协议:RabbitMq除了原生支持AMQP协议,还支持STOMP,MQTT等多种消息 中间件协议。

  • 多语言客户端:RabbitMq几乎支持所有常用语言,比如Java、Python、Ruby、PHP、C#、JavaScript等。

  • 管理界面:RabbitMq提供了一个易用的用户界面,使得用户可以监控和管理消息、集 群中的节点等。

  • 令插件机制:RabbitMq提供了许多插件 ,以实现从多方面进行扩展,当然也可以编写自 己的插件。

2、基本的概念模型

5015984-367dd717d89ae5db.jpg

  • Message消息:消息是不具名的,它由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)等。

  • Publisher消息的生产者:也是一个向交换器发布消息的客户端应用程序。

  • Exchange交换器:用来接收生产者发送的消息并将这些消息路由给服务器中的队列。

  • Binding绑定:用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。

  • Queue消息队列:用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走

  • Connection网络连接:比如一个TCP连接。

  • Channel信道:多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的TCP连接内地虚拟连接,AMQP 命令都是通过信道发出去的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。因为对于操作系统来说建立和销毁 TCP 都是非常昂贵的开销,所以引入了信道的概念,以复用一条 TCP 连接。

  • Virtual Host虚拟主机:表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个 vhost 本质上就是一个 mini 版的 RabbitMq服务器,拥有自己的队列、交换器、绑定和权限机制。vhost 是 AMQP 概念的基础,必须在连接时指定,RabbitMq默认的vhost是/

  • Broker:表示消息队列服务器实体。

3、六种工作模式

3.1、simple简单模式

在这里插入图片描述

最简单的一对一模式,一个生产者,一个消费者。

3.2、work工作模式(资源的竞争)

在这里插入图片描述

一对多模式,一个生产者,多个消费者,一个队列。一个消息只能被一个消费者消费。

消息分发机制:

  • 轮询分发:在消费之前就会把消息均等分成几份,然后把这几份消息一次性的发给消费者,让消费者消费 ,加入有10个消息,2个消费者,那么就是消息队列把消息平均分成两份,把5个消息发给一个消费者,另外5个消息发给另外5个消费者。

  • 公平分发:只要有消费者空闲,就会给这个消费者发送消息。

3.3、publish/subscribe发布订阅(共享资源)

在这里插入图片描述

生产者把消息发送到交换机之后,交换机将消息转发到每个与它绑定的队列中。消费者监听自己队列里的消息并消费。把交换机的路由模式设置为广播(fanout)模式。

3.4、routing路由模式

在这里插入图片描述

生产者将消息发送给交换机,由交换机根据routing_key分发到不同的消息队列,然后消费者同样根据routing_key来消费对应队列上的消息。把交换机的路由模式设置为定向(direct)模式。

3.5、topic主题模式(路由模式的一种)

在这里插入图片描述

主题模式应该算是路由模式的一种,也是通过routing_key来分发,只不过是routing_key支持了正则表达式,更加灵活。把交换机的路由模式设置为(topic)模式。

  • * #代表通配符。

  • *代表一个单词,#代表零个或多个单词。

3.6、RPC远程过程调用

img

RPC即客户端远程调用服务端的方法 ,使用MQ可以实现RPC的异步调用,基于Direct交换机实现,流程如下:

  1. 客户端既是生产者也是消费者(生产了消息,消费了消息),向RPC请求队列发送RPC调用消息,同时监听RPC响应队列。

  2. 服务端监听RPC请求队列的消息,收到消息后执行服务端的方法,得到方法返回的结果。

  3. 服务端将RPC方法的结果发送到RPC响应队列。

  4. 客户端(RPC调用方)监听RPC响应队列,接收到RPC调用结果。

4、问题及解决方案

4.1、消息丢失

img

4.1.1、生产者没有成功把消息发送到MQ

  • 丢失的原因:因为网络传输的不稳定性,当生产者在向MQ发送消息的过程中,MQ没有成功接收到消息,但是生产者却以为MQ成功接收到了消息,不会再次重复发送该消息,从而导致消息的丢失。

  • 解决办法: 有两个解决办法:事务机制confirm机制,最常用的是confirm机制。

4.1.1.1、事务机制

伪代码:

 // 开启事务
 channel.txSelect;
 try {
 // 这里发送消息
 } catch (Exception e) {
 channel.txRollback;
 // 这里再次重发这条消息
 }
 // 提交事务
 channel.txCommit;
4.1.1.2、confirm机制

RabbitMq可以开启 confirm 模式,在生产者那里设置开启 confirm 模式之后,生产者每次写的消息都会分配一个唯一的 id,如果消息成功写入 RabbitMq中,RabbitMq会给生产者回传一个 ack 消息,告诉你说这个消息 ok 了。如果 RabbitMq没能处理这个消息,会回调你的一个 nack 接口,告诉你这个消息接收失败,生产者可以发送。而且你可以结合这个机制自己在内存里维护每个消息 id 的状态,如果超过一定时间还没接收到这个消息的回调,那么可以重发。

4.1.2、RabbitMq接收到消息之后丢失了消息

  • 丢失的原因:RabbitMq接收到生产者发送过来的消息,是存在内存中的,如果没有被消费完,此时RabbitMq宕机了,那么再次启动的时候,原来内存中的那些消息都丢失了。

  • 解决办法:开启RabbitMq的持久化。当生产者把消息成功写入RabbitMq之后,RabbitMq就把消息持久化到磁盘。结合上面的说到的confirm机制,只有当消息成功持久化磁盘之后,才会回调生产者的接口返回ack消息,否则都算失败,生产者会重新发送。存入磁盘的消息不会丢失,就算RabbitMq挂掉了,重启之后,他会读取磁盘中的消息,不会导致消息的丢失。

持久化的配置:

  • 第一点是创建 queue 的时候将其设置为持久化,这样就可以保证 RabbitMq持久化 queue 的元数据,但是它是不会持久化 queue 里的数据的。

  • 第二个是发送消息的时候将消息的 deliveryMode 设置为 2,就是将消息设置为持久化的,此时 RabbitMq就会将消息持久化到磁盘上去。

4.1.3、消费者弄丢了消息

  • 丢失的原因:如果RabbitMq成功的把消息发送给了消费者,那么RabbitMq的ack机制会自动的返回成功,表明发送消息成功,下次就不会发送这个消息。但如果就在此时,消费者还没处理完该消息,然后宕机了,那么这个消息就丢失了。

  • 解决的办法:简单来说,就是必须关闭 RabbitMq的自动 ack,可以通过一个 api 来调用就行,然后每次在自己代码里确保处理完的时候,再在程序里 ack 一把。这样的话,如果你还没处理完,不就没有 ack了?那 RabbitMq就认为你还没处理完,这个时候 RabbitMq会把这个消费分配给别的 consumer 去处理,消息是不会丢的。

4.2、重复消费

  • 原因:正常情况下,消费者在消费消息的时候,消费完毕后,会发送一个确认消息给消息队列,消息队列就知道该消息被消费了,就会将该消息从消息队列中删除;但是因为网络传输等等故障,确认信息没有传送到消息队列,导致消息队列不知道自己已经消费过该消息了,再次将消息分发给其他的消费者。

  • 解决方法:保证消息的唯一性,就算是多次传输,不要让消息的多次消费带来影响;保证消息等幂性

    • 在消息生产时,MQ内部针对每条生产者发送的消息生成一个inner-msg-id,作为去重和幂等的依据(消息投递失败并重传),避免重复的消息进入队列;

    • 在消息消费时,要求消息体中必须要有一个bizId(对于同一业务全局唯一,如支付ID、订单ID、帖子ID等)作为去重和幂等的依据,避免同一条消息被重复消费。

posted @ 2022-02-18 16:08  是老胡啊  阅读(47)  评论(0)    收藏  举报