RabbitMQ相关资料

一、RabbitMQ是什么?

RabbitMQ是一个开源的AMQP的实现,服务器端用Erlang语言编写,支持多种客户端,如:Python、Java、PHP等。用于在分布式系统中存储转发消息,在易用性、可扩展性、高可用性等方面表现不俗。

AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息中间件设计,基于此协议的客户端与消息中间件可传递消息,不受产品、开发语言等条件的限制。

消息中间件是一种由消息传送机制或消息队列模式组成的中间件技术,利用高效可靠的消息传递机制进行与平台无关的数据交流,并基于数据通信来进行分布式系统的集成。消息中间件主要用于组件之间的解耦,像邮件那样为离线消费者存储消息,同邮件相比,AMQP隐藏了消息的发送方和接收方,消息的发送者无需知道消息使用者的存在,反之亦然。消息中间件从发布者(publisher)那里接收消息(发布消息的应用,也称为producer),然后将他们转发给消息订阅者(subscriber)(处理消息的应用,也称为consumer)。

由于AMQP是一个网络协议,所以发布者、订阅者以及消息中间件可以部署到不同的物理机器上面。

二、为啥要使用消息队列?

消息队列是一种应用间的异步协作机制,为了提升系统服务的性能,我们可以将一些不需要立即生效的操作拆分出来进行异步执行。

比如,在下单的主流程(扣减库存、生成单据)完成之后发送一条消息到RabbitMQ让主流程快速完结,而由另外的单独线程拉取RabbitMQ的消息(或者向RabbitMQ推送消息),当发现MQ中有发短信、发邮件等之类的消息时,执行相应的业务逻辑。另外一方面会减少系统等待时长,给用户带来更好的体验。

再如,订单的排队结算,也用到了消息队列机制,把需要结算的订单放入通道里面一个一个排队结算处理,而不是某个时间点突然涌入大批量的新增、查询操作把数据库给搞宕机,所以RabbitMQ本质上起到的作用是削峰填谷,为业务保驾护航。

总结:RabbitMQ实现的最重要的三个作用:
(1)异步:数据量大、处理耗时操作,用RabbitMQ异步的方式去实现,减少客户端的等待,提升响应的速度。
(2)解耦:对于改动比较大的系统之间,引入RabbitMQ减少系统之间直接的依赖,达到解耦的目的。
(3)削峰:对于会瞬间出现流量峰值的系统,引入RabbitMQ可以实现流量的削峰,达到保护业务系统和数据库目的。

三、市面上有很多MQ可以选择,比如ActiveMQ、RocketMQ等,为什么要选择RabbitMQ?

RabbitMQ是唯一一个由Erlang语言开发并实现了AMQP标准的消息服务器,其特性有:
1)可靠性,RabbitMQ的持久化支持,保证了消息的稳定性。RabbitMQ使用一些机制来保证可靠性,如持久化、传输确认、发布确认。
2)灵活的路由,在消息进入队列之前,通过 exchange 来路由消息。
3)消息集群,多个RabbitMQ服务器可以组成一个集群,形成一个逻辑Broker。
4)高并发、高可用,RabbitMQ使用Erlang开发语言,Erlang天生自带高并发光环和高可用特性。队列可以在集群中的机器上进行镜像,使得在部分节点出问题的情况下队列仍然可用。
5)集群部署简单,正是因为Erlang使得RabbitMQ集群部署变的超级简单。
6)多语言客户端,RabbitMQ几乎支持所有常用语言(java、python、php、c++)。
7)多协议支持,RabbitMQ支持多种消息队列协议,如 AMQP、STOMP、MQTT 等。
8)管理界面,RabbitMQ提供了一个可视化用户管理界面,用户可以轻松监控和管理消息Broker的许多方面。
9)跟踪机制,RabbitMQ提供了消息跟踪机制,如果消息异常使用者可以找出发生了什么。
10)插件机制,RabbitMQ提供了许多插件,来从多方面进行扩展。

四、工作机制

(1)生产者、消费者和代理
生产者:消息的创建者,负责创建和推送数据到消息服务器;
消费者:消息的接收方,用于处理数据和确认消息;
代理:就是RabbitMQ本身,用于扮演“快递”的角色。

