SpringBoot整合Rabbitmq

创建项目:

新建一个项目,并且引入RabbitMq依赖。

spring:
    rabbitmq:
     host: 110.41.51.65
     port: 5673 #默认为5672
     username: study
     password: study
     virtual-host: bite

简单模式

生产者发送channel时,就会记录一个deliveryTag,这个tag就是记录下来该信道发送几次消息,如果重新启动并且创建的话,就会有新的信道,这个值就会重置。

生产者

package com.syx.rabbitmqspring.Controller;

import com.syx.rabbitmqspring.Constant.Constants;
import jakarta.annotation.Resource;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/producer")
@RestController
public class ProducerController {

    @Resource
    RabbitTemplate rabbitTemplate;


    @RequestMapping("/work")
    public String work(){
        //发送消息
        /*
        *void convertAndSend(String exchange//交换机名字, String routingKey//队列名字, Object message发送信息) throws AmqpException;
        *
        *使用内置交换机,routingkey和和队列名字一样,
        *
        * */
        rabbitTemplate.convertAndSend("" , Constants.WORK_QUEUE,"hello mq hello spring");
        return "发送成功";


    }
}

消费者

package com.syx.rabbitmqspring.Listener;

import com.syx.rabbitmqspring.Constant.Constants;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;



@Component
public class WorkListener {

    //监听要接收的队列,只要有消息就会立马进行监听
    @RabbitListener(queues = Constants.WORK_QUEUE)
    public void queueListener(Message message){
        System.out.println(Constants.WORK_QUEUE+"接收到消息"+message);


    }
}

在这里一个rabbitlistener就是一个消费者,就是spring框架用于监听mq的注解, 可以接收message参数,或者String类型,如果是string类型就是发送消息的一个具体内容。

都可以在消费者部分进行指定。

交换机已经创建好了,并且已经指定好类型了,就不能进行修改了,比如原本是direct,就不能修改成topic。并且也不能修改队列持久化的参数.

基于springboot和rabbitmq的通信

订单系统接收到消息,发送给mq,mq接收到消息再发送给物流系统,然后物流系统开始发货,订单系统就是生产者,物流系统就是消费者。

@Component
@RabbitListener(queues = Constant.ORDER_QUEUE)
public class OrderListener {

    @RabbitHandler
    public void handMessage(Message message,String orderInfo){
        System.out.println("接收到订单消息1"+orderInfo);

    }

    
    @RabbitHandler
    public void handMessage(Integer orderInfo){
        System.out.println("接收到订单消息1"+orderInfo);

    }


}

如果这里是将rabbitlistener加到类上面去,这时候就要在类上面加上rabbitHandler,就会根据收到信息的类型进行自动匹配。 但是这里需要对双方都进行配置config如果有一方没有配置的话,另外一方就接收不到。一个类型只能有一个。

package com.syx.rabbitmq.orderserver.Config;

import com.syx.rabbitmq.orderserver.Constant.Constant;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitMqConfig {

    //简单模式
    @Bean("orderQueue")
    public Queue orderQueue(){
        return QueueBuilder.durable(Constant.ORDER_QUEUE).build();

    }

    @Bean("jackson2JsonMessageConverter")
    public Jackson2JsonMessageConverter jackson2JsonMessageConverter(){
        return new Jackson2JsonMessageConverter();
    }


    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory,@Qualifier("jackson2JsonMessageConverter") Jackson2JsonMessageConverter jackson2JsonMessageConverter){
        RabbitTemplate rabbitTemplate=new RabbitTemplate(connectionFactory);
        rabbitTemplate.setMessageConverter(jackson2JsonMessageConverter);
        return rabbitTemplate;

    }



}

Rabbitmq高级特性

消息确认机制(消费者确认)

rabbitmq发送消息到消费者后,可能中间生产者成功发送消息到mq服务器,但是当mq服务器发送给消费者的时候,就可能出现消息丢失,mq以为消费者接收到了消息,然后mq把消息删除了,但是实际上发送失败(消费者处理失败才是关键)。

