9.RabbitMQ消息的可靠性

九、消息的可靠性

1、生产者确认

9.1.1、Confirm模式简介

可能因为网络或者Broker的问题导致①失败,而此时应该让生产者知道消息是否正确发送到了Broker的exchange中;

有两种解决方案:

第一种是开启Confirm(确认)模式;(异步)

第二种是开启Transaction(事务)模式;(性能低,实际项目中很少用)

消息的confirm确认机制,是指生产者投递消息后,到达了消息服务器Broker里面的exchange交换机,则会给生产者一个应答

生产者接收到应答,用来确定这条消息是否正常的发送到Broker的exchange中,这也是消息可靠性投递的重要保障;

9.1.2、Confirm模式实现

  • 开启生产者确认模式

    yml 复制代码
    publisher-confirm-type: correlated # 开启生产者的确认模式,设置关联模式
  • 实现RabbitTemplate.ConfirmCallback交界口,重写confirm()方法

    判断成功和失败的ack结果,可以根据具体的结果,如果ack为false,对消息进行重新发送或记录日志等处理

  • 设置rabbitTemplate的确认回调方法

    java 复制代码
    rabbitTemplate.setConfirmCallback(messageConfirmCallBack);
(1)、外部类实现

测试模块:rabbitmq-09-confirm-01

引入依赖

xml 复制代码
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

配置MQ

yml 复制代码
server:
  port: 8080

spring:
  application:
    name: confirm-learn01

  rabbitmq:
    host: 192.168.1.101
    port: 5672
    username: admin
    password: 123456
    virtual-host: longdidi
    publisher-confirm-type: correlated # 开启生产者的确认模式,设置关联模式

生产者

设置回调方法

java 复制代码
package com.longdidi.service;

import com.longdidi.config.MyConfirmCallBack;
import com.longdidi.constants.RabbitMQConstant;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;

@Service
@Slf4j
public class SendMessageService {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Resource
    private MyConfirmCallBack confirmCallBack;

    //构造方法执行后会调用一次该方法,只调用一次,起到初始化的作用
    @PostConstruct
    public void init() {
        rabbitTemplate.setConfirmCallback(confirmCallBack);
    }

    public void sendMsg() {
        Message message = MessageBuilder.withBody("hello world".getBytes()).build();
        //关联数据对象
        CorrelationData correlationData = new CorrelationData(); //关联数据
        //比如设置一个订单ID,到时候在confirm回调里面就可以知道是哪个订单没有发送到交换机上去
        correlationData.setId("order_123456"); //发送订单信息
        rabbitTemplate.convertAndSend(RabbitMQConstant.EXCHANGE_NAME + "error", RabbitMQConstant.ROUTING_NAME1, message, correlationData);
        log.info("消息发送完毕,发送时间为:{}", new Date());
    }
}

定义常量

java 复制代码
package com.longdidi.constants;

public class RabbitMQConstant {
    // 正常交换机
    public static final String EXCHANGE_NAME = "exchange.confirm.normal.1";

    // 队列
    public static final String QUEUE_NAME1 = "queue.confirm.normal.1";

    // 路由key
    public static final String ROUTING_NAME1 = "key.confirm.normal.1";
}

定义MQ

java 复制代码
package com.longdidi.config;

import com.longdidi.constants.RabbitMQConstant;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class RabbitConfig {

    /**
     * 正常交换机
     *
     * @return
     */
    @Bean
    public DirectExchange normalExchange() {
        return ExchangeBuilder.directExchange(RabbitMQConstant.EXCHANGE_NAME).build();
    }

    /**
     * 正常队列
     *
     * @return
     */
    @Bean
    public Queue normalQueue() {
        //Queue(String name, boolean durable, boolean exclusive, boolean autoDelete, @Nullable Map<String, Object> arguments)
        Map<String, Object> arguments = new HashMap<>();
        return QueueBuilder.durable(RabbitMQConstant.QUEUE_NAME1)
                .withArguments(arguments)
                .build();
    }

    /**
     * 正常交换机和正常队列绑定
     *
     * @param normalExchange
     * @param normalQueue
     * @return
     */
    @Bean
    public Binding bindingNormal(DirectExchange normalExchange, Queue normalQueue) {
        return BindingBuilder.bind(normalQueue).to(normalExchange).with(RabbitMQConstant.ROUTING_NAME1);
    }

}

实现接口

java 复制代码
package com.longdidi.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class MyConfirmCallBack implements RabbitTemplate.ConfirmCallback {
    /**
     * 交换机收到消息后,会回调该方法
     *
     * @param correlationData 相关联的数据
     * @param ack             有两个取值,true和false,true表示成功:消息正确地到达交换机,反之false就是消息没有正确地到达交换机
     * @param cause           消息没有正确地到达交换机的原因是什么
     */

    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        log.info("关联id为:{}", correlationData.getId() + "");
        if (ack) {
            log.info("消息正确的达到交换机");
            return;
        } else {
            //ack =false 没有到达交换机
            log.error("消息没有到达交换机,原因为:{}", cause);
        }
    }
}

发送消息

java 复制代码
package com.longdidi;

import com.longdidi.service.SendMessageService;
import jakarta.annotation.Resource;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Rabbitmq09Confirm01Application implements ApplicationRunner {

    public static void main(String[] args) {
        SpringApplication.run(Rabbitmq09Confirm01Application.class, args);
    }

    @Resource
    private SendMessageService sendMessageService;

    /**
     * 程序一启动就会运行该方法
     *
     * @param args
     * @throws Exception
     */
    @Override
    public void run(ApplicationArguments args) throws Exception {
        sendMessageService.sendMsg();
    }
}

测试

写错交换机的名字,查看日志输出

(2)、生产者实现

测试模块:rabbitmq-09-confirm-02

引入依赖

xml 复制代码
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

配置MQ

yml 复制代码
server:
  port: 8080

spring:
  application:
    name: confirm-learn02

  rabbitmq:
    host: 192.168.1.101
    port: 5672
    username: admin
    password: 123456
    virtual-host: longdidi
    publisher-confirm-type: correlated # 开启生产者的确认模式,设置关联模式

生产者

生产者直接实现RabbitTemplate.ConfirmCallback接口,重写confirm()方法并设置回调函数

java 复制代码
package com.longdidi.service;

import com.longdidi.constants.RabbitMQConstant;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;

@Service
@Slf4j
public class SendMessageService implements RabbitTemplate.ConfirmCallback {

    @Resource
    private RabbitTemplate rabbitTemplate;

    @PostConstruct //构造方法后执行它,相当于初始化作用
    public void init() {
        rabbitTemplate.setConfirmCallback(this);
    }

