三、SpringBoot整合RabbitMQ

一、SpringBoot整合RabbitMQ

1、环境准备

1、由于在上一篇中的创建项目是SpringBoot的,直接修改RabbitMQ依赖即可,删除掉之前使用的RabbitMQ依赖

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-amqpartifactId>
dependency>
2、在yml中添加RabbitMQ的配置
server:
  port: 9000

spring:
  rabbitmq:
    # 主机地址
    host: 101.26.156.147
    # 虚拟主机
    virtual-host: test_host
    # 端口号默认是5672,可以不写
    port: 5672
    # 用户名
    username: test
    # 密码
    password: 123456
3、启动类
@ComponentScan(basePackages = {"com.itan.*"}) //扫描含有@Configuration的类,并使其生效
@SpringBootApplication
public class RabbitmqApplication {
    public static void main(String[] args) {
        SpringApplication.run(RabbitmqApplication.class, args);
    }
}

2、创建交换器与队列并实现绑定关系

1、实现下图中的关系,先将RabbitMQ中已经创建的删除掉

三、SpringBoot整合RabbitMQ_第1张图片

import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;

/**
 * @Date: 2022/3/29
 * 创建交换器、队列及绑定关系,放入容器中,项目启动自动创建,一旦RabbitMQ中有要创建的交换器、队列,即使修改属性之后,也不会更改,需要删除再创建
 */
@Configuration
public class RabbitMQInitConfig {
    private static final String NORMAL_EXCHANGE = "normal_exchange";
    private static final String DEAD_EXCHANGE = "dead_exchange";
    private static final String NORMAL_QUEUE = "normal_queue";
    private static final String DEAD_QUEUE = "dead_queue";

    /**
     * 声明交换器,几种不同类型的交换机对应的类如下:
     * 1、DirectExchange:声明路由模式交换器
     * 2、FanoutExchange:声明订阅模式交换器
     * 3、TopicExchange:声明带有模式匹配的交换器
     * @return
     */
    @Bean("normalExchange")
    public DirectExchange normalExchange() {
        return new DirectExchange(NORMAL_EXCHANGE);
    }

    @Bean("deadExchange")
    public DirectExchange deadExchange() {
        return new DirectExchange(DEAD_EXCHANGE);
    }

    /**
     * 声明队列有两种方式:
     * 1、使用QueueBuilder构建
     * 2、使用new Queue()
     * @return
     */
    @Bean("normalQueue")
    public Queue normalQueue() {
        //队列的一些属性
        Map<String, Object> arguments = new HashMap<>();
        arguments.put("x-message-ttl", 10000);//消息过期时间,单位ms,可以不设置,可以由生产者设置消息过期时间
        arguments.put("x-dead-letter-exchange", DEAD_EXCHANGE);//设置消息过期后转发到哪个交换器
        arguments.put("x-dead-letter-routing-key", "b");//设置消息过期后由交换器路由到哪个队列的路由键
        // arguments.put("x-max-length", 6);//设置队列的长度,能存储消息的个数
        /**
         * 声明一个持久的队列
         * durable:持久的
         * withArguments:添加队列的属性(批量方式)
         * withArgument:添加队列的属性(单个方式)
         */
        return QueueBuilder.durable(NORMAL_QUEUE).withArguments(arguments).build();
    }

    @Bean("deadQueue")
    public Queue deadQueue() {
        //默认是一个持久队列
        Queue queue = new Queue(DEAD_QUEUE);
        return queue;
    }

    /**
     * 将队列绑定到交换器
     * @return
     */
    @Bean
    public Binding normalQueueBindingNormalExchange(@Qualifier("normalQueue") Queue normalQueue, @Qualifier("normalExchange") DirectExchange normalExchange) {
        //绑定队列到那个交换器,并指定路由键
        return BindingBuilder.bind(normalQueue).to(normalExchange).with("a");
    }

    @Bean
    public Binding deadQueueBindingDeadExchange(@Qualifier("deadQueue") Queue deadQueue, @Qualifier("deadExchange") DirectExchange deadExchange) {
        //绑定队列到那个交换器,并指定路由键
        return BindingBuilder.bind(deadQueue).to(deadExchange).with("b");
    }
}

