分布式消息中间件 Rabbit Mq

RabbitMq

一 MQ的相关概念

1.什么是MQ

本质是个队列,MQ是一种非常常见的上下游“逻辑解耦+物理解耦”的消息通信服务。使用了MQ之后,消息发送上游只需要依赖MQ,不用依赖其他服务。

2.为什么要用MQ

1、异步处理

在注册服务的时候,如果同步串行化的方式处理,让存储数据、邮件通知等挨着完成,延迟较大
采用消息队列,可以将邮件服务分离开来,将邮件任务之间放入消息队列中,之间返回,减少了延迟,提高了用户体验
2、应用解耦

电商里面,在订单与库存系统的中间添加一个消息队列服务器,在用户下单后,订单系统将数据先进行持久化处理
然后将消息写入消息队列,直接返回订单创建成功,然后库存系统使用拉/推的方式,获取订单信息再进行库存操作
3、流量削锋

秒杀活动中,一般会因为流量过大,导致流量暴增,应用挂掉。为解决这个问题,一般需要在应用前端加入消息队列
服务器在接收到用户请求后,首先写入消息队列,这时如果消息队列中消息数量超过最大数量,则直接拒绝用户请求或返回跳转到错误页面
秒杀业务根据秒杀规则读取消息队列中的请求信息,进行后续处理

​ RabbitMQ是一个消息中间件:它接受并转发消息。RabbitMQ是一个消息中间件:它接受并转发消息。你可以把它当做一个快递站点, RabbitMQ与快递站的主要区别在于,它不处理快件而是接收,存储和转发消息数据。

3. 四大核心概念

生产者

产生数据发送消息的程序是生产者

交换机

交换机是RabbitMQ非常重要的一个部件,一方面它接收来自生产者的消息,另一方面它将消息推送到队列中。交换机必须确切知道如何处理它接收到的消息,是将这些消息推送到特定队列还是推送到多个队列,亦或者是把消息丢弃,这个得有交换机类型决定

队列

队列是RabbitMQ内部使用的一种数据结构, 但它们只能存储在队列中。队列仅受主机的内存和磁盘限制的约束,本质上是一个大的消息缓冲。

消费者

消费者大多时候是一个等待接收消息的程序。请注意生产者,消费者和消息中间件很多时候并不在同一机器上。同一个应用程序既可以是生产者又是可以是消费者。

4.RabbitMQ核心部分

在这里插入图片描述

5. 安装

​ 第一步 文件上传

​ 第二步 安装文件(分别按照以下顺序安装)

rpm -ivh erlang-21.3-1.el7.x86_64.rpm

yum install socat -y

rpm -ivh rabbitmq-server-3.8.8-1.el7.noarch.rpm

​ 常用命令

#添加开机启动RabbitMQ服务
	chkconfig rabbitmq-server on
#启动服务
	/sbin/service rabbitmq-server start 
#查看服务状态
	/sbin/service rabbitmq-server status
    停止服务(选择执行)
	/sbin/service rabbitmq-server stop   
#开启web管理插件
	rabbitmq-plugins enable rabbitmq_management
#用默认账号密码(guest)访问地址http://121.89.208.247:15672/出现权限问题

添加一个新的用户

#创建账号
	rabbitmqctl add_user admin 123
设置用户角色
	rabbitmqctl set_user_tags admin administrator
#设置用户权限
	rabbitmqctl set_permissions -p "/" admin ".*" ".*" ".*"
set_permissions [-p <vhostpath>] <user> <conf> <write> <read>(不用这个)
用户user_admin具有/vhost1这个virtual host中所有资源的配置、写、读权限
#当前用户和角色
	rabbitmqctl list_users


重置命令

#关闭应用的命令为
	rabbitmqctl stop_app
#清除的命令为
	rabbitmqctl reset
#重新启动命令为
	rabbitmqctl start_app

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EtB5EQKp-1626859724141)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1626099659233.png)]

二. Work Queues (工作队列)

​ 工作队列(又称任务队列)的主要思想是避免立即执行资源密集型任务,而不得不等待它完成。相反我们安排任务在之后执行。我们把任务封装为消息并将其发送到队列。在后台运行的工作进程将弹出任务并最终执行作业。当有多个工作线程时,这些工作线程将一起处理这些任务。