要保证消费者确认,必须要消息确认机制。

自动确认:

只要发送成功给消费者就可以自动确认,不管消费者是否处理成功,这种场景对消息可靠性不高的场景。消费者不需要进行任何处理。

手动确认:

消费者显示调用ack确认信号,mq才认为消息已经正确到达消费者,否则则认为没有收到,适合对消息可靠性要求高的场景。

当然了这里在basicconsume有个autoack。

如果消费端没有确认,就说明消息消费者处理失败了,这时候就可能要求mq服务器重发,如果确认可能就可以在mq中删除这个数据。

deliveryTag:消息的唯一标识,他是一个单调递增的64位长整型,单调递增,每个channel独立维护一deliveryTag。
multiple:是否批量确认,是否对一系列进行确认。

拒绝消息:

requeue会在拒绝的时候,是否重新放入队列,如果是true的话就会重新放入队列, 否则就会丢弃。

批量拒绝消息,既可以批量,也可以单独拒绝消息。

Spring-ampq三种策略:

  • AcknowledgeMode.AUTO(默认):在这种模式下,消息一旦投递给消费者就直接删除,不管消费者内部有没有处理成功。
  • AcknowledgeMode.NONE:这种模式下,消费者在消息确认会自动确认消息,如果处理过程中抛出异常则不会确认消息。
  • AcknowledgeMode.MANUAL:必须显示调用basicnck来确认消息,即使消息处理导致丢失,消息也不会丢失,而是可以被重新处理。

Rabbit持久性

持久性也是可靠性的保证之一。图中有多个环节会出现消息丢失。

生产者自己把数据弄丢了,broker自己把数据弄丢了(断电了),也有可能消费者自己弄丢数据(消费者处理异常)。

生产者到达broker可以通过发送方确认,比如对方保管好了返回一个ack确认,消息确认机制,分为手动确认和自动确认。

交换机的持久化

在设置交换机的时候,有一个durable属性,把durable设置为true,就把交换机的一些数据存储在磁盘上而不是内存上了,即使重启,交换机还是会有数据。如果是长期使用,建议使用持久化。默认是持久化的。这时候就设置为非持久性了。

队列持久化

队列设置为持久化,队列的消息不会消失,默认是持久化的。

消息持久化

channel发送数据

消息持久化是和队列有一定关系的,消息持久化需要队列和消息双持久化才可以,消息是存储在队列中的,所以需要消息和队列同时持久化,如果只设置其中一个持久化,mq重启之后消息都会丢失。

使用rabbitTemplate

只有在队列持久化和消息持久化的时候消息才不会丢失,其他情况都会丢失。

Rabbitmq数据丢失

但是并不是只要交换机,队列,消息都持久化了就可以,实际上,mq是把消息存储在缓存中,等固定时间后一起写入硬盘,如果在缓存上的时候,还没来得及写入硬盘(比如电脑挂机了),就会出现消息丢失的情况。

发送方确认两种模式(保证消息可靠性)

生产者发送消息给broker,如果消息都没有到达服务器,更不用说后续的持久化了。

confirm确认模式

不管对方收没收到,监听都会被执行,如果exchange收到,ack返回为true,否则为false。

return模式

消息到达exchange,可能到达exchange,但是无法到达queue,就是routingkekey不匹配,退回发送者,发送者对消息进行处理。将消息退回给发送方。

实际上confirm和return是结合一起使用的。

如何确保Rabbitmq的可靠传输(面试题)

从生产者到交换机可能有错误,交换机到队列可能有问题,队列到消费者可能有问题。

生产者到broker:

采用发送方确认,生产者发送后,不管对方有没有收到,监听都会被执行,如果exchange收到,ack返回为true,否则为false。

exchange到queue:

通过return 模式,如果routingkey不同导致没有成功发到队列中,就可以使用return方法,来进行下一步的逻辑处理。

broker内部出现问题:

消息成功到达队列,但是服务器忽然宕机了 ,这时候就通过交换机持久化,队列持久化,消息持久化,默认就是队列消息持久化。但是并不是每一条消息都进行持久化,可能还没有持久化,主机就挂了,这时候也会让数据丢失。

