【RabbitMQ使用】

1.导入依赖

		<!-- 导入Spring AMQP依赖,包含rabbitMQ       -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

2.application.yml配置

spring:
  rabbitmq:
    #主机地址
    host: 127.0.0.1
    #端口号
    port: 5672
    #虚拟主机
    virtual-host: /demo
    #用户名
    username: guest
    #密码
    password: guest
    #消费者需要添加该监听器配置
    listener:
      simple:
        #每次只能获取一条消息,处理完成才能获取下一条消息
        prefetch: 1

3.消息收发示例

消息发送者需要注入RabbitTemplate

 	@Autowired
    private RabbitTemplate rabbitTemplate;

注解式声明队列和交换机

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name="队列名称"),
            exchange = @Exchange(name="交换器名称",type = ExchangeTypes.TOPIC),
            key = {"Routing key"}
    ))

(1)fanout模式

fanout交换机会将接收到的消息广播到每一个跟其绑定的队列,所以也叫广播模式

消息发送者

    @Test
    void testFanoutExchange() {
        //使用rabbitMQ的fanout交换机往队列中发送消息
        //交换机需要绑定指定的队列
        //交换机名称
        String exchangeName = "demo_fanout";
        //消息内容
        String message = "Hello World!";
        rabbitTemplate.convertAndSend(exchangeName,null, message);
    }

消息接收者

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name="fanout_queue1"),
            exchange = @Exchange(name="demo_fanout",type = ExchangeTypes.FANOUT)
    ))
    public void getFanoutQueueMessage1(String message) {
        System.out.println(message);
    }
    
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name="fanout_queue2"),
            exchange = @Exchange(name="demo_fanout",type = ExchangeTypes.FANOUT)
    ))
    public void getFanoutQueueMessage2(String message) {
        System.err.println(message);
    }

接收到的消息

在这里插入图片描述

(2)direct模式

direct交换机会将接收到的消息根据规则路由到指定的队列,因此成为定向路由。

  • 每一个队列都要与交换器设置一个Binding Key;
  • 发布者发送消息时,需要指定消息的Routing Key;
  • 交换器会将消息路由到BindingKey与消息Routing Key一直的队列。

消息发送者

    @Test
    void testDirectExchange() {
        //使用rabbitMQ的direct交换机往队列中发送消息
        //交换机需要绑定指定的队列,并设置相应的routing key
        //交换机名称
        String exchangeName = "demo_direct";
        //消息内容
        String message1 = "red message";
        //根据路由键routing key的值进行分发
        rabbitTemplate.convertAndSend(exchangeName,"red", message1);
        //消息内容
        String message2 = "blue message";
        //根据路由键routing key的值进行分发
        rabbitTemplate.convertAndSend(exchangeName,"blue", message2);
    }

消息接收者

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name="direct_queue1"),
            exchange = @Exchange(name="demo_direct",type = ExchangeTypes.DIRECT),
            key = {"red","blue"}
    ))
    public void getDirectQueueMessage1(String message) {
        System.out.println("direct_queue1====>>"+message);
    }
    
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name="direct_queue2"),
            exchange = @Exchange(name="demo_direct",type = ExchangeTypes.DIRECT),
            key = {"red"}
    ))
    public void getDirectQueueMessage2(String message) {
        System.err.println("direct_queue2====>>"+message);
    }

接收到的消息

在这里插入图片描述

(2)topic模式

topic交换器与direct交换器类似,区别在于RoutingKey可以是多个单词的列表,并且以. 分割
队列与交换器指定BindingKey时可以使用通配符:
#:代指0个或多个单词
*:代指一个单词

消息发送者

    @Test
    void testTopicExchange() {
        //使用rabbitMQ的topic交换机往队列中发送消息
        //交换机需要绑定指定的队列,并设置相应的routing key,可以是多个单词列表,并以.分割,#代指0个或多个单词,*代指一个单词
        //交换机名称
        String exchangeName = "demo_topic";
        //消息内容
        String message1 = "china's news";
        //根据路由键routing key的值进行分发
        rabbitTemplate.convertAndSend(exchangeName,"china.news", message1);
        //消息内容
        String message2 = "japan's news";
        //根据路由键routing key的值进行分发
        rabbitTemplate.convertAndSend(exchangeName,"japan.news", message2);
        //消息内容
        String message3 = "china's weather";
        //根据路由键routing key的值进行分发
        rabbitTemplate.convertAndSend(exchangeName,"china.weather", message3);
    }

