RabbitMQ SpringBoot整合

SrpingBoot -RabbitMQ 整合

注解整合,五种常用模式,producer生产者消息发送确认,customer消费者收到消息确认

RabbitMQ 五种模式初体验

一.Producer 生产者搭建

1.pom引入依赖

springboot项目为底层框架添加rabbitmq需要的依赖

 1   <!--引入rabbitmq集成的依赖-->
 2         <dependency>
 3             <groupId>org.springframework.boot</groupId>
 4             <artifactId>spring-boot-starter-amqp</artifactId>
 5             <version>2.1.7.RELEASE</version>
 6         </dependency>
 7   <!--JSON依赖-->
 8         <dependency>
 9             <groupId>com.alibaba</groupId>
10             <artifactId>fastjson</artifactId>
11             <version>1.2.78</version>
12         </dependency>

 

2.配置application.yml 配置文件

配置producer 开启消息确认,回退,重试策略

spring:
  application:
    name: rabbitmq-springboot
  rabbitmq:
    host: 192.168.31.177
    port: 5672
    username: admin
    password: admin
    virtual-host: /
    # 开启回退模式 消息从 exchange 到 queue 投递失败有一个 returnCallback 退回模式。
    publisher-returns: true
    # 开启发布确认 消息从 producer 到 rabbitmq broker有一个 confirmCallback 确认模式。
    publisher-confirm-type: correlated
    listener:
      simple:
        acknowledge-mode: auto
        retry:
          #开启重试
          enabled: true
          #最大重试次数
          max-attempts: 3
          #重试间隔
          max-interval: 1000ms
server:
  port: 8888

 

二.Customer消费者搭建

1.pom引入依赖

springboot项目为底层框架添加rabbitmq需要的依赖

<!--引入rabbitmq集成的依赖-->       
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
<!--引入阿里mq依赖-->
        <dependency>
            <groupId>com.alibaba.mq-amqp</groupId>
            <artifactId>mq-amqp-client</artifactId>
            <version>1.0.3</version>
        </dependency>
<!--引入JSON-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.73</version>
        </dependency>

2.配置application.yml 配置文件

spring:
  #rabbitmq
  rabbitmq:
    #    host: 192.168.31.189
    host: 192.168.31.177
    port: 5672
    virtual-host: /
    username: admin
    password: admin
    listener:
      simple:
#手动确认消息 ack acknowledge
-mode: manual retry: enabled: true max-attempts: 3 max-interval: 1000ms

 

三.Producer生产者消息发送确认

发送消息确认:用来确认生产者 producer 将消息发送到 broker ,broker 上的交换机 exchange 再投递给队列 queue的过程中,消息是否成功投递。

消息从 producer 到 rabbitmq broker有一个 confirmCallback 确认模式。

消息从 exchange 到 queue 投递失败有一个 returnCallback 退回模式。

我们可以利用这两个Callback来确保消的100%送达。

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


/**
 * @Author: GG
 * @Date: 2022/2/28 16:21
 */
@Component
@Slf4j
public class MqProducerCallBack implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnsCallback {
    @Override
    public void confirm(CorrelationData correlationData,boolean ack, String cause) {
        /**
         * correlationData:对象内部只有一个 id 属性,用来表示当前消息的唯一性。
         * ack:消息投递到broker 的状态,true表示成功。
         * cause:表示投递失败的原因。
         */
        if (!ack) {
            log.error("消息发送异常!");
        } else {
            log.info("发送者已经收到确认,correlationData={} ,ack={}, cause={}", correlationData.getId(), ack, cause);
        }
    }

    @Override
    public void returnedMessage(ReturnedMessage returnedMessage) {
        System.err.println("ReturnedMessage: " + returnedMessage);
        System.out.println("退回的消息是:"+new String(returnedMessage.getMessage().getBody()));
        System.out.println("退回的replyCode是:"+returnedMessage.getReplyCode());
        System.out.println("退回的replyText是:"+returnedMessage.getReplyText());
        System.out.println("退回的exchange是:"+returnedMessage.getExchange());
        System.out.println("退回的routingKey是:"+returnedMessage.getRoutingKey());
    }
}

 

四.work模型

work-Producer 生产者

1.在hello world模型上的升级,不需要交换机绑定,可以有多个消费者消费消息默认情况下,消息的分发平均分配到每个不同的消费者上* 可以进行“能者多劳”的设置,即:那个消费者线程处理的快,就可在获得同一队列更多的消息进行处理

