springboot整合rabbitmq之@RabbitListener懒启动

场景

springboot项目整合rabbitmq后,在项目启动后要先去加载一些文件作为缓存,比如加载千万ID去重(详见https://blog.youkuaiyun.com/qq_41578037/article/details/137463619),但因为项目rabbitmq消费者是用的@RabbitListener,最后发现在项目启动时,@RabbitListener和缓存的加载顺序不分先后,导致可能因为缓存没有加载完成,但是消息已经消费掉了。

要解决的问题

在@RabbitListener消费者开始消费消息前,保证缓存已经全部加载完毕。

好了,其实问题就在于加载顺序这块,网上也有一些rabbitmq懒加载的说明,自己试了试,发现很多文章都是只说了部分,导致自己加到项目中发现不生效,唉,还是自己多试试吧。。。。

项目pom

   <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

配置文件

rabbitmq的连接配置

  rabbitmq:
    virtual-host: test
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    template:
      mandatory: true
    listener:
      simple:
        acknowledge-mode: manual
        prefetch: 10

项目启动实现CommandLineRunner

import com.test.GlobalCache;
import com.test.util.FileUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;



/**
 * 初始化操作
 */
@Component
public class TestStartOverRun implements CommandLineRunner {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private ApplicationContext context;
    
    @Autowired
    private RabbitListenerEndpointRegistry endpointRegistry;

    @Override
    public void run(String... args) throws Exception {
		//项目启动后要做的事情...
        readFiles();
        logger.info("task start over something over");
		//启动rabbitmq消费者
        endpointRegistry.start();
    }


    private void readFiles(){
       logger.info("启动后去读取文件");
    }


}

在CommandLineRunner 中注入RabbitListenerEndpointRegistry ,并且在自己项目启动完成,然后做完要做的事情后,调用 endpointRegistry.start();启动消费者。

RabbitmqConfig配置

@Component
public class RabbitConfig {


    @Autowired
    ConnectionFactory rabbitConnectionFactory;
	//这个是rabbitmq批量消费
 	@Bean("batchRabbitContainerFactory")
    public SimpleRabbitListenerContainerFactory batchQueueRabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        //设置批量
        factory.setBatchListener(true);
        factory.setConsumerBatchEnabled(true);
        factory.setBatchSize(20);
        factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        //重点!!!
        factory.setAutoStartup(false);
        return factory;
    }
    //这个是默认单条消费
    @Bean
    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
 		//重点!!!
        factory.setAutoStartup(false);
        return factory;
    }


}

这一步主要就是为了factory.setAutoStartup(false);关闭消费者的自动启动。

消费者

@Component
public class TestListener {


	//正常配置,之前怎么配置就怎么配置
  	@RabbitListener(queues = "consumer_queue", concurrency = "1-5")
    public void testHandler(Channel channel, Message msg) {
        String content = new String(msg.getBody());
    }
}

效果

直接重启项目,可以发现只有在调用endpointRegistry.start();后,定义的消费者才开始消费消息,在rabbitmq控制台中,也可以看到,项目启动后,在队列的消费者那里刚开始是没有消费者信息的。

至此,可以说效果上已经达到了我们刚开始想要的效果。

背后原理

我们可以看下endpointRegistry.start();方法究竟是怎么做的。

	@Override
	public void start() {
		for (MessageListenerContainer listenerContainer : getListenerContainers()) {
			startIfNecessary(listenerContainer);
		}
	}

	private void startIfNecessary(MessageListenerContainer listenerContainer) {
		if (this.contextRefreshed || listenerContainer.isAutoStartup()) {
			listenerContainer.start();
		}
	}

其实就是两个步骤,遍历MessageListenerContainer ,然后启动。
在启动的时候还判断this.contextRefreshed || listenerContainer.isAutoStartup()为true才开始启动,可以看到这是两个条件!!!

contextRefreshed 为true还是false呢?


	private boolean contextRefreshed;


	@Override
	public void onApplicationEvent(ContextRefreshedEvent event) {
		if (event.getApplicationContext().equals(this.applicationContext)) {
			this.contextRefreshed = true;
		}
	}

可以看到这个值默认是false的,只有在ContextRefreshedEvent 事件后才被设置为true。

那listenerContainer.isAutoStartup()默认是true还是false呢?

	default boolean isAutoStartup() {
		return true;
	}

isAutoStartup默认是true,也就是rabbitmq config中我们要设置autoStartUp为false的原因!!!

等于默认如果不设置autoStartUp为false的话,它是true,所以项目启动后this.contextRefreshed || listenerContainer.isAutoStartup() 这个条件为true,然后消费者启动消费,从而和我们要提前做的事情顺序混乱。

延伸

至此,我们已经知道了前因后果,找到了rabbitmq消费者启动消费的时机,我们其实有多种方式去实现消费者的懒启动!!!
可以重写ContextRefreshedEvent事件,等我们要做的事情做完,手动去发一个事件!当然这样做的前提是要把AutoStartup设置为false!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值