    public void sendMsg() {
        Message message = MessageBuilder.withBody("hello world".getBytes()).build();
        CorrelationData correlationData = new CorrelationData(); //关联数据
        correlationData.setId("order_123456"); //发送订单信息
        rabbitTemplate.convertAndSend(RabbitMQConstant.EXCHANGE_NAME + "error", RabbitMQConstant.QUEUE_NAME1, message, correlationData);
        log.info("消息发送完毕,发送时间为:{}", new Date());
    }

    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        log.info("关联id为:{}", correlationData.getId() + "");
        if (ack) {
            log.info("消息正确的达到交换机");
            return;
        }
        //ack =false 没有到达交换机
        log.error("消息没有到达交换机,原因为:{}", cause);

    }
}

定义常量

java 复制代码
package com.longdidi.constants;

public class RabbitMQConstant {
    // 正常交换机
    public static final String EXCHANGE_NAME = "exchange.confirm.normal.2";

    // 队列
    public static final String QUEUE_NAME1 = "queue.confirm.normal.2";

    // 路由key
    public static final String ROUTING_NAME1 = "key.confirm.normal.2";
}

定义MQ队列

java 复制代码
package com.longdidi.config;

import com.longdidi.constants.RabbitMQConstant;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class RabbitConfig {

    /**
     * 正常交换机
     *
     * @return
     */
    @Bean
    public DirectExchange normalExchange() {
        return ExchangeBuilder.directExchange(RabbitMQConstant.EXCHANGE_NAME).build();
    }

    /**
     * 正常队列
     *
     * @return
     */
    @Bean
    public Queue normalQueue() {
        //Queue(String name, boolean durable, boolean exclusive, boolean autoDelete, @Nullable Map<String, Object> arguments)
        Map<String, Object> arguments = new HashMap<>();
        return QueueBuilder.durable(RabbitMQConstant.QUEUE_NAME1)
                .withArguments(arguments)
                .build();
    }

    /**
     * 正常交换机和正常队列绑定
     *
     * @param normalExchange
     * @param normalQueue
     * @return
     */
    @Bean
    public Binding bindingNormal(DirectExchange normalExchange, Queue normalQueue) {
        return BindingBuilder.bind(normalQueue).to(normalExchange).with(RabbitMQConstant.ROUTING_NAME1);
    }

}

发送消息

java 复制代码
package com.longdidi;

import com.longdidi.service.SendMessageService;
import jakarta.annotation.Resource;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Rabbitmq09Confirm02Application implements ApplicationRunner {

    public static void main(String[] args) {
        SpringApplication.run(Rabbitmq09Confirm02Application.class, args);
    }

    @Resource
    private SendMessageService sendMessageService;

    /**
     * 程序一启动就会运行该方法
     *
     * @param args
     * @throws Exception
     */
    @Override
    public void run(ApplicationArguments args) throws Exception {
        sendMessageService.sendMsg();
    }
}

测试

(3)、匿名内部类实现

测试模块:rabbitmq-09-confirm-03

引入依赖

xml 复制代码
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

配置MQ

yml 复制代码
server:
  port: 8080

spring:
  application:
    name: confirm-learn03

  rabbitmq:
    host: 192.168.1.101
    port: 5672
    username: admin
    password: 123456
    virtual-host: longdidi
    publisher-confirm-type: correlated # 开启生产者的确认模式,设置关联模式

生产者

生产者使用匿名内部类实现RabbitTemplate.ConfirmCallback接口,重写confirm()方法并设置回调函数

java 复制代码
package com.longdidi.service;

import com.longdidi.constants.RabbitMQConstant;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;

import java.util.Date;

@Service
@Slf4j
public class SendMessageService {

    @Resource
    private RabbitTemplate rabbitTemplate;

    @PostConstruct //构造方法后执行它,相当于初始化作用
    public void init() {
        rabbitTemplate.setConfirmCallback(
                //匿名内部类
                new RabbitTemplate.ConfirmCallback() {
                    @Override
                    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                        log.info("关联id为:{}", correlationData.getId() + "");
                        if (ack) {
                            log.info("消息正确的达到交换机");
                            return;
                        }
                        //ack =false 没有到达交换机
                        log.error("消息没有到达交换机,原因为:{}", cause);
                    }
                }
        );
    }

    public void sendMsg() {
        Message message = MessageBuilder.withBody("hello world".getBytes()).build();
        CorrelationData correlationData = new CorrelationData(); //关联数据
        correlationData.setId("order_123456"); //发送订单信息
        rabbitTemplate.convertAndSend(RabbitMQConstant.EXCHANGE_NAME + "error", RabbitMQConstant.QUEUE_NAME1, message, correlationData);
        log.info("消息发送完毕,发送时间为:{}", new Date());
    }

}

定义MQ队列

java 复制代码
package com.longdidi.config;

import com.longdidi.constants.RabbitMQConstant;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class RabbitConfig {

    /**
     * 正常交换机
     *
     * @return
     */
    @Bean
    public DirectExchange normalExchange() {
        return ExchangeBuilder.directExchange(RabbitMQConstant.EXCHANGE_NAME).build();
    }

    /**
     * 正常队列
     *
     * @return
     */
    @Bean
    public Queue normalQueue() {
        //Queue(String name, boolean durable, boolean exclusive, boolean autoDelete, @Nullable Map<String, Object> arguments)
        Map<String, Object> arguments = new HashMap<>();
        return QueueBuilder.durable(RabbitMQConstant.QUEUE_NAME1)
                .withArguments(arguments)
                .build();
    }

    /**
     * 正常交换机和正常队列绑定
     *
     * @param normalExchange
     * @param normalQueue
     * @return
     */
    @Bean
    public Binding bindingNormal(DirectExchange normalExchange, Queue normalQueue) {
        return BindingBuilder.bind(normalQueue).to(normalExchange).with(RabbitMQConstant.ROUTING_NAME1);
    }

}

定义常量

java 复制代码
package com.longdidi.constants;

public class RabbitMQConstant {
    // 正常交换机
    public static final String EXCHANGE_NAME = "exchange.confirm.normal.3";

    // 队列
    public static final String QUEUE_NAME1 = "queue.confirm.normal.3";

    // 路由key
    public static final String ROUTING_NAME1 = "key.confirm.normal.3";
}

发送消息

java 复制代码
package com.longdidi;

import com.longdidi.service.SendMessageService;
import jakarta.annotation.Resource;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Rabbitmq09Confirm03Application implements ApplicationRunner {

    public static void main(String[] args) {
        SpringApplication.run(Rabbitmq09Confirm03Application.class, args);
    }

    @Resource
    private SendMessageService sendMessageService;

    /**
     * 程序一启动就会运行该方法
     *
     * @param args
     * @throws Exception
     */
    @Override
    public void run(ApplicationArguments args) throws Exception {
        sendMessageService.sendMsg();
    }
}

测试

(4)、Lambad实现

测试模块:rabbitmq-09-confirm-04

引入依赖

xml 复制代码
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

配置MQ

yml 复制代码
server:
  port: 8080

spring:
  application:
    name: confirm-learn04

  rabbitmq:
    host: 192.168.1.101
    port: 5672
    username: admin
    password: 123456
    virtual-host: longdidi
    publisher-confirm-type: correlated # 开启生产者的确认模式,设置关联模式

生产者