消息接收者

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name="topic_queue1"),
            exchange = @Exchange(name="demo_topic",type = ExchangeTypes.TOPIC),
            key = {"china.#"}
    ))
    public void getTopicQueueMessage1(String message) {
        System.out.println("topic_queue1====>>"+message);
    }
    
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name="topic_queue2"),
            exchange = @Exchange(name="demo_topic",type = ExchangeTypes.TOPIC),
            key = {"#.news"}
    ))
    public void getTopicQueueMessage2(String message) {
        System.err.println("topic_queue2====>>"+message);
    }

接收到的消息

在这里插入图片描述

4.发送和接收Object类型数据

Spring amqp对消息对象的处理是基于JDK的序列化完成的,其存在安全风险大、消息存储空间太大、可读性差等问题,建议采用JSON序列化来代替默认的JDK序列化。

(1)导入依赖

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>

(2)添加Configuration

@Configuration
public class MqConfig {
    //采用JSON序列化代替Spring amqp默认的JDK序列化
    @Bean
    public MessageConverter jacksonMessageConverter() {
        return new Jackson2JsonMessageConverter();
    }

}

注:上述两步在生产者和消费者中都要添加

(3)消息发送者

    @Test
    void testSendObject() {
        //往队列中发送消息Object类型消息
        String exchangeName = "object_exchange";
        //消息内容
        JSONObject message = new JSONObject();
        message.put("name","jack");
        message.put("age",20);
        rabbitTemplate.convertAndSend(exchangeName,null, message);
    }

(4)消息接收者

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name="object_queue",durable = "true"),
            exchange = @Exchange(name="object_exchange",type = ExchangeTypes.FANOUT)
    ))
    public void getObjectMessage(JSONObject message) {
        System.out.println(message);
    }

(5)接收到的消息

在这里插入图片描述

5.生产者可靠性

(1)生产者重连机制

有的时候由于网络波动,可能会出现客户端连接MQ失败的情况,可以通过配置开启链接失败后的重连机制。
application.yml配置:

spring:
  rabbitmq:
    #生产者重连机制
    #MQ的连接超时时间
    connection-timeout: 1s
    template:
      retry:
        #开启超时重试机制
        enabled: true
        #失败后的初始等待时间
        initial-interval: 1000ms
        #失败后下次的等待时长倍数
        multiplier: 1
        #最大重试次数
        max-attempts: 3

注:当网络不稳定的时候,利用重试机制可以有效提高消息发送的成功率。不过SpringAMQP提供的重试机制是阻塞式的重试,也就是说多次重试等待的过程中,当前线程是被阻塞的,会影响业务性能。如果对于业务性能有要求,建议禁用重试机制。如果一定要使用,需要合理配置等待时长和重试次数,当然也可以考虑使用异步线程来执行发送消息的代码。

(2)生产者确认机制

RabbitMQ提供了Publisher Confirm和Publisher Return两种确认机制,开启确认机制后,在MQ成功收到消息后会返回确认消息给生产者。消息投递成功会返回ACK,投递失败会返回NACK

application.yml配置:

spring:
  rabbitmq:
    #生产者确认机制
    #开启生产者确认机制,并设置confirm类型,none:关闭confirm机制,simple:同步阻塞等待MQ的回执消息,correlated:MQ异步回调方式返回回执消息
    publisher-confirm-type: correlated
    #开启生产者回调机制
    publisher-returns: true

Publisher Return机制

@Slf4j
@Configuration
public class CommonConfig implements ApplicationContextAware {


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        //获取RabbitTemplate
        RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class);
        //设置ReturnCallback
        rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {

            @Override
            public void returnedMessage(ReturnedMessage returnedMessage) {
                log.debug("收到消息的return callback,exchange:{},routingKey:{},msg:{},code:{}.text:{}",
                        returnedMessage.getExchange(), returnedMessage.getRoutingKey(),returnedMessage.getMessage(),
                        returnedMessage.getReplyCode(),returnedMessage.getReplyText());
            }
        });
    }
}

