RabbitMQ从入门到放弃

消息队列简介

MQ的诞生,1983年的时候,有个MIT工作的印度小伙子突发奇想,以前我们软件相互通信,都是点对点的,而且要实现相同的协议,能不能有一种专门用来通信的中间件

,就像主板(BUS)一样,把不同的软件集成起来?于是他搞了一家公司(Teknekron),开发了世界上第一个消息队列软件The Information Bus(TIB)。

最开始是在高盛这些公司用在金融交易里面。因为TIB实现了发布订阅(Publish/Subscribe)模型,信息的生产者和消费者可以完全解耦,这个特性引起了电信行业特别是新闻机构的注意、

1994年路透社收购了Teknekron。TIB的成功马上引起了IBM的注意,他们研发了自己的IBM MQ(IBM Wesphere)。后面微软也加入了这场战斗,研发了MSMQ。这个时候,每个厂商的产品都是孤立的。

大家都有自己的技术壁垒。比如一个应用订阅了IBM MQ的消息,如果有要订阅MSMQ的消息,因为协议、API不同,又要重复去实现。为什么大家都不愿意去创建标准接口,

来实现不同的MQ产品的互通呢?和现在微信里面不能打开淘宝页面是一个道理(商业竞争)

2001年SUN公司发布了JMS规范,它想要在各大厂商的MQ上面统一包装一层java的规范,大家都只需要针对API编程就可以了,不需要关注使用了什么样的消息中间件,只要选择合适的MQ驱动。

但是JMS只适用于Java语言,它是跟语言绑定的,没有从根本上解决这个问题(只有一个API)

2006年的时候,Rabbit技术公司基于AMQP开发了RabbitMQ1.0.为什么要用Erlang语言呢?因为Erlang是作者Matthias擅长的开发语言。第二个就是Erlang是为电话交换机编写的语言,天生适合

分布式和高并发。

什么是MQ

消息队列,又叫做消息中间件,是指用高效可靠的消息传递机制进行与平台无关的数据交流,并基于数据通信来进行分布式系统的集成。通过提供消息传递和消息队列模型,可以在分布式环境下扩展进程的通信(维基百科)

特点:

1,是一个独立运行的服务,生产者发送消息,消费者接收消息,需要先跟服务器建立连接。

2,采用队列作为数据结构,有先进先出的特点。

3,具有发布订阅的模型,消费者可以获取自己需要的信息

> rabbitMq总结 > image2021-1-6 17:58:36.png

为什么要使用MQ

解耦

异步

削峰

同步通信:发出一个调用请求后,在没有得到结果之前,就不返回。由调用者主动等待这个调用结果。

异步通信:调用在发出之后,这个调用就直接返回了,所以没有返回结果。也就是说,当一个异步过程调用发出之后,调用者不会马上得到结果,而是在调用发出后,被调用者通过状态

通知调用者,或通过回调函数处理这个调用。

耦合:系统内部或者外部之间存在相互作用,相互影响和相互依赖。

削峰:有一个瞬间流量达到峰值的情况,比如首销。普通的硬件服务器支撑不了这种高平发。如果通过堆硬件的方式去解决,那么在流量峰值过去以后

就会出现巨大的资源浪费。如果限流的话,会导致用户的丢失,没有达到我们的目的。

引用队列的话,队列特性,先进先出(FIFO)

这样就可以先把所有的流量承接下来,换成MQ消息发送到消息队列服务器上,业务层就可以根据自己的消费速率去处理这个消息,处理之后在返回结果。

主要应用的场景是:

1,对于数据量大或者处理耗时长的操作,我们可以引入MQ实现异步通信,减少客户端等待,提升响应速度。

2,对于改动影响大的系统之间,可以引入MQ实现解耦,减少系统之间的直接依赖。

3,对于会出现瞬间的流量峰值的系统,我们可以引入MQ实现流量削峰,达到保护应用和数据库的目的。

使用消息队列带来的一些问题