broker到consumer

消息到达consumer的时候消息就会自动删除(自动确认),但是可能因为消费者代码逻辑和内部错误导致消费消息失败了,从而找不到消息,就需要手动确认,消费者消费后手动确认才会删除(手动确认)。

rabbitmq重试机制(交换机和消费者)

Rabbitmq提供的SDK方式,自动确认,消息到达消费者就自动删除,手动确认,消息发送成功需要进行确认。SpringAMQP提供了三种方式,none,manual,auto三种方法,auto当消费者处理消息的时候抛出异常就会进行重试。

但是比如出现代码问题的话即使重试也没有用。

未配置重试策略,消息就会一直发送,不会停止,如果配置了就可以根据配置进行重试发送。重试机制在自动配置的机制下才生效。

spring:
  rabbitmq:
    listener:
      simple:
        prefetch: 1
        retry:
          enabled: true # 开启消费者失败重试
          initial-interval: 5000ms # 初始失败等待时长为5秒
          max-attempts: 5 # 最大重试次数
     publisher-confirm-type: correlated #消息自动确认模式

TTL(过期时间)

当消息到达过期的时间还没有被消费掉就会被清除。

设置队列的TTL

比如在队列设置20s,那么存储在该队列中所有的消息的ttl和队列的ttl都是20s,如果消息的ttl为10s,队列为20s,那么就是10s,取最小的那个。

比如我买了个平板,订单系统发送到mq,mq再发送到支付系统,但是我超时未支付,订单消息就会丢失。

设置消息的ttl

@RequestMapping("ttl")
    public String ttl(){
        System.out.println("设置ttl时间");
        //设置过期时间
        MessagePostProcessor messagePostProcessor=new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                message.getMessageProperties().setExpiration("10000");
                return message;
            }
        };
        rabbitTemplate.convertAndSend(Constants.TTL_EXCHANGE,"ttl","hello ttl hello we are ttl",messagePostProcessor);
        return "发送ttl成功";



    }

如果设置队列的ttl,如果消息过期就会马上从队列中删除,如果设置消息过期时间的话,会在投递到消费者之前进行判定。

当为队列设置 TTL 时,意味着队列中的所有消息都有一个统一的存活时间。RabbitMQ 会在内部维护队列中消息的时间信息,一旦某个消息在队列中的存在时间达到了设定的 TTL,RabbitMQ 会立即将其从队列中移除。

当为单个消息设置过期时间时,RabbitMQ 不会像处理队列 TTL 那样实时监控并删除过期消息。这是因为不同消息的过期时间可能各不相同,实时删除会带来较大的性能开销。如果设置消息过期时间的话,会在投递到消费者之前进行判定。也会在队列中存在,在即将被消费的时候才被判定。

死信队列

由于种种原因无法被消费的消息就称之为死信,比如设置ttl之后,消息超过ttl规定的时间后已经过期了,就称之为死信。存储死信消息就称之为死信队列。

变成死信的几种情况

  1. 消息发送后被拒绝,并且没有进行requeue操作,也就是无法重新入队。
  2. 消息可能过期了,无法被consumer1消费掉。
  3. 队列达到最大长度了。

如果时间过期了后,消息就会自动放到死信队列中 。

死信面试题

死信队列是一种特殊的队列用来存储无法被正常消费的队列 ,它指的是那些无法被正常消费和处理的消息,被存储在一个新的队列中。

死信的几种情况:

  1. 消息发送后被拒绝,并且没有进行requeue操作,也就是无法重新入队。
  2. 消息可能过期了,无法被consumer1消费掉。
  3. 队列达到最大长度了,已经满了。

nack可能会出现一个问题,比如发送给消费者但是消费者由于代码问题出现逻辑错误吗,抛出异常,导致消息重新入队,后面还有源源不断的消息发送过来,就会导致信息积压。

这时候我们可以不重新入队,将消息发送到新的死信队列,通过新的死信队列进行消费。死信接收后,人工进行处理。

