springboot 整合rabbitMq

RabbitMq入门

RabbitMq名词

queue队列

最最最核心的队列,用于存储消息

exchange

交换机,用来接收消息,发送到队列

routing key

路由,生产者发送消息到交换机,一般会指定路由key,用来指定发送到绑定的哪个队列

virtual

虚拟机,类似于不同的数据库

消息类型

在这里插入图片描述

Hello World

生产者直接发送消息到队列,消费者直接从队列获取消息
发送消息时,只需要指定队列,不需要指定交换机,以及路由key,只有一个消费者

Work queues

发送消息时,只需要指定队列,不需要指定交换机,以及路由key,设定多个消费者

Publish/subscribe

广播模型交换机,发送消息时,需要新建Fanout交换机,队列,交换机和队列绑定时,不需要路由key,所以队列都能获取到交换机中的消息;消息发送时需要指定交换机,消息接收时需要指定队列,每个消息都会发送到不同的队列,并且每个队列都会被消费一次;

Routing

Declare交换机类型,发送消息时,需要新建Declare交换机,队列,交换机和队列绑定时,需要指定routing key;如果不指定,则所有消息都会发送到该队列;消息发送时,需要指定交换机,还有Routing key,每个消息会根据不同的Routing key,发送到不同的队列,消费者绑定不同的队列来获取消息;

Topics