配置work模式 config类,项目运行自动创建队列queue

import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

/**
 * @Author: GG
 * @Date: 2022/3/7 11:23
 */
@Component
public class WorkExchangeQueueConfig {
    public final static String TEST_WORK_QUEUE="TEST_WORK_QUEUE";

    /**
     * work queue 模型
     */
    @Bean
    Queue workQueue(){
        return new Queue(TEST_WORK_QUEUE);
    }

};

2.world模型消息发送

rabbitTemplate:调用消息确认
rabbitTemplate.setConfirmCallback(mqFanoutCallBack);
rabbitTemplate.setReturnsCallback(mqFanoutCallBack);
rabbitTemplate.convertAndSend(队列,msg消息,MessagePostProcessor,CorrelationData:唯一ID)
/**
 * @Author: GG
 * @Date: 2022/2/28 15:52
 */
@RestController
@AllArgsConstructor
public class TestController {

    private final RabbitTemplate rabbitTemplate;
    private final MqFanoutCallBack mqFanoutCallBack;
 @GetMapping("/Work")
    public String test3(){
        for (int i = 0; i < 5; i++) {
            User user = User.builder()
                    .name("Work" + i)
                    .build();
            String msg = JSONObject.toJSONString(user);
            CorrelationData correlationData = new CorrelationData("id_" + System.currentTimeMillis() + "");
            rabbitTemplate.setConfirmCallback(mqFanoutCallBack);
            rabbitTemplate.setReturnsCallback(mqFanoutCallBack);
            rabbitTemplate.convertAndSend(WorkExchangeQueueConfig.TEST_WORK_QUEUE, msg, message -> {
                message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
                return message;
            },correlationData);
        }
        return "———————————Work—————————Work—————————————";
    }
}

work-Producer Customer 消费者

配置另外一个springboot Customer 消费者项目 获取RabbitMq消息

/**
 * @Author: GG
 * @Date: 2022/2/22 14:04
 */
public class ExchangeQueueConstant {
    /**
     * QUEUE_xxx_xxx
     * Queue 队列
     */
    public static final String TEST_WORK_QUEUE="TEST_WORK_QUEUE";
}

2. 消息监听

注解监听消息

@RabbitListener(queues = "queue_hf_send_message")
/**
 * @Author: GG
 * @Date: 2022/2/16 14:06
 */
@Component
@Slf4j
public class TestRabbitMqReceiver {

 @RabbitListener(queuesToDeclare = @Queue(value = "TEST_WORK_QUEUE"))
    @RabbitHandler
    public void testWork1(String msg, Channel channel, Message message) throws IOException {
        try {
            log.info("小富收到消息:", msg);
            User user = JSON.parseObject(msg, User.class);

            /**
             * basicAck:表示成功确认,使用此回执方法后,消息会被rabbitmq broker 删除。
             * void basicAck(long deliveryTag, boolean multiple)
             * deliveryTag:表示消息投递序号,每次消费消息或者消息重新投递后,deliveryTag都会增加。手动消息确认模式下,我们可以对指定deliveryTag的消息进行ack、nack、reject等操作。
             * multiple:是否批量确认,值为 true 则会一次性 ack所有小于当前消息 deliveryTag 的消息。
             * */
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            System.out.println("进入try____________" + user);
            System.out.println("22222222222__________" + message.getMessageProperties().getDeliveryTag());
            System.out.println("3333333333__________" + message.getMessageProperties().getRedelivered());
            //TODO 具体业务
        } catch (Exception e) {
            if (message.getMessageProperties().getRedelivered()) {
                System.out.println("消息已重复处理失败,拒绝再次接收!");
                // 拒绝消息,requeue=false 表示不再重新入队,如果配置了死信队列则进入死信队列
                /**
                 * basicReject:拒绝消息,与basicNack区别在于不能进行批量操作,其他用法很相似。
                 * deliveryTag:表示消息投递序号。
                 * requeue:值为 true 消息将重新入队列。
                 */
                channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
            } else {
                System.out.println("消息即将再次返回队列处理!");
                // requeue为是否重新回到队列,true重新入队
                /**
                 * deliveryTag:表示消息投递序号。
                 * multiple:是否批量确认。
                 * requeue:值为 true 消息将重新入队列。
                 */
                channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
            }
        }
    }

}

END 

WORK 模型运行结果

1.Producer 执行

发送五条消息

ID:correlationData=id_1646727964029

ack状态:ack=true  发送成功

cause:表示投递失败的原因