Publisher Confirm机制

    @Test
    void textConfirmCallback() throws InterruptedException {
        //1.创建CorrelationData
        CorrelationData cd = new CorrelationData(UUID.randomUUID().toString());
        //2.添加ConfirmCallback
        cd.getFuture().addCallback(new ListenableFutureCallback<CorrelationData.Confirm>() {

            @Override
            public void onSuccess(CorrelationData.Confirm result) {
                System.out.println("收到confirm callback回执");
                log.debug("收到confirm callback回执");
                if (result.isAck()){
                    log.debug("消息发送成功,收到ack");
                }else{
                    log.error("消息发送失败,收到nack,原因:{}",result.getReason());
                    //这里可以进行消息重发
                }
            }

            @Override
            public void onFailure(Throwable ex) {
                log.error("消息回调失败",ex);
            }
        });

        //交换机名称
        String exchangeName = "demo_direct";
        //消息内容
        String message1 = "red message";
        //根据路由键routing key的值进行分发
        rabbitTemplate.convertAndSend(exchangeName,"red", message1,cd);

        Thread.sleep(2000);
    }

注:

  • 生产者确认需要额外的网络和系统资源开销,尽量不要使用
  • 如果一定要使用,无需开启Publisher-Return机制,因为一般路由失败是自己业务问题
  • 对于nack消息可以有限次数重试,依然失败则记录异常消息

(3)数据持久化

将消息数据存储在磁盘中以防止数据丢失,实现数据的持久化,可以设置durable = "true"开启数据持久化,Spring amqp默认队列就是持久化的。

Lazy Queue

从RabbitMQ 3.6版本开始,增加了Lazy Queue概念,也称惰性队列。
Lazy Queue的特征:

  • 接收到消息后直接存入磁盘而非内存
  • 消费者要消费消息时才会从磁盘中读取并加载到内存
  • 支持百万条的消息内存

在3.12版本后,默认队列就是Lazy Queue,无法更改

//设置队列为lazy queue
//arguments = @Argument(name="x-queue-mode",value = "lazy")),
@RabbitListener(bindings = @QueueBinding(
            value = @Queue(name="lazy_queue",
                    arguments = @Argument(name="x-queue-mode",value = "lazy")),
            exchange = @Exchange(name="lazy_exchange",type = ExchangeTypes.FANOUT)
    ))

6.消费者可靠性

(1)消费者确认机制

SpringAMOP已经实现了消息确认功能。并允许我们通过配置文件选择ACK处理方式,有三种方式:

  • none:不处理。即消息投递给消费者后立刻ack,消息会立刻从MQ删除。非常不安全,不建议使用
  • manual:手动模式。需要自己在业务代码中调用api,发送ack或reject,存在业务入侵,但更灵活
  • auto:自动模式。SpringAMOP利用AOP对我们的消息处理逻辑做了环绕增强,当业务正常执行时则自动返回ack(建议使用的)
    当业务出现异常时,根据异常判断返回不同结果:
    • 如果是业务异常,会自动返回nack
    • 如果是消息处理或校验异常,自动返回reiect

application.yml

spring:
  rabbitmq:
    listener:
      simple:
        #消息确认模式,none:关闭ack,manual:手动ack,auto:自动ack(建议使用)
        acknowledge-mode: auto

(2)失败重试机制

当消费者出现异常后,消息会不断requeue(重新入队)到队列,再重新发送给消费者,然后再次异常,再次requeue,无限循环,导致mq的消息处理飙升,带来不必要的压力。
我们可以利用Spring的retry机制,在消费者出现异常时利用本地重试,而不是无限制的requeue到mq队列。

application.yml

spring:
  rabbitmq:
    listener:
      simple:
        #每次只能获取一条消息,处理完成才能获取下一条消息
        prefetch: 1
        #消息确认模式,none:关闭ack,manual:手动ack,auto:自动ack(建议使用)
        acknowledge-mode: auto
        retry:
          #开启失败重试机制
          enabled: true
          #失败后的初始等待时间
          initial-interval: 1000ms
          #失败后下次的等待时长倍数
          multiplier: 1
          #最大重试次数
          max-attempts: 3
          #true无状态;false有状态。如果业务中包含事务,这里改为false
          stateless:true