Topic交换机类型,发送消息时,需要新建Topic交换机,队列,交换机和队列绑定时,需要指定routing key,routing key指定有个规则(列如 发送 CN.HUNAN.CHANGSHA 指定routing key CN.HUNAN.* 或者 CN.# 队列能接受到消息;*只能代表一个单词,#可以代表多个单词);发送消息时,和Routing模式是一样的,指定交换机以及routing key,发送到不同的队列,消费者绑定不同的队列来获取消息;

配置

maven依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

yml配置

spring:
  rabbitmq:
    host: 127.0.0.1 #ip
    port: 5672      #端口
    username: guest #账号
    password: guest #密码
    virtual-host: / #链接的虚拟主机
#    addresses: 127.0.0.1:5672     #多个以逗号分隔,与host功能一样。
#    requestedHeartbeat: 60 #指定心跳超时,单位秒,0为不指定;默认60s
#    publisherConfirms: true  #消息发送到交换机确认机制,是否确认回调
#    publisherReturns: #消息发送到交换机确认机制,是否返回回馈
#    connectionTimeout: #链接超时。单位ms。0表示无穷大不超时
#    ### ssl相关
#    ssl:
#      enabled: #是否支持ssl
#      keyStore: #指定持有SSL certificate的key store的路径
#      keyStoreType: #key store类型 默认PKCS12
#      keyStorePassword: #指定访问key store的密码
#      trustStore: #指定持有SSL certificates的Trust store
#      trustStoreType: #默认JKS
#      trustStorePassword: #访问密码
#      algorithm: #ssl使用的算法,例如,TLSv1.1
#      verifyHostname: #是否开启hostname验证
#    ### cache相关
#    cache:
#      channel:
#        size: #缓存中保持的channel数量
#        checkoutTimeout: #当缓存数量被设置时,从缓存中获取一个channel的超时时间,单位毫秒;如果为0,则总是创建一个新channel
#      connection:
#        mode: #连接工厂缓存模式:CHANNEL 和 CONNECTION
#        size: #缓存的连接数,只有是CONNECTION模式时生效
#    ### listener
#    listener:
#      type: #两种类型,SIMPLE,DIRECT
#      ## simple类型
#      simple:
#        concurrency: #最小消费者数量
#        maxConcurrency: #最大的消费者数量
#        transactionSize: #指定一个事务处理的消息数量,最好是小于等于prefetch的数量
#        missingQueuesFatal: #是否停止容器当容器中的队列不可用
#        ## 与direct相同配置部分
#        autoStartup: #是否自动启动容器
#        acknowledgeMode: #表示消息确认方式,其有三种配置方式,分别是none、manual和auto;默认auto
#        prefetch: #指定一个请求能处理多少个消息,如果有事务的话,必须大于等于transaction数量
#        defaultRequeueRejected: #决定被拒绝的消息是否重新入队;默认是true(与参数acknowledge-mode有关系)
#        idleEventInterval: #container events发布频率,单位ms
#        ##重试机制
#        retry:
#          stateless: #有无状态
#          enabled:  #是否开启
#          maxAttempts: #最大重试次数,默认3
#          initialInterval: #重试间隔
#          multiplier: #对于上一次重试的乘数
#          maxInterval: #最大重试时间间隔
#      direct:
#        consumersPerQueue: #每个队列消费者数量
#        missingQueuesFatal:
#        #...其余配置看上方公共配置
#    ## template相关
#    template:
#      mandatory: #是否启用强制信息;默认false
#      receiveTimeout: #`receive()`接收方法超时时间
#      replyTimeout: #`sendAndReceive()`超时时间
#      exchange: #默认的交换机
#      routingKey: #默认的路由
#      defaultReceiveQueue: #默认的接收队列
#      ## retry重试相关
#      retry:
#        enabled: #是否开启
#        maxAttempts: #最大重试次数
#        initialInterval: #重试间隔
#        multiplier: #失败间隔乘数
#        maxInterval: #最大间隔

相关注解

@RabbitListener
使用 @RabbitListener 注解标记方法,当监听队列中有消息时则会进行接收并处理
@Payload
可以获取消息中的 body
@Headers
headers 信息

@RabbitListener(queues = "queue")
public void processMessage(@Payload String body, @Headers Map<String,Object> headers) {
    System.out.println("body:"+body);
    System.out.println("Headers:"+headers);
}

@RabbitListener(bindings = @QueueBinding(
        exchange = @Exchange(value = "topic.exchange",durable = "true",type = "topic"),
        value = @Queue(value = "consumer_queue",durable = "true"),
        key = "key.#"
))
public void processMessage1(Message message) {
    System.out.println(message);
}

//@RabbitListener 和 @RabbitHandler 搭配使用 (不推荐)
//@RabbitListener 可以标注在类上面,需配合 @RabbitHandler 注解一起使用
//@RabbitListener 标注在类上面表示当有收到消息的时候,就交给 @RabbitHandler 的方法处理,具体使用哪个方法处理,根据 MessageConverter 转换后的参数类型
@Component
@RabbitListener(queues = "consumer_queue")
public class Receiver {

    @RabbitHandler
    public void processMessage1(String message) {
        System.out.println(message);
    }

    @RabbitHandler
    public void processMessage2(byte[] message) {
        System.out.println(new String(message));
    }
    
}

Message 内容对象序列化与反序列化

默认的 SimpleMessageConverter 在发送消息时会将对象序列化成字节数组,若要反序列化对象,需要自定义 MessageConverter

使用 JSON 序列化与反序列化

RabbitMQ 提供了 Jackson2JsonMessageConverter 来支持消息内容 JSON 序列化与反序列化
消息发送者在发送消息时应设置 MessageConverter 为 Jackson2JsonMessageConverter
消息消费者也应配置 MessageConverter 为 Jackson2JsonMessageConverter
注意:被序列化对象应提供一个无参的构造函数,否则会抛出异常

@Configuration
@ConditionalOnClass(RabbitTemplate.class)
public class RabbitMqConfigurer implements RabbitListenerConfigurer {

    @Override
    public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
        registrar.setMessageHandlerMethodFactory(messageHandlerMethodFactory());
    }

    @Bean
    public MessageHandlerMethodFactory messageHandlerMethodFactory() {
        DefaultMessageHandlerMethodFactory defaultMessageHandlerMethodFactory = new DefaultMessageHandlerMethodFactory();
        defaultMessageHandlerMethodFactory.setMessageConverter(consumerJackson2MessageConverter());
        return defaultMessageHandlerMethodFactory;
    }

    @Bean
    public MappingJackson2MessageConverter consumerJackson2MessageConverter() {
        return new MappingJackson2MessageConverter();
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory, ObjectMapper objectMapper) {
        final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter(objectMapper));
        return rabbitTemplate;
    }

}