2.RabbitMQ 消息状态

消息详细属性————点此了解属性含义

5条消息已进入RabbitMQ中

3.Customer消息接收

启动 customer消息消费者项目接收消息

接收到5条消息

此时RabbitMQ中的消息已经被消费

 

五.Fanout发布订阅模式

Fanout Producer 生产者

1.发送消息

FanoutConfig

声明交换机 声明队列 队列绑定交换机,一个交换机可以有多个队列queue共同消费同一批消息,分享不共有

/**
 * @Author: GG
 * @Date: 2022/2/28 17:18
 */
@Configuration
public class FanoutExchangeQueueConfig {

    //fanoutQueue
    public final static String TEST_FANOUT_SEND_MESSAGES_QUEUE = "TEST_FANOUT_SEND_MESSAGES_QUEUE";
    //fanoutExchange
    public final static String TEST_FANOUT_SEND_MESSAGES_EXCHANGE = "TEST_FANOUT_SEND_MESSAGES_EXCHANGE";

    /**
     * 声明fanout 队列
     */
    @Bean
    public Queue testQueue() {
        return new Queue(TEST_FANOUT_SEND_MESSAGES_QUEUE);
    }
    /**
     * 声明fanout交换机
     */
    @Bean
    public FanoutExchange fanoutExchange() {
        return new FanoutExchange(TEST_FANOUT_SEND_MESSAGES_EXCHANGE);
    }

    @Bean
    Binding bindingFanoutExchangeOrderDicQueue(){
        return BindingBuilder.bind(testQueue()).to(fanoutExchange());
    }

}

发送消息

 @GetMapping("/fanout")
    public String test1(){
        for (int i = 0; i < 5; i++) {
            User user = User.builder()
                    .name("王五" + i)
                    .build();
            String msg = JSONObject.toJSONString(user);
            CorrelationData correlationData = new CorrelationData("id_" + System.currentTimeMillis() + "");
            rabbitTemplate.setConfirmCallback(mqFanoutCallBack);
            rabbitTemplate.setReturnsCallback(mqFanoutCallBack);
            rabbitTemplate.convertAndSend(FanoutExchangeQueueConfig.TEST_FANOUT_SEND_MESSAGES_EXCHANGE, FanoutExchangeQueueConfig.TEST_FANOUT_SEND_MESSAGES_QUEUE, msg, message -> {
                message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
                return message;
            },correlationData);
        }
        return "———————————fanout—————————fanout—————————————";
    }

 Fanout Customer 消费者 消费消息

发布订阅模型-接收消息 

 @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue(value = "TEST_FANOUT_SEND_MESSAGES_QUEUE"),
                    exchange = @Exchange(value = "TEST_FANOUT_SEND_MESSAGES_EXCHANGE", type = "fanout")//绑定交换机
            )
    })
    public void testFanout(String msg, Channel channel, Message message) throws IOException {
        try {
            log.info("小富收到消息:", msg);
            User user = JSON.parseObject(msg, User.class);

            /**
             * basicAck:表示成功确认,使用此回执方法后,消息会被rabbitmq broker 删除。
             * void basicAck(long deliveryTag, boolean multiple)
             * deliveryTag:表示消息投递序号,每次消费消息或者消息重新投递后,deliveryTag都会增加。手动消息确认模式下,我们可以对指定deliveryTag的消息进行ack、nack、reject等操作。
             * multiple:是否批量确认,值为 true 则会一次性 ack所有小于当前消息 deliveryTag 的消息。
             * */
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            System.out.println("进入try____________" + user);
            System.out.println("22222222222__________" + message.getMessageProperties().getDeliveryTag());
            System.out.println("3333333333__________" + message.getMessageProperties().getRedelivered());
            //TODO 具体业务
        } catch (Exception e) {
            if (message.getMessageProperties().getRedelivered()) {
                System.out.println("消息已重复处理失败,拒绝再次接收!");
                // 拒绝消息,requeue=false 表示不再重新入队,如果配置了死信队列则进入死信队列
                /**
                 * basicReject:拒绝消息,与basicNack区别在于不能进行批量操作,其他用法很相似。
                 * deliveryTag:表示消息投递序号。
                 * requeue:值为 true 消息将重新入队列。
                 */
                channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
            } else {
                System.out.println("消息即将再次返回队列处理!");
                // requeue为是否重新回到队列,true重新入队
                /**
                 * deliveryTag:表示消息投递序号。
                 * multiple:是否批量确认。
                 * requeue:值为 true 消息将重新入队列。
                 */
                channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
            }
        }
    }

 

