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方法,通过线程池进行消费者线程的创建并启动。

相关推荐
不能放弃治疗3 小时前
第 29 章 - ES 源码篇 - 网络 IO 模型及其实现概述
后端·elasticsearch
颜淡慕潇5 小时前
【K8S问题系列 | 21 】K8S中如果PV处于Bound状态,如何删除?【已解决】
后端·云原生·容器·kubernetes·pv
SomeB1oody5 小时前
【Rust自学】7.6. 将模块拆分为不同文件
开发语言·后端·rust
赛博末影猫5 小时前
SpringBoot(Ⅱ-2)——,SpringBoot版本控制,自动装配原理补充(源码),自动导包原理补充(源码),run方法
java·spring boot·后端
光岳楼观景6 小时前
Springboot -- JSON
spring boot·后端·json
掘金酱6 小时前
稀土掘金社区2024年度影响力榜单正式公布
android·前端·后端
qq_2518364577 小时前
asp.net 高校学生勤工俭学系统设计与实现
开发语言·数据库·后端·学习·asp.net
孙尚香蕉8 小时前
Spring IOC详解:掌握控制反转的核心原理与实践应用
java·后端·spring
SomeB1oody9 小时前
【Rust自学】8.2. Vector + Enum的应用
开发语言·后端·rust