使用

Direct Exchange 模式

Direct Exchange(直连交换机):将一个消息队列与一个直连交换机通过路由键绑定在一起,当Msg通过路由键发送到直连交换机上时,直连交换机会把Msg根据路由键分发到匹配的队列,这种模式类似于一对一

在这里插入图片描述

@Configuration
public class DirectProducer {

    public static final String DIRECT_EXCHANGE = "DirectExchange";
    public static final String DIRECT_QUEUE_1 = "DirectQueue1";
    public static final String DIRECT_QUEUE_2 = "DirectQueue2";
    public static final String DIRECT_ROUTING_1 = "DirectRouting1";
    public static final String DIRECT_ROUTING_2 = "DirectRouting2";

    // 队列 起名:DirectQueue
    @Bean
    public Queue directQueue1() {
        return new Queue(DIRECT_QUEUE_1, true);
    }

    // 队列 起名:DirectQueue
    @Bean
    public Queue directQueue2() {
        return new Queue(DIRECT_QUEUE_2, true);
    }

    //Direct交换机 起名:DirectExchange
    @Bean
    public DirectExchange directExchange() {
        return new DirectExchange(DIRECT_EXCHANGE,true,false);
    }

    //绑定  将队列和交换机绑定, 并设置用于匹配键:DirectRouting
    @Bean
    public Binding bindingDirect1() {
        return BindingBuilder.bind(directQueue1()).to(directExchange()).with(DIRECT_ROUTING_1);
    }

    @Bean
    public Binding bindingDirect2() {
        return BindingBuilder.bind(directQueue2()).to(directExchange()).with(DIRECT_ROUTING_2);
    }

}

@Component
@Slf4j
public class DirectCustomer {

    @RabbitListener(bindings = @QueueBinding(
            exchange = @Exchange(value = DirectProducer.DIRECT_EXCHANGE, durable = "true", type = ExchangeTypes.DIRECT),
            value = @Queue(value = DirectProducer.DIRECT_QUEUE_1, durable = "true"),
            key = DirectProducer.DIRECT_ROUTING_1))
    public void direct1(@Payload Map message) {
        log.info("{}--{}--收到消息:{}", DirectProducer.DIRECT_QUEUE_1, DirectProducer.DIRECT_ROUTING_1, message);
    }

    @RabbitListener(bindings = @QueueBinding(
            exchange = @Exchange(value = DirectProducer.DIRECT_EXCHANGE, durable = "true", type = ExchangeTypes.DIRECT),
            value = @Queue(value = DirectProducer.DIRECT_QUEUE_2, durable = "true"),
            key = DirectProducer.DIRECT_ROUTING_2)
    )
    public void direct2(@Payload Map message) {
        log.info("{}--{}--收到消息:{}", DirectProducer.DIRECT_QUEUE_2, DirectProducer.DIRECT_ROUTING_2, message);
    }
}

@RestController
@RequestMapping("/direct")
@Slf4j
public class DirectController {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostMapping("/send1")
    public ResponseEntity<?> sendDirectMsg1()  {
        Map<String, Object> map = new HashMap<>();
        map.put("messageData", "Direct message 1");
        map.put("createTime", new Date());
        log.info("{}--{}--发送消息", DirectProducer.DIRECT_EXCHANGE, DirectProducer.DIRECT_ROUTING_1);
        rabbitTemplate.convertAndSend(DirectProducer.DIRECT_EXCHANGE, DirectProducer.DIRECT_ROUTING_1, map);
        return ResponseEntity.ok("success");
    }