(2)消息发送原理
首先必须连接到RabbitMQ才能发布或消费消息,那怎么连接和发送消息的呢?
应用程序和RabbitMQ服务器之间会创建一条TCP连接,一旦TCP打开,并通过了认证(连接RabbitMQ之前发送的RabbitMQ服务器连接信息:用户名和密码),一旦认证通过,你的应用程序和RabbitMQ就创建了一条AMQP信道。

信道:信道即传输信号的通道,信道是生产、消费者与rabbit通信的渠道,生产者publish或消费者subscribe一个队列都是通过信道来通信的。

信道是创建在“真实”TCP上的虚拟连接,RabbitMQ在一条TCP上建立成百上千个信道来达到多个线程处理,这个TCP被多个线程共享,每个线程对应一个信道,每个信道在rabbit上都有一个唯一的ID ,保证了信道私有性,对应上唯一的线程使用。

AMQP命令都是通过信道发送出去的。每一条信道都会被指派一个唯一ID(AMQP库会帮我们记住这个ID)。无论是发布消息、订阅队列、接收消息,这些动作都是通过信道完成的。

问题:为什么不直接通过TCP连接发送AMQP命令呢?

对于操作系统来说创建和销毁TCP会话是非常昂贵的开销,假设高峰期每秒有成千上万条连接,系统为每个线程开辟一个TCP是非常消耗性能的。而且操作系统每秒能创建的TCP也是有限的,因此很快就会遇到系统瓶颈。

RabbitMQ为保证性能,如果所有请求都使用一条TCP连接,既满足了性能的需要,又能确保每个连接的私密性,这就是引入信道概念的原因。
在这里插入图片描述

类似概念:TCP是电缆,AMQP信道就是里面的光纤束,每个光纤都是独立的,互不影响。

(3)必须知道的RabbitMQ专有名词
ConnectionFactory(连接管理器):应用程序与RabbitMQ之间建立连接的管理器,程序代码中使用;
Channel(信道):消息推送使用的通道;
Exchange(交换器):用于接受、分配消息;
Queue(队列):用于存储生产者的消息;
RoutingKey(路由键):用于把生产者的发送的数据分配到交换器上;
BindingKey(绑定键):用于把交换器的消息绑定到队列上;

(4)消息持久化
Rabbit队列在默认情况下重启服务器会导致消息丢失,为了保证Rabbit在重启的时候数据不丢失,需要做消息持久化。
当把消息发送到Rabbit服务器的时候,我们需要选择是否要进行持久化,想要Rabbit消息能恢复必须满足3个条件:

a.投递消息的时候durable设置为true,消息持久化,代码:channel.queueDeclare(x, true, false, false, null),参数2设置为true持久化;
b.设置投递模式deliveryMode设置为2(持久),代码:channel.basicPublish(x, x, MessageProperties.PERSISTENT_TEXT_PLAIN,x),参数3设置为存储纯文本到磁盘;
c.消息已经到达持久化交换器;
d.消息已经到达持久化的队列;
持久化工作原理:
Rabbit会将你的持久化消息写入磁盘上的持久化日志文件,等消息被消费之后,Rabbit会把这条消息标识为等待垃圾回收。
持久化的缺点:
消息持久化的优点显而易见,但缺点也很明显,那就是性能,因为写入硬盘要比写入内存性能低很多,从而降低了服务器的吞吐量,
尽管使用SSD硬盘可以使事情得到缓解,但他仍然吸干了Rabbit的性能,当消息成千上万条要写入磁盘的时候,性能是很低的。
所以我们要求使用者根据自己的情况,选择适合自己的方式。

(5)虚拟主机
每个Rabbit都能创建很多vhost,我们称之为虚拟主机,每个虚拟主机其实都是mini版的RabbitMQ,拥有自己的队列,交换器和绑定,拥有自己的权限机制。
vhost特性
RabbitMQ默认的vhost是“/”开箱即用;
多个vhost是隔离的,多个vhost无法通讯,并且不用担心命名冲突(队列、交换器和绑定),实现了多层分离;
创建用户的时候必须指定vhost;
vhost操作:
可以通过rabbitmqctl工具命令创建:rabbitmqctl add_vhost [vhost_name]
删除vhost:rabbitmqctl delete_vhost [vhost_name]
查看所有的vhost:rabbitmqctl list_vhosts