2. 1 轮训分发消息

在这个案例中我们会启动两个工作线程,一个消息发送线程,我们来看看他们两个工作线程是如何工作的。

步骤
 1.   抽取工具类

 2. 启动两个工作线程

 3.启动一个发送线程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FsfRP7mI-1626859724142)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1626524103621.png)]

2.2 消息应答

2.2.1 概念:

消费者完成一个任务可能需要一段时间,如果其中一个消费者处理一个长的任务并仅只完成了部分突然它挂掉了,会发生什么情况。RabbitMQ一旦向消费者传递了一条消息,便立即将该消息标记为删除。在这种情况下,突然有个消费者挂掉了,我们将丢失正在处理的消息。以及后续发送给该消费这的消息,因为它无法接收到。

为了保证消息在发送过程中不丢失,rabbitmq引入消息应答机制,消息应答就是:消费者在接收到消息并且处理该消息之后,告诉rabbitmq它已经处理了,rabbitmq可以把该消息删除了。

消息应答分类

2.2.2 消息的自动和手动应答
boolean autoAck = true;//消息自动应答
channel.basicConsume(WQ_QUEUE,autoAck,consumer);

​ 默认情况下,rabbitmq开启了消息的自动应答。此时,一旦rabbitmq将消息分发给了消费者,就会将消息从内存中删除。这种情况下,如果正在执行的消费者被“杀死”或“崩溃”,就会丢失正在处理的消息。

​ 如果想要确保消息不丢失,我们需要设置消息应答方式为手动应答。设置为手工应答后,消费者接受并处理完一个消息后,会发送应答给rabbitmq,rabbitmq收到应答后,会将该条消息从内存中删除。如果一个消费者在处理消息的过程中“崩溃”,rabbitmq没有收到应答,那么”崩溃“前正在处理的这条消息会重新被分发到别的消费者。

boolean autoAck = false;//消息手动应答
channel.basicConsume(WQ_QUEUE,autoAck,consumer);

2.3 RabbitMQ持久化

2.3.1 概念

​ 刚刚我们已经看到了如何处理任务不丢失的情况,但是如何保障当RabbitMQ服务停掉以后消息生产者发送过来的消息不丢失。默认情况下RabbitMQ退出或由于某种原因崩溃时,它忽视队列和消息,除非告知它不要这样做。确保消息不会丢失需要做两件事:我们需要将队列和消息都标记为持久化

2.3.2 队列如何实现持久化

​ 之前我们创建的队列都是非持久化的,rabbitmq如果重启的化,该队列就会被删除掉,如果 要队列实现持久化 需要在声明队列的时候把durable参数设置为持久化

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ccwQzrjE-1626859724144)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1626524809686.png)]

但是需要注意的就是如果之前声明的队列不是持久化的,需要把原先队列先删除,或者重新创建一个持久化的队列,不然就会出现错误

2.3.3 消息实现持久化

​ 要想让消息实现持久化需要在消息生产者修改代码,MessageProperties.PERSISTENT_TEXT_PLAIN添 加这个属性。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-etgJD5BW-1626859724146)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1626524885121.png)]

​ 将消息标记为持久化并不能完全保证不会丢失消息。尽管它告诉RabbitMQ将消息保存到磁盘,但是这里依然存在当消息刚准备存储在磁盘的时候 但是还没有存储完,消息还在缓存的一个间隔点。此时并没有真正写入磁盘。持久性保证并不强,但是对于我们的简单任务队列而言,这已经绰绰有余了。

三 交换机

3.1 Exchanges

3.1.1 Exchanges概念

​ RabbitMQ消息传递模型的核心思想是: 生产者生产的消息从不会直接发送到队列。实际上,通常生产者甚至都不知道这些消息传递传递到了哪些队列中。

​ 相反,生产者只能将消息发送到交换机==(exchange)==,交换机工作的内容非常简单,一方面它接收来自生产者的消息,另一方面将它们推入队列。交换机必须确切知道如何处理收到的消息。是应该把这些消息放到特定队列还是说把他们到许多队列中还是说应该丢弃它们。这就的由交换机的类型来决定。