3、收发消息测试

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Date: 2022/3/29
 * 发送消息
 */
@Slf4j
@RestController
@RequestMapping("/send")
public class SendMessageCotroller {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @GetMapping("message/{message}")
    public void sendMessage(@PathVariable String message) {
        log.info("发送消息到MQ - 入参 [{}]", JSON.toJSONString(message));
        rabbitTemplate.convertAndSend("normal_exchange", "a", ("来自normal_queue的死信消息:" + message).getBytes());
        log.info("发送消息到MQ - 完成");
    }

    @GetMapping("message/{message}/{ttlTime}")
    public void sendMessageTTL(@PathVariable String message, @PathVariable String ttlTime) {
        log.info("发送带有过期时间的消息到MQ - 消息内容 [{}] - 存活时间 [{}]", JSON.toJSONString(message), JSON.toJSONString(ttlTime));
        rabbitTemplate.convertAndSend("normal_exchange", "a", message.getBytes(), correlationData -> {
            //设置消息过期时间
            correlationData.getMessageProperties().setExpiration(ttlTime);
            return correlationData;
        });
        log.info("发送带有过期时间的消息到MQ - 完成");
    }
}
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

/**
 * @Date: 2022/3/29
 * 消费MQ消息
 */
@Slf4j
@Component
public class ReceiveMessage {
    /**
     * queues:声明需要监听的所有队列,多个队列用逗号隔开
     * message:原生消息详细信息,包含消息(头+体);还可直接使用对应类型接收消息body内容,但若方法参数类型不正确会抛异常
     * channel:当前传输数据的通道
     */
    @RabbitListener(queues = {"dead_queue"})
    public void receive1(Message message, Channel channel) {
        log.info("收到dead_queue队列的消息:[{}]", new String(message.getBody()));
    }

    @RabbitListener(queues = {"queue1"})
    public void receive1(String msg) {
        log.info("接收到queue1队列的消息:[{}]", msg);
    }
}
1、启动项目,发送两个请求
  • http://localhost:9000/send/message/测试过期
  • http://localhost:9000/send/message/带有过期时间的消息/5000
2、运行结果

在这里插入图片描述

4、消息序列化及传输对象信息

1、前面的都是发送字符串类型的消息,如果发送的消息是个对象,那么需要使用序列化机制将对象写出去,对象必须实现序列化(Serializable)接口
2、涉及网络传输的应用,序列化是不可避免的,发送端以某种规则将消息转成byte数组发送,接收端则以约定的规则进行byte数组解析。
3、RabbitMQ的序列化是指Message的body属性(即真正需要传输的内容)。RabbitMQ抽象出一个MessageConverter接口处理消息的序列化,其实现有SimpleMessageConverter(默认)、Jackson2JsonMessageConverter等
4、当调用了convertAndSend方法时会使用MessageConvert进行消息的序列化。SimpleMessageConverter对于要发送的消息体body为byte数组时不进行处理,如果是String则转成字节数组,如果是Java对象,则使用JDK序列化将消息转成字节数组,转出来的结果较大,含class类名,类相应方法等信息。因此性能较差。此时就要考虑使用类似Jackson2JsonMessageConverter等序列化形式以此提高性能。
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import java.io.Serializable;

/**
 * 订单对象 
 * @Date: 2022/3/31
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Order implements Serializable {
    private Integer id;

    private String orderNo;

    private Float price;

    private String remark;
}

/**
 * 用户对象 
 * @Date: 2022/3/31
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable{
    private Integer id;

    private String userName;

    private Integer age;
}
import com.alibaba.fastjson.JSON;
import com.itan.entity.Order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.utils.SerializationUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;

/**
 * @Date: 2022/3/31
 * 订单消息发送
 */
@Slf4j
@RestController
@RequestMapping("/order")
public class OrderMessageController {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 使用序列化对象,要求对象必须实现Serializable接口
     */
    @GetMapping("/serializable/{message}")
    public void sendMessage1(@PathVariable String message) {
        log.info("发送消息到MQ - 入参 [{}]", JSON.toJSONString(message));
        Order order = Order.builder().id(1)
                        .orderNo(UUID.randomUUID().toString())
                        .price(15000f).remark(message).build();
        rabbitTemplate.convertAndSend("queue1", order);
        log.info("发送消息到MQ - 完成");
    }