六.Direct 直连交换机

Direct  Producer 生产者

创建交换机,队列。队列绑定到交换机

@Bean("directExchange")
@Qualifier("directExchange") DirectExchange exchange
防止Bean加载时重复
/**
 * @Author: GG
 * @Date: 2022/3/7 10:34
 */
@Component
public class DirectExchangeQueueConfig {
    public final static String TEST_DIRECT_QUEUE="TEST_DIRECT_QUEUE";
    public final static String TEST_DIRECT_EXCHANGE="TEST_DIRECT_EXCHANGE";

    @Bean
    public Queue testDirectQueue(){
        return new Queue(TEST_DIRECT_QUEUE);
    }

    @Bean("directExchange")
    DirectExchange directExchange(){
        return new DirectExchange(TEST_DIRECT_EXCHANGE);
    }

    @Bean
    Binding bingDirectExchange(Queue testDirectQueue, @Qualifier("directExchange") DirectExchange exchange){
        return BindingBuilder.bind(testDirectQueue).to(exchange).with(TEST_DIRECT_QUEUE);
    }

}

发送消息

 @GetMapping("/Direct")
    public String test2(){
        for (int i = 0; i < 5; i++) {
            User user = User.builder()
                    .name("Direct" + i)
                    .build();
            String msg = JSONObject.toJSONString(user);
            CorrelationData correlationData = new CorrelationData("id_" + System.currentTimeMillis() + "");
            rabbitTemplate.setConfirmCallback(mqFanoutCallBack);
            rabbitTemplate.setReturnsCallback(mqFanoutCallBack);
            rabbitTemplate.convertAndSend(DirectExchangeQueueConfig.TEST_DIRECT_EXCHANGE,DirectExchangeQueueConfig.TEST_DIRECT_QUEUE, msg, message -> {
                message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
                return message;
            },correlationData);
        }
        return "———————————Direct—————————Direct—————————————";
    }

Direct Customer 消费者 消费消息

注解

@RabbitListener  ---exchange 不用指定type类型 默认为 direct类型
  @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue(value = "TEST_DIRECT_QUEUE"),
                    exchange = @Exchange(value = "TEST_DIRECT_EXCHANGE")//绑定交换机
            )
    })
    public void testDirect(String msg, Channel channel, Message message) throws IOException {
        try {
            log.info("小富收到消息:", msg);
            User user = JSON.parseObject(msg, User.class);

            /**
             * basicAck:表示成功确认,使用此回执方法后,消息会被rabbitmq broker 删除。
             * void basicAck(long deliveryTag, boolean multiple)
             * deliveryTag:表示消息投递序号,每次消费消息或者消息重新投递后,deliveryTag都会增加。手动消息确认模式下,我们可以对指定deliveryTag的消息进行ack、nack、reject等操作。
             * multiple:是否批量确认,值为 true 则会一次性 ack所有小于当前消息 deliveryTag 的消息。
             * */
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            System.out.println("进入try____________" + user);
            System.out.println("22222222222__________" + message.getMessageProperties().getDeliveryTag());
            System.out.println("3333333333__________" + message.getMessageProperties().getRedelivered());
            //TODO 具体业务
        } catch (Exception e) {
            if (message.getMessageProperties().getRedelivered()) {
                System.out.println("消息已重复处理失败,拒绝再次接收!");
                // 拒绝消息,requeue=false 表示不再重新入队,如果配置了死信队列则进入死信队列
                /**
                 * basicReject:拒绝消息,与basicNack区别在于不能进行批量操作,其他用法很相似。
                 * deliveryTag:表示消息投递序号。
                 * requeue:值为 true 消息将重新入队列。
                 */
                channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
            } else {
                System.out.println("消息即将再次返回队列处理!");
                // requeue为是否重新回到队列,true重新入队
                /**
                 * deliveryTag:表示消息投递序号。
                 * multiple:是否批量确认。
                 * requeue:值为 true 消息将重新入队列。
                 */
                channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
            }
        }
    }

 

七.Routing路由模型

Routing Producer 生产者

路由模式: 一个生产者,多个消费者。需要设置类型为direct的交换机,交换机和队列进行绑定,并且指定routing key,当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列

RoutingExchangeQueueConfig

创建两个队列 一个交换机,两个Routing key 路由: info error

/**
 * @Author: GG
 * @Date: 2022/3/7 13:19
 */