系统的可用性降低:原来是两个节点的通信,现在还需要独立运行一个服务,如果MQ服务器或者通信网络出现问题,就会导致请求失败。

系统的复杂性提高:为什么说复杂?第一个就是你必须要理解相关的模型和概念,才能正确地配置和使用MQ。第二个,使用MQ发送消息必须要考虑消息丢失和消息重复的问题,

一旦消息没有被正确地消费,就会带来数据一致性的问题。

RabbitMQ简介

官网:https://www.rabbitmq.com/documentation.html

特性:

高可靠:rabbitMq提供了多种多样的特性让你在可靠性和性能之间做出平衡,包括持久化、发送应答、发布确认以及高可靠。

灵活路由:通过交换机(Exchange)实现消息的灵活路由。

支持多客户端:对主流开发语言(Python、java、Ruby、Php、C#、JavaScript、Go、Elixir、Objective-C、Swift等)都有客户端实现。

集群与扩展性:多个节点组成一个逻辑的服务器,支持负载。

权限管理:通过用户与虚拟机实现权限管理。

插件系统:支持各种丰富的插件扩展,同时也支持自定义插件。

与Spring集成:Spring对AMQP进行了封装。

AMQP协议

AMQP:高级消息队列协议,是一个工作于应用层的协议

杨立文 > rabbitMq总结 > image2021-1-8 18:31:48.png

除了RabbitMq之外,AMQP的实现还有OpenAMQ,ApacheQpid,Redhat Enterprise MRG、AMQP Infrastructure、QMQ、Zyre.

除了AMQP之外,RabbitMQ支持多种协议,STOMP、MQTT、HTTP and WebSockets

工作模型
杨立文 > rabbitMq总结 > image2021-1-8 18:35:7.png

RabbitMQ原理

1,Broker

我们要使用RabbitMQ来收发消息,必须要安装一个RabbitMQ的服务,可以安装在Windows上面也可以安装在Linux上面,默认端口是5672。这台Rabbit’MQ的服务器我们把它叫做Broker,中文翻译是代理/中介。MQ

帮我们做的事情就是存储、转发消息。

2,connection

无论是生产者发送消息,还是消费者接收消息,都必须要跟Broker之间建立一个连接,这个连接是TCP长连接。

3,Channel

如果所有的生产者发送消息和消费者接收消息,都直接创建和释放TCP长连接的话,对于Broker来说肯定会造成很大的性能损耗,因为TCP是非常宝贵的资源,创建和释放也要消耗时间。

在AMQP里面引入了Channel的概念,它是一个虚拟的连接。我们把它翻译成通道,或者消息通道。这样我们就可以保持在TCP长连接里面去创建和释放Channel,大大减少资源消耗。另外需要注意的是。

Channel是RabbitMQ原生API里面的最重要的编程接口,也就是说我们定义交换机、队列、绑定关系、发送消息,调用的都是Channel接口上的方法。

https://stackoverflow.com/questions/18418936/rabbitmq-and-relationship-between-channel-and-connection

4,Queue

现在我们已经连接到Broker了,可以收发消息了。在其他一些MQ里面,比如ActiveMQ和Kafka,我们的消息都是发送到队列上的。

队列是真正用来存储消息的,是一个独立运行的进程,有自己的数据库(Mnesia)。

消费者获取消息有两种模式,一种是Push模式,只要生产者发送到服务器,就马上推送给消费者。另一种是Pull模式,消息存放在服务端,只要消费者主动获取才能拿到消息。

消费者需要写一个while循环不断的从队列获取消息吗?不需要,我们可以基于事件机制,实现消费者对队列的监听。

由于队列有FIFO的特性,只有确定前一条消息被消费者接收之后,才会把这条消息从数据库删除,继续投递下一条消息。

5,Exchange

在RabbitMQ里面永远不会出现消息直接发送到队列的情况。因为在AMQP里面引入了交换机(Exchange)的概念,用来实现消息的灵活路由。

交换机是一个绑定列表,用来查找匹配的绑定关系。

队列使用绑定键(Binding Key),交换机收到消息时会根据它保存的绑定列表,决定将消息路由到哪些与它绑定的队列上。

注意:交换机与队列、队列与消费者都是多对多的关系。

6,Vhost

我们每个需要实现基于RabbitMQ的异步通信的系统,都需要在服务器上创建自己要用到的交换机、队列和他们的绑定关系。如果某个业务系统不想和别人混用一个系统。

怎么办?再采购一台硬件服务器单独安装一个RabbitMQ服务?这种方式成本太高了。在同一个硬件服务器上安装多个RabbitMQ的服务呢?

Vhost除了可以提高

路由方式

直连Direct

队列与直连类型的交换机绑定,需要指定一个精确的绑定键。

生产者发送消息时会携带一个路由键。只有当路由键与其中的某个绑定键完全匹配时,这条消息才会从交换机路由到满足路由关系队列上

杨立文 > rabbitMq总结 > image2021-1-19 18:16:19.png

eg: channel.basicPublish(“MY_DIRECT_EXCHANGE”,“spring”,“msgs”) ;只有第一个spring_queue队列才能收到消息

主题Topic

队列与主题类型的交换机绑定时,可以在绑定键中使用通配符,两个通配符:

#0 个或者多个单词

*不多不少于一个单词

杨立文 > rabbitMq总结 > image2021-1-19 18:18:14.png

解读:

第一个队列支持路由键以spirng开头的消息路由,后面可以有单词,也可以没有。

第二个队列支持路由键以dubbo开头,并且后面是一个单词的消息路由。

第三个队列支持路由键以mysql结尾,并且前面是一个单词的消息路由

eg:

channel.basicPublish(“MY_TOPIC_EXCHANGE”,“spring.fgh.dkd”,“MSG1”) ; 只有第一个队列能接收到消息。

channel.basicPublish(“MY_TOPIC_EXCHANGE”,“spring.mysql”,“MSG1”) ; 第一个和第三个队列能接收到消息。

广播Fanout

主题类型的交换机与队列绑定时,不需要指定绑定键。因此生产者发送消息到广播类型的交换机上,也不需要携带路由键。消息达到交换机时,所有与之绑定了队列,

都会收到相同的消息副本

杨立文 > rabbitMq总结 > image2021-1-19 18:26:54.png

eg:channel.basicPublish(“MY_FANOUT_EXCHANGE”,"',“msg4”);三个队列都会收到消息

RabbitMq本地安装

https://www.cnblogs.com/saryli/p/9729591.html

MQClient的选用
Rabbit Client 与springAMQP的去呗

消息的过期时间设置
1,通过队列属性设置消息过期时间

所有队列中的消息超过时间未被消费时,都会过期

2,设置单条信息的过期时间

在发送消息的时候指定消息属性。

如果同时指定了Message TTL和Queue TTL,则小的那个时间生效。

死信队列

Rabbit Dead Letter Quque使用

消息在某些情况下会变成死信(Dead Letter)

队列在创建的时候可以指定一个死信交换机DLX(Dead Letter Exchange),死信交换机绑定的队列被称为死信队列DLQ(Dead Letter Queue),ELX实际上也是普通的交换机,

DLQ也是普通的队列(例如替补球员也是普通球员)

杨立文 > rabbitMq总结 > image2021-1-21 19:50:45.png

什么情况下会变成死信?

1)消息被消费者拒绝并且未设置重回队列:(NACK||Reject)&&requeue==false

2)消息过期