    /**
     * 使用序列化字节数组,二进制字节数组存储
     */
    @GetMapping("/byte/{message}")
    public void sendMessage2(@PathVariable String message) {
        log.info("发送消息到MQ - 入参 [{}]", JSON.toJSONString(message));
        Order order = Order.builder().id(2)
                        .orderNo(UUID.randomUUID().toString())
                        .price(15000f).remark(message).build();
        //序列化数组
        byte[] bytes = SerializationUtils.serialize(order);
        rabbitTemplate.convertAndSend("queue2", bytes);
        log.info("发送消息到MQ - 完成");
    }
}
运行之后查看queue1中存放的消息类型为JAVA对象序列化

三、SpringBoot整合RabbitMQ_第2张图片

queue2中存放的消息类型为二进制字节数组

三、SpringBoot整合RabbitMQ_第3张图片

import com.alibaba.fastjson.JSON;
import com.itan.entity.Order;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.utils.SerializationUtils;
import org.springframework.stereotype.Component;

/**
 * @Date: 2022/3/31
 * 消费MQ消息
 */
@Slf4j
@Component
public class ReceiveMessage {
    /**
     * T<发送消息的类型> t:可以直接写发送消息的类型,好处是不用手动转换消息的类型
     * 比如:直接使用Order order对象接收消息
     */
    @RabbitListener(queues = {"queue1"})
    public void receive1(Order order) {
        log.info("接收到queue1队列的消息:[{}]", order);
    }

    @RabbitListener(queues = {"queue2"})
    public void receive2(byte[] bytes) {
        //接收byte类型的消息,并反序列化
        Order order = (Order) SerializationUtils.deserialize(bytes);
        log.info("接收到queue2队列的消息:[{}]", order);
    }
}
1、使用JSON序列化与反序列化,需要实现自定义消息类型转换器,Jackson2JsonMessageConverter支持消息内容JSON序列化与反序列化
2、注意:在接收消息的时候,被序列化对象应提供一个无参的构造函数,否则会抛出异常
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitMQInitConfig {
    /**
     * 添加消息类型转换器,如果自定义实现则使用自定义的,否则使用默认的SimpleMessageConverter
     * 消息生产者在发送消息时自动设置MessageConverter为Jackson2JsonMessageConverter
     * 消息消费者在接收消息时自动设置MessageConverter为Jackson2JsonMessageConverter
     */
    @Bean
    public MessageConverter messageConverter() {
        return new Jackson2JsonMessageConverter();
    }
}
/**
 * @Date: 2022/3/31
 * 订单消息发送
 */
@Slf4j
@RestController
@RequestMapping("/order")
public class OrderMessageController {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @GetMapping("/json/{message}")
    public void sendMessage3(@PathVariable String message) {
        log.info("发送消息到MQ - 入参 [{}]", JSON.toJSONString(message));
        Order order = Order.builder().id(2)
                        .orderNo(UUID.randomUUID().toString())
                        .price(15000f).remark(message).build();
        rabbitTemplate.convertAndSend("queue3", order);
        log.info("发送消息到MQ - 完成");
    }
}
实现自定义消息类型转换器后,queue2中存放的消息类型为JSON

三、SpringBoot整合RabbitMQ_第4张图片

import com.alibaba.fastjson.JSON;
import com.itan.entity.Order;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.utils.SerializationUtils;
import org.springframework.stereotype.Component;

/**
 * @Date: 2022/3/31
 * 消费MQ消息
 */
@Slf4j
@Component
public class ReceiveMessage {
    @RabbitListener(queues = "queue3")
    public void receive3(Order order) {
        log.info("接收到queue3队列的消息:[{}]", order);
    }
}

5、@RabbitListener与@RabbitHandler搭配使用

