RabbitMq消费者是如何监听消息

前言

前段时间遇到个面试问题,因为好久没回顾相关知识点,导致没回答上来,属实有点小尴尬,问题是在使用消息中间件的过程中,消费者是如何进行消息消费的,当时我记得就回答了个,消费者通过RabbitListener注解监听broker里对应的queue队列里面的消费,从而进行进行服务端的消费,这么回答显然很拉,那么下面就让我们对mq的具体消费场景来看看,消费者具体消费的步骤是什么;

RabbitMq消费的两种方式

注解配置方式

@RabbitListener注解是RabbitMq里面的一个核心的组件,当生产者将消息推送到指定的queue中,@RabbitListener注解会将消息转发给标记该注解的方法;

通过对源码注解的参数分析,可以看到有containerFactory(监听工厂)这个参数,我们可以通过自定义监听工厂,让消费者通过自定义的消费模式进行消费;

这时候就需要将SimpleRabbitListenerContainerFactory创建SimpleMessageListenerContainer对象,放入Spring容器;

直接在RabbitMqConfig的配置里面,声明SimpleRabbitListenerContainerFactory的bean,将mq的connectionFactory连接工厂,mq默认是自动确认,改为手动,可以自定义MessageListener 方法,通过日志打印消息信息

typescript 复制代码
@Bean
public SimpleMessageListenerContainer simpleMessageListenerContainer(ConnectionFactory connectionFactory) {
    SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
    // RabbitMQ默认是自动确认,这里改为手动确认消息
    container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
    // 设置需要手动确认消息的队列,可以同时设置多个,前提是队列需要提前创建好
    container.setQueueNames("order.message.queue");
    // 设置监听消息的方法,匿名内部类方式
    container.setMessageListener((ChannelAwareMessageListener) (message, channel) -> {
        // 开始消费消息
        log.info("body:\n{}", JSON.toJSONString(new String(message.getBody())));
        log.info("prop:\n{}", JSON.toJSONString(message.getMessageProperties()));

        // 手动确认
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        channel.basicAck(deliveryTag, false); // 肯定确认
    });
    return container;
}

消费者使用自己声明的监听工厂

typescript 复制代码
@RabbitListener(containerFactory = "simpleMessageListenerContainer")
public void consumer(Message msg){
    //simpleMessageListenerContainer 定义了消息的监听日志打印,这里就不需要打log了
    //可以专注于写消费者业务逻辑
}

发布订阅模式

直接通过注解绑定Exchange上的队列,有新消息的时候,会自动发送到它绑定的队列上;

less 复制代码
@RabbitListener(bindings = @QueueBinding(
        value = @Queue,
        exchange = @Exchange(name = "exchange.system", type = ExchangeTypes.FANOUT)
), containerFactory = "simpleMessageListenerContainer")
public void consumer2(Message msg){
    //simpleMessageListenerContainer 定义了消息的监听日志打印,这里就不需要打log了
    //可以专注于写消费者业务逻辑
}

RabbitMq消费者源码分析

前面介绍了消费者消费的两种方式,下面该进行源码的分析了,消费者是如何一步步进行消费的;

监听配置类启动

首先,消费者也是需要启动的,离不开Spring Bean初始化过程,RabbitListenerAnnotationBeanPostProcessor继承了BeanPostProcessor 、BeanFactoryAware看起来是不是很熟悉,bean的初始化的关键接口

  • 首先是后置处理器解析注解
java 复制代码
	@Override
	public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
	//获取代理bean
		Class<?> targetClass = AopUtils.getTargetClass(bean);
		//buildMetadata 获取注解信息
		final TypeMetadata metadata = this.typeCache.computeIfAbsent(targetClass, this::buildMetadata);
		for (ListenerMethod lm : metadata.listenerMethods) {
		//循环注解配置,一个注解可能有多个配置
			for (RabbitListener rabbitListener : lm.annotations) {
				processAmqpListener(rabbitListener, lm.method, bean, beanName);
			}
		}
		if (metadata.handlerMethods.length > 0) {
			processMultiMethodListeners(metadata.classAnnotations, metadata.handlerMethods, bean, beanName);
		}
		return bean;
	}