(6)源码地址
git clone https://github.com/rabbitmq/rabbitmq-erlang-client.git
git clone https://github.com/rabbitmq/rabbitmq-server.git

五、消息是如何到达队列的呢?

RabbitMQ代理服务器在应用程序之间扮演着路由的角色,所以当程序链接到RabbitMQ时它就必须做个决定:我是在发送还是在接收?或者从AMQP的角度思考:我是一个生产者还是一个消费者?

生产者创建的消息(包含两部分,有效载荷+标签),然后发布到代理服务器RabbitMQ上。代理服务器RabbitMQ接收到消息后,RabbitMQ会根据标签把消息发送给感兴趣的接收方。

有效载荷:传输的具体数据,如文本、字符串、对象。

标签:描述了有效载荷,并且RabbitMQ用它来决定谁将获得消息的拷贝。队列名就是标签的一部分。

那么,消息是如何到达队列的呢?通过AMQP的交换器和绑定!

AMQP元素:交换器、队列、绑定。

当我们想要把消息投递到队列时,我们需要先把消息发送给交换器,然后根据特定的规则(路由键),RabbitMQ将会决定消息该投递到哪个队列。队列是通过路由键绑定到交换器的。

常用的交换器类型:direct、fanout、topic。每一种交换器都实现了不同的路由算法。

(1)direct交换器:如果路由键匹配,消息就被投递到对应的队列。服务器必须实现direct类型交换器,包含一个空白字符串名称的默认交换器。

当声明一个队列时,如果不给它指定交换器,它会自动绑定到默认交换器,并以队列的名称作为路由键。
在这里插入图片描述
在这里插入图片描述

(2)fanout交换器:会将收到的消息广播到绑定的队列上。消息通信的模式是,当你发送一条消息到fanout交换器时,它会把消息投递给所有附加在此交换器上的队列,所以这种方式允许你对单条消息做不同方式的反应。

比如有场景:假设应用程序的第一个需求是在图片上传到网站后,清除用户相册缓存。这个需求可以只通过一个队列就能轻易完成,但是当产品需要让你实现一个新功能:在上传完之后给用户一点奖励,你该怎么办?如果你是直接将消息发送给队列的话,就不得不修改发送方代码,以便将消息也发送给新的用户积分队列。而如果你是用的是fanout交换器的话,那你唯一需要做的就是为新的消费者新写一段代码,然后声明新的队列并将其绑定到fanout交换器上。发送方代码和消费方代码完全解耦,我们可以轻而易举的添加应用程序的功能。
在这里插入图片描述
在这里插入图片描述

(3)topic交换器:可以使来自不同源头的消息能够到达同一个队列。

举例:你拥有多个不同级别的日志,如info、error、warning,与此同时你的应用程序分为以下几个模块:user-profile、image-gallery、msg-inbox等。
在这里插入图片描述
在这里插入图片描述

例如,

channel.basicPublish("MY_TOPIC_EXCHANGE","junior.jvm","my message");

这条发送代码会把消息发送到junior_queue和jvm_queue两个队列上。

总结:
1、AMQP架构中最关键的几个组件:交换器、队列和绑定。
2、根据绑定规则(路由键)将队列绑定到交换器上。
3、有三种类型的交换器:direct、fanout和topic。
4、基于消息的路由键规则和交换器类型,RabbitMQ服务器会决定将消息投递到哪个队列去。消息不能直接发送到RabbitMQ队列的Mnesia数据库,消息是根据路由规则发布到对应交换器上,然后RabbitMQ服务器根据绑定规则和交换器类型决定将消息转发到哪个队列。

六、RabbitMQ工作模型

在这里插入图片描述

七、多租户模式:虚拟主机和隔离

每个RabbitMQ服务器都能创建虚拟消息服务器,我们称之为虚拟主机vhost。每一个vhost本质上是一个mini版的RabbitMQ服务器,拥有自己的队列、交换器和绑定,当然也有自己的权限机制。