@Component
public class RoutingExchangeQueueConfig {
    public final static String TEST_ROUTING_QUEUE_A = "TEST_ROUTING_QUEUE";
    public final static String TEST_ROUTING_QUEUE_B = "TEST_ROUTING_QUEUE_B";
    public final static String TEST_ROUTING_EXCHANGE = "TEST_ROUTING_EXCHANGE";

    /**
     * 路由模式: 一个生产者,多个消费者。需要设置类型为direct的交换机,
     * 交换机和队列进行绑定,并且指定routing key,当发送消息到交换机后,
     * 交换机会根据routing key将消息发送到对应的队列
     */

    @Bean
    public Queue routingQueueA() {
        return new Queue(TEST_ROUTING_QUEUE_A);
    }

    @Bean
    public Queue routingQueueB() {
        return new Queue(TEST_ROUTING_QUEUE_B);
    }

    @Bean("routingExchange")
    public DirectExchange routingExchange() {
        // 创建direct类型交换机,表示与此交换机会将消息发送给 routing_key 完全相同的队列
        return new DirectExchange(TEST_ROUTING_EXCHANGE);
    }

    @Bean
    public Binding bindExchangeQueueA(Queue routingQueueA,@Qualifier("routingExchange") DirectExchange routingExchange) {
        // 队列二绑定direct交换机,并设置 routing_key 为 routing_second_queue_routing_key
        return BindingBuilder.bind(routingQueueA).to(routingExchange).with("info");
    }

    @Bean
    public Binding bindExchangeQueueB(Queue routingQueueB,@Qualifier("routingExchange") DirectExchange routingExchange) {
        // 队列二绑定direct交换机,并设置 routing_key 为 routing_second_queue_routing_key
        return BindingBuilder.bind(routingQueueB).to(routingExchange).with("info,error");
    }

}

发送消息

   @GetMapping("/Routing")
    public String test4(){
        for (int i = 0; i < 2; i++) {
            User user = User.builder()
                    .name("Routing" + i)
                    .build();
            String msg = JSONObject.toJSONString(user);
            CorrelationData correlationData = new CorrelationData("id_" + System.currentTimeMillis() + "");
            rabbitTemplate.setConfirmCallback(mqFanoutCallBack);
            rabbitTemplate.setReturnsCallback(mqFanoutCallBack);
            rabbitTemplate.convertAndSend(RoutingExchangeQueueConfig.TEST_ROUTING_EXCHANGE,"info",msg,message -> {
                message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
                return message;
            },correlationData);
        }
        return "———————————Routing—————————Routing—————————————";
    }

Routing Customer 消费者 消费消息

同一个交换机下 三个监听者,ABC  

路由Ruouting Key 不同消费消息不同。但是同一路由下则为分配共同的消息 

 /**
      *  rouing 路由模式 同一路由下 接收相同的数据。
     */

        @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue(value = "TEST_ROUTING_QUEUE"),
                    exchange = @Exchange(value = "TEST_ROUTING_EXCHANGE",type = "direct"),//绑定交换机 //默认直连 direct
                    key = {"info"}
            )
    })
    public void testRouting1(String msg, Channel channel, Message message) throws IOException {
        try {
            log.info("小富收到消息:", msg);
            User user = JSON.parseObject(msg, User.class);
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            System.out.println("进入BBB____________" + user);
            //TODO 具体业务
        } catch (Exception e) {
            if (message.getMessageProperties().getRedelivered()) {
                System.out.println("消息已重复处理失败,拒绝再次接收!");
                // 拒绝消息,requeue=false 表示不再重新入队,如果配置了死信队列则进入死信队列
                channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
            } else {
                System.out.println("消息即将再次返回队列处理!");
                // requeue为是否重新回到队列,true重新入队
                channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
            }
        }
    }

        @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue(value = "TEST_ROUTING_QUEUE_B"),
                    exchange = @Exchange(value = "TEST_ROUTING_EXCHANGE",type = "direct"),//绑定交换机 //默认直连 direct
                    key = {"info","error"}
            )
    })
    public void testWorkRouting2(String msg, Channel channel, Message message) throws IOException {
        try {
            log.info("小富收到消息:", msg);
            User user = JSON.parseObject(msg, User.class);
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            System.out.println("进入AAAA____________" + user);
            //TODO 具体业务
        } catch (Exception e) {
            if (message.getMessageProperties().getRedelivered()) {
                System.out.println("消息已重复处理失败,拒绝再次接收!");
                // 拒绝消息,requeue=false 表示不再重新入队,如果配置了死信队列则进入死信队列
                channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
            } else {
                System.out.println("消息即将再次返回队列处理!");
                // requeue为是否重新回到队列,true重新入队
                channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
            }
        }
    }
    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue(value = "TEST_ROUTING_QUEUE_B"),
                    exchange = @Exchange(value = "TEST_ROUTING_EXCHANGE",type = "direct"),//绑定交换机 //默认直连 direct
                    key = {"info","error"}
            )
    })
    public void testWorkRouting3(String msg, Channel channel, Message message) throws IOException {
        try {
            log.info("小富收到消息:", msg);
            User user = JSON.parseObject(msg, User.class);
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            System.out.println("进入CCC____________" + user);
            //TODO 具体业务
        } catch (Exception e) {
            if (message.getMessageProperties().getRedelivered()) {
                System.out.println("消息已重复处理失败,拒绝再次接收!");
                channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
            } else {
                System.out.println("消息即将再次返回队列处理!");
                channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
            }
        }
    }
    /**
     * 路由模式 end
     * 一个交换机多个队列 监听不同key 下的数据
     * 同一个队列多个消费者:数据分配消费
     */

