RabbitMq

RabbitMq

什么是MQ:

是一种存放消息的队列;还是一种跨进程的通讯机制,用于两个微服务之间的消息通讯;

消息中间件

作用于分布式系统之间的通讯

且必须是异步处理的场景

提升系统的吞吐量:单位时间处理请求的个数

底层就是一个队列,但是队列是不支持持久化的;且不支持跨进程的;

mq的作用

流量削峰:当请求超过了系统能处理的最大值时,使用消息队列来作为缓冲,将一秒内的请求分成一段时间来处理;

应用解耦:微服务之间的依赖性降低,当一个微服务出现故障时,正常功能依旧能够实现,等出现故障的微服务修复后,依旧能够完成自己的功能: (比如物流系统因为发生故障,需要几分钟来修复。在这几分钟的时间里,物流系统要处理的内存被缓存在消息队列中,用户的下单操作可以正常完成。当物流系统恢复后,继续处理订单信息即可,中单用户感受不到物流系统的故障,提升系统的可用性。)

异步处理:用于提升系统的吞吐量

MQ的分类

kafka:为大数据而生的消息中间件,以其百万级TPS的吞吐量名声大噪

优点:性能卓越,单机写入TPS约在百万条/秒,最大的优点,就是吞吐量高

但是需要专业的团队来进行维护,且一般的企业是不需要那么高的吞吐量的

RabbitMQ:

吞吐量也可以达到万级,足够一般公司使用

性能稳定,使用erlang语言编写的;

开源提供的管理界面很好

社区活跃度高--->更新频率高

RabbitMq的四大核心概念

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

交换机:接收生产者发送到的消息,并且推送到队列中:

  • 注意的是交换机必须确切的知道如何处理接收到的消息
    • 可以推送到多个队列
    • 也可以推送到特定的队列
    • 或者是将消息丢弃
  • 这些都是和交换机的类型有关的

队列:队列的本质就是一个很大的消息缓冲区,队列仅受主机的内存和磁盘的约束;

多个生产者可以向同一个队列发送消息,多个消费者也可以从同一个队列中接收消息

消费者:消费者就是等待从队列中接收消息的程序;

消费者和生产者是同一个程序

RabbitMq的基本架构

由生产者通过信道发送消息哦到信道--->信道(原来会发送一次信息给交换机就需要建立一次连接,这样很耗费资源,所以使用信道来减少资源的浪费Channel作为轻量级的Connection极大减少了操作系统建立TCP connection的开销)

由交换机将消息推送至队列,需要注意的是交换机是不能储存消息的,根据交换机的类型不同,匹配队列的方式是不同的:逻辑上的结构是由一个一个的虚拟机组成一个Broker

Broker:是负责消息的分发的

且Broker里面包含多个虚拟机

每个虚拟机里面都可以由一个交换机和多个队列组成

队列是整个消息中间件的核心组件

可以设置队列中消息的过期时间,以及持久化

队列和交换机的绑定需要路由key的,由路由key--->发送到交换机的消息,根据路由key匹配到队列中

关于AMQP协议的补充

AMQP协议是具有现代特征的二进制协议。是一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。

  • Server:接收客户端的连接,实现AMQP实体服务。
  • Connection:连接,应用程序与Server的网络连接,TCP连接。
  • Channel:信道,消息读写等操作在信道中进行。客户端可以建立多个信道,每个信道代表一个会话任务。
  • Message:消息,应用程序和服务器之间传送的数据,消息可以非常简单,也可以很复杂。由Properties和Body组成。Properties为外包装,可以对消息进行修饰,比如消息的优先级、延迟等高级特性;Body就是消息体内容。
  • Virtual Host:虚拟主机,用于逻辑隔离。一个虚拟主机里面可以有若干个Exchange和Queue,同一个虚拟主机里面不能有相同名称的Exchange或Queue。
  • Exchange:交换器,接收消息,按照路由规则将消息路由到一个或者多个队列。如果路由不到,或者返回给生产者,或者直接丢弃。RabbitMQ常用的交换器常用类型有direct、topic、fanout、headers四种,后面详细介绍。
  • Binding:绑定,交换器和消息队列之间的虚拟连接,绑定中可以包含一个或者多个RoutingKey。
  • RoutingKey:路由键,生产者将消息发送给交换器的时候,会发送一个RoutingKey,用来指定路由规则,这样交换器就知道把消息发送到哪个队列。路由键通常为一个“.”分割的字符串,例如“com.rabbitmq”。
  • Queue:消息队列,用来保存消息,供消费者消费。

消费原理