生产者使用lambda实现RabbitTemplate.ConfirmCallback接口,重写confirm()方法并设置回调函数

java 复制代码
package com.longdidi.service;

import com.longdidi.constants.RabbitMQConstant;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;

@Service
@Slf4j
public class SendMessageService {

    @Resource
    private RabbitTemplate rabbitTemplate;

    @PostConstruct //构造方法后执行它,相当于初始化作用
    public void init() {
        rabbitTemplate.setConfirmCallback(
                //lambda 表达式
                (correlationData, ack, cause) -> {
                    log.info("关联id为:{}", correlationData.getId() + "");
                    if (ack) {
                        log.info("消息正确的达到交换机");
                        return;
                    }
                    //ack =false 没有到达交换机
                    log.error("消息没有到达交换机,原因为:{}", cause);
                }

        );
    }

    public void sendMsg() {
        Message message = MessageBuilder.withBody("hello world".getBytes()).build();
        CorrelationData correlationData = new CorrelationData(); //关联数据
        correlationData.setId("order_123456"); //发送订单信息
        rabbitTemplate.convertAndSend(RabbitMQConstant.EXCHANGE_NAME + "error", RabbitMQConstant.ROUTING_NAME1, message, correlationData);
        log.info("消息发送完毕,发送时间为:{}", new Date());
    }
}

定义MQ队列

java 复制代码
package com.longdidi.config;

import com.longdidi.constants.RabbitMQConstant;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class RabbitConfig {

    /**
     * 正常交换机
     *
     * @return
     */
    @Bean
    public DirectExchange normalExchange() {
        return ExchangeBuilder.directExchange(RabbitMQConstant.EXCHANGE_NAME).build();
    }

    /**
     * 正常队列
     *
     * @return
     */
    @Bean
    public Queue normalQueue() {
        //Queue(String name, boolean durable, boolean exclusive, boolean autoDelete, @Nullable Map<String, Object> arguments)
        Map<String, Object> arguments = new HashMap<>();
        return QueueBuilder.durable(RabbitMQConstant.QUEUE_NAME1)
                .withArguments(arguments)
                .build();
    }

    /**
     * 正常交换机和正常队列绑定
     *
     * @param normalExchange
     * @param normalQueue
     * @return
     */
    @Bean
    public Binding bindingNormal(DirectExchange normalExchange, Queue normalQueue) {
        return BindingBuilder.bind(normalQueue).to(normalExchange).with(RabbitMQConstant.ROUTING_NAME1);
    }

}

定义常量

java 复制代码
package com.longdidi.constants;

public class RabbitMQConstant {
    // 正常交换机
    public static final String EXCHANGE_NAME = "exchange.confirm.normal.4";

    // 队列
    public static final String QUEUE_NAME1 = "queue.confirm.normal.4";

    // 路由key
    public static final String ROUTING_NAME1 = "key.confirm.normal.4";
}

发送消息

java 复制代码
package com.longdidi;

import com.longdidi.service.SendMessageService;
import jakarta.annotation.Resource;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Rabbitmq09Confirm04Application implements ApplicationRunner {

    public static void main(String[] args) {
        SpringApplication.run(Rabbitmq09Confirm04Application.class, args);
    }

    @Resource
    private SendMessageService sendMessageService;

    /**
     * 程序一启动就会运行该方法
     *
     * @param args
     * @throws Exception
     */
    @Override
    public void run(ApplicationArguments args) throws Exception {
        sendMessageService.sendMsg();
    }
}

测试

2、交换机确认

可能因为路由关键字错误,或者队列不存在,或者队列名称错误导致②失败

使用return模式可以实现消息无法路由的时候返回给生产者

消息从 exchange --> queue 投递失败则会返回一个 returnCallback;

利用这个callback控制消息的可靠性投递;

9.2.1、return模式实现

实现步骤

  • 开启return确认模式

    yml 复制代码
    publisher-returns: true #开启return模式
  • 实现RabbitTemplate.ReturnsCallback接口并重写returnedMessage()方法

  • 设置回调

    使用rabbitTemplate.setReturnCallback设置退回函数

    当消息从exchange路由到queue失败后,则会将消息退回给producer,并执行回调函数returnedMessage;

(1)、外部类实现

测试模块:rabbitmq-09-return-01

引入依赖

xml 复制代码
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

配置MQ

yml 复制代码
server:
  port: 8080

spring:
  application:
    name: return-learn01

  rabbitmq:
    host: 192.168.1.101
    port: 5672
    username: admin
    password: 123456
    virtual-host: longdidi
    publisher-returns: true #开启return模式

实现回调接口

java 复制代码
package com.longdidi.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ReturnedMessage;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;

/**
 * 写一个类实现RabbitTemplate.ReturnsCallback接口
 */
@Component
@Slf4j
public class MyReturnCallBack implements RabbitTemplate.ReturnsCallback {
    /**
     * 当消息从交换机 没有正确地 到达队列,则会触发该方法
     * 如果消息从交换机 正确地 到达队列了,那么就不会触发该方法
     *
     * @param returnedMessage
     */
    @Override
    public void returnedMessage(ReturnedMessage returnedMessage) {
        log.error("消息从交换机没有正确的路由到(投递到)队列,原因为:{}", returnedMessage.getReplyText());
    }
}

生产者

设置回调函数

java 复制代码
package com.longdidi.service;

import com.longdidi.config.MyReturnCallBack;
import com.longdidi.constants.RabbitMQConstant;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;

import java.util.Date;

@Service
@Slf4j
public class SendMessageService {

    @Resource
    private RabbitTemplate rabbitTemplate;

    @Resource
    private MyReturnCallBack myReturnCallBack;

    @PostConstruct
    public void init() {
        rabbitTemplate.setReturnsCallback(myReturnCallBack); //设置回调
    }

    public void sendMsg() {
        Message message = MessageBuilder.withBody("hello world".getBytes())
                .build();
        rabbitTemplate.convertAndSend(RabbitMQConstant.EXCHANGE_NAME, RabbitMQConstant.ROUTING_NAME1 + "error", message);
        log.info("消息发送完毕,发送时间为:{}", new Date());
    }
}

定义MQ

java 复制代码
package com.longdidi.config;

import com.longdidi.constants.RabbitMQConstant;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitConfig {

    /**
     * 正常交换机
     *
     * @return
     */
    @Bean
    public DirectExchange normalExchange() {
        return ExchangeBuilder.directExchange(RabbitMQConstant.EXCHANGE_NAME).build();
    }

    /**
     * 正常队列
     *
     * @return
     */
    @Bean
    public Queue normalQueue() {
        return QueueBuilder.durable(RabbitMQConstant.QUEUE_NAME1)
                .build();
    }

    /**
     * 正常交换机和正常队列绑定
     *
     * @param normalExchange
     * @param normalQueue
     * @return
     */
    @Bean
    public Binding bindingNormal(DirectExchange normalExchange, Queue normalQueue) {
        return BindingBuilder.bind(normalQueue).to(normalExchange).with(RabbitMQConstant.ROUTING_NAME1);
    }

}