END

Routing Producer 生产者消息发送成功

Routing Customer 消费消息

三个消费者 B消费者独自消费两条消息。AC 一人消费一条消息(AC监听同一个路由)

 

八.Topic模型 

Producer 生产者

TopicExchangeQueueConfig
设置一个交换机 三个队列 三个路由 one,two,three
/**
 * @Author: GG
 * @Date: 2022/3/7 17:39
 */
@Component
public class TopicExchangeQueueConfig {
    public final static String TEST_TOPIC_QUEUE_A ="TEST_TOPIC_QUEUE_A";
    public final static String TEST_TOPIC_QUEUE_B ="TEST_TOPIC_QUEUE_B";
    public final static String TEST_TOPIC_QUEUE_C ="TEST_TOPIC_QUEUE_C";
    public final static String TEST_TOPIC_EXCHANGE="TEST_TOPIC_EXCHANGE";

    @Bean
    public Queue topicQueueA(){
        return new Queue(TEST_TOPIC_QUEUE_A);
    }
    @Bean
    public Queue topicQueueB(){
        return new Queue(TEST_TOPIC_QUEUE_B);
    }
    @Bean
    public Queue topicQueueC(){
        return new Queue(TEST_TOPIC_QUEUE_C);
    }
    @Bean("topicExchange")
    public TopicExchange topicExchange(){
        return new TopicExchange(TEST_TOPIC_EXCHANGE);
    }
    @Bean
    public Binding topicExchangeBindingA(Queue topicQueueA,@Qualifier("topicExchange") TopicExchange topicExchange){
        return BindingBuilder.bind(topicQueueA).to(topicExchange).with("one");
    }
    @Bean
    public Binding topicExchangeBindingB(Queue topicQueueB,@Qualifier("topicExchange") TopicExchange topicExchange){
        return BindingBuilder.bind(topicQueueB).to(topicExchange).with("two");
    }
    @Bean
    public Binding topicExchangeBindingC(Queue topicQueueC,@Qualifier("topicExchange") TopicExchange topicExchange){
        return BindingBuilder.bind(topicQueueC).to(topicExchange).with("three");
    }

}

发送消息 

指定交换机,不用指定队列

路由为 one

    @GetMapping("/Topic")
    public String test5(){
        for (int i = 0; i < 3; i++) {
            User user = User.builder()
                    .name("Topic" + i)
                    .build();
            String msg = JSONObject.toJSONString(user);
            CorrelationData correlationData = new CorrelationData("id_" + System.currentTimeMillis() + "");
            rabbitTemplate.setConfirmCallback(mqFanoutCallBack);
            rabbitTemplate.setReturnsCallback(mqFanoutCallBack);
            rabbitTemplate.convertAndSend(TopicExchangeQueueConfig.TEST_TOPIC_EXCHANGE,"one",msg, message -> {
                message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
                return message;
            },correlationData);
        }
        return "———————————Topic—————————Topic—————————————";
    }

Topic Customer 消费者 消费消息

Topic exchange 交换机type类型设置为 topic