比如订单出现问题,mq就会将出现问题的订单进行发送到死信队列,然后将死信队列中的数据重新发送给消费者进行处理。

延迟队列

发送消息之后,并不想消费者直接拿到数据,而是希望消费者过一会额儿才拿到数据。

如图就是延迟队列的实现,由于mq没有实现延迟队列,所以只能自己实现一个延迟队列。设置消息的ttl可能会出现问题。先发送30s的ttl,再发送10s的ttl,会在30s过期之后才进行处理。

第二个消息比第一个先过期了,就会发生第二个延迟不生效。

插件安装

https://www.rabbitmq.com/blog/2015/04/16/scheduling-messages-with-rabbitmq

在该页面中打开该连接,跳转到github上面获取安装包。

安装第一个。

在/usr/lib/rabbitmq/plugins下面安装该插件,将我们在window上面的插件拖进来就可以看到插件安装成功了。(要使用相匹配版本的插件,比如这里就是需要3.9.x的)。

在交换机下面就可以看见我们刚刚添加的类型。如果想禁用插件,就把

的enable变成diasable就可以了。

基于插件的延迟队列

@Bean("delayExchange")
    public DirectExchange delayExchange(){
        //表明是延迟队列
        return ExchangeBuilder.directExchange(Constants.DELAY_EXCHANGE).delayed().build();
    }

注意这边要声明队列是延迟队列,上面ttl加死信队列会发生消息乱序的情况下,但是这里官方提供的插件就不会出现这件事。

延迟队列面试题

基于死信实现的延迟队列:

  • 优点:灵活不需要额外的插件支持。
  • 缺点:存在消息顺序的问题,需要额外的逻辑来处理消息死信顺序的问题。增加系统复杂性。

基于插件实现的延迟队列:

  • 优点:可以通过插件直接创建延迟队列,增加系统复杂性。
  • 缺点:只能用特定版本(有运维的成本)。

rabbitmq事务机制

@Bean
    public RabbitTemplate transRabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate  transRabbitTemplate=new RabbitTemplate(connectionFactory);
        //设置事务为true,开启事务。
        transRabbitTemplate.setChannelTransacted(true);
        return transRabbitTemplate;

    }

    @Bean
    public RabbitTransactionManager rabbitTransactionManager(ConnectionFactory connectionFactory){
        return new RabbitTransactionManager(connectionFactory);

    }
@Transactional
    @RequestMapping("trans")
    public String trans(){
        System.out.println("发送trans消息");
        //没有绑定的话就直接发给队列名字
        transRabbitTemplate.convertAndSend("",Constants.TRANS_QUEUE,"发送trans消息1");
        int a=3/10;
        transRabbitTemplate.convertAndSend("",Constants.TRANS_QUEUE,"发送trans消息2");
        return "success";


    }

采用事务的方式两条消息同时成功或者同时失败。

可以看到当中间有异常的时候就会回滚,使得两个都没有成功插入。如果没有异常就都成功。

消息分发

正常rabbitmq是以轮询的方式进行分发,mq为消费端计数,如果达到消费端上限了,mq就不会再发送消息了。

限流:

在秒杀的情况下,抢购物品就会导致订单系统压力很大,就可以基于mq实现限流机制。这里就可以限制最多有五千个流量,如果没处理完,就不会给订单系统发送消息。

非公平分发(负载均衡):

按照一定的情况进行分发数据,干的快就多分发一点数据量,慢的话就少分发一点数据量。

    listener:
      simple:
        acknowledge-mode: manual  #手动确认
        #acknowledge-mode: auto  #消息接收确认
        #acknowledge-mode: manual
          #acknowledge-mode: none 自动确认
          #消息接收确认

        prefetch: 1
        retry:
          enabled: true # 开启消费者失败重试
          initial-interval: 5000ms # 初始失败等待时长为5秒
          max-attempts: 5 # 最大重试次数

具体代码可以参考我的gitee链接syxcyt (syxcyt) - Gitee.com

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值