定义常量

java 复制代码
package com.longdidi.constants;

public class RabbitMQConstant {
    // 正常交换机
    public static final String EXCHANGE_NAME = "exchange.return.normal.1";

    // 队列
    public static final String QUEUE_NAME1 = "queue.return.normal.1";

    // 路由key
    public static final String ROUTING_NAME1 = "key.return.normal.1";
}

发送消息

java 复制代码
package com.longdidi;

import com.longdidi.service.SendMessageService;
import jakarta.annotation.Resource;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Rabbitmq09Return01Application implements ApplicationRunner {

    public static void main(String[] args) {
        SpringApplication.run(Rabbitmq09Return01Application.class, args);
    }

    @Resource
    private SendMessageService sendMessageService;

    /**
     * 程序一启动就会运行该方法
     *
     * @param args
     * @throws Exception
     */
    @Override
    public void run(ApplicationArguments args) throws Exception {
        sendMessageService.sendMsg();
    }
}

测试

(2)、生产者实现

测试模块:rabbitmq-09-return-02

引入依赖

xml 复制代码
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

配置MQ

yml 复制代码
server:
  port: 8080

spring:
  application:
    name: return-learn02

  rabbitmq:
    host: 192.168.1.101
    port: 5672
    username: admin
    password: 123456
    virtual-host: longdidi
    publisher-returns: true #开启return模式

生产者

java 复制代码
package com.longdidi.service;

import com.longdidi.constants.RabbitMQConstant;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.ReturnedMessage;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;

import java.util.Date;

@Service
@Slf4j
public class SendMessageService implements RabbitTemplate.ReturnsCallback {
    @Resource
    private RabbitTemplate rabbitTemplate;


    @PostConstruct
    public void init() {
        rabbitTemplate.setReturnsCallback(this); //设置回调
    }

    public void sendMsg() {
        Message message = MessageBuilder.withBody("hello world".getBytes())
                .build();
        rabbitTemplate.convertAndSend(RabbitMQConstant.EXCHANGE_NAME, RabbitMQConstant.ROUTING_NAME1 + "error", message);
        log.info("消息发送完毕,发送时间为:{}", new Date());
    }

    /**
     * 当消息从交换机 没有正确地 到达队列,则会触发该方法
     * 如果消息从交换机 正确地 到达队列了,那么就不会触发该方法
     *
     * @param returnedMessage
     */
    @Override
    public void returnedMessage(ReturnedMessage returnedMessage) {
        log.error("消息从交换机没有正确的路由到(投递到)队列,原因为:{}", returnedMessage.getReplyText());
    }
}

定义MQ

java 复制代码
package com.longdidi.config;

import com.longdidi.constants.RabbitMQConstant;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitConfig {

    /**
     * 正常交换机
     *
     * @return
     */
    @Bean
    public DirectExchange normalExchange() {
        return ExchangeBuilder.directExchange(RabbitMQConstant.EXCHANGE_NAME).build();
    }

    /**
     * 正常队列
     *
     * @return
     */
    @Bean
    public Queue normalQueue() {
        return QueueBuilder.durable(RabbitMQConstant.QUEUE_NAME1)
                .build();
    }

    /**
     * 正常交换机和正常队列绑定
     *
     * @param normalExchange
     * @param normalQueue
     * @return
     */
    @Bean
    public Binding bindingNormal(DirectExchange normalExchange, Queue normalQueue) {
        return BindingBuilder.bind(normalQueue).to(normalExchange).with(RabbitMQConstant.ROUTING_NAME1);
    }

}

定义常量

java 复制代码
package com.longdidi.constants;

public class RabbitMQConstant {
    // 正常交换机
    public static final String EXCHANGE_NAME = "exchange.return.normal.2";

    // 队列
    public static final String QUEUE_NAME1 = "queue.return.normal.2";

    // 路由key
    public static final String ROUTING_NAME1 = "key.return.normal.2";
}

发送消息

java 复制代码
package com.longdidi;

import com.longdidi.service.SendMessageService;
import jakarta.annotation.Resource;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Rabbitmq09Return02Application implements ApplicationRunner {

    public static void main(String[] args) {
        SpringApplication.run(Rabbitmq09Return02Application.class, args);
    }

    @Resource
    private SendMessageService sendMessageService;

    /**
     * 程序一启动就会运行该方法
     *
     * @param args
     * @throws Exception
     */
    @Override
    public void run(ApplicationArguments args) throws Exception {
        sendMessageService.sendMsg();
    }
}

测试

(3)、匿名内部类实现

测试模块:rabbitmq-09-return-03

引入依赖

xml 复制代码
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

配置MQ

yml 复制代码
server:
  port: 8080

spring:
  application:
    name: return-learn03

  rabbitmq:
    host: 192.168.1.101
    port: 5672
    username: admin
    password: 123456
    virtual-host: longdidi
    publisher-returns: true #开启return模式

生产者

java 复制代码
package com.longdidi.service;

import com.longdidi.constants.RabbitMQConstant;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.ReturnedMessage;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;

import java.util.Date;

@Service
@Slf4j
public class SendMessageService {
    @Resource
    private RabbitTemplate rabbitTemplate;

    @PostConstruct
    public void init() {
        /**
         * 当消息从交换机 没有正确地 到达队列,则会触发该方法
         * 如果消息从交换机 正确地 到达队列了,那么就不会触发该方法
         */
        rabbitTemplate.setReturnsCallback(
                //匿名内部类 实现了接口
                new RabbitTemplate.ReturnsCallback() {
                    @Override
                    public void returnedMessage(ReturnedMessage returnedMessage) {
                        log.error("消息从交换机没有正确的路由到(投递到)队列,原因为:{}", returnedMessage.getReplyText());
                    }
                }

        ); //设置回调
    }

    public void sendMsg() {
        Message message = MessageBuilder.withBody("hello world".getBytes())
                .build();
        rabbitTemplate.convertAndSend(RabbitMQConstant.EXCHANGE_NAME, RabbitMQConstant.ROUTING_NAME1 + "error", message);
        log.info("消息发送完毕,发送时间为:{}", new Date());
    }
}

定义MQ

java 复制代码
package com.longdidi.config;

import com.longdidi.constants.RabbitMQConstant;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitConfig {

    /**
     * 正常交换机
     *
     * @return
     */
    @Bean
    public DirectExchange normalExchange() {
        return ExchangeBuilder.directExchange(RabbitMQConstant.EXCHANGE_NAME).build();
    }

    /**
     * 正常队列
     *
     * @return
     */
    @Bean
    public Queue normalQueue() {
        return QueueBuilder.durable(RabbitMQConstant.QUEUE_NAME1)
                .build();
    }

    /**
     * 正常交换机和正常队列绑定
     *
     * @param normalExchange
     * @param normalQueue
     * @return
     */
    @Bean
    public Binding bindingNormal(DirectExchange normalExchange, Queue normalQueue) {
        return BindingBuilder.bind(normalQueue).to(normalExchange).with(RabbitMQConstant.ROUTING_NAME1);
    }

}

