SpringBoot 集成 RabbitMQ

568 阅读7分钟

依赖

<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 #密码
    virtualHost:    #链接的虚拟主机
    addresses: 127.0.0.1:5672     #多个以逗号分隔,与host功能一样。
    requestedHeartbeat: 60 #指定心跳超时,单位秒,0为不指定;默认60s
    publisher-confirm-type: correlated  #发布确认机制是否启用
    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: #最大间隔

Java 配置文件

@Configuration
public class RabbitmqMQConfig {

    /**
     * SimpleMessageListenerContainer
     *
     * @param connectionFactory
     * @return
     */
    @Bean(name = "simpleContainerFactory")
    public SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory simpleContainerFactory = new SimpleRabbitListenerContainerFactory();
        //设置连接工厂
        simpleContainerFactory.setConnectionFactory(connectionFactory);
        //接收消息采用Jackson2JsonMessageConverter序列化
        simpleContainerFactory.setMessageConverter(this.jackson2JsonMessageConverter());
        //设置初始消费者数量(SimpleRabbitListenerContainerFactory配置类的配置优先级比配置文件高)
        simpleContainerFactory.setConcurrentConsumers(2);
        //设置最大消费者数量(SimpleRabbitListenerContainerFactory配置类的配置优先级比配置文件高)
        simpleContainerFactory.setMaxConcurrentConsumers(10);
        //设置消费者每次获取的消息数,默认250(SimpleRabbitListenerContainerFactory配置类的配置优先级比配置文件高)
        simpleContainerFactory.setPrefetchCount(30);
        //消费者listener抛出异常,是否重回队列,默认true:重回队列, false为不重回队列(结合死信交换机)
        simpleContainerFactory.setDefaultRequeueRejected(false);
        //应答模式NONE:不确认模式,MANUAL:手动确认模式,AUTO:自动确认模式
        simpleContainerFactory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        return simpleContainerFactory;
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate();
        //设置连接工厂
        rabbitTemplate.setConnectionFactory(connectionFactory);
        //接收消息采用Jackson2JsonMessageConverter序列化(支持java 8时间)
        rabbitTemplate.setMessageConverter(this.jackson2JsonMessageConverter());
        //Mandatory为true时,消息通过交换器无法匹配到队列会返回给生产者,为false时匹配不到会直接被丢弃
        rabbitTemplate.setMandatory(true);
        return rabbitTemplate;
    }

    @Bean("jacksonMessageConverter")
    public MessageConverter jackson2JsonMessageConverter() {
        ObjectMapper mapper = getMapper();
        return new Jackson2JsonMessageConverter(mapper);
    }

    /**
     * 使用com.fasterxml.jackson.databind.ObjectMapper
     * 对数据进行处理包括java8里的时间
     * @return
     */
    private ObjectMapper getMapper() {
        ObjectMapper mapper = new ObjectMapper();
        //设置可见性
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        //默认键入对象
        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        //设置Java 8 时间序列化
        JavaTimeModule timeModule = new JavaTimeModule();
        timeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        timeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        timeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
        timeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        timeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        timeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
        //禁用把时间转为时间戳
        mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        //遇到未知属性或者属性不匹配的时候不抛出异常
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        mapper.registerModule(timeModule);
        return mapper;
    }
}

基本组件定义

队列

定义queue

@Bean
public Queue queue(){
    return new Queue(String name, boolean durable, boolean exclusive, boolean autoDelete,  Map<String, Object> arguments);
}

队列参数

  • x-message-ttl 发送到队列的消息在丢弃之前可以存活多长时间(毫秒)。

  • x-expires 队列在被自动删除(毫秒)之前可以使用多长时间。

  • x-max-length 队列在开始从头部删除之前可以包含多少就绪消息。

  • x-max-length-bytes 队列在开始从头部删除之前可以包含的就绪消息的总体大小。

  • x-dead-letter-exchange 设置队列溢出行为。这决定了在达到队列的最大长度时消息会发生什么。有效值为drop-head或reject-publish。交换的可选名称,如果消息被拒绝或过期,将重新发布这些名称。

  • x-dead-letter-routing-key 可选的替换路由密钥,用于在消息以字母为单位时使用。如果未设置,将使用消息的原始路由密钥。

  • x-max-priority 队列支持的最大优先级数;如果未设置,队列将不支持消息优先级。

  • x-queue-mode 将队列设置为延迟模式,在磁盘上保留尽可能多的消息以减少内存使用;如果未设置,队列将保留内存缓存以尽快传递消息。

  • x-queue-master-locator 将队列设置为主位置模式,确定在节点集群上声明时队列主机所在的规则。

交换器

分类