同一个exchange交换机 三个不同队列,路由为 one、two、one/three
producer 发送消息 customer根据路由接收对应消息
 /**
     * Topic 模式star
     */
    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue(value = "TEST_TOPIC_QUEUE_A"),
                    exchange = @Exchange(value = "TEST_TOPIC_EXCHANGE",type = "topic"),//绑定交换机 //默认直连 direct
                    key = {"one"}
            )
    })
    public void testWorkTopic1(String msg, Channel channel, Message message) throws IOException {
        try {
            log.info("小富收到消息:", msg);
            User user = JSON.parseObject(msg, User.class);
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            System.out.println("进入one____________" + user);
            System.out.println("WWWWWWWWWWWW__________" + message.getMessageProperties().getDeliveryTag());
            System.out.println("CCCCCCCCCCCCC__________" + message.getMessageProperties().getRedelivered());
            //TODO 具体业务
        } catch (Exception e) {
            if (message.getMessageProperties().getRedelivered()) {
                System.out.println("消息已重复处理失败,拒绝再次接收!");
                // 拒绝消息,requeue=false 表示不再重新入队,如果配置了死信队列则进入死信队列
                channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
            } else {
                System.out.println("消息即将再次返回队列处理!");
                // requeue为是否重新回到队列,true重新入队
                channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
            }
        }
    }

    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue(value = "TEST_TOPIC_QUEUE_B"),
                    exchange = @Exchange(value = "TEST_TOPIC_EXCHANGE",type = "topic"),//绑定交换机 //默认直连 direct
                    key = {"two"}
            )
    })
    public void testWorkTopic2(String msg, Channel channel, Message message) throws IOException {
        try {
            log.info("小富收到消息:", msg);
            User user = JSON.parseObject(msg, User.class);
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            System.out.println("进入two____________" + user);
            System.out.println("WWWWWWWWWWWW__________" + message.getMessageProperties().getDeliveryTag());
            System.out.println("CCCCCCCCCCCCC__________" + message.getMessageProperties().getRedelivered());
            //TODO 具体业务
        } catch (Exception e) {
            if (message.getMessageProperties().getRedelivered()) {
                System.out.println("消息已重复处理失败,拒绝再次接收!");
                // 拒绝消息,requeue=false 表示不再重新入队,如果配置了死信队列则进入死信队列
                channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
            } else {
                System.out.println("消息即将再次返回队列处理!");
                // requeue为是否重新回到队列,true重新入队
                channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
            }
        }
    }


    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue(value = "TEST_TOPIC_QUEUE_C"),
                    exchange = @Exchange(value = "TEST_TOPIC_EXCHANGE",type = "topic"),//绑定交换机 //默认直连 direct
                    key = {"three","one"}
            )
    })
    public void testWorkTopic3(String msg, Channel channel, Message message) throws IOException {
        try {
            log.info("小富收到消息:", msg);
            User user = JSON.parseObject(msg, User.class);
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            System.out.println("进入three____________" + user);
            System.out.println("WWWWWWWWWWWW__________" + message.getMessageProperties().getDeliveryTag());
            System.out.println("CCCCCCCCCCCCC__________" + message.getMessageProperties().getRedelivered());
            //TODO 具体业务
        } catch (Exception e) {
            if (message.getMessageProperties().getRedelivered()) {
                System.out.println("消息已重复处理失败,拒绝再次接收!");
                // 拒绝消息,requeue=false 表示不再重新入队,如果配置了死信队列则进入死信队列
                channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
            } else {
                System.out.println("消息即将再次返回队列处理!");
                // requeue为是否重新回到队列,true重新入队
                channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
            }
        }
    }

END

运行结果

Topic Producer 生产者

发送成功三条消息

Topic Customer 消费者

topic one / two 都接收到三条消息

 

 END



消息确认详解
一.消息发送确认

发送消息确认:用来确认生产者 producer 将消息发送到 broker ,broker 上的交换机 exchange 再投递给队列 queue的过程中,消息是否成功投递。

消息从 producer 到 rabbitmq broker有一个 confirmCallback 确认模式。

消息从 exchange 到 queue 投递失败有一个 returnCallback 退回模式。

我们可以利用这两个Callback来确保消的100%送达。

@Slf4j
@Component
public class ConfirmCallbackService implements RabbitTemplate.ConfirmCallback {
    
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {

        if (!ack) {
            log.error("消息发送异常!");
        } else {
            log.info("发送者爸爸已经收到确认,correlationData={} ,ack={}, cause={}", correlationData.getId(), ack, cause);
        }
    }
}

实现接口 ConfirmCallback ,重写其confirm()方法,方法内有三个参数correlationDataackcause

  • correlationData:对象内部只有一个 id 属性,用来表示当前消息的唯一性。
  • ack:消息投递到broker 的状态,true表示成功。
  • cause:表示投递失败的原因。