1、位置及作用
  • @RabbitListener:类上、方法上,当监听到队列中有消息时则会进行接收并处理。
  • @RabbitHandler:方法上,需要与@RabbitListener搭配使用
  • @RabbitListener标注在类上面表示当有收到消息的时候,就交给@RabbitHandler的方法处理,具体使用哪个方法处理,根据MessageConverter转换后的参数类型
/**
 * @Date: 2022/3/31
 * 订单消息发送
 */
@Slf4j
@RestController
@RequestMapping("/order")
public class OrderMessageController {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @GetMapping("/message")
    public void sendMessage4() {
        for (int i = 1; i < 5; i++) {
            if (i % 2 == 0) {
                Order order = Order.builder().id(i)
                        .orderNo(UUID.randomUUID().toString())
                        .price(15000f).remark("创建订单").build();
                rabbitTemplate.convertAndSend("queue4", order);
            } else {
                User user = User.builder().id(i).userName(UUID.randomUUID().toString())
                        .age(20 + i).build();
                rabbitTemplate.convertAndSend("queue4", user);
            }
        }
    }
}
import com.itan.entity.Order;
import com.itan.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

/**
 * @Date: 2022/4/5
 * @RabbitListener+@RabbitHandler配合使用:接收同一个队列不同类型的消息
 */
@Slf4j
@Component
@RabbitListener(queues = {"queue4"})
public class ReceiveMessage1 {
    @RabbitHandler
    public void receive(Order order) {
        log.info("接收到queue4中Order消息:[{}]", order);
    }

    @RabbitHandler
    public void receive(User user) {
        log.info("接收到queue4中User消息:[{}]", user);
    }
}
启动项目,访问接口,控制台输出如下:

在这里插入图片描述

二、SpringBoot中使用消息确认机制

1、概述

1、生产者将消息发送到RabbitMQ服务器,如果消息成功抵达,触发confirmCallback回调
2、交换器将消息投递到队列时候,如果失败了,则触发returnCallback回调
3、消费者消费消息时候手动确认消息

三、SpringBoot整合RabbitMQ_第5张图片

2、消息抵达MQ服务回调confirmCallBack

1、开启生产者确认模式spring.rabbitmq.publisher-confirms=true(默认是false),但是很有可能这种配置过时了,使用如下方式spring.rabbitmq.publisher-confirm-type=correlated
  • NONE:禁用发布确认模式(默认)
  • CORRELATED:消息成功抵达交换器后会触发回调方法
  • SIMPLE:有两种效果,一是和CORRELATED值一样会触发回调方法,二是在消息发布成功后使用RabbitTemplate调用waitForConfirms或waitForConfirmsOrDie方法等待Broker节点返回发送结果,根据返回结果来判定下一步的逻辑,要注意的点是waitForConfirmsOrDie方法如果返回false则会关闭Channel,则接下来无法发送消息到Broker。
2、消息只要被Broker接收到就会执行ConfirmCallBack,如果是集群模式,需要所有的Broker接收到才会调用触发ConfirmCallBack回调。
server:
  port: 9000

spring:
  rabbitmq:
    # 开启发送端确认机制
    publisher-confirm-type: correlated
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;

/**
 * @Date: 2022/4/7
 * 设置确认回调
 */
@Slf4j
@Configuration
public class RabbitTemplateConfig {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * @PostConstruct:RabbitTemplateConfig对象创建完成之后,执行这个注解标识的方法
     */
    @PostConstruct
    public void RabbitTemplateInit() {
        //设置确认回调,MQ只要收到消息,此方法会自动回调
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            /**
             * 用于监听消息确认结果(消息是否发送到交换机),只要成功抵达(即ack=ture)
             * @param correlationData:当前消息的唯一关联数据(里面包含消息id,correlationData.getId()获取),可以在发送消息的时候进行设置new CorrelationData("消息id");
             * @param ack:消息是否成功抵达
             * @param cause:失败的原因
             */
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                log.info("消息成功到达MQ");
                log.info("当前消息的数据:{}", correlationData);
                log.info("消息是否成功抵达:{}", ack);
                log.info("失败原因:{}", cause);
            }
        });
    }
}
1、启动服务,访问接口http://localhost:9000/order/message,查看控制台日志,ACK全部为true,表示成功消息抵达