    @PostMapping("/send2")
    public ResponseEntity<?> sendDirectMsg2() {
        Map<String, Object> map = new HashMap<>();
        map.put("messageData", "Direct message 2");
        map.put("createTime", new Date());
        log.info("{}--{}--发送消息", DirectProducer.DIRECT_EXCHANGE, DirectProducer.DIRECT_ROUTING_2);
        rabbitTemplate.convertAndSend(DirectProducer.DIRECT_EXCHANGE, DirectProducer.DIRECT_ROUTING_2, map);
        return ResponseEntity.ok("success");
    }

}

接口调用依次输出

DirectExchange--DirectRouting1--发送消息
DirectQueue1--DirectRouting1--收到消息:{createTime=2021-12-20 15:32:21, messageData=Direct message 1}

DirectExchange--DirectRouting2--发送消息
DirectQueue2--DirectRouting2--收到消息:{createTime=2021-12-20 15:32:24, messageData=Direct message 2}

Topic Exchange 模式

Topic Exchange(主题交换机):主题交换机是一种发布/订阅的模式,结合了直连交换机与扇形交换机的特点,消息队列与主题交换机的绑定也是通过路由键的。当一个Msg和路由键规则发送到一个主题交换机时,主题交换机会根据路由键规则来筛选出符合规则的绑定到自身消息队列的路由键(可能是1个,也可能是N个,也可能是0个),根据符合的路由键,将消息发送到其对应的消息队列里。这个模式类似于多播,当消息的路由规则只匹配到一个路由键时,此时主题交换机可以看作是直连交换机,当路由规则匹配了主题交换机上所有绑定的队列的路由键时,此时主题交换机可以看作是扇形交换机
在这里插入图片描述

发送消息时,需要新建Topic交换机,队列;交换机和队列绑定时,需要指定routing key。
也可通过通配符模式:例如 发送 TOPIC_QUEUE_ONE 指定routing key TOPIC.# 或者 TOPIC.QUEUE.* 队列能接受到消息;*只能代表一个单词,#可以代表多个单词

@Slf4j
@Component
public class TopicCustomer {

    @RabbitListener(bindings = @QueueBinding(
            exchange = @Exchange(value = TOPIC_EXCHANGE, durable = "true", type = ExchangeTypes.TOPIC),
            value = @Queue(value = TOPIC_QUEUE_ONE, durable = "true"),
            key = {TOPIC_ROUTING_ONE,TOPIC_ROUTING_HASH_1}))
    public void process1(@Payload Map message) {
        log.info("{}--收到消息:{}", TOPIC_QUEUE_ONE, message);
    }

    @RabbitListener(bindings = @QueueBinding(
            exchange = @Exchange(value = TOPIC_EXCHANGE, durable = "true", type = ExchangeTypes.TOPIC),
            value = @Queue(value = TOPIC_QUEUE_TWO, durable = "true"),
            key = {TOPIC_ROUTING_TWO, TOPIC_ROUTING_HASH_2})
    )
    public void process2(@Payload Map message) {
        log.info("{}--收到消息:{}", TOPIC_QUEUE_TWO, message);
    }

}

@Configuration
public class TopicProducer {

    public static final String TOPIC_EXCHANGE = "TOPIC_EXCHANGE";
    public static final String TOPIC_QUEUE_ONE = "TOPIC_QUEUE_ONE";
    public static final String TOPIC_QUEUE_TWO = "TOPIC_QUEUE_TWO";

    public static final String TOPIC_ROUTING_ONE = "TOPIC.ROUTING.ONE";
    public static final String TOPIC_ROUTING_TWO = "TOPIC.ROUTING.TWO";
    // *只能代表一个单词,#可以代表多个单词
    public static final String TOPIC_ROUTING_HASH_1 = "TOPIC.#";
    public static final String TOPIC_ROUTING_HASH_2 = "TOPIC.*";
    public static final String TOPIC_ROUTING = "TOPIC.ROUTING";

    @Bean
    public TopicExchange topicExchange() {
        return new TopicExchange(TOPIC_EXCHANGE,true,false);
    }

    @Bean
    public Queue topicQueue1() {
        return new Queue(TOPIC_QUEUE_ONE, true);
    }