3)队列达到最大长度,超过了Max Length(消息数)或者Max Length bytes(字节数),最先入队的消息会被发送到DLX。

死信队列如何使用?

1,声明原交换机(oneplus_exchange_evan)、原队列(evam_mq_test)相互绑定,

队列中的消息10秒钟过期,获取消费者拒绝消费,会变成死信。指定原队列的死信交换机(oneplus_exchange_evan_dead)

2,声明死信交换机(oneplus_exchange_evan_dead),死信队列(evam_mq_test_dead),相互绑定

3,最终消费者 监听死信队列

4,生产者发送消息

杨立文 > rabbitMq总结 > image2021-1-21 20:53:18.png

—消息队列延迟

服务端流控

当RabbitMQ生产MQ消息的速度远大于消费消息的速度时,会产生大量的消息堆积,占用系统资源,导致机器新能下降。我们想要控制服务端接收消息的数量,应该怎么做呢?

1,队列有两个控制长度的属性:

x-max-length:队列中最大存储最大消息数,超过这个数量,对头的消息会被丢弃。

x-max-length-bytes:队列中存储的最大消息容量(单位bytes),超过这个容量,对头的消息会被丢弃。

杨立文 > rabbitMq总结 > image2021-1-21 20:59:30.png

注意:设置队列长度只在消息堆积的情况下有意义,而且会删除先入队的消息,不能真正实现服务端限流。