3.1.2Exchanges的类型
  直接(direct),   主题(topic) ,  标题(headers) ,    扇出(fanout)
3.1. 3 无名exchange

之前能实现的原因是因为我们使用的是默认交换,我们通过空字符串(“”)进行标识。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dzPXz78S-1626859724147)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1626525332944.png)]

3.2绑定(bindings)

​ binding其实是exchange(交换机)和queue(队列)之间的桥梁,它告诉我们exchange和那个队列进行了绑定关系。比如说下面这张图告诉我们的就是X与Q1和Q2进行了绑定

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FNDr7eAp-1626859724148)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1626525478283.png)]

3.3 Fanout

​ Fanout这种类型非常简单。正如从名称中猜到的那样,它是将接收到的所有消息广播到它知道的所有队列中。系统中默认有些exchange类型

3.4 Direct exchange

​ 上一节中的我们的日志系统将所有消息广播给所有消费者,对此我们想做一些改变,例如我们希望将日志消息写入磁盘的程序仅接收严重错误(errros),而不存储哪些警告(warning)或信息(info)日志消息避免浪费磁盘空间。Fanout这种交换类型并不能给我们带来很大的灵活性-它只能进行无意识的广播,在这里我们将使用direct这种类型来进行替换,这种类型的工作方式是,消息只去到它绑定的routingKey队列中去。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PG5lp5if-1626859724149)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1626525941216.png)]

3.5Topics

​ 尽管使用direct交换机改进了我们的系统,但是它仍然存在局限性-比方说我们想接收的日志类型有info.base和info.advantage,某个队列只想info.base的消息,那这个时候direct就办不到了。这个时候就只能使用topic类型

​ 发送到类型是topic交换机的消息的routing_key不能随意写,必须满足一定的要求,它必须是一个单词列表,以点号分隔开。这些单词可以是任意单词,比如说:“stock.usd.nyse”, “nyse.vmw”, “quick.orange.rabbit”.这种类型的。当然这个单词列表最多不能超过255个字节。

​ 在这个规则列表中,其中有两个替换符是大家需要注意的

*(星号)可以代替一个单词

#(井号)可以替代零个或多个单词

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WfojIINb-1626859724150)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1626526142159.png)]

当队列绑定关系是下列这种情况时需要引起注意

当一个队列绑定键是#,那么这个队列将接收所有数据,就有点像fanout了

如果队列绑定键当中没有#和*出现,那么该队列绑定类型就是direct了

四 死信队列

4.1TTL是什么呢?

​ TTL是RabbitMQ中一个消息或者队列的属性,表明一条消息或者该队列中的==所有消息的最大存活时间==,单位是毫秒。换句话说,如果一条消息设置了TTL属性或者进入了设置TTL属性的队列,那么这条消息如果在TTL设置的时间内没有被消费,则会成为死信

4.2概念

​ 一般来说,producer将消息投递到broker或者直接到queue里了,consumer从queue取出消息进行消费,但某些时候由于特定的原因导致queue中的某些消息无法被消费,这样的消息如果没有后续的处理,就变成了死信,有死信自然就有了死信队列。

应用场景:为了保证订单业务的消息数据不丢失,需要使用到RabbitMQ的死信队列机制,当消息消费发生异常时,将消息投入死信队列中. 还有比如说: 用户在商城下单成功并点击去支付后在指定时间未支付时自动失效

4.3 死信的来源

消息TTL过期

队列达到最大长度(队列满了,无法再添加数据到mq中)

消息被拒绝(basic.reject或basic.nack)并且requeue=false.

4.4 死信实战

死信架构图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VFh4itj4-1626859724151)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1626526927709.png)]

五. 延迟队列

5.1 延迟队列概念

​ 延时队列,队列内部是有序的,最重要的特性就体现在它的延时属性上,延时队列中的元素是希望在指定时间到了以后或之前取出和处理,简单来说,延时队列就是用来存放需要在指定时间被处理的元素的队列。

5.2 延迟队列使用场景