    @Bean
    public Queue topicQueue2() {
        return new Queue(TOPIC_QUEUE_TWO, true);
    }

    @Bean
    public Binding bindingTopic1() {
        return BindingBuilder.bind(topicQueue1()).to(topicExchange()).with(TOPIC_ROUTING_ONE);
    }

    @Bean
    public Binding bindingTopic2() {
        return BindingBuilder.bind(topicQueue2()).to(topicExchange()).with(TOPIC_ROUTING_TWO);
    }

    @Bean
    public Binding bindingTopic3() {
        return BindingBuilder.bind(topicQueue1()).to(topicExchange()).with(TOPIC_ROUTING_HASH_1);
    }

    @Bean
    public Binding bindingTopic4() {
        return BindingBuilder.bind(topicQueue2()).to(topicExchange()).with(TOPIC_ROUTING_HASH_2);
    }

}

@RestController
@RequestMapping("/topic")
@Slf4j
public class TopicController {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostMapping("/send1")
    public ResponseEntity<?> sendTopicMsg1()  {
        Map<String, Object> map = new HashMap<>();
        map.put("messageData", "Topic message 1");
        map.put("createTime", new Date());
        log.info("{}--{}--发送消息", TOPIC_EXCHANGE, TOPIC_ROUTING_ONE);
        rabbitTemplate.convertAndSend(TOPIC_EXCHANGE, TOPIC_ROUTING_ONE, map);
        return ResponseEntity.ok("success");
    }

    @PostMapping("/send2")
    public ResponseEntity<?> sendTopicMsg2() {
        Map<String, Object> map = new HashMap<>();
        map.put("messageData", "Topic message 2");
        map.put("createTime", new Date());
        log.info("{}--{}--发送消息", TOPIC_EXCHANGE, TOPIC_ROUTING_TWO);
        rabbitTemplate.convertAndSend(TOPIC_EXCHANGE, TOPIC_ROUTING_TWO, map);
        return ResponseEntity.ok("success");
    }

    @PostMapping("/send3")
    public ResponseEntity<?> sendTopicMsg3() {
        Map<String, Object> map = new HashMap<>();
        map.put("messageData", "Topic message 3");
        map.put("createTime", new Date());
        log.info("{}--{}--发送消息", TOPIC_EXCHANGE, TOPIC_ROUTING);
        rabbitTemplate.convertAndSend(TOPIC_EXCHANGE, TOPIC_ROUTING, map);
        return ResponseEntity.ok("success");
    }

}

接口调用依次输出

TOPIC_EXCHANGE--TOPIC.ROUTING.ONE--发送消息
TOPIC_QUEUE_ONE--收到消息:{createTime=2021-12-20 15:35:23, messageData=Topic message 1}

TOPIC_EXCHANGE--TOPIC.ROUTING.TWO--发送消息
TOPIC_QUEUE_ONE--收到消息:{createTime=2021-12-20 15:35:25, messageData=Topic message 2}
TOPIC_QUEUE_TWO--收到消息:{createTime=2021-12-20 15:35:25, messageData=Topic message 2}

TOPIC_EXCHANGE--TOPIC.ROUTING--发送消息
TOPIC_QUEUE_TWO--收到消息:{createTime=2021-12-20 15:35:26, messageData=Topic message 3}
TOPIC_QUEUE_ONE--收到消息:{createTime=2021-12-20 15:35:26, messageData=Topic message 3}

Fanout Exchange 模式

Fanout Exchange(扇型交换机):当一个Msg发送到扇形交换机上时,则扇形交换机会将消息分别发送给所有绑定到F上的消息队列。扇形交换机将消息路由给绑定到自身的所有消息队列,也就是说路由键在扇形交换机里没有作用,故消息队列绑定扇形交换机时,路由键可为空。这个模式类似于广播。

@Configuration
public class FanoutProducer {

    public static final String FANOUT_EXCHANGE = "FANOUT_EXCHANGE";