定义常量

java 复制代码
package com.longdidi.constants;

public class RabbitMQConstant {
    // 正常交换机
    public static final String EXCHANGE_NAME = "exchange.return.normal.3";

    // 队列
    public static final String QUEUE_NAME1 = "queue.return.normal.3";

    // 路由key
    public static final String ROUTING_NAME1 = "key.return.normal.3";
}

发送消息

java 复制代码
package com.longdidi;

import com.longdidi.service.SendMessageService;
import jakarta.annotation.Resource;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Rabbitmq09Return03Application implements ApplicationRunner {

    public static void main(String[] args) {
        SpringApplication.run(Rabbitmq09Return03Application.class, args);
    }

    @Resource
    private SendMessageService sendMessageService;

    /**
     * 程序一启动就会运行该方法
     *
     * @param args
     * @throws Exception
     */
    @Override
    public void run(ApplicationArguments args) throws Exception {
        sendMessageService.sendMsg();
    }
}

测试

(4)、lambda实现

测试模块:rabbitmq-09-return-04

引入依赖

xml 复制代码
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

配置MQ

yml 复制代码
server:
  port: 8080

spring:
  application:
    name: return-learn04

  rabbitmq:
    host: 192.168.1.101
    port: 5672
    username: admin
    password: 123456
    virtual-host: longdidi
    publisher-returns: true #开启return模式

生产者

java 复制代码
package com.longdidi.service;

import com.longdidi.constants.RabbitMQConstant;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;

import java.util.Date;

@Service
@Slf4j
public class SendMessageService {
    @Resource
    private RabbitTemplate rabbitTemplate;


    @PostConstruct
    public void init() {
        /**
         * 当消息从交换机 没有正确地 到达队列,则会触发该方法
         * 如果消息从交换机 正确地 到达队列了,那么就不会触发该方法
         */
        rabbitTemplate.setReturnsCallback(
                //使用lambda表达式
                message -> {
                    log.error("消息从交换机没有正确的路由到(投递到)队列,原因为:{}", message.getReplyText());
                }

        ); //设置回调
    }

    public void sendMsg() {
        Message message = MessageBuilder.withBody("hello world".getBytes())
                .build();
        rabbitTemplate.convertAndSend(RabbitMQConstant.EXCHANGE_NAME, RabbitMQConstant.ROUTING_NAME1 + "error", message);
        log.info("消息发送完毕,发送时间为:{}", new Date());
    }
}

定义MQ

java 复制代码
package com.longdidi.config;

import com.longdidi.constants.RabbitMQConstant;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitConfig {

    /**
     * 正常交换机
     *
     * @return
     */
    @Bean
    public DirectExchange normalExchange() {
        return ExchangeBuilder.directExchange(RabbitMQConstant.EXCHANGE_NAME).build();
    }

    /**
     * 正常队列
     *
     * @return
     */
    @Bean
    public Queue normalQueue() {
        return QueueBuilder.durable(RabbitMQConstant.QUEUE_NAME1)
                .build();
    }

    /**
     * 正常交换机和正常队列绑定
     *
     * @param normalExchange
     * @param normalQueue
     * @return
     */
    @Bean
    public Binding bindingNormal(DirectExchange normalExchange, Queue normalQueue) {
        return BindingBuilder.bind(normalQueue).to(normalExchange).with(RabbitMQConstant.ROUTING_NAME1);
    }

}

定义常量

java 复制代码
package com.longdidi.constants;

public class RabbitMQConstant {
    // 正常交换机
    public static final String EXCHANGE_NAME = "exchange.return.normal.4";

    // 队列
    public static final String QUEUE_NAME1 = "queue.return.normal.4";

    // 路由key
    public static final String ROUTING_NAME1 = "key.return.normal.4";
}

发送消息

java 复制代码
package com.longdidi;

import com.longdidi.service.SendMessageService;
import jakarta.annotation.Resource;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Rabbitmq09Return04Application implements ApplicationRunner {

    public static void main(String[] args) {
        SpringApplication.run(Rabbitmq09Return04Application.class, args);
    }

    @Resource
    private SendMessageService sendMessageService;

    /**
     * 程序一启动就会运行该方法
     *
     * @param args
     * @throws Exception
     */
    @Override
    public void run(ApplicationArguments args) throws Exception {
        sendMessageService.sendMsg();
    }
}

测试

9.2.2、使用备用交换机

使用备份交换机(alternate-exchange),无法路由的消息会发送到这个备用交换机上;

测试模块:rabbitmq-09-backup-01

【示例】

引入依赖

xml 复制代码
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

配置MQ

yml 复制代码
server:
  port: 8080

spring:
  application:
    name: backup-learn01

  rabbitmq:
    host: 192.168.1.101
    port: 5672
    username: admin
    password: 123456
    virtual-host: longdidi
    publisher-returns: true #开启return模式

生产者

java 复制代码
package com.longdidi.service;

import com.longdidi.constants.RabbitMQConstant;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
@Slf4j
public class SendMessageService {

    @Resource
    private RabbitTemplate rabbitTemplate;

    public void sendMsg() {
        Message message = MessageBuilder.withBody("hello world".getBytes())
                .build();
        rabbitTemplate.convertAndSend(RabbitMQConstant.EXCHANGE_NORMAL_NAME, RabbitMQConstant.QUEUE_NORMAL_NAME + "error", message);
        log.info("消息发送完毕,发送时间为:{}", new Date());
    }
}

定义常量

java 复制代码
package com.longdidi.constants;

public class RabbitMQConstant {
    // 正常交换机
    public static final String EXCHANGE_NORMAL_NAME = "exchange.normal.backup.5";
    //备用交换机
    public static final String EXCHANGE_BACKUP_NAME = "exchange.backup.5";
    //正常队列
    public static final String QUEUE_NORMAL_NAME = "queue.normal.backup.5";
    //备用队列
    public static final String QUEUE_BACKUP_NAME = "queue.backup.5";

    public static final String ROUTING_WARNING_KEY = "info5";
}

定义MQ

java 复制代码
package com.longdidi.config;