内存控制

RabbitMQ会在启动时检测机器的物理内存数值。默认当MQ占用40%以上内存时,MQ会主动抛出一个内存告警并阻塞所有连接(Connections)。可以通过修改rabbitmq.config文件

来调整内存阈值,默认值是0.4,如下所示。

不知道rabbitconfig.config的位置可以在这里找到。

杨立文 > rabbitMq总结 > image2021-1-22 10:51:33.png

杨立文 > rabbitMq总结 > image2021-1-22 10:54:30.png

也可以用命令动态设置,如果设置成0,则所有的消息都不能发布。

磁盘控制

另一种方式是通过磁盘来控制消息的发布。当磁盘空间低于指定的值时(默认是50M),触发流控措施。

disk_free_limit.relative = 3.0
disk_free_limit.absolute = 2 GB
https://www.rabbitmq.com/configure.html

杨立文 > rabbitMq总结 > image2021-1-22 10:58:44.png

消费端限流

默认情况下,如果不进行配置,RabbitMQ会尽可能快速地把队列中的消息发送到消费者。因为消费者会在本地缓存消息,如果消息数量过多,可能会导致OOM或者影响其他进程的正常运行。

在消费者处理消息的能力有限,例如消费者数量太少,或者单条消息的处理时间过长的情况下,如果我们希望在一定数量的消息消费完之前,不再推送消息过来,就要用到消费端的流控限制措施。

可以基于Consumer或者channel设置prefetch count的值,含义为Consumer端最大的unacked messages数目。当超过这个数值未被确认,rabbitMQ会停止投递新的消息给改消费者。

杨立文 > rabbitMq总结 > image2021-1-22 11:12:26.png

eg:

channel的prefetch count设置为5。当消费者有5条消息没有给Broker发送ACK后,RabbitMQ不再给这个消费者投递消息

RabbitMQ可靠性投递

要是先操作数据库在发送消息,避免因为数据库回滚导致数据不一致。但是如果先操作数据,后发送消息,发送消息出来问题,那不是一样会出现业务数据的不一致?

实现异步通信的时候,消息丢了怎么办,消息重复消费怎么办*?

RabbitMQ的工作模型

杨立文 > rabbitMq总结 > image2021-1-22 11:41:45.png

使用RabbitMQ收发消息的时候,几个主要环节

1,代表信息从生产者发送到Broker

2,代表消息从Exchange路由到Queue

Exchange是一个绑定列表,如果消息没有办法路由到正确的队列

3,代表消息在Queue中存储

队列是一个独立运行的服务,有自己的数据库(Mnesia),它是真正用来存储消息的。如果还没有消费者来消费,那么消息要一直存储在队列里面,如果队列处理问题,消息肯肯定会丢失,怎么保证消息在队列稳定的存储呢?

4,代表消费者订阅Queue并消费消息。

队列的特性是FIFO。队列里面的消息是一条一条的投递的,也就是说,只有一条消息被消费者接收以后,才能把这条消息从数据库删除,继续投递下一条消息。

Broker怎么知道消费者已经接收了消息呢?