三、SpringBoot整合RabbitMQ_第6张图片

3、消息正确抵达队列回调returnCallback

1、消息被Broker接收到只能表示消息已经到达服务器,并不能保证消息一定会被投递到目标队列中,所以需要returnCallback,如果消息没有到达目标队列中,那么消息会被直接丢弃,生产者是不知道消息被丢弃的。因此在消息没有路由到目标队列时将回调returnCallback,可以记录下详细的投递数据,定期的巡检这些数据。
2、开启生产者消息抵达队列的确认:
  • spring.rabbitmq.publisher-returns=true
  • spring.rabbitmq.template.mandatory=true
server:
  port: 9000

spring:
  rabbitmq:
    # 开启发送端确认机制
    publisher-confirm-type: correlated
    # 开启发送端消息抵达队列的确认
    publisher-returns: true
    # 只要消息抵达队列,就以异步方式优先回调returnConfirm
    template:
      mandatory: true    
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;

/**
 * @Date: 2022/4/7
 * 设置确认回调
 */
@Slf4j
@Configuration
public class RabbitTemplateConfig {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * @PostConstruct:RabbitTemplateConfig对象创建完成之后,执行这个注解标识的方法
     */
    @PostConstruct
    public void RabbitTemplateInit() {
        // 设置消息抵达队列的确认回调,消息正确抵达队列不会回调此方法,如果没有抵达,则会触发此回调
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            /**
             * 只要消息没有正确投递到队列,就触发这个失败的回调
             * @param message:投递失败的消息详细信息
             * @param replyCode:回复的状态码
             * @param replyText:回复的文本内容
             * @param exchange:这个消息发送到哪个交换器
             * @param routingKey:这个消息用哪个路由键
             */
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
                log.info("消息抵达队列失败");
                log.info("当前消息的内容:{}", message);
                log.info("回复的状态码:{}", replyCode);
                log.info("回复的文本内容:{}", replyText);
                log.info("交换机:{}", exchange);
                log.info("路由键:{}", routingKey);
            }
        });
    }
}
@GetMapping("/returnCallback")
public void sendMessage5() {
    for (int i = 1; i < 5; i++) {
        if (i % 2 == 0) {
            Order order = Order.builder().id(i)
                .orderNo(UUID.randomUUID().toString())
                .price(15000f).remark("创建订单").build();
            //给消息绑定一个id值
            CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
            rabbitTemplate.convertAndSend("direct_exchange","a", order, correlationData);
        } else {
            User user = User.builder().id(i).userName(UUID.randomUUID().toString())
                .age(20 + i).build();
            //给消息绑定一个id值
            CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
            //使用不存在路由键,这样消息就无法抵达去正确的队列
            rabbitTemplate.convertAndSend("direct_exchange","c", user, correlationData);
        }
    }
}
1、启动服务,访问接口http://localhost:9000/order/returnCallback,查看控制台日志

三、SpringBoot整合RabbitMQ_第7张图片

4、消费者确认

1、默认是自动确认的,只要有一个消息被成功处理,就会自动确认所有消息,如果MQ宕机了,就会发生消息丢失,因此需要手动确认
2、开启消费者手动ACK:spring.rabbitmq.listener.simple.acknowledge-mode=manual
server:
  port: 9000

spring:
  rabbitmq:
    # 开启发送端确认机制
    publisher-confirm-type: correlated
    # 开启发送端消息抵达队列的确认
    publisher-returns: true
    # 只要消息抵达队列,就以异步方式优先回调returnConfirm
    template:
      mandatory: true
    # 开启消费者手动确认模式
    listener:
      simple:
        acknowledge-mode: manual
import com.itan.entity.Order;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;

/**
 * @Date: 2022/4/5
 */