import com.longdidi.constants.RabbitMQConstant;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class RabbitConfig {

    /**
     * 定义正常交换机
     *
     * @return
     */
    @Bean
    public DirectExchange normalExchange() {
        return ExchangeBuilder // 默认为持久化的,默认不自动删除
                .directExchange(RabbitMQConstant.EXCHANGE_NORMAL_NAME) // 交换机的名字
                .alternate(RabbitMQConstant.EXCHANGE_BACKUP_NAME) //设置备用交换机 alternate-exchange
                .build();
    }

    /**
     * 定义正常队列
     *
     * @return
     */
    @Bean
    public Queue queueNormal() {
        return QueueBuilder.durable(RabbitMQConstant.QUEUE_NORMAL_NAME).build();
    }

    /**
     * 绑定正常交换机和队列
     *
     * @param normalExchange
     * @param queueNormal
     * @return
     */
    @Bean
    public Binding binding(DirectExchange normalExchange, Queue queueNormal) {
        return BindingBuilder.bind(queueNormal).to(normalExchange).with(RabbitMQConstant.ROUTING_WARNING_KEY);
    }

    /**
     * 定义备用交换机
     *
     * @return
     */

    @Bean
    public FanoutExchange alternateExchange() {
        return ExchangeBuilder.fanoutExchange(RabbitMQConstant.EXCHANGE_BACKUP_NAME).build();
    }

    /**
     * 定义备用队列
     *
     * @return
     */
    @Bean
    public Queue alternateQueue() {
        return QueueBuilder.durable(RabbitMQConstant.QUEUE_BACKUP_NAME).build();
    }

    /**
     * 绑定备用交换机和队列
     *
     * @param alternateExchange
     * @param alternateQueue
     * @return
     */
    @Bean
    public Binding bindingAlternate(FanoutExchange alternateExchange, Queue alternateQueue) {
        return BindingBuilder.bind(alternateQueue).to(alternateExchange);
    }
}

发送消息

java 复制代码
package com.longdidi;

import com.longdidi.service.SendMessageService;
import jakarta.annotation.Resource;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Rabbitmq09Backup01Application implements ApplicationRunner {

    public static void main(String[] args) {
        SpringApplication.run(Rabbitmq09Backup01Application.class, args);
    }

    @Resource
    private SendMessageService sendMessageService;

    /**
     * 程序一启动就会运行该方法
     *
     * @param args
     * @throws Exception
     */
    @Override
    public void run(ApplicationArguments args) throws Exception {
        sendMessageService.sendMsg();
    }
}

测试

3、消息持久化存储

消息的可靠性投递就是要保证消息投递过程中每一个环节都要成功,那么这肯定会牺牲一些性能,性能与可靠性是无法兼得的;

如果业务实时一致性要求不是特别高的场景,可以牺牲一些可靠性来换取性能

  1. 代表消息从生产者发送到Exchange;
  2. 代表消息从Exchange路由到Queue;
  3. 代表消息在Queue中存储;
  4. 代表消费者监听Queue并消费消息;

可能因为系统宕机、重启、关闭等等情况导致存储在队列的消息丢失,即③出现问题;

解决方案:

  1. 队列持久化

    java 复制代码
    QueueBuilder.durable("队列名称").build();
  2. 交换机持久化

    java 复制代码
    ExchangeBuilder.directExchange("交换机名称").durable(true).build();
  3. 消息持久化

    默认就是持久化的

    java 复制代码
    MessageProperties messageProperties = new MessageProperties();
    //设置消息持久化,当然它默认就是持久化,所以可以不用设置,可以查看源码
    messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);

【示例】

测试模块:rabbitmq-09-durable-01

引入依赖

xml 复制代码
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

配置MQ

yml 复制代码
server:
  port: 8080

spring:
  application:
    name: durable-learn01

  rabbitmq:
    host: 192.168.1.101
    port: 5672
    username: admin
    password: 123456
    virtual-host: longdidi
    publisher-confirm-type: correlated # 开启生产者的确认模式,设置关联模式
    publisher-returns: true #开启return模式

生产者

发送消息时设置消息持久化

java 复制代码
package com.longdidi.service;

import com.longdidi.constants.RabbitMQConstant;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;

@Service
@Slf4j
public class SendMessageService {

    @Resource
    private RabbitTemplate rabbitTemplate;

    public void sendMsg() {
        MessageProperties messageProperties = new MessageProperties();
        //设置消息持久化,当然它默认就是持久化,所以可以不用设置,可以查看源码
        messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
        Message message = MessageBuilder.withBody("hello world 1".getBytes())
                .andProperties(messageProperties)
                .build();
        rabbitTemplate.convertAndSend(RabbitMQConstant.EXCHANGE_NAME, RabbitMQConstant.ROUTING_NAME1, message);
    }
}

定义MQ

定义队列和交换机持久化

java 复制代码
package com.longdidi.config;

import com.longdidi.constants.RabbitMQConstant;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitConfig {

    /**
     * 正常交换机
     * 使用durable()方法设置持久化
     *
     * @return
     */
    @Bean
    public DirectExchange normalExchange() {
        return ExchangeBuilder.directExchange(RabbitMQConstant.EXCHANGE_NAME).durable(true).build();
    }

    /**
     * 正常队列
     * durable()方法就是持久化
     *
     * @return
     */
    @Bean
    public Queue normalQueue() {
        return QueueBuilder.durable(RabbitMQConstant.QUEUE_NAME1)
                .build();
    }

    /**
     * 正常交换机和正常队列绑定
     *
     * @param normalExchange
     * @param normalQueue
     * @return
     */
    @Bean
    public Binding bindingNormal(DirectExchange normalExchange, Queue normalQueue) {
        return BindingBuilder.bind(normalQueue).to(normalExchange).with(RabbitMQConstant.ROUTING_NAME1);
    }

}

定义常量

java 复制代码
package com.longdidi.constants;

public class RabbitMQConstant {
    // 正常交换机
    public static final String EXCHANGE_NAME = "exchange.durable.normal.1";

    // 队列
    public static final String QUEUE_NAME1 = "queue.durable.normal.1";

    // 路由key
    public static final String ROUTING_NAME1 = "key.durable.normal.1";
}

发送消息

java 复制代码
package com.longdidi;

import com.longdidi.service.SendMessageService;
import jakarta.annotation.Resource;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Rabbitmq09Durable01Application implements ApplicationRunner {

    public static void main(String[] args) {
        SpringApplication.run(Rabbitmq09Durable01Application.class, args);
    }

    @Resource
    private SendMessageService sendMessageService;

    /**
     * 程序一启动就会运行该方法
     *
     * @param args
     * @throws Exception
     */
    @Override
    public void run(ApplicationArguments args) throws Exception {
        sendMessageService.sendMsg();
    }
}

测试

4、消费者手动确认

9.4.1、手动确认消息

采用消息消费时的手动ack确认机制来保证;

如果消费者收到消息后未来得及处理即发生异常,或者处理过程中发生异常,会导致④失败。

为了保证消息从队列可靠地达到消费者,RabbitMQ提供了消息确认机制(message acknowledgement);

properties 复制代码
#开启手动ack消息消费确认
spring.rabbitmq.listener.simple.acknowledge-mode=manual

消费者在订阅队列时,通过上面的配置,不自动确认,采用手动确认,RabbitMQ会等待消费者显式地回复确认信号后才从队列中删除消息;

如果消息消费失败,也可以调用basicReject()或者basicNack()来拒绝当前消息而不是确认。如果requeue参数设置为true,可以把这条消息重新存入队列,以便发给下一个消费者(当然只有一个消费者的时候,这种方式可能会出现无限循环重复消费的情况,可以投递到新的队列中,或者只打印异常日志);

【示例】

测试模块:rabbitmq-09-ack-01