    public static final String FANOUT_QUEUE_ONE = "FANOUT_QUEUE_ONE";
    public static final String FANOUT_QUEUE_TWO = "FANOUT_QUEUE_TWO";
    public static final String FANOUT_QUEUE_THREE = "FANOUT_QUEUE_THREE";

    @Bean
    public FanoutExchange fanoutExchange() {
        return new FanoutExchange(FANOUT_EXCHANGE);
    }

    @Bean
    public Queue queue1() {
        return new Queue(FANOUT_QUEUE_ONE);
    }

    @Bean
    public Queue queue2() {
        return new Queue(FANOUT_QUEUE_TWO);
    }

    @Bean
    public Queue queue3() {
        return new Queue(FANOUT_QUEUE_THREE);
    }

    @Bean
    public Binding binding1() {
        return BindingBuilder.bind(queue1()).to(fanoutExchange());
    }

    @Bean
    public Binding binding2() {
        return BindingBuilder.bind(queue2()).to(fanoutExchange());
    }

    @Bean
    public Binding binding3() {
        return BindingBuilder.bind(queue3()).to(fanoutExchange());
    }

}

@Component
@Slf4j
public class FanoutCustomer {

    @RabbitListener(bindings = @QueueBinding(
            exchange = @Exchange(value = FANOUT_EXCHANGE, durable = "true", type = ExchangeTypes.FANOUT),
            value = @Queue(value = FANOUT_QUEUE_ONE, durable = "true")
    ))
    public void process1(@Payload Map message) {
        log.info("{}--收到消息:{}", FANOUT_QUEUE_ONE, message);
    }

    @RabbitListener(bindings = @QueueBinding(
            exchange = @Exchange(value = FANOUT_EXCHANGE, durable = "true", type = ExchangeTypes.FANOUT),
            value = @Queue(value = FANOUT_QUEUE_TWO, durable = "true")
    ))
    public void process2(@Payload Map message) {
        log.info("{}--收到消息:{}", FANOUT_QUEUE_TWO, message);
    }

    @RabbitListener(bindings = @QueueBinding(
            exchange = @Exchange(value = FANOUT_EXCHANGE, durable = "true", type = ExchangeTypes.FANOUT),
            value = @Queue(value = FANOUT_QUEUE_THREE, durable = "true")
    ))
    public void process3(@Payload Map message) {
        log.info("{}--收到消息:{}", FANOUT_QUEUE_THREE, message);
    }
}

@RestController
@RequestMapping("/fanout")
@Slf4j
public class FanoutController {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostMapping("/send1")
    public ResponseEntity<?> send1() {
        Map<String, Object> map = new HashMap<>();
        map.put("messageData", "Fanout message 1");
        map.put("createTime", new Date());
        log.info("{}--发送消息", FANOUT_EXCHANGE);
        rabbitTemplate.convertAndSend(FANOUT_EXCHANGE, null, map);
        return ResponseEntity.ok("success");
    }
}

接口调用输出

FANOUT_EXCHANGE--发送消息
FANOUT_QUEUE_TWO--收到消息:{createTime=2021-12-20 16:15:42, messageData=Fanout message 1}
FANOUT_QUEUE_THREE--收到消息:{createTime=2021-12-20 16:15:42, messageData=Fanout message 1}
FANOUT_QUEUE_ONE--收到消息:{createTime=2021-12-20 16:15:42, messageData=Fanout message 1}

Headers Exchange 模式

Headers Exchange(头交换机):头交换机类似与主题交换机,但是却和主题交换机有着很大的不同。主题交换机使用路由键来进行消息的路由,而头交换机使用消息属性来进行消息的分发,通过判断消息头的值能否与指定的绑定相匹配来确立路由规则。在头交换机里有一个特别的参数”x-match”,当”x-match”的值为“any”时,只需要消息头的任意一个值匹配成功即可,当”x-match”值为“all”时,要求消息头的所有值都需相等才可匹配成功。

消息确认机制(ACK)

什么是消息确认机制?