vhost之于Rabbit就想虚拟机之于物理服务器一样:它们通过在各个实例间提供逻辑上的分离,允许你为不同的应用程序安全保密的运行数据。

八、当RabbitMQ服务器崩溃或者重启时,如何确保关键消息不丢?

Rabbit里创建队列和交换器有个不可告人的秘密:默认情况下,它们无法幸免于服务器重启。原因在于每个队列和交换器的durable属性,该属性默认false,它决定了RabbitMQ是否需要在崩溃或重启之后重新创建队列|交换器。

将它设置为true,这样我们就不需要在服务器断电后重建创建队列和交换器了。

注意:队列和交换器当然必须设置成true,但,光这样还不够!

能从AMQP服务器崩溃中恢复的消息,我们称之为持久化消息。在消息发布前,把投递模式(delivery mode)设置为2来把消息标记成持久化,注意这只是说明消息被标识为持久化的,但是它还必须发布到持久化的交换器中并到达持久化的队列中才行。如果不是这样的话,则包含持久化消息的队列|交换器会在RabbitMQ崩溃重启后不复存在,从而导致消息成功孤儿。

所以,如果消息想要从Rabbit崩溃中恢复,那么必须:

1)把消息的投递模式设置为2(持久)
2)发送到持久化的交换器
3)到达持久化的队列

RabbitMQ确保持久性消息能从服务器重启中恢复的方式:将它们写入磁盘上的一个持久化日志文件。当发布一条持久性消息到持久交换器上时,rabbit会在消息提交到日志文件后才发送响应,之后这条消息如果路由到了非持久化队列的话,它会自动从持久性日志中移除,并且无法从服务区重启中恢复。

一旦你从持久化队列中消费了一条持久性消息的话(并且确认了它),RabbitMQ会在持久化日志中把这条消息标记为等待垃圾收集状态。在你消费持久化消息前,如果RabbitMQ重启的话,服务器会自动重建交换器和队列以及它们的绑定关系,重播持久性日志文件中的消息到合适的队列或交换器上(取决于RabbitMQ服务器宕机的时候,消息处在路由过程的哪个环节)。

当然持久化也会随之带来一些性能问题:写入磁盘比写入内存慢的不止一点点,而且会极大减少RabbitMQ服务器每秒可处理的消息总数。导致消息吞吐量明显降低。

九、RabbitMQ配置文件

RabbitMQ允许你设置系统范围的可调参数并通过配置文件进行设置。该配置文件位于:/etc/rabbitmq/rabbitmq.config

其中配置项:
dump_log_write_threshold,1000 含义:将仅限追加的日志内存刷出|转储至真实数据库文件的频度。它明确指定了在转储操作发生前,必须有多少个条目存储在日志中。设置更高的数值将减少I/O负载并增加持久化消息的性能。

vm_memory_high_watermark,十进制百分数,控制RabbitMQ允许消耗的内存与安装内存的百分比,0.4=40%

十、理解RabbitMQ的日志

1)检查日志,需要查看LOG_BASE环境变量设置,默认值在 rabbitmq-server脚本:

LOG_BASE=/var/log/rabbitmq

2)当RabbitMQ记录Erlang相关信息时,它会将日志写入 rabbit-sasl.log文件, 所以我们可以在这个文件中找到Erlang的崩溃报告,有助于调试无法启动的RabbitMQ节点。

日志路径:/usr/local/rabbitmq_server-3.5.6/var/log/rabbitmq

3)切换|轮换日志:./rabbitmqctl xxx.log .1

十一、RabbitMQ集群

RabbitMQ内建集群,从第一台rabbit启动开始,然后在运行时添加更多的Rabbit以增加高可用性或提升性能,而完全不用停机!

RabbitMQ内建集群的设计用于完成两个目标:允许消费者和生产者在Rabbit节点崩溃的情况下继续运行,以及通过添加更多的节点来线性扩展消息通信吞吐量。

RabbitMQ通过利用Erlang提供的开放电信平台(Open Telecom Platform,OTP)分布式通信框架来巧妙的满足上面两个需求。