ListenerMethod对象存储了注解的信息,method和RabbitListener

kotlin 复制代码
private static class ListenerMethod {

	final Method method; // NOSONAR

	final RabbitListener[] annotations; // NOSONAR

	ListenerMethod(Method method, RabbitListener[] annotations) { // NOSONAR
		this.method = method;
		this.annotations = annotations;
	}

}
  • 然后就是上面循环里的processAmqpListener方法,创建MethodRabbitListenerEndpoint
scss 复制代码
		protected Collection<Declarable> processAmqpListener(RabbitListener rabbitListener, Method method, Object bean,
			String beanName) {
			//检查是否是jdk动态代理
		Method methodToUse = checkProxy(method, bean);
		//创建监听端点
		MethodRabbitListenerEndpoint endpoint = new MethodRabbitListenerEndpoint();
		//填充端点方法
		endpoint.setMethod(methodToUse);
        //填充端点配置
		return processListener(endpoint, rabbitListener, bean, methodToUse, beanName);
	}
  • 端点填充后,需要进行注册,调用RabbitListenerEndpointRegistrar 的registerEndpoint方法,这里也是通过继承BeanFactoryAware ,InitializingBean接口,会调用afterPropertiesSet方法
kotlin 复制代码
		public void registerEndpoint(RabbitListenerEndpoint endpoint,
			@Nullable RabbitListenerContainerFactory<?> factory) {
			//断言判断endpoint不为空
		Assert.notNull(endpoint, "Endpoint must be set");
		Assert.hasText(endpoint.getId(), "Endpoint id must be set");
		Assert.state(!this.startImmediately || this.endpointRegistry != null, "No registry available");
		// Factory may be null, we defer the resolution right before actually creating the container
		AmqpListenerEndpointDescriptor descriptor = new AmqpListenerEndpointDescriptor(endpoint, factory);
		synchronized (this.endpointDescriptors) {
		//是否注册时立即启动
			if (this.startImmediately) { // Register and start immediately
				this.endpointRegistry.registerListenerContainer(descriptor.endpoint, // NOSONAR never null
						resolveContainerFactory(descriptor), true);
			}
			else {
				this.endpointDescriptors.add(descriptor);
			}
		}
	}
  • registerListenerContainer监听容器的创建