如果在处理消息的过程中,消费者的服务器在处理消息的时候出现异常,那么可能这条正在处理的消息就没有完成消息消费,数据就会丢失。为了确保数据不会丢失,RabbitMQ支持消息确定-ACK。

消息确认机制介绍

消息的ACK确认机制默认是打开的。

ACK机制是消费者从RabbitMQ收到消息并处理完成后,反馈给RabbitMQ,RabbitMQ收到反馈后才将此消息从队列中删除。

如果一个消费者在处理消息出现了网络不稳定、服务器异常等现象,那么就不会有ACK反馈,RabbitMQ会认为这个消息没有正常消费,会将消息重新放入队列中。

如果在集群的情况下,RabbitMQ会立即将这个消息推送给这个在线的其他消费者。这种机制保证了在消费者服务端故障的时候,不丢失任何消息和任务。消息永远不会从RabbitMQ中删除,只有当消费者正确发送ACK反馈,RabbitMQ确认收到后,消息才会从RabbitMQ服务器的数据中删除。

RabbitMQ的消息确认有两种。

  • 消息发送确认:这种是用来确认生产者将消息发送给交换器,交换器传递给队列的过程中,消息是否成功投递。发送确认分为两步,一是确认是否到达交换器,二是确认是否到达队列。
  • 消费接收确认。这种是确认消费者是否成功消费了队列中的消息。

消息发送确认

  • **RabbitTemplate.ConfirmCallback:**实现ConfirmCallBack接口,消息发送到交换器后触发回调
  • **RabbitTemplate.ReturnCallback:**实现ReturnCallback接口,如果消息从交换器发送到对应队列失败时触发(比如根据发送消息时指定的routingKey找不到队列时会触发)

消息接收确认

  • 配置确认模式

    • AcknowledgeMode.NONE:不确认
    • AcknowledgeMode.AUTO:自动确认
    • AcknowledgeMode.MANUAL:手动确认
  • 手动确认:通过com.rabbitmq.client.Channel确认

    • 成功

      void basicAck(long deliveryTag, boolean multiple)

    • 失败:basicNack可以批量拒绝多条消息,而basicReject一次只能拒绝一条消息

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

      void basicReject(long deliveryTag, boolean requeue)

deliveryTag:该消息的index
multiple:是否批量.true:将一次性拒绝所有小于deliveryTag的消息。
requeue:被拒绝的是否重新入队列

实战

yml配置

spring:
  rabbitmq:
    host: 127.0.0.1 #ip
    port: 5672 #端口
    username: guest #用户名
    password: guest #密码
    virtual-host: demo
    publisherConfirms: true  #消息发送到交换机确认机制,是否确认回调
    publisherReturns: true #消息从交换机到队列,是否返回回馈
    listener:
      simple:
        acknowledgeMode: MANUAL #表示消息确认方式,其有三种配置方式,分别是none、manual和auto;默认auto
      direct:
        acknowledgeMode: MANUAL

RabbitTemplate配置

@Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory, ObjectMapper objectMapper) {
        final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter(objectMapper));
        rabbitTemplate.setConfirmCallback((CorrelationData correlationData, boolean ack, String cause) -> {
//            log.info("ConfirmCallback, correlationData:{}", correlationData);
//            if (ack) {
//                log.info("消息发送成功");
//            } else {
//                log.info("消息发送失败");
//            }
        });
        // 当消息从交换机到队列失败时,该方法被调用。(若成功,则不调用)
        rabbitTemplate.setReturnCallback((Message message, int replyCode, String replyText,
                                           String exchange, String routingKey) -> {
//            log.info("消息推送失败:{}|{}|{}|{}|{}", message, replyCode, replyText, exchange, routingKey);
        });
        return rabbitTemplate;
    }

消费者配置

以Direct Exchange模式为例

@Component
@Slf4j
public class DirectCustomer {

    private int num = 0;