但消息被 broker 接收到只能表示已经到达 MQ服务器,并不能保证消息一定会被投递到目标 queue 里。所以接下来需要用到 returnCallback 。


 ReturnCallback 退回模式

如果消息未能投递到目标 queue 里将触发回调 returnCallback ,一旦向 queue 投递消息未成功,这里一般会记录下当前消息的详细投递数据,方便后续做重发或者补偿等操作。


@Slf4j
@Component
public class ReturnCallbackService implements RabbitTemplate.ReturnCallback {

    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        log.info("returnedMessage ===> replyCode={} ,replyText={} ,exchange={} ,routingKey={}", replyCode, replyText, exchange, routingKey);
    }
}

实现接口ReturnCallback,重写 returnedMessage() 方法,方法有五个参数message(消息体)、replyCode(响应code)、replyText(响应内容)、exchange(交换机)、routingKey(队列)。

下边是具体的消息发送,在rabbitTemplate中设置 Confirm 和 Return 回调,我们通过setDeliveryMode()对消息做持久化处理,为了后续测试创建一个 CorrelationData对象,添加一个id 为10000000000


@Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private ConfirmCallbackService confirmCallbackService;
    @Autowired
    private ReturnCallbackService returnCallbackService;
    public void sendMessage(String exchange, String routingKey, Object msg) {
        /**
         * 确保消息发送失败后可以重新返回到队列中
         * 注意:yml需要配置 publisher-returns: true
         */
        rabbitTemplate.setMandatory(true);
        /**
         * 消费者确认收到消息后,手动ack回执回调处理
         */
        rabbitTemplate.setConfirmCallback(confirmCallbackService);
        /**
         * 消息投递到队列失败回调处理
         */
        rabbitTemplate.setReturnCallback(returnCallbackService);
        /**
         * 发送消息
         */
        rabbitTemplate.convertAndSend(exchange, routingKey, msg,
                message -> {
                    message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
                    return message;
                },
                new CorrelationData(UUID.randomUUID().toString()));
    }

 

二.消息接收确认
消息接收确认要比消息发送确认简单一点,因为只有一个消息回执(ack)的过程。使用@RabbitHandler注解标注的方法要增加 channel(信道)、message 两个参数。
@Slf4j
@Component
@RabbitListener(queues = "confirm_test_queue")
public class ReceiverMessage1 {
    
    @RabbitHandler
    public void processHandler(String msg, Channel channel, Message message) throws IOException {
        try {
            log.info("小富收到消息:{}", msg);
            //TODO 具体业务           
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); } catch (Exception e) { if (message.getMessageProperties().getRedelivered()) { log.error("消息已重复处理失败,拒绝再次接收...");
channel.basicReject(message.getMessageProperties().getDeliveryTag(), false); // 拒绝消息 } else { log.error("消息即将再次返回队列处理...");
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true); } } } }

消费消息有三种回执方法,我们来分析一下每种方法的含义。

1、basicAck

basicAck:表示成功确认,使用此回执方法后,消息会被rabbitmq broker 删除。

void basicAck(long deliveryTag, boolean multiple) 

deliveryTag:表示消息投递序号,每次消费消息或者消息重新投递后,deliveryTag都会增加。手动消息确认模式下,我们可以对指定deliveryTag的消息进行acknackreject等操作。

multiple:是否批量确认,值为 true 则会一次性 ack所有小于当前消息 deliveryTag 的消息。

举个栗子: 假设我先发送三条消息deliveryTag分别是5、6、7,可它们都没有被确认,当我发第四条消息此时deliveryTag为8,multiple设置为 true,会将5、6、7、8的消息全部进行确认。

2、basicNack

basicNack :表示失败确认,一般在消费消息业务异常时用到此方法,可以将消息重新投递入队列。

void basicNack(long deliveryTag, boolean multiple, boolean requeue)

deliveryTag:表示消息投递序号。

multiple:是否批量确认。

requeue:值为 true 消息将重新入队列。

3、basicReject

basicReject:拒绝消息,与basicNack区别在于不能进行批量操作,其他用法很相似。

void basicReject(long deliveryTag, boolean requeue)

deliveryTag:表示消息投递序号。

requeue:值为 true 消息将重新入队列。

 

 

posted @ 2022-03-08 18:30  RunawayProgrammer  阅读(2222)  评论(0编辑  收藏  举报