@Slf4j
@Component
@RabbitListener(queues = {"queue2","queue3","queue4"})
public class ReceiveMessage1 {
    @RabbitHandler
    public void receive(Message message, Channel channel, Order order) {
        log.info("接收到queue2中User消息:[{}]", order);
        //消息头属性
        MessageProperties properties = message.getMessageProperties();
        //获取消息的标签,在channel内是按顺序递增的
        long deliveryTag = properties.getDeliveryTag();
        try {
            /**
             * 成功消费之后进行手动应答,RabbitMQ就可以将消费过的消息丢弃了
             * 参数1:消息的标记(tag)
             * 参数2:是否批量应答;false表示消费一个才应答一个,true表示消费一个之后将channel中小于tag标记的消息都应答了
             */
            channel.basicAck(deliveryTag, false);
        } catch (IOException e) {
            log.info("网络中断...");
            e.printStackTrace();
        }
    }
}
1、以debug方式启动服务,receive方法中打上断点,进行调试,访问接口http://localhost:9000/order/returnCallback

三、SpringBoot整合RabbitMQ_第8张图片

三、常见问题的解决方案

1、幂等性

1、用于对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次操作而产生了副作用,称为幂等。当出现消费者对某条消息重复消费的情况时,重复消费的结果与消费一次的结果是相同的,并且多次消费并未对业务系统产生任何负面影响。

2、常用幂等性保证方法

1、利用数据库的唯一约束实现幂等
  • 比如将订单表中的订单编号设置为唯一索引,创建订单时,根据订单编号就可以保证幂等。
2、防重表
  • 本质也是根据数据库的唯一性约束来实现。其实现大体思路是:首先在防重表上建唯一索引,其次操作时把业务表和防重表放在同个本地事务中,如果出现重复消费,数据库会抛唯一约束异常,操作就会回滚。
3、利用Redis的原子性
  • 每次操作都直接SET到Redis里面,然后将Redis数据定时同步到数据库中。
4、多版本(乐观锁)控制
  • 此方案多用于更新的场景下。其实现的大体思路是:给业务数据增加一个版本号属性,每次更新数据前,比较当前数据的版本号是否和消息中的版本一致,如果不一致则拒绝更新数据,更新数据的同时将版本号+1。
5、token机制
  • 生产者发送每条数据的时候,增加一个全局唯一的Id,这个Id通常是业务的唯一标识,比如订单编号。在消费端消费时,则验证该Id是否被消费过,如果还没消费过,则进行业务处理。处理结束后,在把该Id存入Redis,同时设置状态为已消费。如果已经消费过了,则不进行处理。

3、消息丢失

1、消息发送出去,由于网络问题没有抵达服务器,消息丢失了:
  • 发送消息时可能会网络失败,做好容错(try-catch),失败后要有重试机制,可以将消息记录到数据库,可以定期扫描重发。
  • 做好日志记录,每个消息状态是否都被服务器收到都应该记录。
  • 做好定期重发,如果消息发送失败,定期去数据库查询未成功的消息并进行重发。
2、消息抵达Broker,Broker要将消息写入磁盘(持久化)才算成功,若还未持久化完成就出现宕机了,消息丢失了:
  • 加入确认回调机制,确认成功的消息,修改数据库状态。
3、消息自动ACK,消费者收到消息,但还没有来得及处理消息就宕机了,消息丢失了:
  • 开启手动ACK,消费成功才将队列中的消息移除,失败或者没有来得及处理就noAck并重新入队。

4、消息重复

1、消息成功消费,事务已经提交,ACK时出现宕机,导致ACK失败,Broker的消息重新由unack变为ready,并发送给其他消费者。
  • 消费者的业务消费接口应该设计为幂等性的。
  • 使用防重表(redis/mysql),发送每一条消息都应该有业务的唯一标识,处理过后就不处理了。
  • RabbitMQ的每一条消息都有redelivered属性,可以获取是否是被重新投递过来的,而不是第一次投递过来的。
2、消息消费失败,由于重试机制,自动又将消息发送出去。

5、消息积压

1、消费者宕机导致积压,最终会导致性能下降
2、消费者消费能力不足导致积压
3、发送者发送消息流量太大
4、解决方法:
  • 上线更多的消费者进行消费。
  • 上线专门的队列消费服务,将消息先批量取出来,记录到数据库,离线慢慢处理。

你可能感兴趣的:(RabbitMQ,SpringBoot整合MQ,使用消息确认机制,常见问题解决方法)