在开启重试模式后,重试次数耗尽,如果消息依然失败,则需要有MessageRecoverer接口来处理,它包含三种不同的实现:

  • RejectAndDontRequeueRecoverer:重试耗尽后,直接reject,丢弃消息。默认就是这种方式
  • ImmediateRequeueMessageRecoverer:重试耗尽后,返回nack,消息重新入队
  • RepublishMessageRecoverer:重试耗尽后,将失败消息投递到指定的交换机,可以交由人工手动处理(当前采用此种方式)

Configuration

@Configuration
public class ErrorConfig {

    @Bean
    public DirectExchange errorExchange() {
        return new DirectExchange("error_direct");
    }

    @Bean
    public Queue errorQueue() {
        return new Queue("error_queue");
    }

    @Bean
    public Binding errorBinding(Queue errorQueue, DirectExchange errorExchange) {
        return BindingBuilder.bind(errorQueue).to(errorExchange).with("error");
    }

    @Bean
    public MessageRecoverer messageRecoverer(RabbitTemplate rabbitTemplate) {
        System.err.println("执行messageRecoverer。。。");
        return new RepublishMessageRecoverer(rabbitTemplate,"error_direct","error");
    }
}

7.延迟消息队列

(1)死信交换机

当一个队列中的消息满足下列情况之一时,就会成为死信(dead letter):

  1. 消费者使用basic.reject或 basic.nack声明消费失败,并且消息的requeue参数设置为false
  2. 消息是一个过期消息(达到了队列或消息本身设置的过期时间),超时无人消费
  3. 要投递的队列消息堆积满了,最早的消息可能成为死信
    如果队列通过dead-letter-exchange属性指定了一个交换机,那么该队列中的死信就会投递到这个交换机中。这个交换机称为死信交换机(Dead Letter Exchange,简称DLX)。
    死信交换机

(2)延迟消息插件

该插件的原理是设计了一种支持延迟消息功能的交换机,当消息投递到交换机后可以暂存一定时间,到期后再投递到队列。

安装插件

生产者

 @Test
    void testDelayQueue() {
        //队列名称
        String exchangeName = "delay_exchange";
        //消息内容
        String message = "Hello World!";
        //发送消息,利用消息后置处理器添加消息头
        rabbitTemplate.convertAndSend(exchangeName,"delay", message,new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                //添加延迟消息属性
                message.getMessageProperties().setDelay(5000);
                return message;
            }
        });

    }

消费者

delayed = "true"设置交换机类型为x-delayed-message

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name="delay_queue",durable = "true"),
            exchange = @Exchange(name="delay_exchange",delayed = "true"),
            key = "delay"
    ))
    public void getDelayMessage(String message) {
        log.info("接收到的延迟消息=>{}",message);
    }
RabbitMQ是一个使用Erlang实现的高并发高可靠AMQP消息队列服务器。它支持消息的持久化、事务、拥塞控制、负载均衡等特性,使得RabbitMQ在各种应用场景下被广泛使用RabbitMQ与Erlang和AMQP密切相关。 Erlang是一种编程语言,它特别适合构建高并发、分布式、实时的系统。RabbitMQ使用Erlang作为其底层开发语言,这使得它能够充分利用Erlang在并发处理和容错性方面的优势。 AMQP(Advanced Message Queuing Protocol)是一个开放标准的消息队列协议,用于在应用程序之间进行可靠的消息传递。RabbitMQ实现了AMQP协议,这意味着它可以与其他遵循AMQP协议的应用程序进行通信,实现可靠的消息传递机制。 要使用RabbitMQ,可以通过Docker安装,使用以下命令运行一个带有管理界面的RabbitMQ容器: ``` docker run -itd --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management ``` 在编写RabbitMQ程序时,可以利用其支持的多种消息模型,例如发布-订阅模型、工作队列模型、路由模型等,根据具体需求选择合适的模型来实现消息传递。 在配置RabbitMQ环境时,可以设置RABBITMQ_SERVER环境变量,并将RabbitMQ的安装路径添加到系统的path变量中,以方便在命令行中直接使用RabbitMQ命令。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [RabbitMQ使用详解](https://blog.youkuaiyun.com/qq_43410878/article/details/123656765)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值