RabbitMQ的Exchange(交换器)分为四类:

  • direct 如果路由键匹配的话,消息就投递到相应的队列
  • headers
  • fanout 把消息广播到所有附加到这个交换器的队列上
  • topic 使用路由键进行消息(规则)匹配
    topic路由器的关键在于定义路由键,定义routingKey名称不能超过255字节,使用“.”作为分隔符,例如:com.mq.rabbit.error。
    消费消息的时候routingKey可以使用下面字符匹配消息:
    "*"匹配一个分段(用“.”分割)的内容;
    "#"匹配0和多个字符;

创建交换器

@Bean
public DirectExchange fanoutExchange(){
    return new DirectExchange(String name, boolean durable, boolean autoDelete, Map<String, Object> arguments);
}

交换器参数

绑定

@Bean
public Binding binding(){
    return BindingBuilder.bind(queue()).to(fanoutExchange()).with(ROUTING_KEY);
}

发送端

消息发送

@Resource
RabbitTemplate rabbitTemplate; //使用RabbitTemplate,这提供了接收/发送等等方法

1.直接发送

public void convertAndSend(String exchange, String routingKey, Object message, MessagePostProcessor messagePostProcessor, @Nullable CorrelationData correlationData)
复制代码

2.提前设置参数

public void setExchange(@Nullable String exchange)
public void setRoutingKey(String routingKey)
public void send(Message message) throws AmqpException {
    this.send(this.exchange, this.routingKey, message);
}

消息持久化

public  void sendMessage(String exchange,String routingKey, Object messageBody){
    /*消息持久化*/
    MessagePostProcessor processor = message -> {
        message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
        return message;
    };
    rabbitTemplate.convertAndSend(exchange, routingKey, messageBody,processor);
}

发送确认机制

修改配置文件

publisher-confirm-type: correlated  #发布确认机制是否启用

修改rabbitTemplate配置

rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
    if(ack){
        System.out.println("发送成功");
    }else {
        System.out.println("发送失败");
    }
});

发送失败处理机制

参考博客
修改配置文件

publisherReturns: true #发布返回是否启用
mandatory:  true # 交换器无法依据自身类型和路由键路由键找到队列时返回给生产者

修改rabbitTemplate配置

rabbitTemplate.setReturnsCallback(returned -> {
    log.error("发往交换器:"+returned.getExchange()+",路由键为"+returned.getRoutingKey()+"的消息:"+
            returned.getMessage().toString()+" 失败,回复信息为"+returned.getReplyText());
});

消费端

Qos

RabbitMQ 提供了一种 qos (服务质量保证)功能,即在非自动确认消息的前提下,如果一定数目的消息(通过基于 consume 或者 channel 设置 Qos 的值)未被确认前,不进行消费新的消息

 prefetch: 10 每次取10条信息,默认250 

接收消息

@RabbitListener 与 @RabbitHandler

//基础注解,指定queue的名称,可以多个。如果是simple不需要交换机的直接写队列名称就好。
    //如果是其他的也想只指定一个queues——name的话,需要上面的配置类配置queue或者其他绑定关系
    @RabbitListener(queues = "ly_simple")
    @RabbitHandler
    public void processSimpleMsg(String message) {
        System.out.println("########################received simple" + message);
    }

   //如果不想使用配置类,可以直接注解通过bindings,绑定,spring会根据注解生成绑定
   //ps:如果已有同名称的类。不会覆盖。会影响功能
    @RabbitListener(bindings = { @QueueBinding(value = @Queue(value = "ly_direct", durable = "true"),
            exchange = @Exchange(value = "ly_direct", type = "direct"), key = "ly") })
    @RabbitHandler
    public void processDirectMsg(String message) {
        System.out.println("########################received" + message);
    }

    @RabbitListener(bindings = { @QueueBinding(value = @Queue(value = "ly_fanout", durable = "true"),
            exchange = @Exchange(value = "ly_fanout", type = "fanout")) })
    @RabbitHandler
    public void processFanoutMsg(String message) {
        System.out.println("########################received" + message);
    }

手动确认

SpringBoot+RabbitMQ发送确认和消费手动确认机制
修改配置文件

spring.rabbitmq.listener.direct.acknowledge-mode=manual

手动确认

 @RabbitListener(queuesToDeclare =@Queue("boot_queue_ManuallySigned")//使用默认的交换机 @Queue:队列消息配置)
    public voidreceiveMsgToManuallySigned(String msg, @Header(AmqpHeaders.DELIVERY_TAG) longdeliveryTag, Channel channel) throws IOException {
       
            channel.basicAck(deliveryTag,false);
            channel.basicNack(deliveryTag,false, false);
        }
    }