集群中有两个节点,每个节点上有一个broker,每个broker负责本机上队列的维护,并且borker之间可以互相通信。集群中有两个队列A和B,每个队列都分为master queue和mirror queue(备份)。

需要注意的是:只有主队列可以完成读写操作,若连接的不是主队列则会通过路由,还是会连接主队列;

由于这种结构,导致RabbitMq,只能是单节点进行读写操作,吞吐量就会受限;

自动应答与手动应答的区别

自动应答:吞吐量大,但是消息容易丢失

手动应答:消息不容易丢失:这也是消息百分百投递的一个重点

  • A.Channel.basicAck(用于肯定确认)
  • B.Channel.basicNack(用于否定确认
  • C.Channel.basicReject(用于否定确认):与Channel.basicNack相比少一个参数不处理该消息了直接拒绝,可以将其丢弃

消息的过期时间

Time To Live,也就是生存时间,是一条消息在队列中的最大存活时间

RabbitMQ可以对消息和队列设置TTL。

RabbitMQ支持设置消息的过期时间,在消息发送的时候可以进行指定,每条消息的过期时间可以不同。

RabbitMQ支持设置队列的过期时间,从消息入队列开始计算,直到超过了队列的超时时间配置,那么消息会变成死信,自动清除。

如果两种方式一起使用,则过期时间以两者中较小的那个数值为准。

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

需要注意的是,若是在一个队列中存在两条消息,A消息的过期时间为10s,B消息的过期时间为5秒,但是A消息先进入队列,那么在A消息没有被抛弃之前B消息也不会被抛弃;

Mq的持久化

RabbitMQ的持久化分为三个部分:交换器持久化、队列持久化和消息的持久化。

交换机的持久化:

① Exchange 的持久化,声明交换机时指定持久化参数(durable)为 true 即可。声明时是默认持久化的

如果交换机不设置持久化,那么在 RabbitMQ 服务器重启之后,相关的交换机元数据会丢失,不过消息不会丢失,只是不能将消息发送到这个交换机中了。对于一个长期使用的交换机来说,建议将其设置为持久化的。

消息的持久化:

③ message 的持久化,使用 convertAndSend 方式发送消息,消息默认是持久化的

在保证了队列的持久化后,开启 Message 的持久化就能保证在其内部的持久化存储

队列的持久化:

② queue 的持久化,声明队列时指定持久化参数(durable)为 true 即可。声明时是默认持久化的

如果队列不设置持久化,那么在RabbitMQ服务重启之后,相关队列的元数据会丢失,此时数据也会丢失。

队列的持久化能保证其本身的元数据不会因异常情况而丢失,但是不能保证内部所存储的消息不会丢失。

消息发布确认(参考----消息的百分百投递)

发布确认:生产者向交换机发送一个消息,交换机需要给一个回馈(若给交换机发送一个消息,但是队列中不存在,就存在两种可能性:第一生产者发送给交换机时就消息丢失了;第二种:消息在交换机推送到队列中失败了,可能没有对应路由的交换机);即若没有消息发布确认机制,消息丢失以后可能不知在哪丢失了

回调方法:

配置一个发布确认模式开启

回退消息

在交换机无法处理消息时,将消息回退

交换机:

交换机的类型

direct(直连交换机):将队列绑定到交换机,消息的 routeKey 需要与队列绑定的 routeKey 相同。

fanout (扇形交换机):不处理 routeKey ,直接把消息转发到与其绑定的所有队列中。

topic(主题交换机):根据一定的规则,根据 routeKey 把消息转发到符合规则的队列中,其中 # 用于匹配符合一个或者多个词(范围更广), * 用于匹配一个词。

headers (头部交换机):根据消息的 headers 转发消息而不是根据 routeKey 来转发消息, 其中 header 是一个 Map,也就意味着不仅可以匹配字符串类型,也可以匹配其他类型数据。 规则可以分为所有键值对匹配或者单一键值对匹配。

绑定

交换机和队列的桥梁

死信队列

什么是死信:没办法被消费的消息

  • 消息ttl:消息的存放时间超时了
  • 消息被拒
  • 队列满了,消息无法进入队列

死信队列

死信交换机:

死信队列:

实质上就是普通的队列

延迟队列

一般用在订单的自动取消

利用TTL和死信队列是可以完成延时队列效果的

方案一:使用队列的过期时间

   @Bean
   public Queue testQueue(){
       return QueueBuilder.durable(TEST_QUEUE)
               .deadLetterExchange(DEAD_EXCHANGE)
               .deadLetterRoutingKey(DEAD_ROUTING_KEY)
               // 10s 过期
               .ttl(10000)
               .build();
   }

方案二:使用消息的过期时间

当两条消息进入同一个交换机时,且都设置了过期时间,那么若先过期的消息没有进入死信交换机,后面进入的消息就算过期了也会被阻塞住!

(补充知识点----当消息和队列都设置了过期时间,以时间先到的为准)

实现精准延时的方法

使用插件

延迟交换机并不是rabbitmq自带的功能,而是要通过安装延迟交换机插件delayed_message_exchange来实现

通过延迟交换机实现的延迟消息,其重点主要在交换机上,队列就是普通队列,消息发送到交换机上后,会记录消息的延迟时间,到达时间后才会发送到队列中,这样消费者通过监控队列,就能在指定时间获取到消息

因此延迟交换机与普通交换机的实现,只在创建交换机时,其他的操作与普通交换机无异,因此使用起来也很方便

创建延迟交换机,通过x-delayed-type属性声明交换机类型,可以是direct也可以是topic,具体支持4中交换机类型

应用场景

1.订单超时未支付,自动关闭订单

通过使用延时队列来实现

由订单微服务自己发送消息---->订单微服务自己消费

2.自动签收

使用实例:

依赖:

<!--RabbitMQ依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

配置文件

spring:
  rabbitmq:
    host: 192.168.18.230
    port: 5672
    username: admin
    password: 123
    listener:
      simple:
        acknowledge-mode: manual//设置手动签收

配置类(用来设置交换机为延迟交换机,注意需要延时交换机的插件)

@Configuration
public class CancelOrderQueueConfig {
    @Bean
    public Queue cancelOrderQueue(){
        return new Queue(MqConst.CANCEL_ORDER_QUEUE,false);
    }
    //由于采用延迟插件 自定义交换机
    @Bean
    public CustomExchange cancelOrderExchange(){
        Map<String, Object> arguments=new HashMap<>();
        //常规交换机类型
        arguments.put("x-delayed-type","direct");
        return new CustomExchange(MqConst.CANCEL_ORDER_EXCHANGE,"x-delayed-message",false,true,arguments);
    }
    //队列和交换机的绑定
    @Bean
    public Binding bindingDelayedQueue(@Qualifier("cancelOrderQueue") Queue cancelOrderQueue,
                                       @Qualifier("cancelOrderExchange") CustomExchange cancelOrderExchange){
        return BindingBuilder.bind(cancelOrderQueue).to(cancelOrderExchange).with(MqConst.CANCEL_ORDER_ROUTE_KEY).noargs();
    }
}

生产者

   @Autowired
    private RabbitTemplate rabbitTemplate; 

//发送一个延迟消息 超时自动取消订单
rabbitTemplate.convertAndSend(MqConst.CANCEL_ORDER_EXCHANGE, MqConst.CANCEL_ORDER_ROUTE_KEY, orderInfoId, correlationData -> {
            correlationData.getMessageProperties().setDelay(cancelOrderDelay);
            return correlationData;

消费者

   @Autowired
    private RabbitTemplate rabbitTemplate;

@RabbitListener(queues = MqConst.CANCEL_ORDER_QUEUE)
public void cancelOrder(Long orderInfoId, Channel channel, Message message)throws Exception{
        //手动的确认消息
        channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
    }

本文作者:鸽宗

本文链接:https://www.cnblogs.com/xuzhidong/p/16845459.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   鸽宗  阅读(66)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示
  1. 1 水星记 REOL
水星记 - REOL
00:00 / 00:00
An audio error has occurred.

作词 : 郭顶

作曲 : 郭顶

编曲 : 郭顶

监制 : 郭顶

着迷于你眼睛

银河有迹可循

穿过时间的缝隙

它依然真实地

吸引我轨迹

这瞬眼的光景

最亲密的距离

沿着你皮肤纹理

走过曲折手臂

做个梦给你

做个梦给你

等到看你银色满际

等到分不清季节更替

才敢说沉溺

还要多远才能进入你的心

还要多久才能和你接近

咫尺远近却

无法靠近的那个人

也等着和你相遇

环游的行星

怎么可以

拥有你

这瞬眼的光景

最亲密的距离

沿着你皮肤纹理

走过曲折手臂

做个梦给你

做个梦给你

等到看你银色满际

等到分不清季节更替

才敢说沉溺

还要多远才能进入你的心

还要多久才能和你接近

咫尺远近却

无法靠近的那个人

也等着和你相遇

环游的行星

怎么可以

拥有你

还要多远才能进入你的心

还要多久才能和你接近

咫尺远近却无法靠近的那个人

要怎么探寻

要多么幸运

才敢让你发觉你并不孤寂

当我还可以再跟你飞行

环游是无趣

至少可以

陪着你