消息发送到RabbitMQ服务器

第一个环节是生产者发送消息到Broker,可能因为网络或者Broker的问题导致消息发送失败,生产者不能确定Broker有没有正确的接收。

在RabbitMQ里面提供了两种机制 服务确认机制,也就是生产者发送消息给RabbitMQ的服务端的时候,服务端会通过某种方式返回一个应答,只要生产者收到了这个应答,就知道消息成功了。

Transaction(事务)模式

我们通过一个channel.txSelect()的方法把信道设置成事务模式,然后就可以发布消息给RabbitMQ了,如果channel.txCommit();的方法调用成功,就说明事务提交成功,则消息一定

到达了RabbitMQ中。

如果在事务提交执行之前由于RabbitMQ异常崩溃或者其他原因抛出异常,这个时候我们便可以将其捕获,进而执行channel.txRollback()方法来实现事务回滚。

杨立文 > rabbitMq总结 > image2021-1-22 15:5:38.png

事务模式中,只有收到了服务端的Commit-OK的指令,才能提交成功。所以可以解决生产者和服务端确认的问题。

事务模式是阻塞的,一条消息没有发送完毕,不能发送下一条消息,它会榨干RabbitMQ服务器的性能。

Confirm(确认)模式

普通确认模式:

channel.waitForConfirms()方法将信道设置为Confirm模式,然后发送消息。一旦消息被投递到所有匹配的队列之后,RabbitMQ就会发送一个确认(Basic.Ack)给生产者,也就是

调用channel.waitForConfirms()返回true,这样生产者就知道消息被服务端接收了。

缺点:发送一条确认一条的方式比较

批量确认模式:

开启Confirm模式后,先发送一批消息,只要channel.waitForConfirmsOrDie();方法没有抛出异常,就代表消息都被服务端接收了。比单条会效率高一点

缺点:批量一次发送多少条消息呢?数量少的话,效率提升不上去,数量多的话,比如1000条消息。如果最后一条消息被拒接了,那么前面所有消息都要重发。

异步确认模式:

添加一个ConfirmListener,并且用一个SortedSet来维护没有被确认的消息。

confirm模式是在channle上开启的,因为RabbitTemplate对channel进行了封装,叫做confirmCallback

杨立文 > rabbitMq总结 > image2021-1-22 16:58:6.png

消息从交换机路由到队列

路由键错误,或者队列不存在时,可能消息就无法路由。

处理无法路由的消息:

一种是让服务端重发给生产者,一种是让交换机路由到另一个备份的交换机。

消息回发的方式:使用mandatory参数和ReturnListener(在Spring AMQP中是ReturnCallback).

消息在队列存储

第三个环节是消息在队列存储,如果没有消费者的话,队列一直存在数据库中。

如果RabbitMQ的服务或者硬件发生故障,比如系统宕机、重启、关闭等等,可能会导致内存中的消息丢失,所以我们要把消息本身和元数据(队列、交换机、绑定)都保存到磁盘。

队列、交换机、消息持久化。

集群:如果只有一个RabbitMQ的节点,即使交换机、队列、消息做了持久化,如果服务崩溃或者硬件发生故障,rabbitMQ的服务一样是不可用的。

消息投递到消费者

如果消费者收到消息后没来得及处理了就发生了异常,或者处理过程中发生异常。

RabbitMQ提供了消费者的消息确认机制(message acknowledgement),消费者可以自动或者手动发送ACK服务端。

没有收到ACK的消息,消费者断开连接后,RabbitMQ会把这条消息发送给其他消费者。如果没有其他消费者,消费者重启会重新消费这条消息,重复执行业务逻辑。

NONE:自动ACK

MANUAL:手动ACK

AUTO:如果方法未抛出异常,则发送ack。

生产环境不能创建队列和交换机

QueueDeclare
ExchangeDeclare

Rabbit Client 如何使用

集群

普通集群

rabbitMQ是基于Erlang编写,具有天生的分布式特性。天然支持Clustering。