kotlin 复制代码
		public void registerListenerContainer(RabbitListenerEndpoint endpoint, RabbitListenerContainerFactory<?> factory,
				boolean startImmediately) {
      //省略部分代码
        ...
		synchronized (this.listenerContainers) {
			Assert.state(!this.listenerContainers.containsKey(id),
					"Another endpoint is already registered with id '" + id + "'");
					//创建MessageListenerContainer核心监听容器
			MessageListenerContainer container = createListenerContainer(endpoint, factory);
			this.listenerContainers.put(id, container);
			if (StringUtils.hasText(endpoint.getGroup()) && this.applicationContext != null) {
				List<MessageListenerContainer> containerGroup;
				if (this.applicationContext.containsBean(endpoint.getGroup())) {
					containerGroup = this.applicationContext.getBean(endpoint.getGroup(), List.class);
				}
				else {
					containerGroup = new ArrayList<MessageListenerContainer>();
					this.applicationContext.getBeanFactory().registerSingleton(endpoint.getGroup(), containerGroup);
				}
				//将容器放入list里
				containerGroup.add(container);
			}
			}
  • createListenerContainer看下创建监听的核心方法,核心工厂还是用的RabbitListenerContainerFactory
kotlin 复制代码
public C createListenerContainer(RabbitListenerEndpoint endpoint) {
		C instance = createContainerInstance();

		JavaUtils javaUtils =
				JavaUtils.INSTANCE
						.acceptIfNotNull(this.connectionFactory, instance::setConnectionFactory)
						.acceptIfNotNull(this.errorHandler, instance::setErrorHandler);
		if (this.messageConverter != null && endpoint != null && endpoint.getMessageConverter() == null) {
			endpoint.setMessageConverter(this.messageConverter);
		}
		//省略部分代码
		...

		//公共应用重写 ,将每个SimpleMessageListenerContainer容器放入list中
		applyCommonOverrides(endpoint, instance);

        //监听容器初始化
		initializeContainer(instance, endpoint);

		if (this.containerCustomizer != null) {
			this.containerCustomizer.configure(instance);
		}
  • 接下来就是调用RabbitListenerEndpointRegistry.#start方法进行监听容器开启 通过实现Lifecycle类调用start方法进行开启
typescript 复制代码
		@Override
	public void start() {
	//循环遍历多个监听容器,循环进行开启
		for (MessageListenerContainer listenerContainer : getListenerContainers()) {
			startIfNecessary(listenerContainer);
		}
	}

private void startIfNecessary(MessageListenerContainer listenerContainer) {
	if (this.contextRefreshed || listenerContainer.isAutoStartup()) {
		listenerContainer.start();
	}
}
  • 监听容器的start方法,org.springframework.amqp.rabbit.listener .AbstractMessageListenerContainer
  • 又回到了SimpleMessageListenerContainer这个对象,我们在上面创建的消费者监听对象,重写doStart方法进行启动,就会一直进行消息的监听;
scss 复制代码
		@Override
	protected void doStart() {
		Assert.state(!this.consumerBatchEnabled || getMessageListener() instanceof BatchMessageListener
				|| getMessageListener() instanceof ChannelAwareBatchMessageListener,
				"When setting 'consumerBatchEnabled' to true, the listener must support batching");
		checkListenerContainerAware();
		super.doStart();
		synchronized (this.consumersMonitor) {
			if (this.consumers != null) {
				throw new IllegalStateException("A stopped container should not have consumers");
			}
			//配置消费者个数
			int newConsumers = initializeConsumers();
			if (this.consumers == null) {
				logger.info("Consumers were initialized and then cleared " +
						"(presumably the container was stopped concurrently)");
				return;
			}
			//如果消费小于等于0,则打印"消费者已经在运行"
			if (newConsumers <= 0) {
				if (logger.isInfoEnabled()) {
					logger.info("Consumers are already running");
				}
				return;
			}
			//将消费者放入set集合
			Set<AsyncMessageProcessingConsumer> processors = new HashSet<AsyncMessageProcessingConsumer>();
			for (BlockingQueueConsumer consumer : this.consumers) {
				AsyncMessageProcessingConsumer processor = new AsyncMessageProcessingConsumer(consumer);
				processors.add(processor);
				//使用线程池执行消费新城
				getTaskExecutor().execute(processor);
				if (getApplicationEventPublisher() != null) {
					getApplicationEventPublisher().publishEvent(new AsyncConsumerStartedEvent(this, consumer));
				}
			}
			//等待消费者线程启动成功
			waitForConsumersToStart(processors);
		}
	}

总结

至此RabbitMq的消费者消费方式,以及消费者监听流程源码分析已经大致梳理完了,总结就是通过@RabbitListener注解创建SimpleMessageListenerContainer核心监听对象,调用start方法,通过线程池进行消费者线程的创建并启动。

相关推荐
why15143 分钟前
腾讯(QQ浏览器)后端开发
开发语言·后端·golang
浪裡遊1 小时前
跨域问题(Cross-Origin Problem)
linux·前端·vue.js·后端·https·sprint
声声codeGrandMaster1 小时前
django之优化分页功能(利用参数共存及封装来实现)
数据库·后端·python·django
呼Lu噜1 小时前
WPF-遵循MVVM框架创建图表的显示【保姆级】
前端·后端·wpf
bing_1582 小时前
为什么选择 Spring Boot? 它是如何简化单个微服务的创建、配置和部署的?
spring boot·后端·微服务
学c真好玩2 小时前
Django创建的应用目录详细解释以及如何操作数据库自动创建表
后端·python·django
Asthenia04122 小时前
GenericObjectPool——重用你的对象
后端
Piper蛋窝2 小时前
Go 1.18 相比 Go 1.17 有哪些值得注意的改动?
后端
excel2 小时前
招幕技术人员
前端·javascript·后端
盖世英雄酱581362 小时前
什么是MCP
后端·程序员