我们可以失去一个RabbitMQ节点,同时客户端能够重新连接到集群中的任何其他节点并继续生产或消费消息,就像什么事都没发生过一样。同样地,如果Rabbit集群正疲于应对庞大的消息通信量的话,那么添加更多的节点会线性地增加更多性能。但是,RabbitMQ集群不能保证消息的万无一失。

十二、设置队列的消息过期时间

方法一:创建队列的时候,在管理平台指定队列的整体失效时间:
在这里插入图片描述

方法二:在代码里面,为单条消息指定失效时间:

MessageProperties mp = new MessageProperties();
mp.setExpiration("5000");// 消息的过期时间属性,单位ms
Message ms = new Message("这条消息5s后过期".getBytes(),mp);
// exchange,TEST_TTL_EXCHANGE
// routingkey,ttl
rabbitTemplate.send("TEST_TTL_EXCHANGE","ttl",ms);

注意:如果我们同时设置了队列中的消息的过期时间 和 消息本身的过期时间,那么哪一个会生效呢?

答案:哪个小哪个优先生效!

十三、死信交换器 — 死信队列

死信交换器(dead-letter-exchange),简称DLX
死信队列(dead-letter-queue),简称DLQ

什么情况下消息会变成死信呢?

1)消息被消费者拒绝,当消费者端手动确认模式下拒绝了某条消息,并且设置了requeue=false之后,这条消息并不会被放回原队列,而是变为死信。

2)消息过期,队列中的消息到达了过期时间还没有被消费,就会变成死信,它就会通过死信交换器(DLX)跑到死信队列(DLQ)里面来。

3)队列达到了最大长度,超过某个队列的最大存储消息个数之后的,其他被exchange分发到该队列的消息直接成为死信队列。

消息从原始队列到死信队列的流转过程:

step1、创建一个死信交换器
在这里插入图片描述

step2、创建一个死信队列
[技术中心 > RabbitMQ介绍 > image2021-10-25_14-45-58.png]

step3、把死信队列绑定到死信交换器上
在这里插入图片描述

step4、创建一个原始队列,为并其设置满足死信相关的属性参数
在这里插入图片描述

step5、测试。
在这里插入图片描述
在这里插入图片描述

可知,原始队列消息过期后,都进入了死信交换器-→死信队列
在这里插入图片描述

消息转死信的过程:
在这里插入图片描述

十四、服务端流控措施

RabbitMQ创建队列的时候有x-max-length和x-max-length-bytes参数,但是这样并不能很好的做流控,因为这种方式达到条件后会把最先入队的消息删除,这是不公平的一种实现方式。

我们可以在服务端做限流控制:

1)内存控制

 rabbitmq.conf中的vm_memory_high_watermark,默认40%,超过40%会限制connection的创建

2)磁盘控制

相对 disk_free_limit.relative,30%
绝对 disk_free_limit.absolute = 2GB

十五、消费端限流措施

如果消费端处理消息的能力有限,或者消费者数量太少,单条消息处理时间过长的话,我们希望在一定数量的消息消费完之前不再推送消息过来,我们可以用RabbitMQ提供的消费端预取消息数量prefetch count的方式,比如设置的prefetch count是5的话,那么当消费者已经接收到了5条消息并且都没有给服务端应答的话,broker就不会再往这个队列的消费者上投递消息。

prefetch count详解:https://www.cnblogs.com/throwable/p/13834465.html

掌握了RabbitMQ基本使用之后,进一步学习如何搭建高可用的集群以及如何保证消息投递的可靠性

一、RabbitMQ消息可靠性投递
(1)备份交换机(注意:备份交换机是交换机的一个属性,而死信交换机的队列的属性,别混淆了)

二、集群
集群的目的:高可用,负载
如何支持集群:普通集群,镜像队列
集群节点类型:磁盘节点Disc(元数据如队列名、交换机、绑定关系,放在磁盘里),内存节点ram(元数据放内存里)

一个集群至少需要一个磁盘节点。
(1)普通集群:节点之间互相复制元数据,但不会复制队列中的数据内容
在这里插入图片描述
(2)镜像集群:在普通集群的基础上,创建镜像策略,如下,即可实现集群之间镜像的复制。
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值