引入依赖

xml 复制代码
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

配置MQ

yml 复制代码
server:
  port: 8080

spring:
  application:
    name: ack-learn01

  rabbitmq:
    host: 192.168.1.101
    port: 5672
    username: admin
    password: 123456
    virtual-host: longdidi
    publisher-confirm-type: correlated # 开启生产者的确认模式,设置关联模式
    publisher-returns: true #开启return模式
    # 开启消费者手动确认
    listener:
      simple:
        acknowledge-mode: manual

生产者

java 复制代码
package com.longdidi.service;

import com.longdidi.constants.RabbitMQConstant;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;

import java.util.Date;

@Service
@Slf4j
public class SendMessageService {
    @Resource
    private RabbitTemplate rabbitTemplate;

    /**
     * 构造方法执行后自动执行
     */
    @PostConstruct
    public void init() {
        //开启生产者的确定模式
        rabbitTemplate.setConfirmCallback(
                (correlationData, ack, cause) -> {
                    if (!ack) {
                        log.error("消息没有到达交换机,原因为:{}", cause);
                        //TODO 重发消息或者记录错误日志
                    }
                }
        );
        // 开启交换机确认模式
        rabbitTemplate.setReturnsCallback(
                returnedMessage -> {
                    log.error("消息没有从交换机正确的投递(路由)到队列,原因为:{}", returnedMessage.getReplyText());
                    //TODO 记录错误日志,给程序员发短信或者或者邮件
                }
        );
    }

    public void sendMsg() {
        MessageProperties messageProperties = new MessageProperties();
        //设置单条消息的持久化,默认就是持久化
        messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
        Message message = MessageBuilder.withBody("hello world".getBytes())
                .andProperties(messageProperties).build();
        rabbitTemplate.convertAndSend(RabbitMQConstant.EXCHANGE_NAME, RabbitMQConstant.ROUTING_NAME1+"1", message);
        log.info("消息发送完毕,发送时间为:{}", new Date());
    }
}

消费者

java 复制代码
package com.longdidi.service;

import com.longdidi.constants.RabbitMQConstant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.io.IOException;

import com.rabbitmq.client.Channel;

@Component
@Slf4j
public class ReceiveMessageService {

    @RabbitListener(queues = {RabbitMQConstant.QUEUE_NAME1})
    public void receiveMsg(Message message, Channel channel) {
        //获取消息的唯一标识
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            log.info("接收到的消息为:{}", new String(message.getBody()));
            // TODO 插入订单等
            //int a=10/0;
            //手动确认
            channel.basicAck(deliveryTag, false);
        } catch (Exception e) {
            log.error("消息处理出现问题");
            try {
                channel.basicNack(deliveryTag, false, true);
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
            throw new RuntimeException(e);
        }
    }
}

定义常量

java 复制代码
package com.longdidi.constants;

public class RabbitMQConstant {
    // 正常交换机
    public static final String EXCHANGE_NAME = "exchange.ack.normal.1";

    // 队列
    public static final String QUEUE_NAME1 = "queue.ack.normal.1";

    // 路由key
    public static final String ROUTING_NAME1 = "key.ack.normal.1";
}

定义MQ

java 复制代码
package com.longdidi.config;

import com.longdidi.constants.RabbitMQConstant;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitConfig {

    /**
     * 正常交换机
     * 使用durable()方法设置持久化
     *
     * @return
     */
    @Bean
    public DirectExchange normalExchange() {
        return ExchangeBuilder.directExchange(RabbitMQConstant.EXCHANGE_NAME).durable(true).build();
    }

    /**
     * 正常队列
     * durable()方法就是持久化
     *
     * @return
     */
    @Bean
    public Queue normalQueue() {
        return QueueBuilder.durable(RabbitMQConstant.QUEUE_NAME1)
                .build();
    }

    /**
     * 正常交换机和正常队列绑定
     *
     * @param normalExchange
     * @param normalQueue
     * @return
     */
    @Bean
    public Binding bindingNormal(DirectExchange normalExchange, Queue normalQueue) {
        return BindingBuilder.bind(normalQueue).to(normalExchange).with(RabbitMQConstant.ROUTING_NAME1);
    }

}

发送消息

java 复制代码
package com.longdidi;

import com.longdidi.service.SendMessageService;
import jakarta.annotation.Resource;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Rabbitmq09Ack01Application implements ApplicationRunner {

    public static void main(String[] args) {
        SpringApplication.run(Rabbitmq09Ack01Application.class, args);
    }

    @Resource
    private SendMessageService sendMessageService;

    /**
     * 程序一启动就会运行该方法
     *
     * @param args
     * @throws Exception
     */
    @Override
    public void run(ApplicationArguments args) throws Exception {
        sendMessageService.sendMsg();
    }
}

9.4.2、消息的幂等性

消息消费时的幂等性(消息不被重复消费)

同一个消息,第一次接收,正常处理业务,如果该消息第二次再接收,那就不能再处理业务,否则就处理重复了;

幂等性是:对于一个资源,不管你请求一次还是请求多次,对该资源本身造成的影响应该是相同的,不能因为重复的请求而对该资源重复造成影响;

以接口幂等性举例:

接口幂等性是指:一个接口用同样的参数反复调用,不会造成业务错误,那么这个接口就是具有幂等性的;

比如同一个订单我支付两次,但是只会扣款一次,第二次支付不会扣款,这说明这个支付接口是具有幂等性的;

如何避免消息的重复消费问题?(消息消费时的幂等性)

全局唯一ID + Redis

生产者在发送消息时,为每条消息设置一个全局唯一的messageId,消费者拿到消息后,使用setnx命令,将messageId作为key放到redis中:setnx(messageId, 1),若返回1,说明之前没有消费过,正常消费;若返回0,说明这条消息之前已消费过,抛弃;

【示例】

测试模块:rabbitmq-09-idempotent-01

引入依赖

xml 复制代码
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
    </dependencies>

配置MQ

yml 复制代码
server:
  port: 8080

spring:
  application:
    name: idempotent-learn01

  rabbitmq:
    host: 192.168.1.101
    port: 5672
    username: admin
    password: 123456
    virtual-host: longdidi
    publisher-confirm-type: correlated # 开启生产者的确认模式,设置关联模式
    publisher-returns: true #开启return模式
    # 开启消费者手动确认
    listener:
      simple:
        acknowledge-mode: manual

  data:
    redis:
      host: 192.168.1.4
      port: 6379
      #password: 123456
      database: 0 # 0号数据库

生产者

java 复制代码
package com.longdidi.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.longdidi.constants.RabbitMQConstant;
import com.longdidi.vo.Orders;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.Date;

@Service
@Slf4j
public class SendMessageService {
    @Resource
    private RabbitTemplate rabbitTemplate;

    //这个对象可以进行序列化和反序列化(json格式)
    @Resource
    private ObjectMapper objectMapper;