不用通过Zookeeper分别来实现HA高可用

杨立文 > rabbitMq总结 > image2021-1-28 16:47:34.png

RabbitMQ集群元数据同步

RabbitMQ:集群会始终同步四种类型的内部元数据

队列元数据:队列名称、属性

交换器元数据:交换器名称、类型和属性

绑定元数据:交换器与队列绑定绑定关系、eg:bingding_key

Vhost元数据:虚拟主机内部配置和属性

队列只同步元数据信息,不会同步存储的信息,消息只会存在于创建改队列的节点上,其他节点只知道这个队列的元数据信息和一个指向队列的owner node地址。

为何RabbitMQ集群仅采用元数据同步的方式

第一:存储空间,如果每个集群节点都拥有所有Queue的完全数据拷贝,那么每个节点的存储空间会非常大,集群的消息积压会非常弱

(无法通过集群节点的扩容提高消息的积压能力)

第二:性能 消息的发布者需要将消息复制到每个集群节点,对应持久化消息,网路和磁盘同步复制的开销都会非常明显

基本原理

eg1:客户端直接连接队列所在的节点

如果有一个消息生产者或者消息消费者通过客户端连接至节点1进行消息的发布/订阅,那么此时集群的消息收发之和节点一有关。

eg2:客户端连接的是非队列数据所在的节点

如果消息生产者所连接的节点2或者节点3,此时队列1的完整数据不在该两个节点上,那么发消息的过程中,这两个几点起了一个路由转发作用、

根据两个节点上的元数据。

由于节点之间存在路由转发的情况,所以对于RabbitMQ集群最好是在一个局域网。

缺点
不能保证队列的高可用性,因为队列内容不会复制,如果节点失效将导致队列不可用

镜像集群

镜像队列模式下,消息内容会在镜像节点间同步,可用性更高

内存节点1、内存节点2

磁盘节点3

1,两个内存节点,一个磁盘节点。所有的节点之间通过镜像队列的方式同步数据。内存节点用来给应用访问,磁盘节点用来持久化数据。

2,HAproxy用来实现对两个内存节点的负载,

3,安装两个keepalived,一主一备。

杨立文 > rabbitMq总结 > image2021-1-28 17:16:51.png

Rabbit MQ如何搭建集群

RabbitMQ是一个开源的消息中间件,它实现了高级消息队列协议(AMQP)并提供可靠的消息传递机制。下面是RabbitMQ入门到精通的介绍: 1. RabbitMQ基础概念: - 消息:在RabbitMQ中,消息是指要传递的数据单元。 - 队列:消息在RabbitMQ中存储的地方,类似于一个邮箱。 - 交换机:接收生产者发送的消息,并将消息路由到一个或多个队列。 - 绑定:用于将交换机和队列关联起来,定义了消息如何从交换机路由到队列。 - 路由键:用于交换机将消息路由到队列的规则。 2. RabbitMQ工作模式: - 简单模式:一个生产者向一个队列发送消息,一个消费者从该队列接收消息。 - 工作队列模式:一个生产者向一个队列发送消息,多个消费者从该队列接收消息。 - 发布/订阅模式:一个生产者发送消息到交换机,多个消费者绑定到该交换机并接收消息。 - 路由模式:一个生产者发送消息到交换机,并指定路由键,多个消费者根据不同的路由键接收消息。 - 主题模式:一个生产者发送消息到交换机,并指定主题,多个消费者根据不同的主题接收消息。 3. RabbitMQ高级特性: - 消息确认机制:生产者发送消息后,可以等待RabbitMQ的确认,确保消息已经被正确接收。 - 消息持久化:将消息存储到磁盘上,即使RabbitMQ服务器重启,消息也不会丢失。 - 消息优先级:可以为消息设置优先级,确保重要的消息被优先处理。 - 死信队列:当消息无法被消费时,可以将其发送到死信队列进行处理。 - 集群和高可用性:通过搭建RabbitMQ集群实现高可用性和负载均衡。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值