    @RabbitListener(bindings = @QueueBinding(
            exchange = @Exchange(value = DirectProducer.DIRECT_EXCHANGE, durable = "true", type = ExchangeTypes.DIRECT),
            value = @Queue(value = DirectProducer.DIRECT_QUEUE_1, durable = "true"),
            key = DirectProducer.DIRECT_ROUTING_1))
    public void direct1(@Payload Map message, @Headers Map<String, Object> headers, Message msg, Channel channel) {
        log.info("{}--{}--收到消息:{}|{}", DirectProducer.DIRECT_QUEUE_1, DirectProducer.DIRECT_ROUTING_1, message, headers);
        try {
            // 模拟处理异常,num=3拒绝消息
            log.info("次数:{}", num);
            Thread.sleep(2000L);
            /**
             * deliveryTag:该消息的index。
             * multiple:是否批量. true:将一次性拒绝所有小于deliveryTag的消息。
             * requeue:被拒绝的是否重新入队列。
             */
            if (num == 3) {
                channel.basicNack(msg.getMessageProperties().getDeliveryTag(), true, true);
                num = 0;
            } else {
                channel.basicAck(msg.getMessageProperties().getDeliveryTag(), true);
                num++;
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }
}

发送8个消息,收到10个消息,uuid为e5de59a7-c0d2-4e8c-beb8-6840d6e3a5046fb9d265-bd45-4efd-b9a4-16fdaaabd589第一次处理被拒绝,所以消息会收到两次

DirectExchange--DirectRouting1--发送消息
DirectExchange--DirectRouting1--发送消息
DirectExchange--DirectRouting1--发送消息
DirectExchange--DirectRouting1--发送消息
DirectExchange--DirectRouting1--发送消息
DirectExchange--DirectRouting1--发送消息
DirectExchange--DirectRouting1--发送消息
DirectExchange--DirectRouting1--发送消息
DirectQueue1--DirectRouting1--收到消息:{createTime=2021-12-21 14:21:02, uuid=135c9b8d-91f1-41f2-af66-3d067c56ef6f, messageData=Direct message 1}
次数:1
DirectQueue1--DirectRouting1--收到消息:{createTime=2021-12-21 14:21:02, uuid=fa45a5e7-46b4-43e3-8828-9d4148f767b7, messageData=Direct message 1}
次数:2
DirectQueue1--DirectRouting1--收到消息:{createTime=2021-12-21 14:21:02, uuid=e5de59a7-c0d2-4e8c-beb8-6840d6e3a504, messageData=Direct message 1}
次数:3
DirectQueue1--DirectRouting1--收到消息:{createTime=2021-12-21 14:21:02, uuid=3e32b0d7-2828-4261-a6c0-ce1fdb06a28b, messageData=Direct message 1}
次数:0
DirectQueue1--DirectRouting1--收到消息:{createTime=2021-12-21 14:21:03, uuid=bf7f0674-dab9-4f7f-bc23-c8a3d19c5794, messageData=Direct message 1}
次数:1
DirectQueue1--DirectRouting1--收到消息:{createTime=2021-12-21 14:21:03, uuid=82a62df0-0537-44e9-8627-4fa70ee73673, messageData=Direct message 1}
次数:2
DirectQueue1--DirectRouting1--收到消息:{createTime=2021-12-21 14:21:03, uuid=6fb9d265-bd45-4efd-b9a4-16fdaaabd589, messageData=Direct message 1}
次数:3
DirectQueue1--DirectRouting1--收到消息:{createTime=2021-12-21 14:21:03, uuid=c9c461b6-8cb8-4308-b8aa-0465e3f8d5c3, messageData=Direct message 1}
次数:0
DirectQueue1--DirectRouting1--收到消息:{createTime=2021-12-21 14:21:02, uuid=e5de59a7-c0d2-4e8c-beb8-6840d6e3a504, messageData=Direct message 1}
次数:1
DirectQueue1--DirectRouting1--收到消息:{createTime=2021-12-21 14:21:03, uuid=6fb9d265-bd45-4efd-b9a4-16fdaaabd589, messageData=Direct message 1}
次数:2

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值