1.订单在十分钟之内未支付则自动取消
2.新创建的店铺,如果在十天内都没有上传过商品,则自动发送消息提醒。
3.用户注册成功后,如果三天内没有登陆则进行短信提醒。
4.用户发起退款,如果三天内没有得到处理则通知相关运营人员。
5.预定会议后,需要在预定的时间点前十分钟通知各个与会人员参加会议

5.3RabbitMQ中的TTL

如果同时配置了队列的TTL和消息的TTL,那么较小的那个值将会被使用,有两种方式设置TTL。

消息设置TTL

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rVRVuSHa-1626859724152)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1626527281726.png)]

队列设置TTL

在创建队列的时候设置队列的“x-message-ttl”属性

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pG9bDoLG-1626859724153)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1626527308289.png)]

5.4 两者的区别

如果设置了队列的TTL属性,那么一旦消息过期,就会被队列丢弃(如果配置了死信队列被丢到死信队列中),

而第二种方式,消息即使过期,也不一定会被马上丢弃,因为消息是否过期是在即将投递到消费者之前判定的

如果当前队列有严重的消息积压情况,则已过期的消息也许还能存活较长时间;另外,还需要注意的一点是,

如果不设置TTL,表示消息永远不会过期,如果将TTL设置为0,则表示除非此时可以直接投递该消息到消费者,否则该消息将会被丢弃。

六 队列TTL

6.1 代码架构图

​ 创建两个队列QA和QB,两者队列TTL分别设置为10S和40S,然后在创建一个交换机X和死信交换机Y,它们的类型都是direct,创建一个死信队列QD,它们的绑定关系如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0YUigoVA-1626859724153)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1626528052805.png)]

​ 第一条消息在10S后变成了死信消息,然后被消费者消费掉,第二条消息在40S之后变成了死信消息,然后被消费掉,这样一个延时队列就打造完成了。

不过,如果这样使用的话,岂不是每增加一个新的时间需求,就要新增一个队列,这里只有10S和40S两个时间选项,如果需要一个小时后处理,那么就需要增加TTL为一个小时的队列,如果是预定会议室然后提前通知这样的场景,岂不是要增加无数个队列才能满足需求?

七 延时队列优化

在这里新增了一个队列QC,绑定关系如下,该队列不设置TTL时间

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J2EAeFOf-1626859724154)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1626528170015.png)]

​ 看起来似乎没什么问题,但是在最开始的时候,就介绍过如果使用在消息属性上设置TTL的方式,消息可能并不会按时“死亡“,因为RabbitMQ****只会检查第一个消息是否过期,如果过期则丢到死信队列,如果第一个消息的延时时长很长,而第二个消息的延时时长很短,第二个消息并不会优先得到执行

八 Rabbitmq插件实现延迟队列

8.1 安装

​ 上文中提到的问题,确实是一个问题,如果不能实现在消息粒度上的TTL,并使其在设置的TTL时间及时死亡,就无法设计成一个通用的延时队列。那如何解决呢,接下来我们就去解决该问题

在官网上下载https://www.rabbitmq.com/community-plugins.html,下载 rabbitmq_delayed_message_exchange插件,然后解压放置到RabbitMQ的插件目录。

进入RabbitMQ的安装目录下的plgins目录,执行下面命令让该插件生效,然后重启RabbitMQ

/usr/lib/rabbitmq/lib/rabbitmq_server-3.8.8/plugins

rabbitmq-plugins enable rabbitmq_delayed_message_exchange

8.2 代码架构图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5f9wkP2O-1626859724155)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1626528438572.png)]

总结

​ 延时队列在需要延时处理的场景下非常有用,使用RabbitMQ来实现延时队列可以很好的利用RabbitMQ的特性,如:消息可靠发送、消息可靠投递、死信队列来保障消息至少被消费一次以及未被正确处理的消息不会被丢弃。另外,通过RabbitMQ集群的特性,可以很好的解决单点故障问题,不会因为单个节点挂掉导致延时队列不可用或者消息丢失。

当然,延时队列还有很多其它选择,比如利用Java的DelayQueue,利用Redis的zset,利用Quartz或者利用kafka的时间轮,这些方式各有特点,看需要适用的场景

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值