    /**
     * 构造方法执行后自动执行
     */
    @PostConstruct
    public void init() {
        //开启生产者的确定模式
        rabbitTemplate.setConfirmCallback(
                (correlationData, ack, cause) -> {
                    if (!ack) {
                        log.error("消息没有到达交换机,原因为:{}", cause);
                        //TODO 重发消息或者记录错误日志
                    }
                }
        );

        rabbitTemplate.setReturnsCallback(
                returnedMessage -> {
                    log.error("消息没有从交换机正确的投递(路由)到队列,原因为:{}", returnedMessage.getReplyText());
                    //TODO 记录错误日志,给程序员发短信或者或者邮件
                }
        );
    }

    public void sendMsg() throws JsonProcessingException {
        {
            //创建订单
            Orders orders1 = Orders.builder()
                    .orderId("order_12345")
                    .orderName("买的手机")
                    .orderMoney(new BigDecimal(2356))
                    .orderTime(new Date())
                    .build();
            //转成json
            String strOrders1 = objectMapper.writeValueAsString(orders1);
            MessageProperties messageProperties = new MessageProperties();
            //设置单条消息的持久化,默认就是持久化
            messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
            Message message = MessageBuilder.withBody(strOrders1.getBytes())
                    .andProperties(messageProperties).build();
            rabbitTemplate.convertAndSend(RabbitMQConstant.EXCHANGE_NAME, RabbitMQConstant.ROUTING_NAME1, message);
        }
        {
            Orders orders2 = Orders.builder()
                    .orderId("order_12345")
                    .orderName("买的手机")
                    .orderMoney(new BigDecimal(2356))
                    .orderTime(new Date())
                    .build();
            String strOrders2 = objectMapper.writeValueAsString(orders2);
            MessageProperties messageProperties = new MessageProperties();
            //设置单条消息的持久化,默认就是持久化
            messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
            Message message = MessageBuilder.withBody(strOrders2.getBytes())
                    .andProperties(messageProperties).build();
            rabbitTemplate.convertAndSend(RabbitMQConstant.EXCHANGE_NAME, RabbitMQConstant.ROUTING_NAME1, message);
        }

        log.info("消息发送完毕,发送时间为:{}", new Date());
    }
}

消费者

java 复制代码
package com.longdidi.service;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.longdidi.constants.RabbitMQConstant;
import com.longdidi.vo.Orders;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.io.IOException;

import com.rabbitmq.client.Channel;

@Component
@Slf4j
public class ReceiveMessageService {
    @Resource
    private ObjectMapper objectMapper;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @RabbitListener(queues = {RabbitMQConstant.QUEUE_NAME1})
    public void receiveMsg(Message message, Channel channel) throws IOException {
        //获取消息的唯一标识
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        //使用objectmapper把字节数组反序列化成对象
        Orders orders = objectMapper.readValue(message.getBody(), Orders.class);

        try {
            log.info("接收到的消息为:{}", orders.toString());
            //如果不存在就在redis中存储
            Boolean setResult = stringRedisTemplate.opsForValue().setIfAbsent("idempotent:" + orders.getOrderId(), orders.getOrderId());
            if (setResult) {
                // TODO 向数据库插入订单等
                log.info("向数据库插入订单");
            }
            //手动确认
            channel.basicAck(deliveryTag, false);
        } catch (Exception e) {
            log.error("消息处理出现问题");
            try {
                channel.basicNack(deliveryTag, false, true);
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
            throw new RuntimeException(e);
        }
    }
}

定义常量

java 复制代码
package com.longdidi.constants;

public class RabbitMQConstant {
    // 正常交换机
    public static final String EXCHANGE_NAME = "exchange.idempotent.normal.1";

    // 队列
    public static final String QUEUE_NAME1 = "queue.idempotent.normal.1";

    // 路由key
    public static final String ROUTING_NAME1 = "key.idempotent.normal.1";
}

定义MQ

java 复制代码
package com.longdidi.config;

import com.longdidi.constants.RabbitMQConstant;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitConfig {

    /**
     * 正常交换机
     * 使用durable()方法设置持久化
     *
     * @return
     */
    @Bean
    public DirectExchange normalExchange() {
        return ExchangeBuilder.directExchange(RabbitMQConstant.EXCHANGE_NAME).durable(true).build();
    }

    /**
     * 正常队列
     * durable()方法就是持久化
     *
     * @return
     */
    @Bean
    public Queue normalQueue() {
        return QueueBuilder.durable(RabbitMQConstant.QUEUE_NAME1)
                .build();
    }

    /**
     * 正常交换机和正常队列绑定
     *
     * @param normalExchange
     * @param normalQueue
     * @return
     */
    @Bean
    public Binding bindingNormal(DirectExchange normalExchange, Queue normalQueue) {
        return BindingBuilder.bind(normalQueue).to(normalExchange).with(RabbitMQConstant.ROUTING_NAME1);
    }

}

实体类

java 复制代码
package com.longdidi.vo;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Orders implements Serializable {
    private String orderId;
    private String orderName;

    private BigDecimal orderMoney;

    private Date orderTime; //下单时间
}

发送消息

java 复制代码
package com.longdidi;

import com.longdidi.service.SendMessageService;
import jakarta.annotation.Resource;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Rabbitmq09Idempotent01Application implements ApplicationRunner {

    public static void main(String[] args) {
        SpringApplication.run(Rabbitmq09Idempotent01Application.class, args);
    }

    @Resource
    private SendMessageService sendMessageService;

    /**
     * 程序一启动就会运行该方法
     *
     * @param args
     * @throws Exception
     */
    @Override
    public void run(ApplicationArguments args) throws Exception {
        sendMessageService.sendMsg();
    }
}

测试

相关推荐
茶杯梦轩2 天前
从零起步学习RabbitMQ || 第三章:RabbitMQ的生产者、Broker、消费者如何保证消息不丢失(可靠性)详解
分布式·后端·面试
回家路上绕了弯3 天前
深入解析Agent Subagent架构:原理、协同逻辑与实战落地指南
分布式·后端
用户8307196840823 天前
Spring Boot 集成 RabbitMQ :8 个最佳实践,杜绝消息丢失与队列阻塞
spring boot·后端·rabbitmq
用户8307196840825 天前
RabbitMQ vs RocketMQ 事务大对决:一个在“裸奔”,一个在“开挂”?
后端·rabbitmq·rocketmq
初次攀爬者6 天前
RabbitMQ的消息模式和高级特性
后端·消息队列·rabbitmq
初次攀爬者8 天前
ZooKeeper 实现分布式锁的两种方式
分布式·后端·zookeeper
让我上个超影吧9 天前
消息队列——RabbitMQ(高级)
java·rabbitmq
塔中妖10 天前
Windows 安装 RabbitMQ 详细教程(含 Erlang 环境配置)
windows·rabbitmq·erlang
断手当码农10 天前
Redis 实现分布式锁的三种方式
数据库·redis·分布式
初次攀爬者10 天前
Redis分布式锁实现的三种方式-基于setnx,lua脚本和Redisson
redis·分布式·后端